// src/api/startDevWorker/utils.ts import assert from "node:assert"; function createDeferred(previousDeferred) { let resolve, reject; const newPromise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); assert(resolve); assert(reject); previousDeferred?.resolve(newPromise); return { promise: newPromise, resolve, reject }; } function urlFromParts(parts, base = "http://localhost") { const url = new URL(base); Object.assign(url, parts); return url; } // templates/startDevWorker/ProxyWorker.ts var LIVE_RELOAD_PROTOCOL = "WRANGLER_PROXYWORKER_LIVE_RELOAD_PROTOCOL"; var ProxyWorker_default = { fetch(req, env) { const singleton = env.DURABLE_OBJECT.idFromName(""); const inspectorProxy = env.DURABLE_OBJECT.get(singleton); return inspectorProxy.fetch(req); } }; var ProxyWorker = class { constructor(state, env) { this.state = state; this.env = env; } proxyData; requestQueue = /* @__PURE__ */ new Map(); requestRetryQueue = /* @__PURE__ */ new Map(); fetch(request) { if (isRequestForLiveReloadWebsocket(request)) { return this.handleLiveReloadWebSocket(request); } if (isRequestFromProxyController(request, this.env)) { return this.processProxyControllerRequest(request); } const deferred = createDeferred(); this.requestQueue.set(request, deferred); this.processQueue(); return deferred.promise; } handleLiveReloadWebSocket(request) { const { 0: response, 1: liveReload } = new WebSocketPair(); const websocketProtocol = request.headers.get("Sec-WebSocket-Protocol") ?? ""; this.state.acceptWebSocket(liveReload, ["live-reload"]); return new Response(null, { status: 101, webSocket: response, headers: { "Sec-WebSocket-Protocol": websocketProtocol } }); } processProxyControllerRequest(request) { const event = request.cf?.hostMetadata; switch (event?.type) { case "pause": this.proxyData = void 0; break; case "play": this.proxyData = event.proxyData; this.processQueue(); this.state.getWebSockets("live-reload").forEach((ws) => ws.send("reload")); break; } return new Response(null, { status: 204 }); } /** * Process requests that are being retried first, then process newer requests. * Requests that are being retried are, by definition, older than requests which haven't been processed yet. * We don't need to be more accurate than this re ordering, since the requests are being fired off synchronously. */ *getOrderedQueue() { yield* this.requestRetryQueue; yield* this.requestQueue; } processQueue() { const { proxyData } = this; if (proxyData === void 0) return; for (const [request, deferredResponse] of this.getOrderedQueue()) { this.requestRetryQueue.delete(request); this.requestQueue.delete(request); const outerUrl = new URL(request.url); const headers = new Headers(request.headers); const userWorkerUrl = new URL(request.url); Object.assign(userWorkerUrl, proxyData.userWorkerUrl); const innerUrl = urlFromParts( proxyData.userWorkerInnerUrlOverrides ?? {}, request.url ); headers.set("MF-Original-URL", innerUrl.href); const encoding = request.cf?.clientAcceptEncoding; if (encoding !== void 0) headers.set("Accept-Encoding", encoding); rewriteUrlRelatedHeaders(headers, outerUrl, innerUrl); for (const [key, value] of Object.entries(proxyData.headers ?? {})) { if (value === void 0) continue; if (key.toLowerCase() === "cookie") { const existing = request.headers.get("cookie") ?? ""; headers.set("cookie", `${existing};${value}`); } else { headers.set(key, value); } } void fetch(userWorkerUrl, new Request(request, { headers })).then((res) => { res = new Response(res.body, res); rewriteUrlRelatedHeaders(res.headers, innerUrl, outerUrl); if (isHtmlResponse(res)) { res = insertLiveReloadScript(request, res, this.env, proxyData); } deferredResponse.resolve(res); }).catch((error) => { const newUserWorkerUrl = this.proxyData && urlFromParts(this.proxyData.userWorkerUrl); if (userWorkerUrl.href === newUserWorkerUrl?.href) { void sendMessageToProxyController(this.env, { type: "error", error: { name: error.name, message: error.message, stack: error.stack, cause: error.cause } }); deferredResponse.reject(error); } else if (request.method === "GET" || request.method === "HEAD") { this.requestRetryQueue.set(request, deferredResponse); } else { deferredResponse.resolve( new Response( "Your worker restarted mid-request. Please try sending the request again. Only GET or HEAD requests are retried automatically.", { status: 503, headers: { "Retry-After": "0" } } ) ); } }); } } }; function isRequestFromProxyController(req, env) { return req.headers.get("Authorization") === env.PROXY_CONTROLLER_AUTH_SECRET; } function isHtmlResponse(res) { return res.headers.get("content-type")?.startsWith("text/html") ?? false; } function isRequestForLiveReloadWebsocket(req) { const websocketProtocol = req.headers.get("Sec-WebSocket-Protocol"); const isWebSocketUpgrade = req.headers.get("Upgrade") === "websocket"; return isWebSocketUpgrade && websocketProtocol === LIVE_RELOAD_PROTOCOL; } function sendMessageToProxyController(env, message) { return env.PROXY_CONTROLLER.fetch("http://dummy", { method: "POST", body: JSON.stringify(message) }); } function insertLiveReloadScript(request, response, env, proxyData) { const htmlRewriter = new HTMLRewriter(); let errorDetails = ""; htmlRewriter.on("#cf-error-details", { text(element) { errorDetails += element.text; } }); htmlRewriter.onDocument({ end(end) { if (response.status === 400 && errorDetails.includes("Invalid Workers Preview configuration")) { void sendMessageToProxyController(env, { type: "previewTokenExpired", proxyData }); } if (proxyData.liveReload) { const websocketUrl = new URL(request.url); websocketUrl.protocol = websocketUrl.protocol === "http:" ? "ws:" : "wss:"; end.append(liveReloadScript, { html: true }); } } }); return htmlRewriter.transform(response); } var liveReloadScript = ` `; function rewriteUrlRelatedHeaders(headers, from, to) { const setCookie = headers.getAll("Set-Cookie"); headers.delete("Set-Cookie"); headers.forEach((value, key) => { if (typeof value === "string" && value.includes(from.host)) { headers.set( key, value.replaceAll(from.origin, to.origin).replaceAll(from.host, to.host) ); } }); for (const cookie of setCookie) { headers.append( "Set-Cookie", cookie.replace( new RegExp(`Domain=${from.hostname}($|;|,)`), `Domain=${to.hostname}$1` ) ); } } export { ProxyWorker, ProxyWorker_default as default };