@exodus/bytes 1.0.0-rc.2 → 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,162 @@
1
+ import { assertUint8 } from '../assert.js'
2
+ import { nativeEncoder, nativeDecoder } from './_utils.js'
3
+
4
+ // See https://datatracker.ietf.org/doc/html/rfc4648
5
+
6
+ const BASE64 = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/']
7
+ const BASE64URL = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_']
8
+ const BASE64_HELPERS = {}
9
+ const BASE64URL_HELPERS = {}
10
+
11
+ export const E_CHAR = 'Invalid character in base64 input'
12
+ export const E_PADDING = 'Invalid base64 padding'
13
+ export const E_LENGTH = 'Invalid base64 length'
14
+ export const E_LAST = 'Invalid last chunk'
15
+
16
+ // Alternatively, we could have mapped 0-255 bytes to charcodes and just used btoa(ascii),
17
+ // but that approach is _slower_ than our toBase64js function, even on Hermes
18
+
19
+ // We construct output by concatenating chars, this seems to be fine enough on modern JS engines
20
+ export function toBase64(arr, isURL, padding) {
21
+ assertUint8(arr)
22
+ const fullChunks = Math.floor(arr.length / 3)
23
+ const fullChunksBytes = fullChunks * 3
24
+ let o = ''
25
+ let i = 0
26
+
27
+ const alphabet = isURL ? BASE64URL : BASE64
28
+ const helpers = isURL ? BASE64URL_HELPERS : BASE64_HELPERS
29
+ if (!helpers.pairs) {
30
+ helpers.pairs = []
31
+ if (nativeDecoder) {
32
+ // Lazy to save memory in case if this is not needed
33
+ helpers.codepairs = new Uint16Array(64 * 64)
34
+ const u16 = helpers.codepairs
35
+ const u8 = new Uint8Array(u16.buffer, u16.byteOffset, u16.byteLength) // write as 1-byte to ignore BE/LE difference
36
+ for (let i = 0; i < 64; i++) {
37
+ const ic = alphabet[i].charCodeAt(0)
38
+ for (let j = 0; j < 64; j++) u8[(i << 7) | (j << 1)] = u8[(j << 7) | ((i << 1) + 1)] = ic
39
+ }
40
+ } else {
41
+ const p = helpers.pairs
42
+ for (let i = 0; i < 64; i++) {
43
+ for (let j = 0; j < 64; j++) p.push(`${alphabet[i]}${alphabet[j]}`)
44
+ }
45
+ }
46
+ }
47
+
48
+ const { pairs, codepairs } = helpers
49
+
50
+ // Fast path for complete blocks
51
+ // This whole loop can be commented out, the algorithm won't change, it's just an optimization of the next loop
52
+ if (nativeDecoder) {
53
+ const oa = new Uint16Array(fullChunks * 2)
54
+ for (let j = 0; i < fullChunksBytes; i += 3) {
55
+ const a = arr[i]
56
+ const b = arr[i + 1]
57
+ const c = arr[i + 2]
58
+ oa[j++] = codepairs[(a << 4) | (b >> 4)]
59
+ oa[j++] = codepairs[((b & 0x0f) << 8) | c]
60
+ }
61
+
62
+ o = nativeDecoder.decode(oa)
63
+ } else {
64
+ for (; i < fullChunksBytes; i += 3) {
65
+ const a = arr[i]
66
+ const b = arr[i + 1]
67
+ const c = arr[i + 2]
68
+ o += pairs[(a << 4) | (b >> 4)] + pairs[((b & 0x0f) << 8) | c]
69
+ }
70
+ }
71
+
72
+ // If we have something left, process it with a full algo
73
+ let carry = 0
74
+ let shift = 2 // First byte needs to be shifted by 2 to get 6 bits
75
+ const length = arr.length
76
+ for (; i < length; i++) {
77
+ const x = arr[i]
78
+ o += alphabet[carry | (x >> shift)] // shift >= 2, so this fits
79
+ if (shift === 6) {
80
+ shift = 0
81
+ o += alphabet[x & 0x3f]
82
+ }
83
+
84
+ carry = (x << (6 - shift)) & 0x3f
85
+ shift += 2 // Each byte prints 6 bits and leaves 2 bits
86
+ }
87
+
88
+ if (shift !== 2) o += alphabet[carry] // shift 2 means we have no carry left
89
+ if (padding) o += ['', '==', '='][length - fullChunksBytes]
90
+
91
+ return o
92
+ }
93
+
94
+ // TODO: can this be optimized? This only affects non-Hermes barebone engines though
95
+ const mapSize = nativeEncoder ? 256 : 65_536 // we have to store 64 KiB map or recheck everything if we can't decode to byte array
96
+
97
+ // Last chunk is rechecked at API
98
+ export function fromBase64(str, isURL) {
99
+ let inputLength = str.length
100
+ while (str[inputLength - 1] === '=') inputLength--
101
+ const paddingLength = str.length - inputLength
102
+ const tailLength = inputLength % 4
103
+ const mainLength = inputLength - tailLength // multiples of 4
104
+ if (tailLength === 1) throw new SyntaxError(E_LENGTH)
105
+ if (paddingLength > 3 || (paddingLength !== 0 && str.length % 4 !== 0)) {
106
+ throw new SyntaxError(E_PADDING)
107
+ }
108
+
109
+ const alphabet = isURL ? BASE64URL : BASE64
110
+ const helpers = isURL ? BASE64URL_HELPERS : BASE64_HELPERS
111
+
112
+ if (!helpers.fromMap) {
113
+ helpers.fromMap = new Int8Array(mapSize).fill(-1) // no regex input validation here, so we map all other bytes to -1 and recheck sign
114
+ alphabet.forEach((c, i) => (helpers.fromMap[c.charCodeAt(0)] = i))
115
+ }
116
+
117
+ const m = helpers.fromMap
118
+
119
+ const arr = new Uint8Array(Math.floor((inputLength * 3) / 4))
120
+ let at = 0
121
+ let i = 0
122
+
123
+ if (nativeEncoder) {
124
+ const codes = nativeEncoder.encode(str)
125
+ if (codes.length !== str.length) throw new SyntaxError(E_CHAR) // non-ascii
126
+ while (i < mainLength) {
127
+ const a = (m[codes[i++]] << 18) | (m[codes[i++]] << 12) | (m[codes[i++]] << 6) | m[codes[i++]]
128
+ if (a < 0) throw new SyntaxError(E_CHAR)
129
+ arr[at++] = a >> 16
130
+ arr[at++] = (a >> 8) & 0xff
131
+ arr[at++] = a & 0xff
132
+ }
133
+ } else {
134
+ while (i < mainLength) {
135
+ const a =
136
+ (m[str.charCodeAt(i++)] << 18) |
137
+ (m[str.charCodeAt(i++)] << 12) |
138
+ (m[str.charCodeAt(i++)] << 6) |
139
+ m[str.charCodeAt(i++)]
140
+ if (a < 0) throw new SyntaxError(E_CHAR)
141
+ arr[at++] = a >> 16
142
+ arr[at++] = (a >> 8) & 0xff
143
+ arr[at++] = a & 0xff
144
+ }
145
+ }
146
+
147
+ // Can be 0, 2 or 3, verified by padding checks already
148
+ if (tailLength < 2) return arr // 0
149
+ const ab = (m[str.charCodeAt(i++)] << 6) | m[str.charCodeAt(i++)]
150
+ if (ab < 0) throw new SyntaxError(E_CHAR)
151
+ arr[at++] = ab >> 4
152
+ if (tailLength < 3) {
153
+ if (ab & 0xf) throw new SyntaxError(E_LAST)
154
+ return arr // 2
155
+ }
156
+
157
+ const c = m[str.charCodeAt(i++)]
158
+ if (c < 0) throw new SyntaxError(E_CHAR)
159
+ arr[at++] = ((ab << 4) & 0xff) | (c >> 2)
160
+ if (c & 0x3) throw new SyntaxError(E_LAST)
161
+ return arr // 3
162
+ }
@@ -0,0 +1,107 @@
1
+ import { assertUint8 } from '../assert.js'
2
+ import { nativeEncoder } from './_utils.js'
3
+
4
+ let hexArray
5
+ let dehexArray
6
+
7
+ export const E_HEX = 'Input is not a hex string'
8
+
9
+ function toHexPart(arr, start, end) {
10
+ let o = ''
11
+ let i = start
12
+ const last3 = end - 3
13
+ // Unrolled loop is faster
14
+ while (i < last3) {
15
+ const a = arr[i++]
16
+ const b = arr[i++]
17
+ const c = arr[i++]
18
+ const d = arr[i++]
19
+ o += hexArray[a]
20
+ o += hexArray[b]
21
+ o += hexArray[c]
22
+ o += hexArray[d]
23
+ }
24
+
25
+ while (i < end) o += hexArray[arr[i++]]
26
+ return o
27
+ }
28
+
29
+ export function toHex(arr) {
30
+ assertUint8(arr)
31
+
32
+ if (!hexArray) hexArray = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))
33
+ const length = arr.length // this helps Hermes
34
+
35
+ if (length > 30_000) {
36
+ // Limit concatenation to avoid excessive GC
37
+ // Thresholds checked on Hermes
38
+ const concat = []
39
+ for (let i = 0; i < length; ) {
40
+ const step = i + 500
41
+ const end = step > length ? length : step
42
+ concat.push(toHexPart(arr, i, end))
43
+ i = end
44
+ }
45
+
46
+ const res = concat.join('')
47
+ concat.length = 0
48
+ return res
49
+ }
50
+
51
+ return toHexPart(arr, 0, length)
52
+ }
53
+
54
+ // TODO: can this be optimized? This only affects non-Hermes barebone engines though
55
+ const mapSize = nativeEncoder ? 256 : 65_536 // we have to store 64 KiB map or recheck everything if we can't decode to byte array
56
+
57
+ export function fromHex(str) {
58
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
59
+ if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
60
+
61
+ // We don't use native Buffer impl, as rechecking input make it slower than pure js
62
+ // This path is used only on older engines though
63
+
64
+ if (!dehexArray) {
65
+ dehexArray = new Int8Array(mapSize).fill(-1) // no regex input validation here, so we map all other bytes to -1 and recheck sign
66
+ for (let i = 0; i < 16; i++) {
67
+ const s = i.toString(16)
68
+ dehexArray[s.charCodeAt(0)] = dehexArray[s.toUpperCase().charCodeAt(0)] = i
69
+ }
70
+ }
71
+
72
+ const length = str.length / 2 // this helps Hermes in loops
73
+ const arr = new Uint8Array(length)
74
+ let j = 0
75
+ if (nativeEncoder) {
76
+ // Native encoder path is beneficial even for small arrays in Hermes
77
+ const codes = nativeEncoder.encode(str)
78
+ if (codes.length !== str.length) throw new SyntaxError(E_HEX) // non-ascii
79
+ const last3 = length - 3 // Unroll nativeEncoder path as this is what modern Hermes takes and a small perf improvement is nice there
80
+ let i = 0
81
+ while (i < last3) {
82
+ const a = (dehexArray[codes[j++]] << 4) | dehexArray[codes[j++]]
83
+ const b = (dehexArray[codes[j++]] << 4) | dehexArray[codes[j++]]
84
+ const c = (dehexArray[codes[j++]] << 4) | dehexArray[codes[j++]]
85
+ const d = (dehexArray[codes[j++]] << 4) | dehexArray[codes[j++]]
86
+ if (a < 0 || b < 0 || c < 0 || d < 0) throw new SyntaxError(E_HEX)
87
+ arr[i++] = a
88
+ arr[i++] = b
89
+ arr[i++] = c
90
+ arr[i++] = d
91
+ }
92
+
93
+ while (i < length) {
94
+ const res = (dehexArray[codes[j++]] << 4) | dehexArray[codes[j++]]
95
+ if (res < 0) throw new SyntaxError(E_HEX)
96
+ arr[i++] = res
97
+ }
98
+ } else {
99
+ for (let i = 0; i < length; i++) {
100
+ const res = (dehexArray[str.charCodeAt(j++)] << 4) | dehexArray[str.charCodeAt(j++)]
101
+ if (res < 0) throw new SyntaxError(E_HEX)
102
+ arr[i] = res
103
+ }
104
+ }
105
+
106
+ return arr
107
+ }
@@ -0,0 +1,280 @@
1
+ export const E_STRICT = 'Input is not well-formed utf8'
2
+ export const E_STRICT_UNICODE = 'Input is not well-formed Unicode'
3
+
4
+ const replacementPoint = 0xff_fd
5
+
6
+ // https://encoding.spec.whatwg.org/#utf-8-decoder
7
+ // We are most likely in loose mode, for non-loose escape & decodeURIComponent solved everything
8
+ export function decode(arr, loose) {
9
+ const start = 0
10
+ const end = arr.length
11
+ let out = ''
12
+ const tmp = []
13
+
14
+ for (let i = start; i < end; i++) {
15
+ if (tmp.length > 0x2_00) {
16
+ // far below MAX_ARGUMENTS_LENGTH in npmjs.com/buffer, we use smaller chunks
17
+ // length can be off by a few as large code points produce two utf-16 char codes, also we overshoot in unrolled loop
18
+ out += String.fromCharCode.apply(String, tmp)
19
+ tmp.length = 0
20
+ }
21
+
22
+ const byte = arr[i]
23
+ if (byte < 0x80) {
24
+ // Fast path ascii
25
+ tmp.push(byte)
26
+ // Unroll the loop a bit for faster ops, overshoot by 20 chars
27
+ for (let j = 0; j < 5; j++) {
28
+ if (i + 1 >= end) break
29
+ const byte1 = arr[i + 1]
30
+ if (byte1 >= 0x80) break
31
+ tmp.push(byte1)
32
+ i++
33
+ if (i + 1 >= end) break
34
+ const byte2 = arr[i + 1]
35
+ if (byte2 >= 0x80) break
36
+ tmp.push(byte2)
37
+ i++
38
+ if (i + 1 >= end) break
39
+ const byte3 = arr[i + 1]
40
+ if (byte3 >= 0x80) break
41
+ tmp.push(byte3)
42
+ i++
43
+ if (i + 1 >= end) break
44
+ const byte4 = arr[i + 1]
45
+ if (byte4 >= 0x80) break
46
+ tmp.push(byte4)
47
+ i++
48
+ }
49
+ } else if (byte < 0xc2) {
50
+ if (!loose) throw new TypeError(E_STRICT)
51
+ tmp.push(replacementPoint)
52
+ } else if (byte < 0xe0) {
53
+ // need 1 more
54
+ if (i + 1 >= end) {
55
+ if (!loose) throw new TypeError(E_STRICT)
56
+ tmp.push(replacementPoint)
57
+ break
58
+ }
59
+
60
+ const byte1 = arr[i + 1]
61
+ if (byte1 < 0x80 || byte1 > 0xbf) {
62
+ if (!loose) throw new TypeError(E_STRICT)
63
+ tmp.push(replacementPoint)
64
+ continue
65
+ }
66
+
67
+ i++
68
+ tmp.push(((byte & 0x1f) << 6) | (byte1 & 0x3f))
69
+ } else if (byte < 0xf0) {
70
+ // need 2 more
71
+ if (i + 1 >= end) {
72
+ if (!loose) throw new TypeError(E_STRICT)
73
+ tmp.push(replacementPoint)
74
+ break
75
+ }
76
+
77
+ const lower = byte === 0xe0 ? 0xa0 : 0x80
78
+ const upper = byte === 0xed ? 0x9f : 0xbf
79
+ const byte1 = arr[i + 1]
80
+ if (byte1 < lower || byte1 > upper) {
81
+ if (!loose) throw new TypeError(E_STRICT)
82
+ tmp.push(replacementPoint)
83
+ continue
84
+ }
85
+
86
+ i++
87
+ if (i + 1 >= end) {
88
+ if (!loose) throw new TypeError(E_STRICT)
89
+ tmp.push(replacementPoint)
90
+ break
91
+ }
92
+
93
+ const byte2 = arr[i + 1]
94
+ if (byte2 < 0x80 || byte2 > 0xbf) {
95
+ if (!loose) throw new TypeError(E_STRICT)
96
+ tmp.push(replacementPoint)
97
+ continue
98
+ }
99
+
100
+ i++
101
+ tmp.push(((byte & 0xf) << 12) | ((byte1 & 0x3f) << 6) | (byte2 & 0x3f))
102
+ } else if (byte <= 0xf4) {
103
+ // need 3 more
104
+ if (i + 1 >= end) {
105
+ if (!loose) throw new TypeError(E_STRICT)
106
+ tmp.push(replacementPoint)
107
+ break
108
+ }
109
+
110
+ const lower = byte === 0xf0 ? 0x90 : 0x80
111
+ const upper = byte === 0xf4 ? 0x8f : 0xbf
112
+ const byte1 = arr[i + 1]
113
+ if (byte1 < lower || byte1 > upper) {
114
+ if (!loose) throw new TypeError(E_STRICT)
115
+ tmp.push(replacementPoint)
116
+ continue
117
+ }
118
+
119
+ i++
120
+ if (i + 1 >= end) {
121
+ if (!loose) throw new TypeError(E_STRICT)
122
+ tmp.push(replacementPoint)
123
+ break
124
+ }
125
+
126
+ const byte2 = arr[i + 1]
127
+ if (byte2 < 0x80 || byte2 > 0xbf) {
128
+ if (!loose) throw new TypeError(E_STRICT)
129
+ tmp.push(replacementPoint)
130
+ continue
131
+ }
132
+
133
+ i++
134
+ if (i + 1 >= end) {
135
+ if (!loose) throw new TypeError(E_STRICT)
136
+ tmp.push(replacementPoint)
137
+ break
138
+ }
139
+
140
+ const byte3 = arr[i + 1]
141
+ if (byte3 < 0x80 || byte3 > 0xbf) {
142
+ if (!loose) throw new TypeError(E_STRICT)
143
+ tmp.push(replacementPoint)
144
+ continue
145
+ }
146
+
147
+ i++
148
+ const codePoint =
149
+ ((byte & 0xf) << 18) | ((byte1 & 0x3f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f)
150
+ if (codePoint > 0xff_ff) {
151
+ // split into char codes as String.fromCharCode is faster than String.fromCodePoint
152
+ const u = codePoint - 0x1_00_00
153
+ tmp.push(0xd8_00 + ((u >> 10) & 0x3_ff), 0xdc_00 + (u & 0x3_ff))
154
+ } else {
155
+ tmp.push(codePoint)
156
+ }
157
+ // eslint-disable-next-line sonarjs/no-duplicated-branches
158
+ } else {
159
+ if (!loose) throw new TypeError(E_STRICT)
160
+ tmp.push(replacementPoint)
161
+ }
162
+ }
163
+
164
+ if (tmp.length > 0) out += String.fromCharCode.apply(String, tmp)
165
+ return out
166
+ }
167
+
168
+ export function encode(string, loose) {
169
+ const length = string.length
170
+ let lead = null
171
+ let small = true
172
+ let bytes = new Uint8Array(length) // assume ascii
173
+ let p = 0
174
+
175
+ for (let i = 0; i < length; i++) {
176
+ const code = string.charCodeAt(i)
177
+ if (code < 0x80) {
178
+ // Fast path for ascii
179
+ if (lead) {
180
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
181
+ bytes[p++] = 0xef
182
+ bytes[p++] = 0xbf
183
+ bytes[p++] = 0xbd
184
+ lead = null
185
+ }
186
+
187
+ bytes[p++] = code
188
+ // Unroll the loop a bit for faster ops
189
+ for (let j = 0; j < 5; j++) {
190
+ if (i + 1 >= length) break
191
+ const c1 = string.charCodeAt(i + 1)
192
+ if (c1 >= 0x80) break
193
+ bytes[p++] = c1
194
+ i++
195
+ if (i + 1 >= length) break
196
+ const c2 = string.charCodeAt(i + 1)
197
+ if (c2 >= 0x80) break
198
+ bytes[p++] = c2
199
+ i++
200
+ if (i + 1 >= length) break
201
+ const c3 = string.charCodeAt(i + 1)
202
+ if (c3 >= 0x80) break
203
+ bytes[p++] = c3
204
+ i++
205
+ if (i + 1 >= length) break
206
+ const c4 = string.charCodeAt(i + 1)
207
+ if (c4 >= 0x80) break
208
+ bytes[p++] = c4
209
+ i++
210
+ }
211
+
212
+ continue
213
+ }
214
+
215
+ if (small) {
216
+ // TODO: use resizable array buffers? will have to return a non-resizeable one
217
+ const bytesNew = new Uint8Array(length * 3) // maximium can be 3x of the string length in charcodes
218
+ bytesNew.set(bytes)
219
+ bytes = bytesNew
220
+ small = false
221
+ }
222
+
223
+ // surrogate, charcodes = [d800 + a & 3ff, dc00 + b & 3ff]; codePoint = 0x1_00_00 | (a << 10) | b
224
+ // lead: d800 - dbff
225
+ // trail: dc00 - dfff
226
+ if (code >= 0xd8_00 && code < 0xe0_00) {
227
+ if (lead && code < 0xdc_00) {
228
+ // a second lead, meaning the previous one was unpaired
229
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
230
+ bytes[p++] = 0xef
231
+ bytes[p++] = 0xbf
232
+ bytes[p++] = 0xbd
233
+ lead = null
234
+ // code is still processed as a new lead
235
+ }
236
+
237
+ if (!lead) {
238
+ if (code > 0xdb_ff || i + 1 >= length) {
239
+ // lead out of range || unpaired
240
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
241
+ bytes[p++] = 0xef
242
+ bytes[p++] = 0xbf
243
+ bytes[p++] = 0xbd
244
+ continue
245
+ }
246
+
247
+ lead = code
248
+ continue
249
+ }
250
+
251
+ // here, codePoint is always between 0x1_00_00 and 0x11_00_00, we encode as 4 bytes
252
+ const codePoint = (((lead - 0xd8_00) << 10) | (code - 0xdc_00)) + 0x1_00_00
253
+ bytes[p++] = (codePoint >> 18) | 0xf0
254
+ bytes[p++] = ((codePoint >> 12) & 0x3f) | 0x80
255
+ bytes[p++] = ((codePoint >> 6) & 0x3f) | 0x80
256
+ bytes[p++] = (codePoint & 0x3f) | 0x80
257
+ lead = null
258
+ continue
259
+ } else if (lead) {
260
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
261
+ bytes[p++] = 0xef
262
+ bytes[p++] = 0xbf
263
+ bytes[p++] = 0xbd
264
+ lead = null
265
+ // code is still processed
266
+ }
267
+
268
+ // We are left with a non-pair char code above ascii, it gets encoded to 2 or 3 bytes
269
+ if (code < 0x8_00) {
270
+ bytes[p++] = (code >> 6) | 0xc0
271
+ bytes[p++] = (code & 0x3f) | 0x80
272
+ } else {
273
+ bytes[p++] = (code >> 12) | 0xe0
274
+ bytes[p++] = ((code >> 6) & 0x3f) | 0x80
275
+ bytes[p++] = (code & 0x3f) | 0x80
276
+ }
277
+ }
278
+
279
+ return bytes.length === p ? bytes : bytes.slice(0, p)
280
+ }
package/hex.js CHANGED
@@ -1,78 +1,21 @@
1
- import { assertTypedArray, assert } from './assert.js'
2
- import { fromTypedArray } from './array.js'
1
+ import { assertUint8 } from './assert.js'
2
+ import { typedView } from './array.js'
3
+ import * as js from './fallback/hex.js'
3
4
 
4
5
  const { Buffer } = globalThis // Buffer is optional, only used when native
5
6
  const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
6
7
  const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
7
8
 
8
- let hexArray
9
- let dehexArray
10
-
11
9
  export function toHex(arr) {
12
- assertTypedArray(arr)
13
- if (!(arr instanceof Uint8Array)) arr = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength)
10
+ assertUint8(arr)
11
+ if (arr.length === 0) return ''
14
12
  if (webHex && arr.toHex === webHex) return arr.toHex()
15
- if (haveNativeBuffer) {
16
- if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.toString('hex')
17
- return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString('hex')
18
- }
19
-
20
- if (!hexArray) hexArray = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))
21
- const length = arr.length // this helps Hermes
22
-
23
- if (length > 30000) {
24
- // Limit concatenation to avoid excessive GC
25
- // Thresholds checked on Hermes
26
- const concat = []
27
- for (let i = 0; i < length; ) {
28
- const step = i + 500
29
- const end = step > length ? length : step
30
- let chunk = ''
31
- for (; i < end; i++) chunk += hexArray[arr[i]]
32
- concat.push(chunk)
33
- }
34
-
35
- const res = concat.join('')
36
- concat.length = 0
37
- return res
38
- }
39
-
40
- let out = ''
41
- for (let i = 0; i < length; i++) out += hexArray[arr[i]]
42
- return out
13
+ if (!haveNativeBuffer) return js.toHex(arr)
14
+ if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.toString('hex')
15
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString('hex')
43
16
  }
