diff --git a/README.md b/README.md index ba52fb5..c334064 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,11 @@ export const Counter = generateHonoObject("/counter", async (app, state) => { You want to find more? Check out the [examples](./examples)! +## Support + +- [x] [`alarm` API](https://developers.cloudflare.com/durable-objects/api/alarms/) +- [x] [`Hibernation Websocket API`](https://developers.cloudflare.com/durable-objects/learning/websockets/#websocket-hibernation) + ## License [MIT](./LICENSE) diff --git a/examples/hibernatable-chat/README.md b/examples/hibernatable-chat/README.md new file mode 100644 index 0000000..cc58e96 --- /dev/null +++ b/examples/hibernatable-chat/README.md @@ -0,0 +1,8 @@ +``` +npm install +npm run dev +``` + +``` +npm run deploy +``` diff --git a/examples/hibernatable-chat/package.json b/examples/hibernatable-chat/package.json new file mode 100644 index 0000000..20c1717 --- /dev/null +++ b/examples/hibernatable-chat/package.json @@ -0,0 +1,21 @@ +{ + "name": "hono-do-example-hibernatable-chat", + "private": true, + "version": "0.0.0", + "scripts": { + "lint": "eslint --fix --ext .ts,.tsx src", + "lint:check": "eslint --ext .ts,.tsx src", + "format": "prettier --write \"src/**/*.{ts,tsx}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx}\"", + "dev": "wrangler dev src/index.ts", + "deploy": "wrangler deploy --minify src/index.ts" + }, + "dependencies": { + "hono": "^3.6.0", + "hono-do": "workspace:*" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20230821.0", + "wrangler": "^3.7.0" + } +} diff --git a/examples/hibernatable-chat/src/chat.ts b/examples/hibernatable-chat/src/chat.ts new file mode 100644 index 0000000..7505d44 --- /dev/null +++ b/examples/hibernatable-chat/src/chat.ts @@ -0,0 +1,58 @@ +import { generateHonoObject } from "hono-do"; + +function uuidv4() { + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0, + v = c == "x" ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} + +declare module "hono-do" { + interface HonoObjectVars { + messages: { + timestamp: string; + text: string; + }[]; + } +} + +export const Chat = generateHonoObject("/chat", (app, state, vars) => { + vars.messages = []; + + app.get("/messages", async (c) => c.json(vars.messages)); + + app.get("/websocket", async (c) => { + if (c.req.header("Upgrade") === "websocket") { + return await handleWebSocketUpgrade(); + } + return c.text("Not found", 404); + }); + + async function handleWebSocketUpgrade() { + const [client, server] = Object.values(new WebSocketPair()); + const clientId = uuidv4(); + state.acceptWebSocket(server); + + server.serializeAttachment({ clientId }); + + return new Response(null, { status: 101, webSocket: client }); + } +}); + +Chat.webSocketMessage(async (webSocket, msg, state, vars) => { + const { clientId: senderClientId } = await webSocket.deserializeAttachment(); + state.getWebSockets().forEach((ws) => { + const { clientId } = ws.deserializeAttachment(); + if (clientId === senderClientId) { + return; + } + + try { + vars.messages.push(JSON.parse(msg.toString())); + ws.send(msg.toString()); + } catch (error) { + ws.close(); + } + }); +}); diff --git a/examples/hibernatable-chat/src/index.ts b/examples/hibernatable-chat/src/index.ts new file mode 100644 index 0000000..77e72d6 --- /dev/null +++ b/examples/hibernatable-chat/src/index.ts @@ -0,0 +1,22 @@ +import { Hono } from "hono"; + +import { Template } from "./template"; + +const app = new Hono<{ + Bindings: { + CHAT: DurableObjectNamespace; + }; +}>(); + +app.get("/", (c) => { + return c.html(Template); +}); + +app.all("/chat/*", (c) => { + const id = c.env.CHAT.idFromName("chat"); + const obj = c.env.CHAT.get(id); + return obj.fetch(c.req.raw); +}); + +export default app; +export * from "./chat"; diff --git a/examples/hibernatable-chat/src/template.ts b/examples/hibernatable-chat/src/template.ts new file mode 100644 index 0000000..01beb3c --- /dev/null +++ b/examples/hibernatable-chat/src/template.ts @@ -0,0 +1,88 @@ +export const Template = /*html*/ ` + + +
+
+
+ + + +`.trim(); diff --git a/examples/hibernatable-chat/tsconfig.json b/examples/hibernatable-chat/tsconfig.json new file mode 100644 index 0000000..9cd8489 --- /dev/null +++ b/examples/hibernatable-chat/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "lib": [ + "esnext" + ], + "types": [ + "@cloudflare/workers-types" + ], + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx" + }, +} \ No newline at end of file diff --git a/examples/hibernatable-chat/wrangler.toml b/examples/hibernatable-chat/wrangler.toml new file mode 100644 index 0000000..f7b30ba --- /dev/null +++ b/examples/hibernatable-chat/wrangler.toml @@ -0,0 +1,9 @@ +name = "chat" +compatibility_date = "2023-01-01" + +[durable_objects] +bindings = [{ name = "CHAT", class_name = "Chat" }] + +[[migrations]] +tag = "v1" +new_classes = ["Chat"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3aea457..f3bee4e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,22 @@ importers: specifier: ^3.7.0 version: 3.8.0 + examples/hibernatable-chat: + dependencies: + hono: + specifier: ^3.6.0 + version: 3.6.0 + hono-do: + specifier: workspace:* + version: link:../../packages/hono-do + devDependencies: + '@cloudflare/workers-types': + specifier: ^4.20230821.0 + version: 4.20230904.0 + wrangler: + specifier: ^3.7.0 + version: 3.8.0 + packages/hono-do: dependencies: hono: