diff --git a/README.md b/README.md index e00c70c..1d1ab2d 100644 --- a/README.md +++ b/README.md @@ -13,22 +13,14 @@ Example in TypeScript, as seen in `mxcId.ts`: ```typescript function toBase64Url(value: string): string { - if (typeof btoa === 'function') { - const bytes = new TextEncoder().encode(value); - let binary = ''; + const bytes = new TextEncoder().encode(value); + let binary = ''; - for (const byte of bytes) { - binary += String.fromCodePoint(byte); - } - - return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll(/=+$/g, ''); - } - - if (typeof Buffer !== 'undefined') { - return Buffer.from(value).toString('base64url'); + for (const byte of bytes) { + binary += String.fromCodePoint(byte); } - throw new Error('No base64 encoder available in this runtime'); + return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll(/=+$/g, ''); } function toMatrixID(fname: string, prefix: string): string { diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..7c942a8 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + sharp: true + workerd: true +packages: + - '.' \ No newline at end of file diff --git a/src/mxcId.ts b/src/mxcId.ts index a6bedc5..e628b4c 100644 --- a/src/mxcId.ts +++ b/src/mxcId.ts @@ -23,22 +23,14 @@ * @return {*} {string} the url-safe-base64 encoded string */ function toBase64Url(value: string): string { - if (typeof btoa === 'function') { - const bytes = new TextEncoder().encode(value); - let binary = ''; + const bytes = new TextEncoder().encode(value); + let binary = ''; - for (const byte of bytes) { - binary += String.fromCodePoint(byte); - } - - return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll(/=+$/g, ''); - } - - if (typeof Buffer !== 'undefined') { - return Buffer.from(value).toString('base64url'); + for (const byte of bytes) { + binary += String.fromCodePoint(byte); } - throw new Error('No base64 encoder available in this runtime'); + return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll(/=+$/g, ''); } /** @@ -48,19 +40,11 @@ function toBase64Url(value: string): string { * @return {*} {string} the usable string */ function fromBase64Url(value: string): string { - if (typeof atob === 'function') { - const normalized = value.replace(/-/g, '+').replace(/_/g, '/'); - const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4); - const binary = atob(padded); - const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0)); - return new TextDecoder().decode(bytes); - } - - if (typeof Buffer !== 'undefined') { - return Buffer.from(value, 'base64url').toString(); - } - - throw new Error('No base64 decoder available in this runtime'); + const normalized = value.replace(/-/g, '+').replace(/_/g, '/'); + const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4); + const binary = atob(padded); + const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0)); + return new TextDecoder().decode(bytes); } /** diff --git a/src/proxy.ts b/src/proxy.ts index a97b6bf..d513c01 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -41,21 +41,42 @@ function decodeMatrixId(rawId: string): string | MatrixError { return id; } +type MultipartPartLike = { + headers: HeadersInit; + body?: BodyInit; +}; + +function encodeMultipart(boundary: string, parts: MultipartPartLike[]): string { + const lines: string[] = []; + + for (const part of parts) { + lines.push(`--${boundary}`); + for (const [key, value] of Object.entries(part.headers)) { + lines.push(`${key}: ${value}`); + } + lines.push(''); + lines.push(part.body ? String(part.body) : ''); + } + + lines.push(`--${boundary}--`, ''); + return lines.join('\r\n'); +} + function buildMultipartRedirect(targetLocation: string): Response { const boundary = `soliditas${Date.now().toString(16)}${Math.random().toString(16).slice(2)}`; - const body = Buffer.from( - `--${boundary}\r\n` + - `Content-Type: application/json\r\n` + - `\r\n` + - `{}\r\n` + - `--${boundary}\r\n` + - `Content-Type: application/octet-stream\r\n` + - `Location: ${targetLocation}\r\n` + - `\r\n` + - `\r\n` + - `--${boundary}--\r\n`, - 'utf8' - ); + + const body = encodeMultipart(boundary, [ + { + headers: { 'Content-Type': 'application/json' }, + body: '{}', + }, + { + headers: { + 'Content-Type': 'application/octet-stream', + 'Location': targetLocation, + }, + }, + ]); return new Response(body, { headers: { diff --git a/test/mxcId.spec.ts b/test/mxcId.spec.ts index fe1da14..d9b0ea9 100644 --- a/test/mxcId.spec.ts +++ b/test/mxcId.spec.ts @@ -60,55 +60,3 @@ describe('fromMatrixID', () => { expect(fromMatrixID(`custom_${encoded.split('_')[1]}`)).toBe('file.txt'); }); }); - -describe('runtime fallbacks', () => { - const originalBtoa = globalThis.btoa; - const originalAtob = globalThis.atob; - const originalBuffer = globalThis.Buffer; - - afterEach(() => { - globalThis.btoa = originalBtoa; - globalThis.atob = originalAtob; - globalThis.Buffer = originalBuffer; - }); - - it('uses Buffer fallback when btoa is unavailable', () => { - // @ts-expect-error test override - globalThis.btoa = undefined; - - const result = toMatrixID('buffer-test.txt', 'mxc_'); - - expect(result).toBe('mxc_YnVmZmVyLXRlc3QudHh0'); - }); - - it('uses Buffer fallback when atob is unavailable', () => { - // @ts-expect-error test override - globalThis.atob = undefined; - - const encoded = toMatrixID('buffer-decode.txt', 'mxc_'); - - expect(fromMatrixID(encoded)).toBe('buffer-decode.txt'); - }); - - it('throws if no encoder runtime is available', async () => { - // @ts-expect-error test override - globalThis.btoa = undefined; - // @ts-expect-error test override - globalThis.Buffer = undefined; - - expect(() => toMatrixID('fail.txt', 'mxc_')).toThrow( - 'No base64 encoder available in this runtime', - ); - }); - - it('throws if no decoder runtime is available', async () => { - // @ts-expect-error test override - globalThis.atob = undefined; - // @ts-expect-error test override - globalThis.Buffer = undefined; - - expect(() => fromMatrixID('mxc_ZmlsZS50eHQ')).toThrow( - 'No base64 decoder available in this runtime', - ); - }); -}); \ No newline at end of file diff --git a/wrangler.jsonc b/wrangler.jsonc index 1a45a26..ea9b6a8 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -11,9 +11,7 @@ "enabled": true }, "upload_source_maps": true, - "compatibility_flags": [ - "nodejs_compat" - ], + "compatibility_flags": [], /** * Smart Placement * https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement