BlueWallet/blue_modules/uint8array-extras/index.js
2025-10-03 09:01:05 +03:00

411 lines
9.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* author: Sindre Sorhus
* license: MIT
* source: https://github.com/sindresorhus/uint8array-extras
*/
const objectToString = Object.prototype.toString;
const uint8ArrayStringified = '[object Uint8Array]';
const arrayBufferStringified = '[object ArrayBuffer]';
function isType(value, typeConstructor, typeStringified) {
if (!value) {
return false;
}
if (value.constructor === typeConstructor) {
return true;
}
return objectToString.call(value) === typeStringified;
}
export function isUint8Array(value) {
return isType(value, Uint8Array, uint8ArrayStringified);
}
function isArrayBuffer(value) {
return isType(value, ArrayBuffer, arrayBufferStringified);
}
function isUint8ArrayOrArrayBuffer(value) {
return isUint8Array(value) || isArrayBuffer(value);
}
export function assertUint8Array(value) {
if (!isUint8Array(value)) {
throw new TypeError(`Expected \`Uint8Array\`, got \`${typeof value}\``);
}
}
export function assertUint8ArrayOrArrayBuffer(value) {
if (!isUint8ArrayOrArrayBuffer(value)) {
throw new TypeError(`Expected \`Uint8Array\` or \`ArrayBuffer\`, got \`${typeof value}\``);
}
}
export function toUint8Array(value) {
if (value instanceof ArrayBuffer) {
return new Uint8Array(value);
}
if (ArrayBuffer.isView(value)) {
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
}
throw new TypeError(`Unsupported value, got \`${typeof value}\`.`);
}
export function concatUint8Arrays(arrays, totalLength) {
if (arrays.length === 0) {
return new Uint8Array(0);
}
totalLength ??= arrays.reduce((accumulator, currentValue) => accumulator + currentValue.length, 0);
const returnValue = new Uint8Array(totalLength);
let offset = 0;
for (const array of arrays) {
assertUint8Array(array);
returnValue.set(array, offset);
offset += array.length;
}
return returnValue;
}
export function areUint8ArraysEqual(a, b) {
assertUint8Array(a);
assertUint8Array(b);
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
// eslint-disable-next-line unicorn/no-for-loop
for (let index = 0; index < a.length; index++) {
if (a[index] !== b[index]) {
return false;
}
}
return true;
}
export function compareUint8Arrays(a, b) {
assertUint8Array(a);
assertUint8Array(b);
const length = Math.min(a.length, b.length);
for (let index = 0; index < length; index++) {
const diff = a[index] - b[index];
if (diff !== 0) {
return Math.sign(diff);
}
}
// At this point, all the compared elements are equal.
// The shorter array should come first if the arrays are of different lengths.
return Math.sign(a.length - b.length);
}
// const cachedDecoders = {
// utf8: new globalThis.TextDecoder("utf8"),
// };
//
// !!!!!!! commented out because we dont have `TextDecoder` as dep anymore !!!!!!!!
//
// export function uint8ArrayToString(array, encoding = "utf8") {
// assertUint8ArrayOrArrayBuffer(array);
// cachedDecoders[encoding] ??= new globalThis.TextDecoder(encoding);
// return cachedDecoders[encoding].decode(array);
// }
function assertString(value) {
if (typeof value !== 'string') {
throw new TypeError(`Expected \`string\`, got \`${typeof value}\``);
}
}
const cachedEncoder = new globalThis.TextEncoder();
export function stringToUint8Array(string) {
assertString(string);
return cachedEncoder.encode(string);
}
function base64ToBase64Url(base64) {
return base64.replaceAll('+', '-').replaceAll('/', '_').replace(/[=]+$/, '');
}
function base64UrlToBase64(base64url) {
return base64url.replaceAll('-', '+').replaceAll('_', '/');
}
// Reference: https://phuoc.ng/collection/this-vs-that/concat-vs-push/
const MAX_BLOCK_SIZE = 65_535;
export function uint8ArrayToBase64(array, { urlSafe = false } = {}) {
assertUint8Array(array);
let base64;
if (array.length < MAX_BLOCK_SIZE) {
// Required as `btoa` and `atob` don't properly support Unicode: https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem
base64 = globalThis.btoa(String.fromCodePoint.apply(this, array));
} else {
base64 = '';
for (const value of array) {
base64 += String.fromCodePoint(value);
}
base64 = globalThis.btoa(base64);
}
return urlSafe ? base64ToBase64Url(base64) : base64;
}
export function base64ToUint8Array(base64String) {
assertString(base64String);
return Uint8Array.from(globalThis.atob(base64UrlToBase64(base64String)), x => x.codePointAt(0));
}
export function stringToBase64(string, { urlSafe = false } = {}) {
assertString(string);
return uint8ArrayToBase64(stringToUint8Array(string), { urlSafe });
}
// export function base64ToString(base64String) {
// assertString(base64String);
// return uint8ArrayToString(base64ToUint8Array(base64String));
// }
const byteToHexLookupTable = Array.from({ length: 256 }, (_, index) => index.toString(16).padStart(2, '0'));
export function uint8ArrayToHex(array) {
assertUint8Array(array);
// Concatenating a string is faster than using an array.
let hexString = '';
// eslint-disable-next-line unicorn/no-for-loop -- Max performance is critical.
for (let index = 0; index < array.length; index++) {
hexString += byteToHexLookupTable[array[index]];
}
return hexString;
}
const hexToDecimalLookupTable = {
0: 0,
1: 1,
2: 2,
3: 3,
4: 4,
5: 5,
6: 6,
7: 7,
8: 8,
9: 9,
a: 10,
b: 11,
c: 12,
d: 13,
e: 14,
f: 15,
A: 10,
B: 11,
C: 12,
D: 13,
E: 14,
F: 15,
};
export function hexToUint8Array(hexString) {
assertString(hexString);
if (hexString.length % 2 !== 0) {
throw new Error('Invalid Hex string length.');
}
const resultLength = hexString.length / 2;
const bytes = new Uint8Array(resultLength);
for (let index = 0; index < resultLength; index++) {
const highNibble = hexToDecimalLookupTable[hexString[index * 2]];
const lowNibble = hexToDecimalLookupTable[hexString[index * 2 + 1]];
if (highNibble === undefined || lowNibble === undefined) {
throw new Error(`Invalid Hex character encountered at position ${index * 2}`);
}
bytes[index] = (highNibble << 4) | lowNibble; // eslint-disable-line no-bitwise
}
return bytes;
}
/**
@param {DataView} view
@returns {number}
*/
export function getUintBE(view) {
const { byteLength } = view;
if (byteLength === 6) {
return view.getUint16(0) * 2 ** 32 + view.getUint32(2);
}
if (byteLength === 5) {
return view.getUint8(0) * 2 ** 32 + view.getUint32(1);
}
if (byteLength === 4) {
return view.getUint32(0);
}
if (byteLength === 3) {
return view.getUint8(0) * 2 ** 16 + view.getUint16(1);
}
if (byteLength === 2) {
return view.getUint16(0);
}
if (byteLength === 1) {
return view.getUint8(0);
}
}
/**
@param {Uint8Array} array
@param {Uint8Array} value
@returns {number}
*/
export function indexOf(array, value) {
const arrayLength = array.length;
const valueLength = value.length;
if (valueLength === 0) {
return -1;
}
if (valueLength > arrayLength) {
return -1;
}
const validOffsetLength = arrayLength - valueLength;
for (let index = 0; index <= validOffsetLength; index++) {
let isMatch = true;
for (let index2 = 0; index2 < valueLength; index2++) {
if (array[index + index2] !== value[index2]) {
isMatch = false;
break;
}
}
if (isMatch) {
return index;
}
}
return -1;
}
/**
@param {Uint8Array} array
@param {Uint8Array} value
@returns {boolean}
*/
export function includes(array, value) {
return indexOf(array, value) !== -1;
}
// we can use this implementation when we will have TextDecoder in RN
// const cachedDecoders = {
// utf8: new globalThis.TextDecoder("utf8"),
// };
// export function uint8ArrayToString(array, encoding = "utf8") {
// assertUint8ArrayOrArrayBuffer(array);
// cachedDecoders[encoding] ??= new globalThis.TextDecoder(encoding);
// return cachedDecoders[encoding].decode(array);
// }
// meanwhile:
/**
* Convert a Uint8Array (or ArrayBuffer) of UTF-8 bytes into a JS string.
* Only "utf8" is supported. For any other encoding youll need a polyfill.
*
* @param {Uint8Array|ArrayBuffer} input
* @param {string} [encoding="utf8"]
* @returns {string}
*/
export function uint8ArrayToString(input, encoding = 'utf8') {
assertUint8ArrayOrArrayBuffer(input);
// Reject anything other than UTF-8
if (!/utf-?8/i.test(encoding)) {
throw new Error('Encoding "' + encoding + '" isnt supported without a TextDecoder polyfill');
}
// Normalise to Uint8Array
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
return decodeUtf8(bytes);
}
/**
* Minimal UTF-8 decoder
* @param {Uint8Array} bytes
* @returns {string}
*/
function decodeUtf8(bytes) {
let i = 0;
const l = bytes.length;
const codeUnits = [];
let result = '';
while (i < l) {
const byte1 = bytes[i++];
// 1-byte (ASCII)
if (byte1 < 0x80) {
codeUnits.push(byte1);
}
// 2-byte
else if (byte1 < 0xe0) {
const byte2 = bytes[i++] & 0x3f;
codeUnits.push(((byte1 & 0x1f) << 6) | byte2);
}
// 3-byte
else if (byte1 < 0xf0) {
const byte2 = bytes[i++] & 0x3f;
const byte3 = bytes[i++] & 0x3f;
codeUnits.push(((byte1 & 0x0f) << 12) | (byte2 << 6) | byte3);
}
// 4-byte (→ surrogate pair)
else {
const byte2 = bytes[i++] & 0x3f;
const byte3 = bytes[i++] & 0x3f;
const byte4 = bytes[i++] & 0x3f;
let cp = ((byte1 & 0x07) << 18) | (byte2 << 12) | (byte3 << 6) | byte4;
cp -= 0x10000;
codeUnits.push(0xd800 + (cp >> 10), 0xdc00 + (cp & 0x3ff));
}
// Flush periodically to avoid huge apply() calls
if (codeUnits.length > 0x8000) {
result += String.fromCharCode.apply(null, codeUnits);
codeUnits.length = 0;
}
}
return result + String.fromCharCode.apply(null, codeUnits);
}