feat: add Durable Objects for WebSocket, production deploy support, update README#143
feat: add Durable Objects for WebSocket, production deploy support, update README#143
Conversation
…pdate README - Implement ChatRoom Durable Object with WebSocket Hibernation API for reliable real-time messaging across Worker isolates - Rewrite /ws route as thin proxy that forwards upgrades to the DO - Add deploy, db:migrate:prod, db:seed:prod scripts to package.json - Update wrangler.toml with DO bindings, migrations, and production vars - Fix E2E CI job: remove stale PostgreSQL service, use wrangler D1 local - Delete stale render.yaml (Render.com config with non-existent scripts) - Rewrite README.md to reflect Cloudflare Workers/D1/Durable Objects stack and add step-by-step Cloudflare deployment instructions
There was a problem hiding this comment.
Pull request overview
Adds Cloudflare Durable Objects–backed WebSocket handling (with hibernation) and updates deployment/CI/docs to reflect a Workers + D1 stack.
Changes:
- Introduce a
ChatRoomDurable Object and refactor/wsinto an auth-gated proxy forwarding upgrades to the DO. - Add production-oriented scripts/config (Wrangler DO bindings/migrations, deploy + remote D1 migrate/seed) and remove stale Render config.
- Update CI and README to use Wrangler D1 local tooling and document Cloudflare deployment steps.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
wrangler.toml |
Adds DO binding + migration config; changes default NODE_ENV. |
render.yaml |
Removes obsolete Render deployment configuration. |
package.json |
Adds deploy and remote D1 migrate/seed scripts. |
hono/types.ts |
Adds CHAT_ROOM binding type. |
hono/testApp.ts |
Extends test env bindings to include CHAT_ROOM. |
hono/routes/ws.ts |
Replaces in-process WS handling with DO-forwarding upgrade proxy. |
hono/routes/ws.test.ts |
Updates unit tests to match new /ws route shape. |
hono/index.ts |
Exports ChatRoom for Wrangler entrypoint usage. |
hono/durableObjects/ChatRoom.ts |
New DO implementing WS message persistence + broadcast via D1/Drizzle. |
hono/deployment.test.ts |
Adds mock for cloudflare:workers and checks DO module importability. |
hono/app.tsx |
Routes /ws through the new proxy route. |
README.md |
Rewrites docs to match Workers/D1/DO stack and adds deployment guide. |
.github/workflows/ci.yml |
Removes Postgres service/env and uses Wrangler D1 local migrate/seed for E2E. |
| const url = new URL(c.req.url); | ||
| url.searchParams.set("userEmail", user.email); | ||
| url.searchParams.set("userName", user.name); | ||
|
|
There was a problem hiding this comment.
User identity is forwarded to the Durable Object via URL query parameters (userEmail, userName). This puts PII into request URLs (which are commonly logged/cached/visible in analytics) and can leak via referrers in some contexts. Prefer passing identity via headers (e.g., a signed/opaque token) or via the WebSocket attachment established server-side after the DO accepts the socket, rather than in the URL.
See below for a potential fix:
const headers = new Headers(c.req.raw.headers);
headers.set("x-user-email", user.email);
headers.set("x-user-name", user.name);
const doRequest = new Request(c.req.raw, { headers });
| it("should return 426 for non-WebSocket requests", async () => { | ||
| const wsRoute = createWsRoute(); | ||
|
|
||
| const response = await wsRoute.request("/ws"); | ||
| expect(response.status).toBeGreaterThanOrEqual(400); | ||
| expect(response.status).toBe(426); | ||
| }); |
There was a problem hiding this comment.
The updated tests only cover route construction and the 426 response for non-WebSocket requests. They no longer cover the key new behavior: rejecting unauthenticated upgrade requests (401) and forwarding authenticated upgrades to env.CHAT_ROOM.getByName("main").fetch() with the expected identity propagation. Adding unit coverage with a mocked DurableObjectNamespace/stub would help prevent regressions.
There was a problem hiding this comment.
Done in commit fcfad02. Added two new test cases to ws.test.ts:
- 401 for unauthenticated upgrade: sends
Upgrade: websocketheaders without an authenticated user → expects 401 - Forwarding with user identity: sends a WebSocket upgrade as an authenticated user, then asserts
CHAT_ROOM.getByName("main")was called and the forwarded request URL contains the correctuserEmailanduserNamequery params
A createWsApp helper was introduced to mock both the session middleware and the DurableObjectNamespace binding inline, following the same pattern used in channels.test.ts and messages.test.ts.
| tag = "v1" | ||
| new_sqlite_classes = ["ChatRoom"] | ||
|
|
||
| [vars] |
There was a problem hiding this comment.
NODE_ENV is set to "production" in the default [vars], which makes local wrangler dev and Playwright’s webServer run with production-only behavior (notably secure: true session cookies in hono/app.tsx, so cookies won’t be set over http://localhost). Consider keeping the default as development (or omitting it) and moving NODE_ENV = "production" under an [env.production.vars] block (or overriding via --var NODE_ENV:production in the deploy command).
| [vars] | |
| [vars] | |
| NODE_ENV = "development" | |
| [env.production.vars] |
…mand The process.env NODE_ENV set by Playwright's webServer.env does not override wrangler.toml [vars]. Use --var NODE_ENV:development so the /test/login endpoint allows mock authentication during E2E tests.
…WebSocket The wrangler dev Durable Object WebSocket Hibernation API has a quirk where client-initiated ws.close() never completes the close handshake, so the browser onclose event never fires. Use Playwright's page.routeWebSocket() to proxy and close the connection at the network layer instead. Also remove redundant ws.close() from ChatRoom's webSocketClose handler.
Agent-Logs-Url: https://github.com/mahata/mlack/sessions/d544a9d3-da61-43d0-8f3a-9a5f08b1d719 Co-authored-by: mahata <23497+mahata@users.noreply.github.com>
Summary
ChatRoomDurable Object using the WebSocket Hibernation API so real-time messaging works reliably across Cloudflare Worker isolates/wsroute as a thin auth-validating proxy that forwards WebSocket upgrades to the Durable Objectdeploy,db:migrate:prod,db:seed:prodscripts and updatedwrangler.tomlwith DO bindings and migrationsrender.yaml(referenced a non-existentstart:prodscript)README.mdto accurately describe the Cloudflare Workers/D1/Durable Objects stack and add a step-by-step deployment guideKey Changes
Durable Object (
hono/durableObjects/ChatRoom.ts)Single
ChatRoominstance manages all WebSocket connections with hibernation support. On message: saves to D1 via Drizzle, looks up channel members, broadcasts to connected members. User identity is persisted through hibernation viaserializeAttachment/deserializeAttachment.WebSocket Route (
hono/routes/ws.ts)Simplified from 111 to 31 lines. Validates session auth, attaches user info as URL params, forwards the upgrade request to
env.CHAT_ROOM.getByName("main").fetch().Configuration
wrangler.toml: Added[[durable_objects.bindings]]and[[migrations]]forChatRoom, setNODE_ENV = "production"package.json: Addeddeploy,db:migrate:prod,db:seed:prodscriptshono/types.ts: AddedCHAT_ROOM: DurableObjectNamespacetoBindingsCI
pnpm db:migrate/pnpm db:seed(wrangler D1 local)Cleanup
render.yamlPost-merge: First Deploy Steps
After merging, the first production deployment requires:
npx wrangler loginnpx wrangler d1 create mlack-db→ updatedatabase_idinwrangler.tomlpnpm db:migrate:prodandpnpm db:seed:prodnpx wrangler secret put SESSION_SECRET(andGOOGLE_ID,GOOGLE_SECRET,GOOGLE_REDIRECT_URI)pnpm deploy