Skip to content

Commit da7c6a6

Browse files
committed
refactor: split platform and _utils
1 parent 77f9716 commit da7c6a6

23 files changed

+157
-141
lines changed

base58.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { typedView } from './array.js'
22
import { assertUint8 } from './assert.js'
3-
import { nativeDecoder, nativeEncoder, isHermes, E_STRING } from './fallback/_utils.js'
3+
import { E_STRING } from './fallback/_utils.js'
4+
import { nativeDecoder, nativeEncoder, isHermes } from './fallback/platform.js'
45
import { encodeAscii, decodeAscii } from './fallback/latin1.js'
56

67
const alphabet58 = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']

base64.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { assertUint8, assertEmptyRest } from './assert.js'
22
import { typedView } from './array.js'
3-
import { isHermes, skipWeb, E_STRING } from './fallback/_utils.js'
3+
import { E_STRING } from './fallback/_utils.js'
4+
import { isHermes, skipWeb } from './fallback/platform.js'
45
import { decodeLatin1, encodeLatin1 } from './fallback/latin1.js'
56
import * as js from './fallback/base64.js'
67

bech32.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assertUint8 } from './assert.js'
2-
import { nativeEncoder, E_STRING } from './fallback/_utils.js'
2+
import { E_STRING } from './fallback/_utils.js'
3+
import { nativeEncoder } from './fallback/platform.js'
34
import { decodeAscii, encodeAscii, encodeLatin1 } from './fallback/latin1.js'
45

56
const alphabet = [...'qpzry9x8gf2tvdw0s3jn54khce6mua7l']

fallback/_utils.js

Lines changed: 3 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,6 @@
1-
const { Buffer } = globalThis
2-
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
3-
export const nativeBuffer = haveNativeBuffer ? Buffer : null
4-
export const isHermes = /* @__PURE__ */ (() => !!globalThis.HermesInternal)()
5-
export const isDeno = /* @__PURE__ */ (() => !!globalThis.Deno)()
6-
export const isLE = /* @__PURE__ */ (() => new Uint8Array(Uint16Array.of(258).buffer)[0] === 2)()
7-
export const E_STRICT_UNICODE = 'Input is not well-formed Unicode'
8-
9-
// We consider Node.js TextDecoder/TextEncoder native
10-
let isNative = (x) => x && (haveNativeBuffer || `${x}`.includes('[native code]'))
11-
if (!haveNativeBuffer && isNative(() => {})) isNative = () => false // e.g. XS, we don't want false positives
12-
13-
export const nativeEncoder = /* @__PURE__ */ (() =>
14-
isNative(globalThis.TextEncoder) ? new TextEncoder() : null)()
15-
export const nativeDecoder = /* @__PURE__ */ (() =>
16-
isNative(globalThis.TextDecoder) ? new TextDecoder('utf-8', { ignoreBOM: true }) : null)()
17-
18-
// Actually windows-1252, compatible with ascii and latin1 decoding
19-
// Beware that on non-latin1, i.e. on windows-1252, this is broken in ~all Node.js versions released
20-
// in 2025 due to a regression, so we call it Latin1 as it's usable only for that
21-
export const nativeDecoderLatin1 = /* @__PURE__ */ (() => {
22-
// Not all barebone engines with TextDecoder support something except utf-8, detect
23-
if (nativeDecoder) {
24-
try {
25-
return new TextDecoder('latin1', { ignoreBOM: true })
26-
} catch {}
27-
}
28-
29-
return null
30-
})()
31-
32-
// Block Firefox < 146 specifically from using native hex/base64, as it's very slow there
33-
// Refs: https://bugzilla.mozilla.org/show_bug.cgi?id=1994067 (and linked issues), fixed in 146
34-
// Before that, all versions of Firefox >= 133 are slow
35-
// TODO: this could be removed when < 146 usage diminishes (note ESR)
36-
// We do not worry about false-negatives here but worry about false-positives!
37-
function shouldSkipBuiltins() {
38-
const g = globalThis
39-
// First, attempt to exclude as many things as we can using trivial checks, just in case, and to not hit ua
40-
if (haveNativeBuffer || isHermes || !g.window || g.chrome || !g.navigator) return false
41-
try {
42-
// This was fixed specifically in Firefox 146. Other engines except Hermes (already returned) get this right
43-
new WeakSet().add(Symbol()) // eslint-disable-line symbol-description
44-
return false
45-
} catch {
46-
// In catch and not after in case if something too smart optimizes out code in try. False-negative is acceptable in that case
47-
if (!('onmozfullscreenerror' in g)) return false // Firefox has it (might remove in the future, but we don't care)
48-
return /firefox/i.test(g.navigator.userAgent || '') // as simple as we can
49-
}
50-
51-
/* c8 ignore next */
52-
return false // eslint-disable-line no-unreachable
53-
}
54-
55-
export const skipWeb = /* @__PURE__ */ shouldSkipBuiltins()
56-
57-
function decodePartAddition(a, start, end, m) {
58-
let o = ''
59-
let i = start
60-
for (const last3 = end - 3; i < last3; i += 4) {
61-
const x0 = a[i]
62-
const x1 = a[i + 1]
63-
const x2 = a[i + 2]
64-
const x3 = a[i + 3]
65-
o += m[x0]
66-
o += m[x1]
67-
o += m[x2]
68-
o += m[x3]
69-
}
1+
export * from './platform.js'
702

71-
while (i < end) o += m[a[i++]]
72-
return o
73-
}
74-
75-
// Decoding with templates is faster on Hermes
76-
function decodePartTemplates(a, start, end, m) {
77-
let o = ''
78-
let i = start
79-
for (const last15 = end - 15; i < last15; i += 16) {
80-
const x0 = a[i]
81-
const x1 = a[i + 1]
82-
const x2 = a[i + 2]
83-
const x3 = a[i + 3]
84-
const x4 = a[i + 4]
85-
const x5 = a[i + 5]
86-
const x6 = a[i + 6]
87-
const x7 = a[i + 7]
88-
const x8 = a[i + 8]
89-
const x9 = a[i + 9]
90-
const x10 = a[i + 10]
91-
const x11 = a[i + 11]
92-
const x12 = a[i + 12]
93-
const x13 = a[i + 13]
94-
const x14 = a[i + 14]
95-
const x15 = a[i + 15]
96-
o += `${m[x0]}${m[x1]}${m[x2]}${m[x3]}${m[x4]}${m[x5]}${m[x6]}${m[x7]}${m[x8]}${m[x9]}${m[x10]}${m[x11]}${m[x12]}${m[x13]}${m[x14]}${m[x15]}`
97-
}
98-
99-
while (i < end) o += m[a[i++]]
100-
return o
101-
}
102-
103-
const decodePart = isHermes ? decodePartTemplates : decodePartAddition
104-
export function decode2string(arr, start, end, m) {
105-
if (end - start > 30_000) {
106-
// Limit concatenation to avoid excessive GC
107-
// Thresholds checked on Hermes for toHex
108-
const concat = []
109-
for (let i = start; i < end; ) {
110-
const step = i + 500
111-
const iNext = step > end ? end : step
112-
concat.push(decodePart(arr, i, iNext, m))
113-
i = iNext
114-
}
115-
116-
const res = concat.join('')
117-
concat.length = 0
118-
return res
119-
}
120-
121-
return decodePart(arr, start, end, m)
122-
}
3+
const { Buffer } = globalThis
1234

