diff --git a/packages/frontend/src/components/composer/Composer.tsx b/packages/frontend/src/components/composer/Composer.tsx index b8b5058bc..8b2acb210 100644 --- a/packages/frontend/src/components/composer/Composer.tsx +++ b/packages/frontend/src/components/composer/Composer.tsx @@ -248,7 +248,9 @@ const Composer = forwardRef< try { // Write clipboard to file then attach it to the draft - const path = await runtime.writeClipboardToTempFile(file.name || `file.${extension(file.type)}`) + const path = await runtime.writeClipboardToTempFile( + file.name || `file.${extension(file.type)}` + ) await addFileToDraft(path, msgType) // delete file again after it was sucessfuly added await runtime.removeTempFile(path) diff --git a/packages/target-browser/Readme.md b/packages/target-browser/Readme.md index 6fd6c885a..23d34ac02 100644 --- a/packages/target-browser/Readme.md +++ b/packages/target-browser/Readme.md @@ -41,3 +41,7 @@ pnpm run start Then point your browser to and acept the locally signed certificate to continue. > If you get an "The connection was reset"-Error, then you have likely forgotten to use http**s** instead of http. + +### Known broken things + +> Image Cropper is broken because accesing tmp files directly isn't implemented. Suggestion: handle image preview in cache or memory. diff --git a/packages/target-browser/runtime-browser/runtime.ts b/packages/target-browser/runtime-browser/runtime.ts index 8ef778595..5b2facb3b 100644 --- a/packages/target-browser/runtime-browser/runtime.ts +++ b/packages/target-browser/runtime-browser/runtime.ts @@ -276,7 +276,8 @@ class BrowserRuntime implements Runtime { const clipboardItems = await navigator.clipboard.read() for (const clipboardItem of clipboardItems) { for (const type of clipboardItem.types) { - if (type === 'text/html') { /* ignores html. Needed for images copied from web */ + if (type === 'text/html') { + /* ignores html. Needed for images copied from web */ continue } const blob = await clipboardItem.getType(type) @@ -296,14 +297,34 @@ class BrowserRuntime implements Runtime { } throw new Error('No supported clipboard item found') } - writeTempFileFromBase64(_name: string, _content: string): Promise { - throw new Error('Method not implemented.') - } - writeTempFile(_name: string, _content: string): Promise { - throw new Error('Method not implemented.') + async writeTempFileFromBase64( + name: string, + content: string + ): Promise { + return ( + await ( + await fetch(`/backend-api/uploadTempFileB64/${name}`, { + method: 'POST', + body: content, + }) + ).json() + ).path + } + async writeTempFile(name: string, content: string): Promise { + return ( + await ( + await fetch(`/backend-api/uploadTempFile/${name}`, { + method: 'POST', + body: content, + }) + ).json() + ).path } - removeTempFile(_name: string): Promise { - throw new Error('Method not implemented.') + async removeTempFile(name: string): Promise { + await fetch(`/backend-api/removeTempFile`, { + method: 'POST', + body: name, + }) } activeNotifications: { [chatId: number]: Notification[] } = {} @@ -520,7 +541,7 @@ class BrowserRuntime implements Runtime { } if (blob.type !== 'image/png') { const img = new Image() - const blobPromise = new Promise((resolve, reject) => { + const blobPromise = new Promise(async (resolve, reject) => { img.onload = async () => { try { const canvas = new OffscreenCanvas( @@ -569,10 +590,38 @@ class BrowserRuntime implements Runtime { } return '' } - async showOpenFileDialog( - _options: RuntimeOpenDialogOptions - ): Promise { - throw new Error('Method not implemented.') + async showOpenFileDialog(options: RuntimeOpenDialogOptions): Promise { + const extstring = options.filters + ?.map(filter => filter.extensions) + .reduce((p, c) => c.concat(p)) + .map(ext => `.${ext}`) + .join() + return new Promise(resolve => { + const input = document.createElement('input') + input.type = 'file' + input.accept = extstring || '' + input.onchange = async () => { + if (input.files != null) { + resolve( + ( + await ( + await fetch( + `/backend-api/uploadTempFile/${input.files[0].name}`, + { + method: 'POST', + body: input.files[0], + } + ) + ).json() + ).path + ) + } else { + resolve('') + } + } + + input.click() + }) } openLink(link: string): void { diff --git a/packages/target-browser/src/backendApi.ts b/packages/target-browser/src/backendApi.ts index 05192278f..0608df69a 100644 --- a/packages/target-browser/src/backendApi.ts +++ b/packages/target-browser/src/backendApi.ts @@ -1,5 +1,5 @@ import express, { json as BodyParserJson, Router } from 'express' -import { mkdtemp, writeFile } from 'fs/promises' +import { mkdtemp, writeFile, unlink } from 'fs/promises' import { basename, join } from 'path' import { tmpdir } from 'os' import { @@ -64,7 +64,7 @@ BackendApiRoute.post( type: () => { return true /* Accept all filetypes */ }, - limit: '500mb' + limit: '500mb', }), async (req, res) => { try { @@ -80,7 +80,57 @@ BackendApiRoute.post( console.log(tmpFile) console.log(filename, tmppath, filepath) } catch (error) { - res.status(500).json({message: 'Failed to create Tempfile'}) + res.status(500).json({ message: 'Failed to create Tempfile' }) + } + } +) + +BackendApiRoute.post( + '/uploadTempFileB64/:filename', + express.raw({ + type: () => { + return true /* Accept all filetypes */ + }, + limit: '500mb', + }), + async (req, res) => { + try { + const tmpFilebin: Buffer = Buffer.from(req.body.toString(), 'base64') + + const filename = basename(req.params.filename) + const tmppath = await mkdtemp(join(tmpdir(), 'tmp-')) + + const filepath = join(tmppath, filename) + await writeFile(filepath, tmpFilebin, 'binary') + + res.status(200).send({ path: filepath }) + console.log(tmpFilebin.toString('utf-8')) + console.log(filename, tmppath, filepath) + } catch (error) { + res.status(500).json({ message: 'Failed to create Tempfile' }) + } + } +) + +BackendApiRoute.post( + '/removeTempFile', + express.raw({ + type: () => { + return true /* Accept all filetypes */ + }, + }), + async (req, _res) => { + try { + const filepath = req.body.toString('utf8') + console.log(filepath) + if (filepath.includes('tmp') && !filepath.includes('..')) { + await unlink(filepath) + } + } catch (e) { + // file doesn't exist, no permissions, etc.. + // full list of possible errors is here + // http://man7.org/linux/man-pages/man2/unlink.2.html#ERRORS + console.log(e) } } )