diff --git a/examples/devtools-test.js b/examples/devtools-test.js index 72c1269..700c762 100644 --- a/examples/devtools-test.js +++ b/examples/devtools-test.js @@ -3,6 +3,20 @@ console.log("Devtools test started", { jobId: _STD_.job.getId(), device: _STD_.device.getAddress() }) console.info("Processor info", { timestamp: Date.now() }) +// Probe runtime for HTTP/upload primitives +console.log("Runtime probe", { + fetch: typeof fetch, + FormData: typeof FormData, + Blob: typeof Blob, + XMLHttpRequest: typeof XMLHttpRequest, + Request: typeof Request, + Response: typeof Response, + Headers: typeof Headers, + Buffer: typeof Buffer, + btoa: typeof btoa, + atob: typeof atob, +}) + let tick = 0 const interval = setInterval(() => { tick++ @@ -20,6 +34,36 @@ const interval = setInterval(() => { } } + if (tick === 2) { + const filename = "test-upload.json" + const content = JSON.stringify({ + message: "hello from acurast processor", + jobId: _STD_.job.getId().id, + device: _STD_.device.getAddress(), + timestamp: Date.now(), + }) + + console.log("Uploading file: " + filename + " (" + content.length + " bytes)") + + _DEVTOOLS_.uploadFile( + filename, + content, + "application/json", + (fileInfo) => { + console.log("Upload succeeded", { + id: fileInfo.id, + filename: fileInfo.filename, + mimeType: fileInfo.mimeType, + fileSize: fileInfo.fileSize, + createdAt: fileInfo.createdAt, + }) + }, + (error) => { + console.error("Upload failed:", error) + } + ) + } + if (tick >= 12) { clearInterval(interval) console.log("Devtools test complete after " + tick + " ticks") diff --git a/src/devtools/acurast-processor.d.ts b/src/devtools/acurast-processor.d.ts index f110dc4..d236439 100644 --- a/src/devtools/acurast-processor.d.ts +++ b/src/devtools/acurast-processor.d.ts @@ -8,6 +8,22 @@ declare function httpPOST( onError: (error: string) => void ): void +declare const _DEVTOOLS_: { + uploadFile( + filename: string, + content: string, + mimeType: string, + onSuccess: (fileInfo: { + id: number + filename: string + mimeType: string + fileSize: number + createdAt: string + }) => void, + onError: (error: string) => void + ): void +} + declare const _STD_: { job: { getId(): { origin: { kind: string; source: string }; id: string } diff --git a/src/devtools/devtools-snippet.ts b/src/devtools/devtools-snippet.ts index 0b2c7cc..49b3c2d 100644 --- a/src/devtools/devtools-snippet.ts +++ b/src/devtools/devtools-snippet.ts @@ -131,6 +131,52 @@ } } + // --- File upload via /v1/files --- + const uploadFile = ( + filename: string, + content: string, + mimeType: string, + onSuccess: (fileInfo: { id: number; filename: string; mimeType: string; fileSize: number; createdAt: string }) => void, + onError: (error: string) => void + ) => { + if (!apiKey) { + onError('[devtools] file upload failed: not authenticated yet') + return + } + + // The processor's httpPOST JSON-parses the body regardless of Content-Type, + // so we cannot send multipart/form-data through it. Use fetch + FormData + // instead — fetch sets the Content-Type header (with boundary) automatically. + const formData = new FormData() + formData.append('file', new Blob([content], { type: mimeType }), filename) + + fetch(`${DEVTOOLS_API_URL}/v1/files`, { + method: 'POST', + headers: { Authorization: 'Bearer ' + apiKey }, + body: formData, + }) + .then((res: Response) => + res.text().then((text: string) => ({ ok: res.ok, status: res.status, text })) + ) + .then(({ ok, status, text }: { ok: boolean; status: number; text: string }) => { + if (!ok) { + onError('[devtools] file upload failed: HTTP ' + status + ' ' + text) + return + } + try { + onSuccess(JSON.parse(text)) + } catch (_e) { + onError('[devtools] failed to parse upload response: ' + text) + } + }) + .catch((err: any) => { + onError('[devtools] file upload failed: ' + (err?.message ?? String(err))) + }) + } + + // Expose _DEVTOOLS_ global + ;(globalThis as any)._DEVTOOLS_ = { uploadFile } + for (const level of Object.keys(originalConsole) as Array< keyof typeof originalConsole >) { diff --git a/test/devtools.test.ts b/test/devtools.test.ts index 62ba0ac..823d004 100644 --- a/test/devtools.test.ts +++ b/test/devtools.test.ts @@ -77,21 +77,6 @@ describe('devtools snippet injection', () => { expect(content).not.toContain('__DEVTOOLS_API_URL__') }) - it('should not contain any unreplaced placeholders', async () => { - const zipPath = createTestZip('index.js', '// user code') - await injectDevtoolsSnippet( - zipPath, - 'index.js', - 'https://api.devtools.acurast.com', - SNIPPET_DIR - ) - - const zip = new AdmZip(zipPath) - const content = zip.getEntry('index.js')!.getData().toString('utf-8') - - expect(content).not.toContain('__DEVTOOLS_API_URL__') - }) - it('should strip TSC artifacts (export {}, sourcemap comment)', async () => { const zipPath = createTestZip('index.js', '// user code')