Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/expose-hono-context.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 21 additions & 3 deletions fixtures/hono/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,34 @@ 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<void> {
console.log("onMessage", message);
this.broadcast(message, [connection.id]);
}
}

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!"));

Expand Down
10 changes: 6 additions & 4 deletions packages/hono-party/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Env>({
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 });
}
}
Expand Down
74 changes: 60 additions & 14 deletions packages/hono-party/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<E extends Env> = Omit<
PartyServerOptions,
"onBeforeConnect" | "onBeforeRequest"
> & {
onBeforeConnect?: (
req: Request,
lobby: Lobby,
c: Context<E>
) => Response | Request | void | Promise<Response | Request | void>;
onBeforeRequest?: (
req: Request,
lobby: Lobby,
c: Context<E>
) =>
| Response
| Request
| void
| Promise<Response | Request | undefined | void>;
};

/**
* Configuration options for the PartyServer middleware
*/
type PartyServerMiddlewareContext<E extends Env> = {
/** PartyServer-specific configuration options */
options?: PartyServerOptions<E>;
options?: HonoPartyServerOptions<E>;
/** Optional error handler for caught errors */
onError?: (error: Error) => void;
};
Expand All @@ -22,12 +47,12 @@ type PartyServerMiddlewareContext<E extends Env> = {
export function partyserverMiddleware<E extends Env = Env>(
ctx?: PartyServerMiddlewareContext<E>
) {
return createMiddleware(async (c, next) => {
return createMiddleware<E>(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) {
Expand All @@ -40,6 +65,30 @@ export function partyserverMiddleware<E extends Env = Env>(
});
}

/**
* Wraps the Hono-specific options into standard PartyServerOptions by
* closing over the Hono context so callbacks receive it as a third arg.
*/
function wrapOptionsWithContext<E extends Env>(
options: HonoPartyServerOptions<E> | undefined,
c: Context<E>
): 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)
Expand All @@ -60,9 +109,9 @@ function createRequestFromContext(c: Context) {
* Handles WebSocket upgrade requests
* Returns a WebSocket upgrade response if successful, null otherwise
*/
async function handleWebSocketUpgrade<E extends Env>(
c: Context<E>,
options?: PartyServerOptions<E>
async function handleWebSocketUpgrade(
c: Context,
options?: PartyServerOptions
) {
const req = createRequestFromContext(c);
const response = await routePartykitRequest(req, env(c), options);
Expand All @@ -81,10 +130,7 @@ async function handleWebSocketUpgrade<E extends Env>(
* Handles standard HTTP requests
* Forwards the request to PartyServer and returns the response
*/
async function handleHttpRequest<E extends Env>(
c: Context<E>,
options?: PartyServerOptions<E>
) {
async function handleHttpRequest(c: Context, options?: PartyServerOptions) {
const req = createRequestFromContext(c);
return routePartykitRequest(req, env(c), options);
}
Loading