44
17
 
45
18
  // Unlike Buffer.from(), throws on invalid input
46
- let fromHex
47
- if (Uint8Array.fromHex) {
48
- fromHex = (str, format = 'uint8') => fromTypedArray(Uint8Array.fromHex(str), format)
49
- } else {
50
- fromHex = (str, format = 'uint8') => {
51
- if (typeof str !== 'string') throw new TypeError('Input is not a string')
52
- assert(str.length % 2 === 0, 'Input is not a hex string')
53
-
54
- // We don't use native Buffer impl, as rechecking input make it slower than pure js
55
- // This path is used only on older engines though
56
-
57
- if (!dehexArray) {
58
- dehexArray = new Array(103) // f is 102
59
- for (let i = 0; i < 16; i++) {
60
- const s = i.toString(16)
61
- dehexArray[s.charCodeAt(0)] = dehexArray[s.toUpperCase().charCodeAt(0)] = i
62
- }
63
- }
64
-
65
- const arr = new Uint8Array(str.length / 2)
66
- let j = 0
67
- const length = arr.length // this helps Hermes
68
- for (let i = 0; i < length; i++) {
69
- const a = dehexArray[str.charCodeAt(j++)] * 16 + dehexArray[str.charCodeAt(j++)]
70
- if (!a && Number.isNaN(a)) throw new Error('Input is not a hex string')
71
- arr[i] = a
72
- }
73
-
74
- return fromTypedArray(arr, format)
75
- }
76
- }
77
-
78
- export { fromHex }
19
+ export const fromHex = Uint8Array.fromHex
20
+ ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
21
+ : (str, format = 'uint8') => typedView(js.fromHex(str), format)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/bytes",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.4",
4
4
  "description": "Various operations on Uint8Array data",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
@@ -39,17 +39,25 @@
39
39
  },
40
40
  "type": "module",
41
41
  "files": [
42
+ "/fallback/_utils.js",
43
+ "/fallback/base32.js",
44
+ "/fallback/base64.js",
45
+ "/fallback/hex.js",
46
+ "/fallback/utf8.js",
47
+ "/array.js",
42
48
  "/assert.js",
49
+ "/base32.js",
43
50
  "/base64.js",
44
- "/array.js",
45
- "/hex.js"
51
+ "/hex.js",
52
+ "/utf8.js"
46
53
  ],
47
54
  "exports": {
48
- "./base64.js": "./base64.js",
49
55
  "./array.js": "./array.js",
50
- "./hex.js": "./hex.js"
56
+ "./base32.js": "./base32.js",
57
+ "./base64.js": "./base64.js",
58
+ "./hex.js": "./hex.js",
59
+ "./utf8.js": "./utf8.js"
51
60
  },
52
- "dependencies": {},
53
61
  "devDependencies": {
54
62
  "@exodus/eslint-config": "^5.24.0",
55
63
  "@exodus/prettier": "^1.0.0",
@@ -62,7 +70,10 @@
62
70
  "buffer": "^6.0.3",
63
71
  "electron": "36.5.0",
64
72
  "eslint": "^8.44.0",
65
- "jsvu": "^3.0.0"
73
+ "fast-base64-decode": "^2.0.0",
74
+ "hi-base32": "^0.5.1",
75
+ "jsvu": "^3.0.0",
76
+ "text-encoding": "^0.7.0"
66
77
  },
67
78
  "prettier": "@exodus/prettier",
68
79
  "packageManager": "pnpm@10.12.1+sha256.889bac470ec93ccc3764488a19d6ba8f9c648ad5e50a9a6e4be3768a5de387a3"