From 55747eada266ea4dd1b02f0d1ddef82afe4bb671 Mon Sep 17 00:00:00 2001 From: David Fiala Date: Sat, 18 Apr 2026 19:05:45 -0700 Subject: [PATCH 1/3] add support for base64 encoded inline source maps of formats: data:application/json;base64,XXX and //# sourceMappingURL=data:application/json;base64,XXX --- src/app.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app.tsx b/src/app.tsx index b443a4c..5b1b382 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -9,6 +9,8 @@ import { ThemeToggle } from './theme-toggle.tsx' import { useSourcemapsStore } from './use-sourcemaps-store.ts' import { setTheme, useTheme } from './use-theme.ts' +const base64PrefixRegex = /^(?:\/\/# sourceMappingURL=)?data:application\/json;(?:charset=[^;]+;)?base64,/ + export default function App() { const [stackTraceInputValue, setStackTraceInputValue] = useState('') const [sourceMapInputValue, setSourceMapInputValue] = useState('') @@ -43,7 +45,19 @@ export default function App() { } async function handleSourceMapTextAreaChange(event: ChangeEvent) { - const text = event.target.value + let text = event.target.value + + if (base64PrefixRegex.test(text)) { + try { + const base64Content = text.replace(base64PrefixRegex, '') + const bytes = Uint8Array.from(atob(base64Content), c => c.charCodeAt(0)) + text = new TextDecoder().decode(bytes) + } catch { + setIsSourceMapInputError(true) + setSourceMapInputValue(event.target.value) + return + } + } setSourceMapInputValue(text) From 5112d10448a5983646ebce55912465cfdad714ce Mon Sep 17 00:00:00 2001 From: David Fiala Date: Sun, 19 Apr 2026 17:36:44 +0000 Subject: [PATCH 2/3] add tests for base64 inline source maps and add vscode devcontainer with node24 for devops security and reproducibility --- .devcontainer/devcontainer.json | 27 +++++++++++++++ src/__tests__/app.test.tsx | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..4741b72 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,27 @@ +{ + "name": "sourcemap_tools_devcontainer", + "image": "public.ecr.aws/docker/library/node:24", + "workspaceMount": "source=${localWorkspaceFolder},target=/sm,type=bind", + "workspaceFolder": "/sm", + "runArgs": ["--name", "sourcemap_tools_devcontainer", "--tmpfs", "/tmp:exec,mode=01777"], + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ], + "settings": { + "editor.rulers": [100], + "editor.defaultFormatter": "esbenp.prettier-vscode", + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, + "npm.packageManager": "npm", + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } + } + } + }, + "postCreateCommand": "cd /sm && npm ci", + "remoteUser": "node" +} diff --git a/src/__tests__/app.test.tsx b/src/__tests__/app.test.tsx index a96cc90..20f7a43 100644 --- a/src/__tests__/app.test.tsx +++ b/src/__tests__/app.test.tsx @@ -267,6 +267,65 @@ describe('source maps', () => { // Should display the fallback with generated ID expect(listItem).toHaveTextContent(/NO NAME \(Generated id:/) }) + + test('decodes base64 data URL source map pasted into textarea', async () => { + render() + const user = userEvent.setup() + + const sourcemapTextarea = screen.getByRole('textbox', { name: /source map/i }) + + const dataUrl = `data:application/json;base64,${btoa(regular.sourcemaps[0].content)}` + + sourcemapTextarea.focus() + await user.paste(dataUrl) + + const sourcemapList = await screen.findByRole('list', { name: /sourcemaps list/i }) + expect(within(sourcemapList).getByRole('listitem')).toHaveTextContent('index-d803759c.js') + }) + + test('decodes base64 data URL with charset parameter', async () => { + render() + const user = userEvent.setup() + + const sourcemapTextarea = screen.getByRole('textbox', { name: /source map/i }) + + const dataUrl = `data:application/json;charset=utf-8;base64,${btoa(regular.sourcemaps[0].content)}` + + sourcemapTextarea.focus() + await user.paste(dataUrl) + + const sourcemapList = await screen.findByRole('list', { name: /sourcemaps list/i }) + expect(within(sourcemapList).getByRole('listitem')).toHaveTextContent('index-d803759c.js') + }) + + test('shows warning for malformed base64 data URL', async () => { + render() + const user = userEvent.setup() + + const sourcemapTextarea = screen.getByRole('textbox', { name: /source map/i }) + + sourcemapTextarea.focus() + await user.paste('data:application/json;base64,!!!not-valid-base64!!!') + + const warning = await screen.findByText(/provided text is not a source map/i) + expect(warning).toBeInTheDocument() + + // The original (undecoded) value should remain in the textarea so the user can fix it. + expect(sourcemapTextarea).toHaveValue('data:application/json;base64,!!!not-valid-base64!!!') + }) + + test('treats plain source map text as non-base64', async () => { + render() + const user = userEvent.setup() + + const sourcemapTextarea = screen.getByRole('textbox', { name: /source map/i }) + + sourcemapTextarea.focus() + await user.paste(regular.sourcemaps[1].content) + + const sourcemapList = await screen.findByRole('list', { name: /sourcemaps list/i }) + expect(within(sourcemapList).getByRole('listitem')).toHaveTextContent('vendor-221d27ba.js') + }) }) describe('theme', () => { From ae6523f86384c1e038684f9e27d17919d277ab37 Mon Sep 17 00:00:00 2001 From: David Fiala Date: Sun, 19 Apr 2026 18:16:05 +0000 Subject: [PATCH 3/3] run prettier --- .devcontainer/devcontainer.json | 5 +---- src/app.tsx | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4741b72..319a830 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,10 +6,7 @@ "runArgs": ["--name", "sourcemap_tools_devcontainer", "--tmpfs", "/tmp:exec,mode=01777"], "customizations": { "vscode": { - "extensions": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode" - ], + "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"], "settings": { "editor.rulers": [100], "editor.defaultFormatter": "esbenp.prettier-vscode", diff --git a/src/app.tsx b/src/app.tsx index 5b1b382..1d7d1e9 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -9,7 +9,8 @@ import { ThemeToggle } from './theme-toggle.tsx' import { useSourcemapsStore } from './use-sourcemaps-store.ts' import { setTheme, useTheme } from './use-theme.ts' -const base64PrefixRegex = /^(?:\/\/# sourceMappingURL=)?data:application\/json;(?:charset=[^;]+;)?base64,/ +const base64PrefixRegex = + /^(?:\/\/# sourceMappingURL=)?data:application\/json;(?:charset=[^;]+;)?base64,/ export default function App() { const [stackTraceInputValue, setStackTraceInputValue] = useState('')