From fc15affab10fca4fbad7df47cb1217669983bd4a Mon Sep 17 00:00:00 2001 From: Phillip Barta Date: Fri, 27 Feb 2026 01:25:13 +0100 Subject: [PATCH] refactor: add cross-environment decodeBase64 method --- .github/workflows/ci.yml | 3 +- globals.d.ts | 61 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 - src/index.ts | 39 +++++++++++++++++++++---- tsconfig.json | 2 +- 5 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 globals.d.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fff328..0612a79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,8 @@ jobs: matrix: node-version: - 18 - - '*' + - 25 # Node.js 25 support Uint8Array.fromBase64() + - 'lts/*' steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 diff --git a/globals.d.ts b/globals.d.ts new file mode 100644 index 0000000..20fd8b2 --- /dev/null +++ b/globals.d.ts @@ -0,0 +1,61 @@ +// Adapted from file://./node_modules/typescript/lib/lib.dom.d.ts so we don't have to include the entire DOM lib +// Ref: https://github.com/microsoft/TypeScript/issues/31535, https://github.com/microsoft/TypeScript/issues/41727, https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1685 + +/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */ +declare function atob(data: string): string; +/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */ +declare function btoa(data: string): string; + +type AllowSharedBufferSource = + | ArrayBufferLike + | ArrayBufferView; + +interface TextDecodeOptions { + stream?: boolean; +} + +interface TextDecoderOptions { + fatal?: boolean; + ignoreBOM?: boolean; +} + +/** + * The **`TextDecoder`** interface represents a decoder for a specific text encoding, such as `UTF-8`, `ISO-8859-2`, `KOI8-R`, `GBK`, etc. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder) + */ +interface TextDecoder extends TextDecoderCommon { + /** + * The **`TextDecoder.decode()`** method returns a string containing text decoded from the buffer passed as a parameter. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode) + */ + decode(input?: AllowSharedBufferSource, options?: TextDecodeOptions): string; +} + +// eslint-disable-next-line no-var +declare var TextDecoder: { + prototype: TextDecoder; + new (label?: string, options?: TextDecoderOptions): TextDecoder; +}; + +interface TextDecoderCommon { + /** + * Returns encoding's name, lowercased. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/encoding) + */ + readonly encoding: string; + /** + * Returns true if error mode is "fatal", otherwise false. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/fatal) + */ + readonly fatal: boolean; + /** + * Returns the value of ignore BOM. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/ignoreBOM) + */ + readonly ignoreBOM: boolean; +} diff --git a/package.json b/package.json index dde152b..98fe6ac 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ }, "devDependencies": { "@borderless/ts-scripts": "^0.15.0", - "@types/node": "^20.19.35", "@vitest/coverage-v8": "^3.2.4", "typescript": "^5.9.3", "vitest": "^3.2.4" diff --git a/src/index.ts b/src/index.ts index 98bc6c4..e90e7de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,8 +6,6 @@ * MIT Licensed */ -import { Buffer } from 'node:buffer'; - export = auth; /** @@ -101,14 +99,45 @@ const CREDENTIALS_REGEXP = const USER_PASS_REGEXP = /^([^:]*):(.*)$/; +type Uint8ArrayWithBase64 = typeof Uint8Array & { + fromBase64?: (str: string) => Uint8Array; +}; + +type BufferLike = { + from( + input: string, + encoding: 'base64', + ): { toString(encoding: 'utf-8'): string }; +}; + +const NodeBuffer = (globalThis as any).Buffer as BufferLike | undefined; + +const textDecoder = new TextDecoder('utf-8'); + /** * Decode base64 string. * @private */ +const decodeBase64: (str: string) => string = (() => { + // 1) Modern Web / some runtimes + if (typeof (Uint8Array as Uint8ArrayWithBase64).fromBase64 === 'function') { + return (str: string) => + textDecoder.decode((Uint8Array as Uint8ArrayWithBase64).fromBase64!(str)); + } -function decodeBase64(str: string): string { - return Buffer.from(str, 'base64').toString(); -} + // 2) Node.js (fast path) + if (typeof NodeBuffer?.from === 'function') { + return (str: string) => NodeBuffer.from(str, 'base64').toString('utf-8'); + } + + // 3) Browser fallback + return (str: string) => { + const binary = atob(str); + return textDecoder.decode( + Uint8Array.from(binary, (char) => char.charCodeAt(0)), + ); + }; +})(); /** * Get the Authorization header from request object. diff --git a/tsconfig.json b/tsconfig.json index 8eef991..0e01057 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "dist", "module": "nodenext", "moduleResolution": "nodenext", - "types": ["node"] + "types": ["./globals.d.ts"] }, "include": ["src/**/*"] }