1245
export function assert(condition, msg) {
1256
if (!condition) throw new Error(msg)
@@ -132,3 +13,4 @@ export const toBuf = (x) =>
13213
: Buffer.from(x.buffer, x.byteOffset, x.byteLength)
13314

13415
export const E_STRING = 'Input is not a string'
16+
export const E_STRICT_UNICODE = 'Input is not well-formed Unicode'

fallback/base32.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { assertUint8 } from '../assert.js'
2-
import { nativeEncoder, nativeDecoder, isHermes } from './_utils.js'
2+
import { nativeEncoder, nativeDecoder, isHermes } from './platform.js'
33
import { encodeAscii, decodeAscii } from './latin1.js'
44

55
// See https://datatracker.ietf.org/doc/html/rfc4648

fallback/base64.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { assertUint8 } from '../assert.js'
2-
import { nativeEncoder, nativeDecoder } from './_utils.js'
2+
import { nativeEncoder, nativeDecoder } from './platform.js'
33
import { encodeAscii, decodeAscii } from './latin1.js'
44

55
// See https://datatracker.ietf.org/doc/html/rfc4648

fallback/hex.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assertUint8 } from '../assert.js'
2-
import { nativeDecoder, nativeEncoder, decode2string, E_STRING } from './_utils.js'
2+
import { E_STRING } from './_utils.js'
3+
import { nativeDecoder, nativeEncoder, decode2string } from './platform.js'
34
import { encodeAscii, decodeAscii } from './latin1.js'
45

56
let hexArray // array of 256 bytes converted to two-char hex strings

fallback/latin1.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
isDeno,
88
isLE,
99
skipWeb,
10-
} from './_utils.js'
10+
} from './platform.js'
1111

1212
const atob = /* @__PURE__ */ (() => globalThis.atob)()
1313
const web64 = /* @__PURE__ */ (() => Uint8Array.prototype.toBase64)()

fallback/percent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { decodeAscii, encodeLatin1 } from './latin1.js'
2-
import { decode2string } from './_utils.js'
2+
import { decode2string } from './platform.js'
33

44
const ERR = 'percentEncodeSet must be a string of unique increasing codepoints in range 0x20 - 0x7e'
55
const percentMap = new Map()

