Skip to content

Commit 8fec6a1

Browse files
benvinegarclaude
andcommitted
refactor(server): dedupe decodeBase64 into a shared module
The same runtime-agnostic base64 decoder was defined identically in both app.ts and mcpHttp.ts. Extract it to server/base64.ts and import it in both, so a future fix (error handling, etc.) lives in one place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7f86b13 commit 8fec6a1

4 files changed

Lines changed: 18 additions & 27 deletions

File tree

.changeset/grumpy-spies-sing.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

server/app.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Hono, type Context } from "hono";
22
import { getCookie, setCookie } from "hono/cookie";
33
import { streamSSE } from "hono/streaming";
4+
import { decodeBase64 } from "./base64.ts";
45
import { EventBus } from "./events.ts";
56
import { kitSummaries } from "./kits.ts";
67
import { registerMcp } from "./mcpHttp.ts";
@@ -72,19 +73,6 @@ function inferAssetKind(contentType: string): AssetKind {
7273
const isAssetKind = (v: unknown): v is AssetKind => v === "image" || v === "trace" || v === "file";
7374

7475
// base64 -> bytes, runtime-agnostic (atob is a global in Node and Workers).
75-
// atob throws on malformed input; rethrow as a clean error the callers turn
76-
// into a 400 instead of letting a raw DOMException surface as a 500.
77-
function decodeBase64(b64: string): Uint8Array {
78-
let bin: string;
79-
try {
80-
bin = atob(b64);
81-
} catch {
82-
throw new Error("invalid base64 in `data`");
83-
}
84-
const bytes = new Uint8Array(bin.length);
85-
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
86-
return bytes;
87-
}
8876
// Docs and onboarding snippets are written against the local default; serve
8977
// them with the real origin so a deployed instance shows copy-pasteable URLs.
9078
const LOCAL_ORIGIN = "http://localhost:8228";

server/base64.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// base64 -> bytes, runtime-agnostic (atob is a global in Node and Workers).
2+
// atob throws on malformed input; rethrow as a clean error so callers turn it
3+
// into a 400 instead of letting a raw DOMException surface as a 500.
4+
export function decodeBase64(b64: string): Uint8Array {
5+
let bin: string;
6+
try {
7+
bin = atob(b64);
8+
} catch {
9+
throw new Error("invalid base64 in `data`");
10+
}
11+
const bytes = new Uint8Array(bin.length);
12+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
13+
return bytes;
14+
}

server/mcpHttp.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Hono } from "hono";
22
import type { CommentWait, Feedback } from "./app.ts";
3+
import { decodeBase64 } from "./base64.ts";
34
import {
45
type Asset,
56
type AssetKind,
@@ -47,20 +48,6 @@ export interface McpDeps {
4748
guide: string;
4849
}
4950

50-
// base64 -> bytes, runtime-agnostic (atob is a global in Node and Workers).
51-
// atob throws on malformed input; rethrow as a clean error the tool surfaces.
52-
function decodeBase64(b64: string): Uint8Array {
53-
let bin: string;
54-
try {
55-
bin = atob(b64);
56-
} catch {
57-
throw new Error("invalid base64 in `data`");
58-
}
59-
const bytes = new Uint8Array(bin.length);
60-
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
61-
return bytes;
62-
}
63-
6451
// Coerce loosely-typed tool args into validated SurfacePart[]. Unknown kinds
6552
// and empty parts are dropped rather than rejected, so a slightly-off call
6653
// still publishes what it can.

0 commit comments

Comments
 (0)