UNPKG

hono-party

Version:
157 lines (154 loc) 6.04 kB
import { env } from "hono/adapter"; import { createMiddleware } from "hono/factory"; import { env as env$1 } from "cloudflare:workers"; import "nanoid"; //#region ../partyserver/dist/index.js if (!("OPEN" in WebSocket)) { const WebSocketStatus = { CONNECTING: WebSocket.READY_STATE_CONNECTING, OPEN: WebSocket.READY_STATE_OPEN, CLOSING: WebSocket.READY_STATE_CLOSING, CLOSED: WebSocket.READY_STATE_CLOSED }; Object.assign(WebSocket, WebSocketStatus); Object.assign(WebSocket.prototype, WebSocketStatus); } /** * Cache websocket attachments to avoid having to rehydrate them on every property access. */ var AttachmentCache = class { #cache = /* @__PURE__ */ new WeakMap(); get(ws) { let attachment = this.#cache.get(ws); if (!attachment) { attachment = WebSocket.prototype.deserializeAttachment.call(ws); if (attachment !== void 0) this.#cache.set(ws, attachment); else throw new Error("Missing websocket attachment. This is most likely an issue in PartyServer, please open an issue at https://github.com/cloudflare/partykit/issues"); } return attachment; } set(ws, attachment) { this.#cache.set(ws, attachment); WebSocket.prototype.serializeAttachment.call(ws, attachment); } }; const attachments = new AttachmentCache(); const serverMapCache = /* @__PURE__ */ new WeakMap(); function camelCaseToKebabCase(str) { if (str === str.toUpperCase() && str !== str.toLowerCase()) return str.toLowerCase().replace(/_/g, "-"); let kebabified = str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`); kebabified = kebabified.startsWith("-") ? kebabified.slice(1) : kebabified; return kebabified.replace(/_/g, "-").replace(/-$/, ""); } /** * A utility function for PartyKit style routing. */ async function routePartykitRequest(req, env$1$1 = env$1, options) { if (!serverMapCache.has(env$1$1)) serverMapCache.set(env$1$1, Object.entries(env$1$1).reduce((acc, [k, v]) => { if (v && typeof v === "object" && "idFromName" in v && typeof v.idFromName === "function") { Object.assign(acc, { [camelCaseToKebabCase(k)]: v }); return acc; } return acc; }, {})); const map = serverMapCache.get(env$1$1); const prefixParts = (options?.prefix || "parties").split("/"); const parts = new URL(req.url).pathname.split("/").filter(Boolean); if (!prefixParts.every((part, index) => parts[index] === part) || parts.length < prefixParts.length + 2) return null; const namespace = parts[prefixParts.length]; const name = parts[prefixParts.length + 1]; if (name && namespace) { if (!map[namespace]) { if (namespace === "main") { console.warn("You appear to be migrating a PartyKit project to PartyServer."); console.warn(`PartyServer doesn't have a "main" party by default. Try adding this to your PartySocket client:\n party: "${camelCaseToKebabCase(Object.keys(map)[0])}"`); } else console.error(`The url ${req.url} with namespace "${namespace}" and name "${name}" does not match any server namespace. Did you forget to add a durable object binding to the class ${namespace[0].toUpperCase() + namespace.slice(1)} in your wrangler.jsonc?`); return new Response("Invalid request", { status: 400 }); } let doNamespace = map[namespace]; if (options?.jurisdiction) doNamespace = doNamespace.jurisdiction(options.jurisdiction); const id = doNamespace.idFromName(name); const stub = doNamespace.get(id, options); req = new Request(req); req.headers.set("x-partykit-room", name); req.headers.set("x-partykit-namespace", namespace); if (options?.jurisdiction) req.headers.set("x-partykit-jurisdiction", options.jurisdiction); if (options?.props) req.headers.set("x-partykit-props", JSON.stringify(options?.props)); if (req.headers.get("Upgrade")?.toLowerCase() === "websocket") { if (options?.onBeforeConnect) { const reqOrRes = await options.onBeforeConnect(req, { party: namespace, name }); if (reqOrRes instanceof Request) req = reqOrRes; else if (reqOrRes instanceof Response) return reqOrRes; } } else if (options?.onBeforeRequest) { const reqOrRes = await options.onBeforeRequest(req, { party: namespace, name }); if (reqOrRes instanceof Request) req = reqOrRes; else if (reqOrRes instanceof Response) return reqOrRes; } return stub.fetch(req); } else return null; } //#endregion //#region src/index.ts /** * Creates a middleware for handling PartyServer WebSocket and HTTP requests * Processes both WebSocket upgrades and standard HTTP requests, delegating them to PartyServer */ function partyserverMiddleware(ctx) { return createMiddleware(async (c, next) => { try { const response = await (isWebSocketUpgrade(c) ? handleWebSocketUpgrade : handleHttpRequest)(c, ctx?.options); return response === null ? await next() : response; } catch (error) { if (ctx?.onError) { ctx.onError(error); return next(); } throw error; } }); } /** * Checks if the incoming request is a WebSocket upgrade request * Looks for the 'upgrade' header with a value of 'websocket' (case-insensitive) */ function isWebSocketUpgrade(c) { return c.req.header("upgrade")?.toLowerCase() === "websocket"; } /** * Creates a new Request object from the Hono context * Preserves the original request's URL, method, headers, and body */ function createRequestFromContext(c) { return c.req.raw.clone(); } /** * Handles WebSocket upgrade requests * Returns a WebSocket upgrade response if successful, null otherwise */ async function handleWebSocketUpgrade(c, options) { const response = await routePartykitRequest(createRequestFromContext(c), env(c), options); if (!response?.webSocket) return null; return new Response(null, { status: 101, webSocket: response.webSocket }); } /** * Handles standard HTTP requests * Forwards the request to PartyServer and returns the response */ async function handleHttpRequest(c, options) { return routePartykitRequest(createRequestFromContext(c), env(c), options); } //#endregion export { partyserverMiddleware }; //# sourceMappingURL=index.js.map