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
JavaScript
;
(() => {
// 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());
});
})();