fallback/platform.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
const { Buffer } = globalThis
2+
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
3+
export const nativeBuffer = haveNativeBuffer ? Buffer : null
4+
export const isHermes = /* @__PURE__ */ (() => !!globalThis.HermesInternal)()
5+
export const isDeno = /* @__PURE__ */ (() => !!globalThis.Deno)()
6+
export const isLE = /* @__PURE__ */ (() => new Uint8Array(Uint16Array.of(258).buffer)[0] === 2)()
7+
8+
// We consider Node.js TextDecoder/TextEncoder native
9+
let isNative = (x) => x && (haveNativeBuffer || `${x}`.includes('[native code]'))
10+
if (!haveNativeBuffer && isNative(() => {})) isNative = () => false // e.g. XS, we don't want false positives
11+
12+
export const nativeEncoder = /* @__PURE__ */ (() =>
13+
isNative(globalThis.TextEncoder) ? new TextEncoder() : null)()
14+
export const nativeDecoder = /* @__PURE__ */ (() =>
15+
isNative(globalThis.TextDecoder) ? new TextDecoder('utf-8', { ignoreBOM: true }) : null)()
16+
17+
// Actually windows-1252, compatible with ascii and latin1 decoding
18+
// Beware that on non-latin1, i.e. on windows-1252, this is broken in ~all Node.js versions released
19+
// in 2025 due to a regression, so we call it Latin1 as it's usable only for that
20+
export const nativeDecoderLatin1 = /* @__PURE__ */ (() => {
21+
// Not all barebone engines with TextDecoder support something except utf-8, detect
22+
if (nativeDecoder) {
23+
try {
24+
return new TextDecoder('latin1', { ignoreBOM: true })
25+
} catch {}
26+
}
27+
28+
return null
29+
})()
30+
31+
// Block Firefox < 146 specifically from using native hex/base64, as it's very slow there
32+
// Refs: https://bugzilla.mozilla.org/show_bug.cgi?id=1994067 (and linked issues), fixed in 146
33+
// Before that, all versions of Firefox >= 133 are slow
34+
// TODO: this could be removed when < 146 usage diminishes (note ESR)
35+
// We do not worry about false-negatives here but worry about false-positives!
36+
function shouldSkipBuiltins() {
37+
const g = globalThis
38+
// First, attempt to exclude as many things as we can using trivial checks, just in case, and to not hit ua
39+
if (haveNativeBuffer || isHermes || !g.window || g.chrome || !g.navigator) return false
40+
try {
41+
// This was fixed specifically in Firefox 146. Other engines except Hermes (already returned) get this right
42+
new WeakSet().add(Symbol()) // eslint-disable-line symbol-description
43+
return false
44+
} catch {
45+
// In catch and not after in case if something too smart optimizes out code in try. False-negative is acceptable in that case
46+
if (!('onmozfullscreenerror' in g)) return false // Firefox has it (might remove in the future, but we don't care)
47+
return /firefox/i.test(g.navigator.userAgent || '') // as simple as we can
48+
}
49+
50+
/* c8 ignore next */
51+
return false // eslint-disable-line no-unreachable
52+
}
53+
54+
export const skipWeb = /* @__PURE__ */ shouldSkipBuiltins()
55+
56+
function decodePartAddition(a, start, end, m) {
57+
let o = ''
58+
let i = start
59+
for (const last3 = end - 3; i < last3; i += 4) {
60+
const x0 = a[i]
61+
const x1 = a[i + 1]
62+
const x2 = a[i + 2]
63+
const x3 = a[i + 3]
64+
o += m[x0]
65+
o += m[x1]
66+
o += m[x2]
67+
o += m[x3]
68+
}
69+
70+
while (i < end) o += m[a[i++]]
71+
return o
72+
}
73+
74+
// Decoding with templates is faster on Hermes
75+
function decodePartTemplates(a, start, end, m) {
76+
let o = ''
77+
let i = start
78+
for (const last15 = end - 15; i < last15; i += 16) {
79+
const x0 = a[i]
80+
const x1 = a[i + 1]
81+
const x2 = a[i + 2]
82+
const x3 = a[i + 3]
83+
const x4 = a[i + 4]
84+
const x5 = a[i + 5]
85+
const x6 = a[i + 6]
86+
const x7 = a[i + 7]
87+
const x8 = a[i + 8]
88+
const x9 = a[i + 9]
89+
const x10 = a[i + 10]
90+
const x11 = a[i + 11]
91+
const x12 = a[i + 12]
92+
const x13 = a[i + 13]
93+
const x14 = a[i + 14]
94+
const x15 = a[i + 15]
95+
o += `${m[x0]}${m[x1]}${m[x2]}${m[x3]}${m[x4]}${m[x5]}${m[x6]}${m[x7]}${m[x8]}${m[x9]}${m[x10]}${m[x11]}${m[x12]}${m[x13]}${m[x14]}${m[x15]}`
96+
}
97+
98+
while (i < end) o += m[a[i++]]
99+
return o
100+
}
101+
102+
const decodePart = isHermes ? decodePartTemplates : decodePartAddition
103+
export function decode2string(arr, start, end, m) {
104+
if (end - start > 30_000) {
105+
// Limit concatenation to avoid excessive GC
106+
// Thresholds checked on Hermes for toHex
107+
const concat = []
108+
for (let i = start; i < end; ) {
109+
const step = i + 500
110+
const iNext = step > end ? end : step
111+
concat.push(decodePart(arr, i, iNext, m))
112+
i = iNext
113+
}
114+
115+
const res = concat.join('')
116+
concat.length = 0
117+
return res
118+
}
119+
120+
return decodePart(arr, start, end, m)
121+
}

0 commit comments

Comments
 (0)