UNPKG

@wevu/web-apis

Version:

Web API polyfills and global installers for mini-program runtimes

208 lines (207 loc) 7.89 kB
import { h as resolveUrlConstructor$1, m as resolveTextEncoderConstructor, n as cloneArrayBuffer, r as cloneArrayBufferView, t as RequestGlobalsEventTarget } from "./shared-pgiOoBm7.mjs"; import { URLPolyfill } from "./url.mjs"; import { resolveWebSocketMiniProgramOptions } from "./networkDefaults.mjs"; import { BlobPolyfill } from "./web.mjs"; import { wpi } from "@wevu/api"; //#region src/websocket.ts const WHITESPACE_RE = /\s/u; function isValidProtocol(protocol) { return [...protocol].every((char) => { const code = char.charCodeAt(0); return code >= 33 && code <= 126 && char !== "," && !WHITESPACE_RE.test(char); }); } function createDomLikeError(name, message) { if (typeof DOMException === "function") return new DOMException(message, name); const error = new Error(message); error.name = name; return error; } function encodeReasonLength(reason) { const TextEncoderConstructor = resolveTextEncoderConstructor(); if (TextEncoderConstructor) return new TextEncoderConstructor().encode(reason).byteLength; return unescape(encodeURIComponent(reason)).length; } function normalizeCloseCode(code) { if (code == null) return; if (code === 1e3 || code >= 3e3 && code <= 4999) return code; throw createDomLikeError("InvalidAccessError", `Failed to execute close: invalid code "${code}"`); } function normalizeCloseReason(reason) { if (reason == null) return; const normalized = String(reason); if (encodeReasonLength(normalized) > 123) throw createDomLikeError("SyntaxError", "Failed to execute close: reason is longer than 123 bytes"); return normalized; } function resolveUrlConstructor() { return resolveUrlConstructor$1() ?? URLPolyfill; } function normalizeProtocols(protocols) { if (protocols == null) return; const normalized = (Array.isArray(protocols) ? [...protocols] : [protocols]).map((protocol) => String(protocol)); const unique = /* @__PURE__ */ new Set(); for (const protocol of normalized) { if (!isValidProtocol(protocol)) throw new SyntaxError(`Failed to construct 'WebSocket': invalid subprotocol "${protocol}"`); if (unique.has(protocol)) throw new SyntaxError(`Failed to construct 'WebSocket': duplicated subprotocol "${protocol}"`); unique.add(protocol); } return normalized; } function isWebSocketPolyfillInit(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function resolveWebSocketProtocols(protocols) { if (isWebSocketPolyfillInit(protocols)) return normalizeProtocols(protocols.protocols); return normalizeProtocols(protocols); } function resolveWebSocketMiniProgramConfig(protocols) { if (!isWebSocketPolyfillInit(protocols)) return resolveWebSocketMiniProgramOptions(); return resolveWebSocketMiniProgramOptions(protocols.miniProgram, protocols.miniprogram); } function normalizeUrl(url) { const parsed = new (resolveUrlConstructor())(String(url)); if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new SyntaxError(`Failed to construct 'WebSocket': invalid URL "${url}"`); if (parsed.hash) throw new SyntaxError(`Failed to construct 'WebSocket': URL contains fragment "${url}"`); return parsed.toString(); } function isSocketTask(value) { return typeof value === "object" && value !== null && typeof value.send === "function" && typeof value.close === "function"; } function toBinaryPayload(data) { if (typeof data === "string") return data; if (data instanceof ArrayBuffer) return cloneArrayBuffer(data); if (ArrayBuffer.isView(data)) return cloneArrayBufferView(data); if (typeof Blob !== "undefined" && data instanceof Blob) return data.arrayBuffer(); throw new TypeError("Failed to execute send: data must be a string, ArrayBuffer, ArrayBufferView or Blob"); } function createErrorEvent(error) { return { type: "error", error, message: typeof error === "object" && error !== null && "errMsg" in error ? String(error.errMsg ?? "") : error instanceof Error ? error.message : void 0 }; } function createCloseEvent(result) { return { type: "close", code: result?.code ?? 1e3, reason: result?.reason ?? "", wasClean: (result?.code ?? 1e3) === 1e3 }; } function createMessageEvent(url, data) { return { type: "message", data, origin: new (resolveUrlConstructor())(url).origin }; } function getRawConnectSocket() { const adapter = wpi.getAdapter?.() ?? wpi.raw; const target = wpi.resolveTarget?.("connectSocket"); if (!adapter || !target?.supported) return; const method = adapter[target.target]; return typeof method === "function" ? method.bind(adapter) : void 0; } var WebSocketPolyfill = class WebSocketPolyfill extends RequestGlobalsEventTarget { static CONNECTING = 0; static OPEN = 1; static CLOSING = 2; static CLOSED = 3; CONNECTING = WebSocketPolyfill.CONNECTING; OPEN = WebSocketPolyfill.OPEN; CLOSING = WebSocketPolyfill.CLOSING; CLOSED = WebSocketPolyfill.CLOSED; extensions = ""; protocol = ""; url; binaryType = "blob"; bufferedAmount = 0; readyState = WebSocketPolyfill.CONNECTING; onclose = null; onerror = null; onmessage = null; onopen = null; socketTask; constructor(url, protocols) { super(); this.url = normalizeUrl(url); const connectSocket = getRawConnectSocket(); if (!connectSocket) throw createDomLikeError("NotSupportedError", "WebSocket is not supported in the current mini-program runtime"); const normalizedProtocols = resolveWebSocketProtocols(protocols); const task = connectSocket({ ...resolveWebSocketMiniProgramConfig(protocols), url: this.url, protocols: normalizedProtocols, fail: (error) => { this.emitError(error); this.closeFromRuntime(); } }); if (!isSocketTask(task)) throw createDomLikeError("NetworkError", "Failed to create mini-program SocketTask"); this.socketTask = task; task.onOpen(() => { if (this.readyState !== WebSocketPolyfill.CONNECTING) return; this.readyState = WebSocketPolyfill.OPEN; this.dispatchEvent({ type: "open" }); }); task.onMessage((result) => { if (this.readyState === WebSocketPolyfill.CLOSED) return; const data = typeof result.data === "string" ? result.data : this.binaryType === "arraybuffer" ? cloneArrayBuffer(result.data) : new BlobPolyfill([cloneArrayBuffer(result.data)]); this.dispatchEvent(createMessageEvent(this.url, data)); }); task.onError((error) => { this.emitError(error); }); task.onClose((result) => { this.closeFromRuntime(result); }); } close(code, reason) { if (this.readyState === WebSocketPolyfill.CLOSING || this.readyState === WebSocketPolyfill.CLOSED) return; const normalizedCode = normalizeCloseCode(code); const normalizedReason = normalizeCloseReason(reason); this.readyState = WebSocketPolyfill.CLOSING; this.socketTask?.close({ code: normalizedCode, reason: normalizedReason, fail: (error) => { this.emitError(error); } }); } send(data) { if (this.readyState === WebSocketPolyfill.CONNECTING) throw createDomLikeError("InvalidStateError", "Failed to execute send: WebSocket is still in CONNECTING state"); if (this.readyState !== WebSocketPolyfill.OPEN || !this.socketTask) throw createDomLikeError("InvalidStateError", "Failed to execute send: WebSocket is not open"); const payload = toBinaryPayload(data); if (payload instanceof Promise) { payload.then((resolved) => { this.socketTask?.send({ data: resolved, fail: (error) => { this.emitError(error); } }); }).catch((error) => { this.emitError(error); }); return; } this.socketTask.send({ data: payload, fail: (error) => { this.emitError(error); } }); } closeFromRuntime(result) { if (this.readyState === WebSocketPolyfill.CLOSED) return; this.readyState = WebSocketPolyfill.CLOSED; this.dispatchEvent(createCloseEvent(result)); } emitError(error) { this.dispatchEvent(createErrorEvent(error)); } }; //#endregion export { WebSocketPolyfill };