Skip to content

Commit ba5780d

Browse files
committed
Split base64 utilities to avoid browser polyfills.
Separate base64ToBytes implementation into Node.js and browser-specific files to prevent static analyzers from injecting unnecessary Buffer polyfills into browser bundles. Add browser field aliasing to package.json for automatic environment-specific loading. Changes: - Create lib/util.js with Node.js implementation using Buffer. - Create lib/utilBrowser.js with browser implementation using atob. - Add browser field to package.json for file aliasing. - Update nfcRenderer.js to import from util.js (bundler handles switching). - Remove inline _base64ToBytes function from nfcRenderer.js. - Rename dataUri to dataUrl throughout for consistency. - Update error messages to use 'data URL' instead of 'data URI'. - Update test assertions to match new error messages.
1 parent 60702cf commit ba5780d

5 files changed

Lines changed: 98 additions & 311 deletions

File tree

lib/nfcRenderer.js

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
import * as base58 from 'base58-universal';
1111
import * as base64url from 'base64url-universal';
12+
import {base64ToBytes} from './util.js';
1213

1314
const multibaseDecoders = new Map([
1415
['u', base64url],
@@ -269,9 +270,9 @@ async function _decodeTemplateToBytes({renderMethod, filteredData} = {}) {
269270
}
270271

271272
async function _decodeTemplate({encoded} = {}) {
272-
// data URI format
273+
// data URL format
273274
if(encoded.startsWith('data:')) {
274-
return _decodeDataUri({dataUri: encoded});
275+
return _decodeDataUrl({dataUrl: encoded});
275276
}
276277

277278
// multibase format (base58 'z' or base64url 'u')
@@ -281,7 +282,7 @@ async function _decodeTemplate({encoded} = {}) {
281282

282283
throw new Error(
283284
'Unknown template encoding format. ' +
284-
'Supported formats: data URI (data:...) or multibase (z..., u...)'
285+
'Supported formats: data URL (data:...) or multibase (z..., u...)'
285286
);
286287
}
287288

@@ -290,20 +291,20 @@ async function _decodeTemplate({encoded} = {}) {
290291
// ========================
291292

292293
/**
293-
* Decode a data URI to bytes.
294+
* Decode a data URL to bytes.
294295
* Validates media type is application/octet-stream..
295296
*
296297
* @private
297298
* @param {object} options - Options object.
298-
* @param {string} options.dataUri - Data URI string.
299+
* @param {string} options.dataUrl - Data URL string.
299300
* @returns {Uint8Array} Decoded bytes.
300-
* @throws {Error} If data URI is invalid or has wrong media type.
301+
* @throws {Error} If data URL is invalid or has wrong media type.
301302
*/
302-
function _decodeDataUri({dataUri} = {}) {
303-
// parse data URI format: data:mime/type;encoding,data
304-
const match = dataUri.match(/^data:([^;]+);([^,]+),(.*)$/);
303+
function _decodeDataUrl({dataUrl} = {}) {
304+
// parse data URL format: data:mime/type;encoding,data
305+
const match = dataUrl.match(/^data:([^;]+);([^,]+),(.*)$/);
305306
if(!match) {
306-
throw new Error('Invalid data URI format.');
307+
throw new Error('Invalid data URL format.');
307308
}
308309

309310
const mimeType = match[1];
@@ -313,20 +314,20 @@ function _decodeDataUri({dataUri} = {}) {
313314
// validate media type is application/octet-stream
314315
if(mimeType !== 'application/octet-stream') {
315316
throw new Error(
316-
'Invalid data URI media type. ' +
317+
'Invalid data URL media type. ' +
317318
'NFC templates must use "application/octet-stream" media type. ' +
318319
`Found: "${mimeType}"`
319320
);
320321
}
321322

322323
// decode based on encoding
323324
if(encoding === 'base64') {
324-
return _base64ToBytes({base64String: data});
325+
return base64ToBytes({base64String: data});
325326
}
326327
if(encoding === 'base64url') {
327328
return base64url.decode(data);
328329
}
329-
throw new Error(`Unsupported data URI encoding: ${encoding}`);
330+
throw new Error(`Unsupported data URL encoding: ${encoding}`);
330331
}
331332

332333
/**
@@ -349,29 +350,6 @@ function _decodeMultibase({input} = {}) {
349350
return decoder.decode(encodedData);
350351
}
351352

352-
/**
353-
* Decode standard base64 to bytes.
354-
*
355-
* @private
356-
* @param {object} options - Options object.
357-
* @param {string} options.base64String - Base64 encoded string.
358-
* @returns {Uint8Array} Decoded bytes.
359-
*/
360-
function _base64ToBytes({base64String} = {}) {
361-
// use atob in browser, Buffer in Node
362-
if(typeof atob !== 'undefined') {
363-
const binaryString = atob(base64String);
364-
const bytes = new Uint8Array(binaryString.length);
365-
for(let i = 0; i < binaryString.length; i++) {
366-
bytes[i] = binaryString.charCodeAt(i);
367-
}
368-
return bytes;
369-
}
370-
371-
// Node.js environment
372-
return Buffer.from(base64String, 'base64');
373-
}
374-
375353
// ========================
376354
// JSON pointer utilities
377355
// ========================

lib/util.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*!
2+
* Copyright (c) 2025 Digital Bazaar, Inc. All rights reserved.
3+
*/
4+
5+
/**
6+
* Decode base64 string to bytes (Node.js implmentation).
7+
*
8+
* @param {object} options - Options object.
9+
* @param {string} options.base64String - Base64 encoded string.
10+
* @returns {Uint8Array} Decoded bytes.
11+
*/
12+
export function base64ToBytes({base64String} = {}) {
13+
return new Uint8Array(Buffer.from(base64String, 'base64'));
14+
}
15+
16+
/**
17+
* Encode bytes to base64 string (Node.js implementation).
18+
*
19+
* @param {object} options - Options object.
20+
* @param {Uint8Array} options.bytes - Bytes to encode.
21+
* @returns {string} Base64 encoded string.
22+
*/
23+
export function bytesToBase64({bytes} = {}) {
24+
return Buffer.from(bytes).toString('base64');
25+
}

lib/utilBrowser.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*!
2+
* Copyright (c) 2025 Digital Bazaar, Inc. All rights reserved.
3+
*/
4+
5+
/**
6+
* Decode base64 string to bytes (Browser implmentation).
7+
*
8+
* @param {object} options - Options object.
9+
* @param {string} options.base64String - Base64 encoded string.
10+
* @returns {Uint8Array} Decoded bytes.
11+
*/
12+
export function base64ToBytes({base64String} = {}) {
13+
// Use modern API
14+
if(typeof Uint8Array.fromBase64 === 'function') {
15+
return Uint8Array.fromBase64(base64String);
16+
}
17+
18+
// Fallback to atob
19+
const binaryString = atob(base64String);
20+
const bytes = new Uint8Array(binaryString.length);
21+
for(let i = 0; i < binaryString.length; i++) {
22+
bytes[i] = binaryString.charCodeAt(i);
23+
}
24+
return bytes;
25+
}
26+
27+
/**
28+
* Encode bytes to base64 string (Browser implementation).
29+
*
30+
* @param {object} options - Options object.
31+
* @param {Uint8Array} options.bytes - Bytes to encode.
32+
* @returns {string} Base64 encoded string.
33+
*/
34+
export function bytesToBase64({bytes} = {}) {
35+
// Use modern API
36+
if(typeof Uint8Array.prototype.toBase64 === 'function') {
37+
return bytes.toBase64();
38+
}
39+
40+
// Fallback to btoa
41+
let binaryString = '';
42+
for(let i = 0; i < bytes.length; i++) {
43+
binaryString += String.fromCharCode(bytes[i]);
44+
}
45+
return btoa(binaryString);
46+
}

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"description": "Bedrock Web Wallet",
55
"type": "module",
66
"exports": "./lib/index.js",
7+
"browser": {
8+
"./lib/util.js": "./lib/utilBrowser.js"
9+
},
710
"files": [
811
"lib/*"
912
],

0 commit comments

Comments
 (0)