UNPKG

@wevu/web-apis

Version:

Web API polyfills and global installers for mini-program runtimes

396 lines (395 loc) 16.6 kB
import { a as decodeTextFallback, c as installRequestGlobalBinding, d as resolveRequestGlobalsHosts, g as getMiniProgramRuntimeGlobalKeys, s as encodeTextFallback, t as RequestGlobalsEventTarget, u as resolveRequestGlobalsHost } from "./shared-pgiOoBm7.mjs"; import { URLPolyfill, URLSearchParamsPolyfill } from "./url.mjs"; import { AbortControllerPolyfill, AbortSignalPolyfill } from "./abort.mjs"; import { HeadersPolyfill, RequestPolyfill, ResponsePolyfill } from "./http.mjs"; import { getMiniProgramNetworkDefaults, resetMiniProgramNetworkDefaults, setMiniProgramNetworkDefaults } from "./networkDefaults.mjs"; import { fetch } from "./fetch.mjs"; import { BlobPolyfill, FormDataPolyfill } from "./web.mjs"; import { WebSocketPolyfill } from "./websocket.mjs"; import { XMLHttpRequestPolyfill } from "./xhr.mjs"; import { REQUEST_GLOBAL_ACTUALS_KEY, REQUEST_GLOBAL_PLACEHOLDER_KEY } from "@weapp-core/constants"; //#region src/base64.ts const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const BASE64_LOOKUP = new Uint8Array(256); const BASE64_INVALID_INPUT_RE = /[^A-Za-z\d+/=]/u; for (let index = 0; index < BASE64_LOOKUP.length; index++) BASE64_LOOKUP[index] = 255; for (let index = 0; index < 64; index++) BASE64_LOOKUP[BASE64_CHARS.charCodeAt(index)] = index; function toLatin1Byte(value, index) { const codePoint = value.charCodeAt(index); if (codePoint > 255) throw new TypeError(`Failed to execute 'btoa': character outside Latin1 range at index ${index}`); return codePoint; } function btoaPolyfill(input = "") { const source = String(input); let output = ""; for (let index = 0; index < source.length; index += 3) { const byte0 = toLatin1Byte(source, index); const byte1 = index + 1 < source.length ? toLatin1Byte(source, index + 1) : NaN; const byte2 = index + 2 < source.length ? toLatin1Byte(source, index + 2) : NaN; const triplet = byte0 << 16 | (Number.isNaN(byte1) ? 0 : byte1) << 8 | (Number.isNaN(byte2) ? 0 : byte2); output += BASE64_CHARS[triplet >> 18 & 63]; output += BASE64_CHARS[triplet >> 12 & 63]; output += Number.isNaN(byte1) ? "=" : BASE64_CHARS[triplet >> 6 & 63]; output += Number.isNaN(byte2) ? "=" : BASE64_CHARS[triplet & 63]; } return output; } function atobPolyfill(input = "") { const source = String(input).replace(/[\t\n\f\r ]+/g, ""); if (source.length % 4 === 1 || BASE64_INVALID_INPUT_RE.test(source)) throw new TypeError("Failed to execute 'atob': the input is not correctly encoded"); let output = ""; for (let index = 0; index < source.length; index += 4) { const char0 = source.charCodeAt(index); const char1 = source.charCodeAt(index + 1); const char2 = source.charAt(index + 2); const char3 = source.charAt(index + 3); const sextet0 = BASE64_LOOKUP[char0] ?? 255; const sextet1 = BASE64_LOOKUP[char1] ?? 255; const sextet2 = char2 === "=" ? 64 : BASE64_LOOKUP[source.charCodeAt(index + 2)] ?? 255; const sextet3 = char3 === "=" ? 64 : BASE64_LOOKUP[source.charCodeAt(index + 3)] ?? 255; if (sextet0 > 63 || sextet1 > 63 || sextet2 > 64 || sextet3 > 64) throw new TypeError("Failed to execute 'atob': the input is not correctly encoded"); const triplet = sextet0 << 18 | sextet1 << 12 | (sextet2 & 63) << 6 | sextet3 & 63; output += String.fromCharCode(triplet >> 16 & 255); if (char2 !== "=") output += String.fromCharCode(triplet >> 8 & 255); if (char3 !== "=") output += String.fromCharCode(triplet & 255); } return output; } //#endregion //#region src/crypto.ts const MAX_RANDOM_VALUES_BYTE_LENGTH = 65536; function isIntegerTypedArray(value) { return value instanceof Int8Array || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Int16Array || value instanceof Uint16Array || value instanceof Int32Array || value instanceof Uint32Array || value instanceof BigInt64Array || value instanceof BigUint64Array; } function getNativeCrypto() { const candidate = globalThis.crypto; if (candidate && typeof candidate.getRandomValues === "function") return candidate; } function fillArrayWithMathRandom(target) { const bytes = new Uint8Array(target.buffer, target.byteOffset, target.byteLength); for (let index = 0; index < bytes.length; index++) bytes[index] = Math.floor(Math.random() * 256); } function fillArrayWithHostRandomValues(target) { for (const hostKey of getMiniProgramRuntimeGlobalKeys()) { const host = globalThis[hostKey]; if (!host || typeof host.getRandomValues !== "function") continue; try { host.getRandomValues(target); return true; } catch {} } return false; } function getRandomValuesPolyfill(typedArray) { if (!isIntegerTypedArray(typedArray)) throw new TypeError("Failed to execute 'getRandomValues': input must be an integer TypedArray"); if (typedArray.byteLength > MAX_RANDOM_VALUES_BYTE_LENGTH) throw new TypeError("Failed to execute 'getRandomValues': byteLength exceeds 65536"); const nativeCrypto = getNativeCrypto(); if (nativeCrypto && nativeCrypto.getRandomValues !== getRandomValuesPolyfill) return nativeCrypto.getRandomValues(typedArray); if (!fillArrayWithHostRandomValues(typedArray)) fillArrayWithMathRandom(typedArray); return typedArray; } const cryptoPolyfill = { getRandomValues: getRandomValuesPolyfill }; //#endregion //#region src/events.ts var EventPolyfill = class { bubbles; cancelable; composed; isTrusted = false; timeStamp = Date.now(); type; currentTarget = null; target = null; defaultPrevented = false; cancelBubble = false; constructor(type, init = {}) { this.type = String(type); this.bubbles = init.bubbles === true; this.cancelable = init.cancelable === true; this.composed = init.composed === true; } composedPath() { return this.target == null ? [] : [this.target]; } preventDefault() { if (this.cancelable) this.defaultPrevented = true; } stopImmediatePropagation() { this.cancelBubble = true; } stopPropagation() { this.cancelBubble = true; } }; var CustomEventPolyfill = class extends EventPolyfill { detail; constructor(type, init = {}) { super(type, init); this.detail = init.detail ?? null; } }; //#endregion //#region src/performance.ts const performanceTimeOrigin = Date.now(); const PERFORMANCE_POLYFILL_MARKER = "__weappVitePerformancePolyfill"; function resolveNativePerformance() { const candidate = globalThis.performance; if (candidate && candidate?.[PERFORMANCE_POLYFILL_MARKER] !== true && typeof candidate.now === "function") return candidate; for (const hostKey of getMiniProgramRuntimeGlobalKeys()) { const host = globalThis[hostKey]; if (!host || typeof host.getPerformance !== "function") continue; try { const performance = host.getPerformance(); if (performance && typeof performance.now === "function") return performance; } catch {} } } const performancePolyfill = { [PERFORMANCE_POLYFILL_MARKER]: true, now() { const nativePerformance = resolveNativePerformance(); if (nativePerformance && nativePerformance !== performancePolyfill) try { return Number(nativePerformance.now()); } catch {} return Date.now() - performanceTimeOrigin; }, timeOrigin: performanceTimeOrigin }; //#endregion //#region src/task.ts function queueMicrotaskPolyfill(callback) { if (typeof callback !== "function") throw new TypeError("Failed to execute 'queueMicrotask': callback must be a function"); const nativeQueueMicrotask = globalThis.queueMicrotask; if (typeof nativeQueueMicrotask === "function" && nativeQueueMicrotask !== queueMicrotaskPolyfill) { nativeQueueMicrotask(callback); return; } Promise.resolve().then(callback).catch((error) => { setTimeout(() => { throw error; }, 0); }); } //#endregion //#region src/textCodec.ts function normalizeTextDecoderInput(input) { if (input == null) return /* @__PURE__ */ new ArrayBuffer(0); if (input instanceof ArrayBuffer) return input.slice(0); if (ArrayBuffer.isView(input)) { const copied = new Uint8Array(input.byteLength); copied.set(new Uint8Array(input.buffer, input.byteOffset, input.byteLength)); return copied.buffer; } return /* @__PURE__ */ new ArrayBuffer(0); } var TextEncoderPolyfill = class { encoding = "utf-8"; encode(input = "") { return new Uint8Array(encodeTextFallback(String(input))); } }; var TextDecoderPolyfill = class { encoding = "utf-8"; fatal = false; ignoreBOM = false; decode(input) { return decodeTextFallback(normalizeTextDecoderInput(input)); } }; //#endregion //#region src/index.ts function hasRequestRuntimeBinaryUsage(targets) { return targets.some((target) => target === "fetch" || target === "Request" || target === "Response" || target === "XMLHttpRequest" || target === "WebSocket"); } function resolveInstallTargets(targets) { const installTargets = [...targets]; if (hasRequestRuntimeBinaryUsage(targets)) installTargets.push("TextEncoder", "TextDecoder"); if (targets.includes("CustomEvent")) installTargets.push("Event"); return [...new Set(installTargets)]; } function resolveActualBindingTargets(targets) { const bindingTargets = [...targets]; if (hasRequestRuntimeBinaryUsage(targets)) { bindingTargets.push("TextEncoder", "TextDecoder"); bindingTargets.push("URL", "URLSearchParams", "Blob", "FormData"); } return [...new Set(bindingTargets)]; } function isPlaceholderRequestGlobal(value) { return Boolean(value && typeof value === "function" && value[REQUEST_GLOBAL_PLACEHOLDER_KEY] === true); } function hasUsableConstructor(value, args = []) { if (typeof value !== "function" || isPlaceholderRequestGlobal(value)) return false; try { Reflect.construct(value, args); return true; } catch { return false; } } function assignHostGlobal(host, key, value) { try { host[key] = value; return true; } catch { return false; } } function installSingleTarget(host, target) { if (target === "fetch") { if (typeof host.fetch !== "function" || isPlaceholderRequestGlobal(host.fetch)) assignHostGlobal(host, "fetch", fetch); return; } if (target === "Headers") { if (typeof host.Headers !== "function" || isPlaceholderRequestGlobal(host.Headers)) assignHostGlobal(host, "Headers", HeadersPolyfill); return; } if (target === "Request") { if (typeof host.Request !== "function" || isPlaceholderRequestGlobal(host.Request)) assignHostGlobal(host, "Request", RequestPolyfill); return; } if (target === "Response") { if (typeof host.Response !== "function" || isPlaceholderRequestGlobal(host.Response)) assignHostGlobal(host, "Response", ResponsePolyfill); return; } if (target === "TextEncoder") { if (typeof host.TextEncoder !== "function" || isPlaceholderRequestGlobal(host.TextEncoder)) assignHostGlobal(host, "TextEncoder", TextEncoderPolyfill); return; } if (target === "TextDecoder") { if (typeof host.TextDecoder !== "function" || isPlaceholderRequestGlobal(host.TextDecoder)) assignHostGlobal(host, "TextDecoder", TextDecoderPolyfill); return; } if (target === "AbortController") { if (typeof host.AbortController !== "function" || isPlaceholderRequestGlobal(host.AbortController)) assignHostGlobal(host, "AbortController", AbortControllerPolyfill); return; } if (target === "AbortSignal") { if (typeof host.AbortSignal !== "function" || isPlaceholderRequestGlobal(host.AbortSignal)) assignHostGlobal(host, "AbortSignal", AbortSignalPolyfill); return; } if (target === "WebSocket") { if (typeof host.WebSocket !== "function" || isPlaceholderRequestGlobal(host.WebSocket)) assignHostGlobal(host, "WebSocket", WebSocketPolyfill); return; } if (target === "atob") { if (typeof host.atob !== "function" || isPlaceholderRequestGlobal(host.atob)) assignHostGlobal(host, "atob", atobPolyfill); return; } if (target === "btoa") { if (typeof host.btoa !== "function" || isPlaceholderRequestGlobal(host.btoa)) assignHostGlobal(host, "btoa", btoaPolyfill); return; } if (target === "queueMicrotask") { if (typeof host.queueMicrotask !== "function" || isPlaceholderRequestGlobal(host.queueMicrotask)) assignHostGlobal(host, "queueMicrotask", queueMicrotaskPolyfill); return; } if (target === "performance") { if (!host.performance || typeof host.performance.now !== "function") assignHostGlobal(host, "performance", performancePolyfill); return; } if (target === "crypto") { if (!host.crypto || typeof host.crypto.getRandomValues !== "function") assignHostGlobal(host, "crypto", cryptoPolyfill); return; } if (target === "Event") { if (typeof host.Event !== "function" || isPlaceholderRequestGlobal(host.Event)) assignHostGlobal(host, "Event", EventPolyfill); return; } if (target === "CustomEvent") { if (typeof host.CustomEvent !== "function" || isPlaceholderRequestGlobal(host.CustomEvent)) assignHostGlobal(host, "CustomEvent", CustomEventPolyfill); return; } if (target === "XMLHttpRequest" && (typeof host.XMLHttpRequest !== "function" || isPlaceholderRequestGlobal(host.XMLHttpRequest))) assignHostGlobal(host, "XMLHttpRequest", XMLHttpRequestPolyfill); } function installUrlGlobals(host) { if (!hasUsableConstructor(host.URL, ["https://request-globals.invalid"])) assignHostGlobal(host, "URL", URLPolyfill); if (!hasUsableConstructor(host.URLSearchParams, ["client=graphql-request"])) assignHostGlobal(host, "URLSearchParams", URLSearchParamsPolyfill); if (!hasUsableConstructor(host.Blob)) assignHostGlobal(host, "Blob", BlobPolyfill); if (!hasUsableConstructor(host.FormData)) assignHostGlobal(host, "FormData", FormDataPolyfill); } function installGlobalBindingIfNeeded(host, target) { const value = host[target]; if (value == null) return; installRequestGlobalBinding(target, value); } function ensureRuntimeHostAliases(host) { for (const alias of [ "global", "self", "window" ]) { try { host[alias] = host; } catch {} installRequestGlobalBinding(alias, host); } } function syncWeappViteRequestGlobalsActuals(host, targets) { const globalObject = host != null && (typeof host === "object" || typeof host === "function") ? host : resolveRequestGlobalsHost(); let actuals = globalObject[REQUEST_GLOBAL_ACTUALS_KEY]; if (!actuals || typeof actuals !== "object") { actuals = Object.create(null); assignHostGlobal(globalObject, REQUEST_GLOBAL_ACTUALS_KEY, actuals); } for (const target of resolveActualBindingTargets(targets)) { const value = host[target]; if (value != null) actuals[target] = value; } } /** * @description 按需向小程序全局环境注入缺失的 Web Runtime 对象。 */ function installWebRuntimeGlobals(options = {}) { if (options.networkDefaults !== void 0) setMiniProgramNetworkDefaults(options.networkDefaults); const targets = resolveInstallTargets(options.targets ?? [ "fetch", "Headers", "Request", "Response", "TextEncoder", "TextDecoder", "AbortController", "AbortSignal", "XMLHttpRequest", "WebSocket", "atob", "btoa", "queueMicrotask", "performance", "crypto", "Event", "CustomEvent" ]); const hosts = resolveRequestGlobalsHosts(); const primaryHost = resolveRequestGlobalsHost(); const needsUrlGlobals = hasRequestRuntimeBinaryUsage(targets); ensureRuntimeHostAliases(primaryHost); for (const host of hosts) { if (needsUrlGlobals) installUrlGlobals(host); for (const target of targets) installSingleTarget(host, target); } if (needsUrlGlobals) { installGlobalBindingIfNeeded(primaryHost, "URL"); installGlobalBindingIfNeeded(primaryHost, "URLSearchParams"); installGlobalBindingIfNeeded(primaryHost, "Blob"); installGlobalBindingIfNeeded(primaryHost, "FormData"); } for (const target of targets) installGlobalBindingIfNeeded(primaryHost, target); syncWeappViteRequestGlobalsActuals(primaryHost, targets); return hosts[0]; } /** * @description 已废弃,请迁移到 `installWebRuntimeGlobals`。 */ function installRequestGlobals(options = {}) { return installWebRuntimeGlobals(options); } /** * @description 仅安装 AbortController 与 AbortSignal 兼容层。 */ function installAbortGlobals() { return installWebRuntimeGlobals({ targets: ["AbortController", "AbortSignal"] }); } //#endregion export { AbortControllerPolyfill, AbortSignalPolyfill, BlobPolyfill, CustomEventPolyfill, EventPolyfill, FormDataPolyfill, HeadersPolyfill, RequestGlobalsEventTarget, RequestPolyfill, ResponsePolyfill, TextDecoderPolyfill, TextEncoderPolyfill, URLPolyfill, URLSearchParamsPolyfill, WebSocketPolyfill, XMLHttpRequestPolyfill, atobPolyfill, btoaPolyfill, cryptoPolyfill, fetch, getMiniProgramNetworkDefaults, installAbortGlobals, installRequestGlobals, installWebRuntimeGlobals, performancePolyfill, queueMicrotaskPolyfill, resetMiniProgramNetworkDefaults, setMiniProgramNetworkDefaults };