miniflare
Version:
Fun, full-featured, fully-local simulator for Cloudflare Workers
107 lines (104 loc) • 4.25 kB
JavaScript
// src/workers/browser-rendering/binding.worker.ts
import assert from "node:assert";
import { DurableObject } from "cloudflare:workers";
// src/workers/core/constants.ts
var CoreBindings = {
SERVICE_LOOPBACK: "MINIFLARE_LOOPBACK",
SERVICE_USER_ROUTE_PREFIX: "MINIFLARE_USER_ROUTE_",
SERVICE_USER_FALLBACK: "MINIFLARE_USER_FALLBACK",
TEXT_CUSTOM_SERVICE: "MINIFLARE_CUSTOM_SERVICE",
IMAGES_SERVICE: "MINIFLARE_IMAGES_SERVICE",
TEXT_UPSTREAM_URL: "MINIFLARE_UPSTREAM_URL",
JSON_CF_BLOB: "CF_BLOB",
JSON_ROUTES: "MINIFLARE_ROUTES",
JSON_LOG_LEVEL: "MINIFLARE_LOG_LEVEL",
DATA_LIVE_RELOAD_SCRIPT: "MINIFLARE_LIVE_RELOAD_SCRIPT",
DURABLE_OBJECT_NAMESPACE_PROXY: "MINIFLARE_PROXY",
DATA_PROXY_SECRET: "MINIFLARE_PROXY_SECRET",
DATA_PROXY_SHARED_SECRET: "MINIFLARE_PROXY_SHARED_SECRET",
TRIGGER_HANDLERS: "TRIGGER_HANDLERS",
LOG_REQUESTS: "LOG_REQUESTS",
STRIP_DISABLE_PRETTY_ERROR: "STRIP_DISABLE_PRETTY_ERROR"
};
// src/workers/browser-rendering/binding.worker.ts
function isClosed(ws) {
return !ws || ws.readyState === WebSocket.CLOSED;
}
var BrowserSession = class extends DurableObject {
endpoint;
ws;
server;
async fetch(_request) {
assert(
this.endpoint !== void 0,
"endpoint must be set before connecting"
), isClosed(this.ws) || isClosed(this.server) ? (this.ws?.close(), this.server?.close(), this.ws = void 0, this.server = void 0) : assert.fail("WebSocket already initialized");
let webSocketPair = new WebSocketPair(), [client, server] = Object.values(webSocketPair);
server.accept();
let wsEndpoint = this.endpoint.replace("ws://", "http://"), response = await fetch(wsEndpoint, {
headers: {
Upgrade: "websocket"
}
});
assert(response.webSocket !== null, "Expected a WebSocket response");
let ws = response.webSocket;
return ws.accept(), ws.addEventListener("message", (m) => {
let string = new TextEncoder().encode(m.data), data = new Uint8Array(string.length + 4);
new DataView(data.buffer).setUint32(0, string.length, !0), data.set(string, 4), server.send(data);
}), server.addEventListener("message", (m) => {
if (m.data === "ping") {
this.#checkStatus().catch((err) => {
console.error("Error checking browser status:", err);
});
return;
}
ws.send(new TextDecoder().decode(m.data.slice(4)));
}), server.addEventListener("close", ({ code, reason }) => {
ws.close(code, reason), this.ws = void 0;
}), ws.addEventListener("close", ({ code, reason }) => {
server.close(code, reason), this.server = void 0;
}), this.ws = ws, this.server = server, new Response(null, {
status: 101,
webSocket: client
});
}
async setEndpoint(endpoint) {
this.endpoint = endpoint;
}
async #checkStatus() {
if (this.endpoint) {
let url = new URL("http://example.com/browser/status");
url.searchParams.set("wsEndpoint", this.endpoint);
let resp = await this.env[CoreBindings.SERVICE_LOOPBACK].fetch(url), { stopped } = resp.ok ? await resp.json() : {};
if (stopped) {
this.ws?.close(), this.server?.close(), this.ws = void 0, this.server = void 0, this.ctx.storage.deleteAll();
return;
}
}
}
}, binding_worker_default = {
async fetch(request, env) {
let url = new URL(request.url);
switch (url.pathname) {
case "/v1/acquire": {
let wsEndpoint = await (await env[CoreBindings.SERVICE_LOOPBACK].fetch(
"http://example.com/browser/launch"
)).text(), sessionId = crypto.randomUUID(), id = env.BrowserSession.idFromName(sessionId);
return await env.BrowserSession.get(id).setEndpoint(wsEndpoint), Response.json({ sessionId });
}
case "/v1/connectDevtools": {
let sessionId = url.searchParams.get("browser_session");
assert(sessionId !== null, "browser_session must be set");
let id = env.BrowserSession.idFromName(sessionId);
return env.BrowserSession.get(id).fetch(request);
}
default:
return new Response("Not implemented", { status: 405 });
}
}
};
export {
BrowserSession,
binding_worker_default as default
};
//# sourceMappingURL=binding.worker.js.map