From 230618d2a0654a9a56f3572247e31223fb01b5bc Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 25 Aug 2026 20:17:10 -0700 Subject: [PATCH] Fix Set Name Buffer reference for browser Replace Node-only Buffer.byteLength with a TextEncoder-based UTF-8 byte length helper so the Set Name page and byte counter work in the browser. By specifier. --- src/components/app-body/set-name/index.js | 3 +- src/services/memo-set-name.js | 3 +- src/services/set-name-page.js | 3 +- src/services/utf8.js | 15 +++++++++ test/unit/utf8.test.js | 38 +++++++++++++++++++++++ 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/services/utf8.js create mode 100644 test/unit/utf8.test.js diff --git a/src/components/app-body/set-name/index.js b/src/components/app-body/set-name/index.js index 8d6a849..787737d 100644 --- a/src/components/app-body/set-name/index.js +++ b/src/components/app-body/set-name/index.js @@ -12,6 +12,7 @@ import { useNavigate } from 'react-router-dom' // Local libraries import MemoSetName from '../../../services/memo-set-name' import SetNamePage from '../../../services/set-name-page' +import { byteLength } from '../../../services/utf8' function SetName (props) { const { appData } = props @@ -22,7 +23,7 @@ function SetName (props) { const [err, setErr] = useState('') const [settingName, setSettingName] = useState(false) - const remaining = maxBytes - Buffer.byteLength(input, 'utf8') + const remaining = maxBytes - byteLength(input) async function handleSubmit (event) { event.preventDefault() diff --git a/src/services/memo-set-name.js b/src/services/memo-set-name.js index 1e3af95..6b2aa99 100644 --- a/src/services/memo-set-name.js +++ b/src/services/memo-set-name.js @@ -17,6 +17,7 @@ */ const MemoAction = require('./memo-action') +const { byteLength } = require('./utf8') const MEMO_SET_NAME_PREFIX = '6d01' const MAX_NAME_BYTES = 77 @@ -38,7 +39,7 @@ class MemoSetName extends MemoAction { // A name is over-length when it exceeds the byte limit. isTooLong (name) { - return Buffer.byteLength(name, 'utf8') > MAX_NAME_BYTES + return byteLength(name) > MAX_NAME_BYTES } // Compose and broadcast a Memo set-name transaction for the given name. diff --git a/src/services/set-name-page.js b/src/services/set-name-page.js index 5fccc00..73fc7cd 100644 --- a/src/services/set-name-page.js +++ b/src/services/set-name-page.js @@ -15,6 +15,7 @@ const PageController = require('./page-controller') const MemoSetName = require('./memo-set-name') +const { byteLength } = require('./utf8') const SET_NAME_PATH = '/memo/set-name' const ACCOUNT_PATH = '/account' @@ -30,7 +31,7 @@ class SetNamePage extends PageController { // Bytes remaining before the name limit is reached. remainingCount () { - return MemoSetName.MAX_NAME_BYTES - Buffer.byteLength(this.input, 'utf8') + return MemoSetName.MAX_NAME_BYTES - byteLength(this.input) } // Set the in-flight setting-name flag. diff --git a/src/services/utf8.js b/src/services/utf8.js new file mode 100644 index 0000000..52d4531 --- /dev/null +++ b/src/services/utf8.js @@ -0,0 +1,15 @@ +/* + UTF-8 byte-length helper for browser and Node. + + The Node global `Buffer` is not available in the browser, so byte counting + (used by the Memo set-name byte counter and length check) must not depend on + it. TextEncoder is available in both environments and reports the UTF-8 byte + length of a string. +*/ + +// Return the number of UTF-8 bytes in a string. +function byteLength (str) { + return new TextEncoder().encode(String(str)).length +} + +module.exports = { byteLength } diff --git a/test/unit/utf8.test.js b/test/unit/utf8.test.js new file mode 100644 index 0000000..e4e3e3a --- /dev/null +++ b/test/unit/utf8.test.js @@ -0,0 +1,38 @@ +/* + Unit tests for the UTF-8 byte-length helper (src/services/utf8.js). + + The helper must report the same UTF-8 byte length as Node's Buffer without + depending on the Node-only `Buffer` global, so the browser build (which has + no Buffer) can count bytes for the Memo set-name counter and length check. +*/ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert/strict') + +const { byteLength } = require('../../src/services/utf8') + +test('byteLength matches Buffer.byteLength for ASCII text', () => { + for (const s of ['', 'trout', 'a longer name with spaces', 'x'.repeat(77)]) { + assert.equal(byteLength(s), Buffer.byteLength(s, 'utf8')) + } +}) + +test('byteLength matches Buffer.byteLength for multi-byte characters', () => { + for (const s of ['é', 'é'.repeat(38), '😀', '😀'.repeat(20), '日本語']) { + assert.equal(byteLength(s), Buffer.byteLength(s, 'utf8')) + } +}) + +test('byteLength counts UTF-8 bytes, not characters', () => { + // 'é' is 1 character but 2 UTF-8 bytes; an emoji is 1 character but 4 bytes. + assert.equal(byteLength('é'), 2) + assert.equal(byteLength('😀'), 4) + assert.equal(byteLength('a'), 1) +}) + +test('byteLength coerces non-string input to a string', () => { + assert.equal(byteLength(42), 2) + assert.equal(byteLength(null), 4) // String(null) === 'null' +})