UNPKG

dcp-client

Version:

Core libraries for accessing DCP network

518 lines (466 loc) 20.6 kB
/** * @file polyfills.js * * Applies polyfills to the global object. * * @author Sam Cantor, sam@kingsds.network * Ryan Saweczko, ryansaweczko@distributive.network * @date Sept 2020, Mar 2025 */ self.wrapScriptLoading({ scriptName: 'polyfills' }, function accessLists$$fn(protectedStorage) { // Origin time for performance polyfill const pt0 = new Date().getTime(); // Add polyfills for any non-allowed symbols const polyfills = { location: { search: "", href: 'DCP Worker', }, // Assumption that if performance exists, performance.now must exist performance: typeof performance !== 'undefined' ? performance : { now: ()=>{ const res = new Date().getTime() - pt0; return res; }, timeOrigin: pt0, toJSON: () => {return { timeOrigin: performance.timeOrigin } }, }, importScripts: function () { throw new Error('importScripts is not supported on DCP'); }, globalThis: typeof globalThis === 'undefined' ? self : globalThis, WorkerGlobalScope: typeof globalThis === 'undefined' ? self : globalThis, // For browsers/SA-workers that don't support btoa/atob, modified from https://github.com/MaxArt2501/base64-js/blob/master/base64.js btoa: function (string) { var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; string = String(string); var bitmap, a, b, c, result = "", i = 0, rest = string.length % 3; for (; i < string.length;) { if ((a = string.charCodeAt(i++)) > 255 || (b = string.charCodeAt(i++)) > 255 || (c = string.charCodeAt(i++)) > 255) throw new TypeError("Failed to execute 'btoa': The string to be encoded contains characters outside of the Latin1 range."); bitmap = (a << 16) | (b << 8) | c; result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63) + b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63); } // If there's need of padding, replace the last 'A's with equal signs return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result; }, atob: function (string) { var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; string = String(string).replace(/[\t\n\f\r ]+/g, ""); // Adding the padding if missing, for semplicity string += "==".slice(2 - (string.length & 3)); var bitmap, result = "", r1, r2, i = 0; for (; i < string.length;) { bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 | (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++))); result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) : r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) : String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255); } return result; }, // Polyfill for Blob Blob: class Blob { /** @type {Array.<(Blob|Uint8Array)>} */ #parts = []; #type = ''; #size = 0; #endings = 'transparent'; /** * The Blob() constructor returns a new Blob object. The content * of the blob consists of the concatenation of the values given * in the parameter array. * * @param {*} blobParts * @param {{ type?: string, endings?: string }} [options] */ constructor (blobParts = [], options = {}) { if (typeof blobParts !== 'object' || blobParts === null) { throw new TypeError('Failed to construct \'Blob\': The provided value cannot be converted to a sequence.'); } if (typeof blobParts[Symbol.iterator] !== 'function') { throw new TypeError('Failed to construct \'Blob\': The object must have a callable @@iterator property.'); } if (typeof options !== 'object' && typeof options !== 'function') { throw new TypeError('Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.'); } if (options === null) options = {}; const encoder = new TextEncoder(); for (const element of blobParts) { let part; if (ArrayBuffer.isView(element)) { part = new Uint8Array(element.buffer.slice(element.byteOffset, element.byteOffset + element.byteLength)); } else if (element instanceof ArrayBuffer) { part = new Uint8Array(element.slice(0)); } else if (element instanceof Blob) { part = element; } else { part = encoder.encode(`${element}`); } const size = ArrayBuffer.isView(part) ? part.byteLength : part.size; // Avoid pushing empty parts into the array to better GC them if (size) { this.#size += size; this.#parts.push(part); } } this.#endings = `${options.endings === undefined ? 'transparent' : options.endings}`; const type = options.type === undefined ? '' : String(options.type); this.#type = /^[\x20-\x7E]*$/.test(type) ? type : ''; } /** * The Blob interface's size property returns the * size of the Blob in bytes. */ get size() { return this.#size; } /** * The type property of a Blob object returns the MIME type of the file. */ get type() { return this.#type; } /** * The text() method in the Blob interface returns a Promise * that resolves with a string containing the contents of * the blob, interpreted as UTF-8. * * @return {Promise<string>} */ async text() { // More optimized than using this.arrayBuffer() // that requires twice as much ram const decoder = new TextDecoder(); let str = ''; for await (const part of toIterator(this.#parts, false)) { str += decoder.decode(part, { stream: true }); } // Remaining str += decoder.decode(); return str; } /** * The arrayBuffer() method in the Blob interface returns a * Promise that resolves with the contents of the blob as * binary data contained in an ArrayBuffer. * * @return {Promise<ArrayBuffer>} */ async arrayBuffer() { // Easier way... Just a unnecessary overhead // const view = new Uint8Array(this.size); // await this.stream().getReader({mode: 'byob'}).read(view); // return view.buffer; const data = new Uint8Array(this.size); let offset = 0; for await (const chunk of toIterator(this.#parts, false)) { data.set(chunk, offset); offset += chunk.length; } return data.buffer; } /** * stream() requires a polyfill for "ReadableStream" so leave it NYI for * now, in case of feature testing */ // stream() { // } /** * The Blob interface's slice() method creates and returns a * new Blob object which contains data from a subset of the * blob on which it's called. * * @param {number} [start] * @param {number} [end] * @param {string} [type] */ slice(start = 0, end = this.size, type = '') { const { size } = this; let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size); let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size); const span = Math.max(relativeEnd - relativeStart, 0); const parts = this.#parts; const blobParts = []; let added = 0; for (const part of parts) { // don't add the overflow to new blobParts if (added >= span) { break; } const size = ArrayBuffer.isView(part) ? part.byteLength : part.size; if (relativeStart && size <= relativeStart) { // Skip the beginning and change the relative // start & end position as we skip the unwanted parts relativeStart -= size; relativeEnd -= size; } else { let chunk; if (ArrayBuffer.isView(part)) { chunk = part.subarray(relativeStart, Math.min(size, relativeEnd)); added += chunk.byteLength; } else { chunk = part.slice(relativeStart, Math.min(size, relativeEnd)); added += chunk.size; } relativeEnd -= size; blobParts.push(chunk); relativeStart = 0; // All next sequential parts should start at 0 } } const blob = new Blob([], { type: String(type).toLowerCase() }); blob.#size = span; blob.#parts = blobParts; return blob; } get[Symbol.toStringTag]() { return 'Blob'; } static[Symbol.hasInstance](object) { return ( object && typeof object === 'object' && typeof object.constructor === 'function' && ( typeof object.stream === 'function' || typeof object.arrayBuffer === 'function' ) && /^(Blob|File)$/.test(object[Symbol.toStringTag]) ); } }, }; /** @param {(Blob | Uint8Array)[]} parts */ async function * toIterator (parts, clone = true) { for (const part of parts) { if ('stream' in part) { yield * (/** @type {AsyncIterableIterator<Uint8Array>} */ (part.stream())) } else if (ArrayBuffer.isView(part)) { if (clone) { let position = part.byteOffset const end = part.byteOffset + part.byteLength while (position !== end) { const size = Math.min(end - position, POOL_SIZE) const chunk = part.buffer.slice(position, position + size) position += chunk.byteLength yield new Uint8Array(chunk) } } else { yield part } /* c8 ignore next 10 */ } else { // For blobs that have arrayBuffer but no stream method (nodes buffer.Blob) let position = 0, b = (/** @type {Blob} */ (part)) while (position !== b.size) { const chunk = b.slice(position, Math.min(b.size, position + POOL_SIZE)) const buffer = await chunk.arrayBuffer() position += buffer.byteLength yield new Uint8Array(buffer) } } } } // Polyfill for TextEncoder/Decoder var fromCharCode = String.fromCharCode; var Object_prototype_toString = ({}).toString; var sharedArrayBufferString = Object_prototype_toString.call(self["SharedArrayBuffer"]); var undefinedObjectString = '[object Undefined]'; var NativeUint8Array = self.Uint8Array; var patchedU8Array = NativeUint8Array || Array; var nativeArrayBuffer = NativeUint8Array ? ArrayBuffer : patchedU8Array; var arrayBuffer_isView = nativeArrayBuffer.isView || function(x) {return x && "length" in x}; var arrayBufferString = Object_prototype_toString.call(nativeArrayBuffer.prototype); var tmpBufferU16 = new (NativeUint8Array ? Uint16Array : patchedU8Array)(32); if (typeof TextEncoder === "undefined") { polyfills.TextEncoder = function TextEncoder(){}; var TextEncoderPrototype = polyfills.TextEncoder["prototype"]; TextEncoderPrototype["encode"] = function(inputString){ // 0xc0 => 0b11000000; 0xff => 0b11111111; 0xc0-0xff => 0b11xxxxxx // 0x80 => 0b10000000; 0xbf => 0b10111111; 0x80-0xbf => 0b10xxxxxx var encodedString = inputString === void 0 ? "" : ("" + inputString), len=encodedString.length|0; var result=new patchedU8Array((len << 1) + 8|0), tmpResult; var i=0, pos=0, point=0, nextcode=0; var upgradededArraySize=!NativeUint8Array; // normal arrays are auto-expanding for (i=0; i<len; i=i+1|0, pos=pos+1|0) { point = encodedString.charCodeAt(i)|0; if (point <= 0x007f) { result[pos] = point; } else if (point <= 0x07ff) { result[pos] = (0x6<<5)|(point>>6); result[pos=pos+1|0] = (0x2<<6)|(point&0x3f); } else { widenCheck: { if (0xD800 <= point) { if (point <= 0xDBFF) { nextcode = encodedString.charCodeAt(i=i+1|0)|0; // defaults to 0 when NaN, causing null replacement character if (0xDC00 <= nextcode && nextcode <= 0xDFFF) { //point = ((point - 0xD800)<<10) + nextcode - 0xDC00 + 0x10000|0; point = (point<<10) + nextcode - 0x35fdc00|0; if (point > 0xffff) { result[pos] = (0x1e/*0b11110*/<<3) | (point>>18); result[pos=pos+1|0] = (0x2/*0b10*/<<6) | ((point>>12)&0x3f/*0b00111111*/); result[pos=pos+1|0] = (0x2/*0b10*/<<6) | ((point>>6)&0x3f/*0b00111111*/); result[pos=pos+1|0] = (0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/); continue; } break widenCheck; } point = 65533/*0b1111111111111101*/;//return '\xEF\xBF\xBD';//fromCharCode(0xef, 0xbf, 0xbd); } else if (point <= 0xDFFF) { point = 65533/*0b1111111111111101*/;//return '\xEF\xBF\xBD';//fromCharCode(0xef, 0xbf, 0xbd); } } if (!upgradededArraySize && (i << 1) < pos && (i << 1) < (pos - 7|0)) { upgradededArraySize = true; tmpResult = new patchedU8Array(len * 3); tmpResult.set( result ); result = tmpResult; } } result[pos] = (0xe/*0b1110*/<<4) | (point>>12); result[pos=pos+1|0] =(0x2/*0b10*/<<6) | ((point>>6)&0x3f/*0b00111111*/); result[pos=pos+1|0] =(0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/); } } return NativeUint8Array ? result.subarray(0, pos) : result.slice(0, pos); }; } if (typeof TextDecoder === "undefined") { polyfills.TextDecoder = function TextDecoder(){}; polyfills.TextDecoder["prototype"]["decode"] = function(inputArrayOrBuffer){ var inputAs8 = inputArrayOrBuffer, asObjectString; if (!arrayBuffer_isView(inputAs8)) { asObjectString = Object_prototype_toString.call(inputAs8); if (asObjectString !== arrayBufferString && asObjectString !== sharedArrayBufferString && asObjectString !== undefinedObjectString) throw TypeError("Failed to execute 'decode' on 'TextDecoder': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"); inputAs8 = NativeUint8Array ? new patchedU8Array(inputAs8) : inputAs8 || []; } var resultingString = "", tmpStr = "", index = 0, len = inputAs8.length | 0, lenMinus32 = len - 32 | 0, nextEnd = 0, nextStop = 0, cp0 = 0, codePoint = 0, minBits = 0, cp1 = 0, pos = 0, tmp = -1; // Note that tmp represents the 2nd half of a surrogate pair incase a surrogate gets divided between blocks for (; index < len;) { nextEnd = index <= lenMinus32 ? 32 : len - index | 0; for (; pos < nextEnd; index = index + 1 | 0, pos = pos + 1 | 0) { cp0 = inputAs8[index] & 0xff; switch (cp0 >> 4) { case 15: cp1 = inputAs8[index = index + 1 | 0] & 0xff; if ((cp1 >> 6) !== 2 || 247 < cp0) { index = index - 1 | 0; break; } codePoint = ((cp0 & 7) << 6) | (cp1 & 63); minBits = 5; // 20 ensures it never passes -> all invalid replacements cp0 = 0x100; // keep track of th bit size case 14: cp1 = inputAs8[index = index + 1 | 0] & 0xff; codePoint <<= 6; codePoint |= ((cp0 & 15) << 6) | (cp1 & 63); minBits = (cp1 >> 6) === 2 ? minBits + 4 | 0 : 24; // 24 ensures it never passes -> all invalid replacements cp0 = (cp0 + 0x100) & 0x300; // keep track of th bit size case 13: case 12: cp1 = inputAs8[index = index + 1 | 0] & 0xff; codePoint <<= 6; codePoint |= ((cp0 & 31) << 6) | cp1 & 63; minBits = minBits + 7 | 0; // Now, process the code point if (index < len && (cp1 >> 6) === 2 && (codePoint >> minBits) && codePoint < 0x110000) { cp0 = codePoint; codePoint = codePoint - 0x10000 | 0; if (0 <= codePoint/*0xffff < codePoint*/) { // BMP code point //nextEnd = nextEnd - 1|0; tmp = (codePoint >> 10) + 0xD800 | 0; // highSurrogate cp0 = (codePoint & 0x3ff) + 0xDC00 | 0; // lowSurrogate (will be inserted later in the switch-statement) if (pos < 31) { // notice 31 instead of 32 tmpBufferU16[pos] = tmp; pos = pos + 1 | 0; tmp = -1; } else {// else, we are at the end of the inputAs8 and let tmp0 be filled in later on // NOTE that cp1 is being used as a temporary variable for the swapping of tmp with cp0 cp1 = tmp; tmp = cp0; cp0 = cp1; } } else nextEnd = nextEnd + 1 | 0; // because we are advancing i without advancing pos } else { // invalid code point means replacing the whole thing with null replacement characters cp0 >>= 8; index = index - cp0 - 1 | 0; // reset index back to what it was before cp0 = 0xfffd; } // Finally, reset the variables for the next go-around minBits = 0; codePoint = 0; nextEnd = index <= lenMinus32 ? 32 : len - index | 0; /*case 11: case 10: case 9: case 8: codePoint ? codePoint = 0 : cp0 = 0xfffd; // fill with invalid replacement character case 7: case 6: case 5: case 4: case 3: case 2: case 1: case 0: tmpBufferU16[pos] = cp0; continue;*/ default: tmpBufferU16[pos] = cp0; // fill with invalid replacement character continue; case 11: case 10: case 9: case 8: } tmpBufferU16[pos] = 0xfffd; // fill with invalid replacement character } tmpStr += fromCharCode( tmpBufferU16[0], tmpBufferU16[1], tmpBufferU16[2], tmpBufferU16[3], tmpBufferU16[4], tmpBufferU16[5], tmpBufferU16[6], tmpBufferU16[7], tmpBufferU16[8], tmpBufferU16[9], tmpBufferU16[10], tmpBufferU16[11], tmpBufferU16[12], tmpBufferU16[13], tmpBufferU16[14], tmpBufferU16[15], tmpBufferU16[16], tmpBufferU16[17], tmpBufferU16[18], tmpBufferU16[19], tmpBufferU16[20], tmpBufferU16[21], tmpBufferU16[22], tmpBufferU16[23], tmpBufferU16[24], tmpBufferU16[25], tmpBufferU16[26], tmpBufferU16[27], tmpBufferU16[28], tmpBufferU16[29], tmpBufferU16[30], tmpBufferU16[31] ); if (pos < 32) tmpStr = tmpStr.slice(0, pos - 32 | 0);//-(32-pos)); if (index < len) { //fromCharCode.apply(0, tmpBufferU16 : NativeUint8Array ? tmpBufferU16.subarray(0,pos) : tmpBufferU16.slice(0,pos)); tmpBufferU16[0] = tmp; pos = (~tmp) >>> 31;//tmp !== -1 ? 1 : 0; tmp = -1; if (tmpStr.length < resultingString.length) continue; } else if (tmp !== -1) { tmpStr += fromCharCode(tmp); } resultingString += tmpStr; tmpStr = ""; } return resultingString; } } /* Polyfill section of workerBootstrap */ // Apply symbols from polyfill object for (let prop in polyfills) { if (globalThis.hasOwnProperty(prop)) continue; let propValue = polyfills[prop]; Object.defineProperty(self, prop, { get: function getPolyfill() { return propValue; }, set: function setPolyfill(value) { propValue = value; }, configurable: false }); } });