Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/builder/lib/base64.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const base64UrlEncode = (

// Use environment-specific encoding
let base64: string;
if (typeof globalThis !== 'undefined' && 'btoa' in globalThis) {
if (typeof globalThis !== 'undefined' && typeof globalThis.btoa === 'function') {
// Browser environment
base64 = globalThis.btoa(text);
} else {
Expand Down Expand Up @@ -61,7 +61,7 @@ export const base64UrlDecode = (input: string): ArrayBuffer => {
const base64 = base64UrlDecodeString(input);

// Decode based on environment
if (typeof globalThis !== 'undefined' && 'atob' in globalThis) {
if (typeof globalThis !== 'undefined' && typeof globalThis.atob === 'function') {
// Browser environment
const binaryString = globalThis.atob(base64);
const bytes = new Uint8Array(binaryString.length);
Expand All @@ -71,5 +71,9 @@ export const base64UrlDecode = (input: string): ArrayBuffer => {
return bytes.buffer;
}
// Node.js environment
return Buffer.from(base64, 'base64').buffer;
const buffer = Buffer.from(base64, 'base64');
return buffer.buffer.slice(
buffer.byteOffset,
buffer.byteOffset + buffer.byteLength,
);
};
2 changes: 1 addition & 1 deletion packages/builder/lib/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ const padPayload = (payload: Uint8Array): Uint8Array<ArrayBuffer> => {
const maxRandomPadding = Math.min(100, availableSpace);
const paddingSize =
maxRandomPadding > 0
? Math.floor(Math.random() * (maxRandomPadding + 1))
? crypto.getRandomValues(new Uint8Array(1))[0] % (maxRandomPadding + 1)
: 0;

const paddingArray = new ArrayBuffer(
Expand Down
30 changes: 30 additions & 0 deletions packages/builder/test/unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'vitest';
import { base64UrlDecode } from '../lib/base64.js';
import { buildPushHTTPRequest } from '../lib/main.js';
import type {
BuilderOptions,
Expand Down Expand Up @@ -217,6 +218,35 @@ describe('Subscription Keys Validation', () => {
});
});

// ============================================================================
// Base64 Decoding Tests
// ============================================================================
describe('Base64 URL Decoding', () => {
test('decodes to exact byte length in default runtime path', () => {
const decoded = new Uint8Array(base64UrlDecode('AQ'));

expect(decoded).toEqual(new Uint8Array([1]));
expect(decoded.byteLength).toBe(1);
});

test('decodes to exact byte length in Buffer fallback path', () => {
const originalAtob = globalThis.atob;
// Force Node fallback path to verify sliced ArrayBuffer behavior.
// `Buffer#buffer` alone would expose backing store, not exact bytes.
// @ts-expect-error test override
globalThis.atob = undefined;

try {
const decoded = new Uint8Array(base64UrlDecode('AQ'));

expect(decoded).toEqual(new Uint8Array([1]));
expect(decoded.byteLength).toBe(1);
} finally {
globalThis.atob = originalAtob;
}
});
});

// ============================================================================
// TTL Validation Tests
// ============================================================================
Expand Down