diff --git a/.changeset/expose-hono-context.md b/.changeset/expose-hono-context.md new file mode 100644 index 00000000..8704547f --- /dev/null +++ b/.changeset/expose-hono-context.md @@ -0,0 +1,5 @@ +--- +"hono-party": patch +--- + +Expose Hono context as a third argument to `onBeforeConnect` and `onBeforeRequest` callbacks, giving access to `c.env`, `c.var`, `c.get()`, etc. diff --git a/fixtures/hono/src/server.ts b/fixtures/hono/src/server.ts index 1501643e..733fdd63 100644 --- a/fixtures/hono/src/server.ts +++ b/fixtures/hono/src/server.ts @@ -4,7 +4,10 @@ import { Server } from "partyserver"; import type { Connection, WSMessage } from "partyserver"; -// Multiple party servers +type Bindings = { + Chat: DurableObjectNamespace; +}; + export class Chat extends Server { onMessage(connection: Connection, message: WSMessage): void | Promise { console.log("onMessage", message); @@ -12,8 +15,23 @@ export class Chat extends Server { } } -const app = new Hono(); -app.use("*", partyserverMiddleware()); +const app = new Hono<{ Bindings: Bindings }>(); + +app.use( + "*", + partyserverMiddleware<{ Bindings: Bindings }>({ + options: { + onBeforeConnect(req, lobby, c) { + const url = new URL(req.url); + const token = url.searchParams.get("token"); + if (!token) { + return new Response("Unauthorized", { status: 401 }); + } + console.log("env bindings available:", Object.keys(c.env)); + } + } + }) +); app.get("/", (c) => c.text("Hello from Hono!")); diff --git a/packages/hono-party/README.md b/packages/hono-party/README.md index 6e37b374..60111f89 100644 --- a/packages/hono-party/README.md +++ b/packages/hono-party/README.md @@ -24,14 +24,16 @@ export class Document extends Server {} const app = new Hono(); app.use("*", partyserverMiddleware()); -// or with authentication +// or with authentication (using env bindings via Hono context) +type Env = { Bindings: { JWT_SECRET: string } }; app.use( "*", - partyserverMiddleware({ + partyserverMiddleware({ options: { - onBeforeConnect: async (req) => { + onBeforeConnect: async (req, lobby, c) => { const token = req.headers.get("authorization"); - // validate token + const secret = c.env.JWT_SECRET; + // validate token against secret if (!token) return new Response("Unauthorized", { status: 401 }); } } diff --git a/packages/hono-party/src/index.ts b/packages/hono-party/src/index.ts index edfdacc1..74dc568f 100644 --- a/packages/hono-party/src/index.ts +++ b/packages/hono-party/src/index.ts @@ -3,14 +3,39 @@ import { createMiddleware } from "hono/factory"; import { routePartykitRequest } from "partyserver"; import type { Context, Env } from "hono"; -import type { PartyServerOptions } from "partyserver"; +import type { Lobby, PartyServerOptions } from "partyserver"; + +/** + * Extended options for the Hono middleware that pass the Hono context + * to `onBeforeConnect` and `onBeforeRequest` as a third argument, + * giving access to `c.env`, `c.var`, `c.get()`, etc. + */ +export type HonoPartyServerOptions = Omit< + PartyServerOptions, + "onBeforeConnect" | "onBeforeRequest" +> & { + onBeforeConnect?: ( + req: Request, + lobby: Lobby, + c: Context + ) => Response | Request | void | Promise; + onBeforeRequest?: ( + req: Request, + lobby: Lobby, + c: Context + ) => + | Response + | Request + | void + | Promise; +}; /** * Configuration options for the PartyServer middleware */ type PartyServerMiddlewareContext = { /** PartyServer-specific configuration options */ - options?: PartyServerOptions; + options?: HonoPartyServerOptions; /** Optional error handler for caught errors */ onError?: (error: Error) => void; }; @@ -22,12 +47,12 @@ type PartyServerMiddlewareContext = { export function partyserverMiddleware( ctx?: PartyServerMiddlewareContext ) { - return createMiddleware(async (c, next) => { + return createMiddleware(async (c, next) => { try { - const handler = isWebSocketUpgrade(c) - ? handleWebSocketUpgrade - : handleHttpRequest; - const response = await handler(c, ctx?.options); + const options = wrapOptionsWithContext(ctx?.options, c); + const response = isWebSocketUpgrade(c) + ? await handleWebSocketUpgrade(c, options) + : await handleHttpRequest(c, options); return response === null ? await next() : response; } catch (error) { @@ -40,6 +65,30 @@ export function partyserverMiddleware( }); } +/** + * Wraps the Hono-specific options into standard PartyServerOptions by + * closing over the Hono context so callbacks receive it as a third arg. + */ +function wrapOptionsWithContext( + options: HonoPartyServerOptions | undefined, + c: Context +): PartyServerOptions | undefined { + if (!options) return undefined; + + const { onBeforeConnect, onBeforeRequest, ...rest } = options; + return { + ...rest, + ...(onBeforeConnect && { + onBeforeConnect: (req: Request, lobby: Lobby) => + onBeforeConnect(req, lobby, c) + }), + ...(onBeforeRequest && { + onBeforeRequest: (req: Request, lobby: Lobby) => + onBeforeRequest(req, lobby, c) + }) + }; +} + /** * Checks if the incoming request is a WebSocket upgrade request * Looks for the 'upgrade' header with a value of 'websocket' (case-insensitive) @@ -60,9 +109,9 @@ function createRequestFromContext(c: Context) { * Handles WebSocket upgrade requests * Returns a WebSocket upgrade response if successful, null otherwise */ -async function handleWebSocketUpgrade( - c: Context, - options?: PartyServerOptions +async function handleWebSocketUpgrade( + c: Context, + options?: PartyServerOptions ) { const req = createRequestFromContext(c); const response = await routePartykitRequest(req, env(c), options); @@ -81,10 +130,7 @@ async function handleWebSocketUpgrade( * Handles standard HTTP requests * Forwards the request to PartyServer and returns the response */ -async function handleHttpRequest( - c: Context, - options?: PartyServerOptions -) { +async function handleHttpRequest(c: Context, options?: PartyServerOptions) { const req = createRequestFromContext(c); return routePartykitRequest(req, env(c), options); }