Deploy.
This commit is contained in:
+253
@@ -0,0 +1,253 @@
|
||||
// src/api/startDevWorker/utils.ts
|
||||
import assert from "node:assert";
|
||||
var PREVIEW_TOKEN_REFRESH_INTERVAL = 50 * 60 * 1e3;
|
||||
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 LIVE_RELOAD_PATHNAME = "/cdn-cgi/live-reload";
|
||||
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;
|
||||
}
|
||||
state;
|
||||
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
|
||||
);
|
||||
const encoding = request.cf?.clientAcceptEncoding;
|
||||
if (encoding !== void 0) headers.set("Accept-Encoding", encoding);
|
||||
rewriteUrlRelatedHeaders(headers, outerUrl, innerUrl);
|
||||
headers.set("MF-Original-URL", innerUrl.href);
|
||||
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(async (res) => {
|
||||
res = new Response(res.body, res);
|
||||
rewriteUrlRelatedHeaders(res.headers, innerUrl, outerUrl);
|
||||
await checkForPreviewTokenError(res, this.env, proxyData);
|
||||
if (isHtmlResponse(res)) {
|
||||
res = insertLiveReloadScript(request, res, this.env, proxyData);
|
||||
}
|
||||
if (isSseResponse(res)) {
|
||||
void sendMessageToProxyController(this.env, {
|
||||
type: "sseResponseDetected"
|
||||
});
|
||||
}
|
||||
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 isSseResponse(res) {
|
||||
return res.headers.get("content-type")?.startsWith("text/event-stream") ?? false;
|
||||
}
|
||||
function isRequestForLiveReloadWebsocket(req) {
|
||||
if (new URL(req.url).pathname !== LIVE_RELOAD_PATHNAME) {
|
||||
return false;
|
||||
}
|
||||
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)
|
||||
});
|
||||
}
|
||||
async function checkForPreviewTokenError(response, env, proxyData) {
|
||||
if (response.status !== 400) {
|
||||
return;
|
||||
}
|
||||
const clone = response.clone();
|
||||
const text = await clone.text();
|
||||
if (text.includes("Invalid Workers Preview configuration") || text.includes("error code: 1031")) {
|
||||
void sendMessageToProxyController(env, {
|
||||
type: "previewTokenExpired",
|
||||
proxyData
|
||||
});
|
||||
}
|
||||
}
|
||||
function insertLiveReloadScript(request, response, env, proxyData) {
|
||||
const htmlRewriter = new HTMLRewriter();
|
||||
htmlRewriter.onDocument({
|
||||
end(end) {
|
||||
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 = `
|
||||
<script defer type="application/javascript">
|
||||
(function() {
|
||||
var ws;
|
||||
function recover() {
|
||||
ws = null;
|
||||
setTimeout(initLiveReload, 100);
|
||||
}
|
||||
function initLiveReload() {
|
||||
if (ws) return;
|
||||
var origin = (location.protocol === "http:" ? "ws://" : "wss://") + location.host;
|
||||
ws = new WebSocket(origin + "${LIVE_RELOAD_PATHNAME}", "${LIVE_RELOAD_PROTOCOL}");
|
||||
ws.onclose = recover;
|
||||
ws.onerror = recover;
|
||||
ws.onmessage = location.reload.bind(location);
|
||||
}
|
||||
initLiveReload();
|
||||
})();
|
||||
</script>
|
||||
`;
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user