UNPKG

static-browser-server

Version:

A simple service worker used for the static template in sandpack, allowing users to develop websites like they would locally in the browser.

284 lines (279 loc) 8.64 kB
"use strict"; (() => { // src/lib/utils.ts var counter = 0; function generateRandomId() { const now = Date.now(); const randomNumber = Math.round(Math.random() * 1e4); const count = counter += 1; return (+`${now}${randomNumber}${count}`).toString(16); } // node_modules/.pnpm/outvariant@1.4.3/node_modules/outvariant/lib/index.mjs var POSITIONALS_EXP = /(%?)(%([sdijo]))/g; function serializePositional(positional, flag) { switch (flag) { case "s": return positional; case "d": case "i": return Number(positional); case "j": return JSON.stringify(positional); case "o": { if (typeof positional === "string") { return positional; } const json = JSON.stringify(positional); if (json === "{}" || json === "[]" || /^\[object .+?\]$/.test(json)) { return positional; } return json; } } } function format(message, ...positionals) { if (positionals.length === 0) { return message; } let positionalIndex = 0; let formattedMessage = message.replace( POSITIONALS_EXP, (match, isEscaped, _, flag) => { const positional = positionals[positionalIndex]; const value = serializePositional(positional, flag); if (!isEscaped) { positionalIndex++; return value; } return match; } ); if (positionalIndex < positionals.length) { formattedMessage += ` ${positionals.slice(positionalIndex).join(" ")}`; } formattedMessage = formattedMessage.replace(/%{2,2}/g, "%"); return formattedMessage; } var STACK_FRAMES_TO_IGNORE = 2; function cleanErrorStack(error) { if (!error.stack) { return; } const nextStack = error.stack.split("\n"); nextStack.splice(1, STACK_FRAMES_TO_IGNORE); error.stack = nextStack.join("\n"); } var InvariantError = class extends Error { constructor(message, ...positionals) { super(message); this.message = message; this.name = "Invariant Violation"; this.message = format(message, ...positionals); cleanErrorStack(this); } }; var invariant = (predicate, message, ...positionals) => { if (!predicate) { throw new InvariantError(message, ...positionals); } }; invariant.as = (ErrorConstructor, predicate, message, ...positionals) => { if (!predicate) { const formatMessage = positionals.length === 0 ? message : format(message, ...positionals); let error; try { error = Reflect.construct(ErrorConstructor, [ formatMessage ]); } catch (err) { error = ErrorConstructor(formatMessage); } throw error; } }; // node_modules/.pnpm/@open-draft+deferred-promise@2.2.0/node_modules/@open-draft/deferred-promise/build/index.mjs function createDeferredExecutor() { const executor = (resolve, reject) => { executor.state = "pending"; executor.resolve = (data) => { if (executor.state !== "pending") { return; } executor.result = data; const onFulfilled = (value) => { executor.state = "fulfilled"; return value; }; return resolve( data instanceof Promise ? data : Promise.resolve(data).then(onFulfilled) ); }; executor.reject = (reason) => { if (executor.state !== "pending") { return; } queueMicrotask(() => { executor.state = "rejected"; }); return reject(executor.rejectionReason = reason); }; }; return executor; } var DeferredPromise = class extends Promise { #executor; resolve; reject; constructor(executor = null) { const deferredExecutor = createDeferredExecutor(); super((originalResolve, originalReject) => { deferredExecutor(originalResolve, originalReject); executor?.(deferredExecutor.resolve, deferredExecutor.reject); }); this.#executor = deferredExecutor; this.resolve = this.#executor.resolve; this.reject = this.#executor.reject; } get state() { return this.#executor.state; } get rejectionReason() { return this.#executor.rejectionReason; } then(onFulfilled, onRejected) { return this.#decorate(super.then(onFulfilled, onRejected)); } catch(onRejected) { return this.#decorate(super.catch(onRejected)); } finally(onfinally) { return this.#decorate(super.finally(onfinally)); } #decorate(promise) { return Object.defineProperties(promise, { resolve: { configurable: true, value: this.resolve }, reject: { configurable: true, value: this.reject } }); } }; // src/preview/relay/constants.ts var CHANNEL_NAME = "$CSB_RELAY"; // src/preview/relay/service-worker.ts self.addEventListener("install", function() { self.skipWaiting(); }); self.addEventListener("activate", async (event) => { event.waitUntil(self.clients.claim()); }); var pendingRequests = /* @__PURE__ */ new Map(); function initRelayPort(relayPort) { relayPort.onmessage = (event) => { const { data } = event; switch (data.$type) { case "preview/response": { const message = data; const foundRequest = pendingRequests.get(message.id); invariant( foundRequest, 'Failed to handle "PREVIEW_RESPONSE_TYPE" message from the relay: unknown request ID "%s"', message.id ); pendingRequests.delete(message.id); foundRequest.resolve({ status: message.status, headers: message.headers, body: message.body }); break; } } }; } function createRelayPortPromise() { const promise = new DeferredPromise(); promise.then((port) => { initRelayPort(port); return port; }); return promise; } var relayPortPromise = createRelayPortPromise(); async function sendToRelay(message) { const relayPort = await relayPortPromise; invariant( relayPort, "Failed to send message to the relay: relay message port is not defined", message ); relayPort.postMessage(message); } self.addEventListener("message", async (event) => { if (typeof event.data !== "object" || event.data.$channel !== CHANNEL_NAME) { return; } const message = event.data; switch (message.$type) { case "worker/init": { const nextRelayPort = event.ports[0]; invariant( relayPortPromise.state === "pending", "Failed to initialize relay: relay port promise already fulfilled from previous evaluation." ); relayPortPromise.resolve(nextRelayPort); break; } case "worker/ping": { if (!(event.source instanceof Client)) { return; } const client = await self.clients.get(event.source.id); if (client) { const pong = { $channel: CHANNEL_NAME, $type: "worker/pong" }; client.postMessage(pong); } break; } } }); function getResponse(request) { const requestId = generateRandomId(); const requestPromise = new DeferredPromise(); const timeout = setTimeout(() => { pendingRequests.delete(requestId); requestPromise.reject( new Error( `Failed to handle ${request.method} ${request.url} request: no response received from the BroadcastChannel within timeout. There's likely an issue with the relay/worker communication.` ) ); }, 2e4); const requestMessage = { $channel: CHANNEL_NAME, $type: "preview/request", id: requestId, url: request.url, method: request.method }; pendingRequests.set(requestId, requestPromise); sendToRelay(requestMessage); return requestPromise.finally(() => clearTimeout(timeout)); } self.addEventListener("fetch", (event) => { const req = event.request.clone(); const parsedUrl = new URL(req.url); if (parsedUrl.origin !== self.location.origin || parsedUrl.pathname.startsWith("/__csb")) { return; } const handleRequest = async () => { const response = await getResponse(req); const swResponse = new Response(response.body, { headers: response.headers, status: response.status }); return swResponse; }; return event.respondWith(handleRequest()); }); })();