Deploy.
This commit is contained in:
+494
@@ -0,0 +1,494 @@
|
||||
// templates/startDevWorker/InspectorProxyWorker.ts
|
||||
import assert2 from "node:assert";
|
||||
|
||||
// src/api/startDevWorker/events.ts
|
||||
function serialiseError(e) {
|
||||
if (e instanceof Error) {
|
||||
return {
|
||||
message: e.message,
|
||||
name: e.name,
|
||||
stack: e.stack,
|
||||
cause: e.cause && serialiseError(e.cause)
|
||||
};
|
||||
} else {
|
||||
return { message: String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// src/utils/assert-never.ts
|
||||
function assertNever(_value) {
|
||||
}
|
||||
|
||||
// templates/startDevWorker/InspectorProxyWorker.ts
|
||||
var ALLOWED_HOST_HOSTNAMES = ["127.0.0.1", "[::1]", "localhost"];
|
||||
var ALLOWED_ORIGIN_HOSTNAMES = [
|
||||
"devtools.devprod.cloudflare.dev",
|
||||
// Workers + Assets (current deployment)
|
||||
"cloudflare-devtools.devprod.workers.dev",
|
||||
/^[a-z0-9]+-cloudflare-devtools\.devprod\.workers\.dev$/,
|
||||
// Cloudflare Pages (legacy deployment)
|
||||
"cloudflare-devtools.pages.dev",
|
||||
/^[a-z0-9]+\.cloudflare-devtools\.pages\.dev$/,
|
||||
"127.0.0.1",
|
||||
"[::1]",
|
||||
"localhost"
|
||||
];
|
||||
var InspectorProxyWorker_default = {
|
||||
fetch(req, env) {
|
||||
const singleton = env.DURABLE_OBJECT.idFromName("");
|
||||
const inspectorProxy = env.DURABLE_OBJECT.get(singleton);
|
||||
return inspectorProxy.fetch(req);
|
||||
}
|
||||
};
|
||||
function isDevToolsEvent(event, name) {
|
||||
return typeof event === "object" && event !== null && "method" in event && event.method === name;
|
||||
}
|
||||
var InspectorProxyWorker = class {
|
||||
constructor(_state, env) {
|
||||
this.env = env;
|
||||
}
|
||||
env;
|
||||
websockets = {
|
||||
runtimeDeferred: createDeferred()
|
||||
};
|
||||
proxyData;
|
||||
runtimeMessageBuffer = [];
|
||||
// Only allow a limited number of error-based reconnections, so as not to infinite loop
|
||||
reconnectionsRemaining = 3;
|
||||
async fetch(req) {
|
||||
if (req.headers.get("Authorization") === this.env.PROXY_CONTROLLER_AUTH_SECRET) {
|
||||
return this.handleProxyControllerRequest(req);
|
||||
}
|
||||
if (req.headers.get("Upgrade") === "websocket") {
|
||||
return this.handleDevToolsWebSocketUpgradeRequest(req);
|
||||
}
|
||||
return this.handleDevToolsJsonRequest(req);
|
||||
}
|
||||
// ************************
|
||||
// ** PROXY CONTROLLER **
|
||||
// ************************
|
||||
handleProxyControllerRequest(req) {
|
||||
assert2(
|
||||
req.headers.get("Upgrade") === "websocket",
|
||||
"Expected proxy controller data request to be WebSocket upgrade"
|
||||
);
|
||||
const { 0: response, 1: proxyController } = new WebSocketPair();
|
||||
proxyController.accept();
|
||||
proxyController.addEventListener("close", (event) => {
|
||||
this.sendDebugLog(
|
||||
"PROXY CONTROLLER WEBSOCKET CLOSED",
|
||||
event.code,
|
||||
event.reason
|
||||
);
|
||||
if (this.websockets.proxyController === proxyController) {
|
||||
this.websockets.proxyController = void 0;
|
||||
}
|
||||
});
|
||||
proxyController.addEventListener("error", (event) => {
|
||||
const error = serialiseError(event.error);
|
||||
this.sendDebugLog("PROXY CONTROLLER WEBSOCKET ERROR", error);
|
||||
if (this.websockets.proxyController === proxyController) {
|
||||
this.websockets.proxyController = void 0;
|
||||
}
|
||||
});
|
||||
proxyController.addEventListener(
|
||||
"message",
|
||||
this.handleProxyControllerIncomingMessage
|
||||
);
|
||||
this.websockets.proxyController = proxyController;
|
||||
return new Response(null, {
|
||||
status: 101,
|
||||
webSocket: response
|
||||
});
|
||||
}
|
||||
handleProxyControllerIncomingMessage = (event) => {
|
||||
assert2(
|
||||
typeof event.data === "string",
|
||||
"Expected event.data from proxy controller to be string"
|
||||
);
|
||||
const message = JSON.parse(
|
||||
event.data
|
||||
);
|
||||
this.sendDebugLog("handleProxyControllerIncomingMessage", event.data);
|
||||
switch (message.type) {
|
||||
case "reloadStart": {
|
||||
this.sendRuntimeDiscardConsoleEntries();
|
||||
break;
|
||||
}
|
||||
case "reloadComplete": {
|
||||
this.proxyData = message.proxyData;
|
||||
this.reconnectRuntimeWebSocket();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertNever(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
sendProxyControllerMessage(message) {
|
||||
message = typeof message === "string" ? message : JSON.stringify(message);
|
||||
this.websockets.proxyController?.send(message);
|
||||
}
|
||||
async sendProxyControllerRequest(message) {
|
||||
try {
|
||||
const res = await this.env.PROXY_CONTROLLER.fetch("http://dummy", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(message)
|
||||
});
|
||||
return res.ok ? await res.text() : void 0;
|
||||
} catch (e) {
|
||||
this.sendDebugLog(
|
||||
"FAILED TO SEND PROXY CONTROLLER REQUEST",
|
||||
serialiseError(e)
|
||||
);
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
sendDebugLog = (...args) => {
|
||||
this.sendProxyControllerRequest({ type: "debug-log", args });
|
||||
};
|
||||
// ***************
|
||||
// ** RUNTIME **
|
||||
// ***************
|
||||
handleRuntimeIncomingMessage = (event) => {
|
||||
assert2(typeof event.data === "string");
|
||||
const msg = JSON.parse(event.data);
|
||||
this.sendDebugLog("RUNTIME INCOMING MESSAGE", msg);
|
||||
if (isDevToolsEvent(msg, "Runtime.exceptionThrown")) {
|
||||
this.sendProxyControllerMessage(event.data);
|
||||
}
|
||||
if (this.proxyData?.proxyLogsToController && isDevToolsEvent(msg, "Runtime.consoleAPICalled")) {
|
||||
this.sendProxyControllerMessage(event.data);
|
||||
}
|
||||
this.runtimeMessageBuffer.push(msg);
|
||||
this.tryDrainRuntimeMessageBuffer();
|
||||
};
|
||||
handleRuntimeScriptParsed(msg) {
|
||||
if (!this.websockets.devtoolsHasFileSystemAccess && msg.params.sourceMapURL !== void 0 && // Don't try to find a sourcemap for e.g. node-internal: scripts
|
||||
msg.params.url.startsWith("file:")) {
|
||||
const url = new URL(msg.params.sourceMapURL, msg.params.url);
|
||||
if (url.protocol === "file:") {
|
||||
msg.params.sourceMapURL = url.href.replace("file:", "wrangler-file:");
|
||||
}
|
||||
}
|
||||
void this.sendDevToolsMessage(msg);
|
||||
}
|
||||
tryDrainRuntimeMessageBuffer = () => {
|
||||
if (this.websockets.devtools === void 0) return;
|
||||
for (const msg of this.runtimeMessageBuffer.splice(0)) {
|
||||
if (isDevToolsEvent(msg, "Debugger.scriptParsed")) {
|
||||
this.handleRuntimeScriptParsed(msg);
|
||||
} else {
|
||||
void this.sendDevToolsMessage(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
runtimeAbortController = new AbortController();
|
||||
// will abort the in-flight websocket upgrade request to the remote runtime
|
||||
runtimeKeepAliveInterval = null;
|
||||
async reconnectRuntimeWebSocket() {
|
||||
assert2(this.proxyData, "Expected this.proxyData to be defined");
|
||||
this.sendDebugLog("reconnectRuntimeWebSocket");
|
||||
this.websockets.runtime?.close();
|
||||
this.websockets.runtime = void 0;
|
||||
this.runtimeAbortController.abort();
|
||||
this.runtimeAbortController = new AbortController();
|
||||
this.websockets.runtimeDeferred = createDeferred(
|
||||
this.websockets.runtimeDeferred
|
||||
);
|
||||
const runtimeWebSocketUrl = urlFromParts(
|
||||
this.proxyData.userWorkerInspectorUrl
|
||||
);
|
||||
runtimeWebSocketUrl.protocol = this.proxyData.userWorkerUrl.protocol;
|
||||
this.sendDebugLog("NEW RUNTIME WEBSOCKET", runtimeWebSocketUrl);
|
||||
this.sendDevToolsMessage({
|
||||
method: "Runtime.executionContextsCleared",
|
||||
params: void 0
|
||||
});
|
||||
const upgrade = await fetch(runtimeWebSocketUrl, {
|
||||
headers: {
|
||||
...this.proxyData.headers,
|
||||
"User-Agent": `wrangler/${this.env.WRANGLER_VERSION}`,
|
||||
Upgrade: "websocket"
|
||||
},
|
||||
signal: this.runtimeAbortController.signal
|
||||
});
|
||||
const runtime = upgrade.webSocket;
|
||||
if (!runtime) {
|
||||
const error = new Error(
|
||||
`Failed to establish the WebSocket connection: expected server to reply with HTTP status code 101 (switching protocols), but received ${upgrade.status} instead.`
|
||||
);
|
||||
if (upgrade.status === 502 && this.reconnectionsRemaining >= 0) {
|
||||
await scheduler.wait((3 - this.reconnectionsRemaining) * 1e3);
|
||||
this.sendDebugLog(
|
||||
"RECONNECTING RUNTIME WEBSOCKET after 502. Reconnections remaining:",
|
||||
this.reconnectionsRemaining
|
||||
);
|
||||
return this.reconnectRuntimeWebSocket();
|
||||
}
|
||||
this.websockets.runtimeDeferred.reject(error);
|
||||
this.sendProxyControllerRequest({
|
||||
type: "runtime-websocket-error",
|
||||
error: serialiseError(error)
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.websockets.runtime = runtime;
|
||||
runtime.addEventListener("message", this.handleRuntimeIncomingMessage);
|
||||
runtime.addEventListener("close", (event) => {
|
||||
this.sendDebugLog("RUNTIME WEBSOCKET CLOSED", event.code, event.reason);
|
||||
clearInterval(this.runtimeKeepAliveInterval);
|
||||
if (this.websockets.runtime === runtime) {
|
||||
this.websockets.runtime = void 0;
|
||||
}
|
||||
});
|
||||
runtime.addEventListener("error", (event) => {
|
||||
const error = serialiseError(event.error);
|
||||
this.sendDebugLog("RUNTIME WEBSOCKET ERROR", error);
|
||||
clearInterval(this.runtimeKeepAliveInterval);
|
||||
if (this.websockets.runtime === runtime) {
|
||||
this.websockets.runtime = void 0;
|
||||
}
|
||||
this.sendProxyControllerRequest({
|
||||
type: "runtime-websocket-error",
|
||||
error
|
||||
});
|
||||
});
|
||||
runtime.accept();
|
||||
this.handleRuntimeWebSocketOpen(runtime);
|
||||
}
|
||||
#runtimeMessageCounter = 1e8;
|
||||
nextCounter() {
|
||||
return ++this.#runtimeMessageCounter;
|
||||
}
|
||||
handleRuntimeWebSocketOpen(runtime) {
|
||||
this.sendDebugLog("RUNTIME WEBSOCKET OPENED");
|
||||
this.reconnectionsRemaining = 3;
|
||||
this.sendRuntimeMessage(
|
||||
{ method: "Runtime.enable", id: this.nextCounter() },
|
||||
runtime
|
||||
);
|
||||
if (this.websockets.devtools !== void 0) {
|
||||
this.sendRuntimeMessage(
|
||||
{ method: "Debugger.enable", id: this.nextCounter() },
|
||||
runtime
|
||||
);
|
||||
}
|
||||
if (this.websockets.devtools !== void 0) {
|
||||
this.sendRuntimeMessage(
|
||||
{ method: "Network.enable", id: this.nextCounter() },
|
||||
runtime
|
||||
);
|
||||
}
|
||||
clearInterval(this.runtimeKeepAliveInterval);
|
||||
this.runtimeKeepAliveInterval = setInterval(() => {
|
||||
this.sendRuntimeMessage(
|
||||
{ method: "Runtime.getIsolateId", id: this.nextCounter() },
|
||||
runtime
|
||||
);
|
||||
}, 1e4);
|
||||
this.websockets.runtimeDeferred.resolve(runtime);
|
||||
}
|
||||
sendRuntimeDiscardConsoleEntries() {
|
||||
if (this.websockets.runtime) {
|
||||
this.sendRuntimeMessage(
|
||||
{
|
||||
method: "Runtime.discardConsoleEntries",
|
||||
id: this.nextCounter()
|
||||
},
|
||||
this.websockets.runtime
|
||||
);
|
||||
}
|
||||
}
|
||||
async sendRuntimeMessage(message, runtime = this.websockets.runtimeDeferred.promise) {
|
||||
runtime = await runtime;
|
||||
message = typeof message === "string" ? message : JSON.stringify(message);
|
||||
this.sendDebugLog("SEND TO RUNTIME", message);
|
||||
runtime.send(message);
|
||||
}
|
||||
// ****************
|
||||
// ** DEVTOOLS **
|
||||
// ****************
|
||||
#inspectorId = crypto.randomUUID();
|
||||
async handleDevToolsJsonRequest(req) {
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname === "/json/version") {
|
||||
return Response.json({
|
||||
Browser: `wrangler/v${this.env.WRANGLER_VERSION}`,
|
||||
// TODO: (someday): The DevTools protocol should match that of workerd.
|
||||
// This could be exposed by the preview API.
|
||||
"Protocol-Version": "1.3"
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/json" || url.pathname === "/json/list") {
|
||||
const localHost = `${url.host}/ws`;
|
||||
const devtoolsFrontendUrl = `https://devtools.devprod.cloudflare.dev/js_app?theme=systemPreferred&debugger=true&ws=${localHost}`;
|
||||
return Response.json([
|
||||
{
|
||||
id: this.#inspectorId,
|
||||
type: "node",
|
||||
// TODO: can we specify different type?
|
||||
description: "workers",
|
||||
webSocketDebuggerUrl: `ws://${localHost}`,
|
||||
devtoolsFrontendUrl,
|
||||
devtoolsFrontendUrlCompat: devtoolsFrontendUrl,
|
||||
// Below are fields that are visible in the DevTools UI.
|
||||
title: "Cloudflare Worker",
|
||||
faviconUrl: "https://workers.cloudflare.com/favicon.ico"
|
||||
// url: "http://" + localHost, // looks unnecessary
|
||||
}
|
||||
]);
|
||||
}
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
async handleDevToolsWebSocketUpgradeRequest(req) {
|
||||
let hostHeader = req.headers.get("Host");
|
||||
if (hostHeader == null) return new Response(null, { status: 400 });
|
||||
try {
|
||||
const host = new URL(`http://${hostHeader}`);
|
||||
if (!ALLOWED_HOST_HOSTNAMES.includes(host.hostname)) {
|
||||
return new Response("Disallowed `Host` header", { status: 401 });
|
||||
}
|
||||
} catch {
|
||||
return new Response("Expected `Host` header", { status: 400 });
|
||||
}
|
||||
let originHeader = req.headers.get("Origin");
|
||||
if (originHeader === null && !req.headers.has("User-Agent")) {
|
||||
originHeader = "http://localhost";
|
||||
}
|
||||
if (originHeader === null) {
|
||||
return new Response("Expected `Origin` header", { status: 400 });
|
||||
}
|
||||
try {
|
||||
const origin = new URL(originHeader);
|
||||
const allowed = ALLOWED_ORIGIN_HOSTNAMES.some((rule) => {
|
||||
if (typeof rule === "string") return origin.hostname === rule;
|
||||
else return rule.test(origin.hostname);
|
||||
});
|
||||
if (!allowed) {
|
||||
return new Response("Disallowed `Origin` header", { status: 401 });
|
||||
}
|
||||
} catch {
|
||||
return new Response("Expected `Origin` header", { status: 400 });
|
||||
}
|
||||
this.sendDebugLog("DEVTOOLS WEBSOCKET TRYING TO CONNECT");
|
||||
await this.websockets.runtimeDeferred.promise;
|
||||
this.sendDebugLog("DEVTOOLS WEBSOCKET CAN NOW CONNECT");
|
||||
assert2(
|
||||
req.headers.get("Upgrade") === "websocket",
|
||||
"Expected DevTools connection to be WebSocket upgrade"
|
||||
);
|
||||
const { 0: response, 1: devtools } = new WebSocketPair();
|
||||
devtools.accept();
|
||||
if (this.websockets.devtools !== void 0) {
|
||||
devtools.close(
|
||||
1013,
|
||||
"Too many clients; only one can be connected at a time"
|
||||
);
|
||||
} else {
|
||||
devtools.addEventListener("message", this.handleDevToolsIncomingMessage);
|
||||
const disconnectDevtools = () => {
|
||||
if (this.websockets.devtools === devtools) {
|
||||
this.websockets.devtools = void 0;
|
||||
if (this.websockets.runtime) {
|
||||
this.sendRuntimeMessage({
|
||||
id: this.nextCounter(),
|
||||
method: "Debugger.disable"
|
||||
});
|
||||
this.sendRuntimeMessage({
|
||||
id: this.nextCounter(),
|
||||
method: "Network.disable"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
devtools.addEventListener("close", (event) => {
|
||||
this.sendDebugLog(
|
||||
"DEVTOOLS WEBSOCKET CLOSED",
|
||||
event.code,
|
||||
event.reason
|
||||
);
|
||||
disconnectDevtools();
|
||||
});
|
||||
devtools.addEventListener("error", (event) => {
|
||||
const error = serialiseError(event.error);
|
||||
this.sendDebugLog("DEVTOOLS WEBSOCKET ERROR", error);
|
||||
disconnectDevtools();
|
||||
});
|
||||
this.sendRuntimeMessage({
|
||||
id: this.nextCounter(),
|
||||
method: "Debugger.disable"
|
||||
});
|
||||
this.sendDebugLog("DEVTOOLS WEBSOCKET CONNECTED");
|
||||
const userAgent = req.headers.get("User-Agent") ?? "";
|
||||
const hasFileSystemAccess = !/mozilla/i.test(userAgent);
|
||||
this.websockets.devtools = devtools;
|
||||
this.websockets.devtoolsHasFileSystemAccess = hasFileSystemAccess;
|
||||
this.tryDrainRuntimeMessageBuffer();
|
||||
}
|
||||
return new Response(null, { status: 101, webSocket: response });
|
||||
}
|
||||
handleDevToolsIncomingMessage = (event) => {
|
||||
assert2(
|
||||
typeof event.data === "string",
|
||||
"Expected devtools incoming message to be of type string"
|
||||
);
|
||||
const message = JSON.parse(event.data);
|
||||
this.sendDebugLog("DEVTOOLS INCOMING MESSAGE", message);
|
||||
if (message.method === "Network.loadNetworkResource") {
|
||||
return void this.handleDevToolsLoadNetworkResource(message);
|
||||
}
|
||||
this.sendRuntimeMessage(JSON.stringify(message));
|
||||
};
|
||||
async handleDevToolsLoadNetworkResource(message) {
|
||||
const response = await this.sendProxyControllerRequest({
|
||||
type: "load-network-resource",
|
||||
url: message.params.url
|
||||
});
|
||||
if (response === void 0) {
|
||||
this.sendDebugLog(
|
||||
`ProxyController could not resolve Network.loadNetworkResource for "${message.params.url}"`
|
||||
);
|
||||
this.sendRuntimeMessage(JSON.stringify(message));
|
||||
} else {
|
||||
this.sendDevToolsMessage({
|
||||
id: message.id,
|
||||
// @ts-expect-error DevTools Protocol type does not match our patched devtools -- result.resource.text was added
|
||||
result: { resource: { success: true, text: response } }
|
||||
});
|
||||
}
|
||||
}
|
||||
sendDevToolsMessage(message) {
|
||||
message = typeof message === "string" ? message : JSON.stringify(message);
|
||||
this.sendDebugLog("SEND TO DEVTOOLS", message);
|
||||
this.websockets.devtools?.send(message);
|
||||
}
|
||||
};
|
||||
export {
|
||||
InspectorProxyWorker,
|
||||
InspectorProxyWorker_default as default
|
||||
};
|
||||
+3314
File diff suppressed because it is too large
Load Diff
+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
|
||||
};
|
||||
+3719
File diff suppressed because it is too large
Load Diff
+370847
File diff suppressed because one or more lines are too long
+1604
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+224
@@ -0,0 +1,224 @@
|
||||
//#region ../config/dist/public-CFHebTo4.mjs
|
||||
const bindings = {
|
||||
agentMemory: (options) => ({
|
||||
type: "agent-memory",
|
||||
...options
|
||||
}),
|
||||
ai: (options) => ({
|
||||
type: "ai",
|
||||
...options
|
||||
}),
|
||||
aiSearch: (options) => ({
|
||||
type: "ai-search",
|
||||
...options
|
||||
}),
|
||||
aiSearchNamespace: (options) => ({
|
||||
type: "ai-search-namespace",
|
||||
...options
|
||||
}),
|
||||
analyticsEngineDataset: (options) => ({
|
||||
type: "analytics-engine-dataset",
|
||||
...options
|
||||
}),
|
||||
artifacts: (options) => ({
|
||||
type: "artifacts",
|
||||
...options
|
||||
}),
|
||||
assets: () => ({ type: "assets" }),
|
||||
browser: (options) => ({
|
||||
type: "browser",
|
||||
...options
|
||||
}),
|
||||
d1: (options) => ({
|
||||
type: "d1",
|
||||
...options
|
||||
}),
|
||||
dispatchNamespace: (options) => ({
|
||||
type: "dispatch-namespace",
|
||||
...options
|
||||
}),
|
||||
durableObject: (options) => ({
|
||||
type: "durable-object",
|
||||
...options
|
||||
}),
|
||||
flagship: (options) => ({
|
||||
type: "flagship",
|
||||
...options
|
||||
}),
|
||||
hyperdrive: (options) => ({
|
||||
type: "hyperdrive",
|
||||
...options
|
||||
}),
|
||||
images: (options) => ({
|
||||
type: "images",
|
||||
...options
|
||||
}),
|
||||
json: (value) => ({
|
||||
type: "json",
|
||||
value
|
||||
}),
|
||||
kv: (options) => ({
|
||||
type: "kv",
|
||||
...options
|
||||
}),
|
||||
logfwdr: (options) => ({
|
||||
type: "logfwdr",
|
||||
...options
|
||||
}),
|
||||
media: (options) => ({
|
||||
type: "media",
|
||||
...options
|
||||
}),
|
||||
mtlsCertificate: (options) => ({
|
||||
type: "mtls-certificate",
|
||||
...options
|
||||
}),
|
||||
pipeline: (options) => ({
|
||||
type: "pipeline",
|
||||
...options
|
||||
}),
|
||||
queue: (options) => ({
|
||||
type: "queue",
|
||||
...options
|
||||
}),
|
||||
rateLimit: (options) => ({
|
||||
type: "rate-limit",
|
||||
...options
|
||||
}),
|
||||
r2: (options) => ({
|
||||
type: "r2",
|
||||
...options
|
||||
}),
|
||||
secret: () => ({ type: "secret" }),
|
||||
secretsStoreSecret: (options) => ({
|
||||
type: "secrets-store-secret",
|
||||
...options
|
||||
}),
|
||||
sendEmail: (options) => ({
|
||||
type: "send-email",
|
||||
...options
|
||||
}),
|
||||
stream: (options) => ({
|
||||
type: "stream",
|
||||
...options
|
||||
}),
|
||||
text: (value) => ({
|
||||
type: "text",
|
||||
value
|
||||
}),
|
||||
vectorize: (options) => ({
|
||||
type: "vectorize",
|
||||
...options
|
||||
}),
|
||||
versionMetadata: () => ({ type: "version-metadata" }),
|
||||
vpcService: (options) => ({
|
||||
type: "vpc-service",
|
||||
...options
|
||||
}),
|
||||
vpcNetwork: (options) => ({
|
||||
type: "vpc-network",
|
||||
...options
|
||||
}),
|
||||
webSearch: (options) => ({
|
||||
type: "web-search",
|
||||
...options
|
||||
}),
|
||||
worker: (options) => ({
|
||||
type: "worker",
|
||||
...options
|
||||
}),
|
||||
workerLoader: () => ({ type: "worker-loader" })
|
||||
};
|
||||
/**
|
||||
* Triggers builder for configuring event triggers.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { defineWorker, triggers } from "@cloudflare/config";
|
||||
*
|
||||
* export default defineWorker({
|
||||
* triggers: [
|
||||
* triggers.fetch({ pattern: "example.com/*", zone: "example.com" }),
|
||||
* triggers.queue({ name: "my-queue" }),
|
||||
* triggers.scheduled({ schedule: "0 * * * *" }),
|
||||
* triggers.scheduled({ schedule: "30 0 * * *" }),
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
const triggers = {
|
||||
fetch: (options) => ({
|
||||
type: "fetch",
|
||||
...options
|
||||
}),
|
||||
queue: (options) => ({
|
||||
type: "queue",
|
||||
...options
|
||||
}),
|
||||
scheduled: (options) => ({
|
||||
type: "scheduled",
|
||||
...options
|
||||
})
|
||||
};
|
||||
function durableObject(options) {
|
||||
return {
|
||||
type: "durable-object",
|
||||
...options
|
||||
};
|
||||
}
|
||||
function worker(options = {}) {
|
||||
return {
|
||||
type: "worker",
|
||||
...options
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Exports builder for configuring Worker exports.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { defineWorker, exports } from "@cloudflare/config";
|
||||
*
|
||||
* export default defineWorker({
|
||||
* exports: {
|
||||
* MyDurableObject: exports.durableObject({ storage: "sqlite" }),
|
||||
* OldClass: exports.durableObject({ state: "deleted" }),
|
||||
* OldName: exports.durableObject({ state: "renamed", renamedTo: "NewName" }),
|
||||
* Outgoing: exports.durableObject({ state: "transferred", transferredTo: "target-worker" }),
|
||||
* Incoming: exports.durableObject({ state: "expecting-transfer", storage: "sqlite", transferFrom: "source-worker" }),
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
const exports = {
|
||||
durableObject,
|
||||
worker
|
||||
};
|
||||
const CONFIG = Symbol.for("@cloudflare/config:worker-config");
|
||||
function defineWorker(config) {
|
||||
return {
|
||||
[CONFIG]: config,
|
||||
durableObject(options) {
|
||||
return {
|
||||
type: "durable-object",
|
||||
...options
|
||||
};
|
||||
},
|
||||
worker(options) {
|
||||
return {
|
||||
type: "worker",
|
||||
...options
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region src/experimental-config/wrangler-definition.ts
|
||||
function defineWranglerConfig(config) {
|
||||
return config;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
export { bindings, defineWorker, defineWranglerConfig, exports, triggers };
|
||||
//# sourceMappingURL=experimental-config.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user