diff --git a/jsr.json b/jsr.json index ac3177da..a4e3a65d 100644 --- a/jsr.json +++ b/jsr.json @@ -71,6 +71,7 @@ "./vercel": "./src/adapter/vercel/index.ts", "./netlify": "./src/adapter/netlify/index.ts", "./lambda-edge": "./src/adapter/lambda-edge/index.ts", + "./service-worker": "./src/adapter/service-worker/index.ts", "./testing": "./src/helper/testing/index.ts", "./dev": "./src/helper/dev/index.ts", "./ws": "./src/helper/websocket/index.ts", diff --git a/package.json b/package.json index 6f657e54..4a1cd916 100644 --- a/package.json +++ b/package.json @@ -343,6 +343,11 @@ "import": "./dist/adapter/lambda-edge/index.js", "require": "./dist/cjs/adapter/lambda-edge/index.js" }, + "./service-worker": { + "types": "./dist/types/adapter/service-worker/index.d.ts", + "import": "./dist/adapter/service-worker/index.js", + "require": "./dist/cjs/adapter/service-worker/index.js" + }, "./testing": { "types": "./dist/types/helper/testing/index.d.ts", "import": "./dist/helper/testing/index.js", @@ -543,6 +548,9 @@ "lambda-edge": [ "./dist/types/adapter/lambda-edge" ], + "service-worker": [ + "./dist/types/adapter/service-worker" + ], "testing": [ "./dist/types/helper/testing" ], diff --git a/src/adapter/service-worker/handler.test.ts b/src/adapter/service-worker/handler.test.ts new file mode 100644 index 00000000..ae44a824 --- /dev/null +++ b/src/adapter/service-worker/handler.test.ts @@ -0,0 +1,57 @@ +import { Hono } from '../../hono' +import { handle } from './handler' +import type { FetchEvent } from './types' + +describe('handle', () => { + it('Success to fetch', async () => { + const app = new Hono() + app.get('/', (c) => { + return c.json({ hello: 'world' }) + }) + const handler = handle(app) + const json = await new Promise((resolve) => { + handler({ + request: new Request('http://localhost/'), + respondWith(res) { + resolve(res) + }, + } as FetchEvent) + }).then((res) => res.json()) + expect(json).toStrictEqual({ hello: 'world' }) + }) + it('Fallback 404', async () => { + const app = new Hono() + const handler = handle(app, { + async fetch() { + return new Response('hello world') + }, + }) + const text = await new Promise((resolve) => { + handler({ + request: new Request('http://localhost/'), + respondWith(res) { + resolve(res) + }, + } as FetchEvent) + }).then((res) => res.text()) + expect(text).toBe('hello world') + }) + it('Do not fallback 404 when fetch is undefined', async () => { + const app = new Hono() + app.get('/', (c) => c.text('Not found', 404)) + const handler = handle(app, { + fetch: undefined, + }) + const result = await new Promise((resolve) => + handler({ + request: new Request('https://localhost/'), + respondWith(r) { + resolve(r) + }, + } as FetchEvent) + ) + + expect(result.status).toBe(404) + expect(await result.text()).toBe('Not found') + }) +}) diff --git a/src/adapter/service-worker/handler.ts b/src/adapter/service-worker/handler.ts new file mode 100644 index 00000000..edc9efad --- /dev/null +++ b/src/adapter/service-worker/handler.ts @@ -0,0 +1,33 @@ +/** + * Handler for Service Worker + * @module + */ + +import type { Hono } from '../../hono' +import type { FetchEvent } from './types' + +type Handler = (evt: FetchEvent) => void + +/** + * Adapter for Service Worker + */ +export const handle = ( + app: Hono, + opts: { + fetch?: typeof fetch + } = { + fetch: fetch, + } +): Handler => { + return (evt) => { + evt.respondWith( + (async () => { + const res = await app.fetch(evt.request) + if (opts.fetch && res.status === 404) { + return await opts.fetch(evt.request) + } + return res + })() + ) + } +} diff --git a/src/adapter/service-worker/index.ts b/src/adapter/service-worker/index.ts new file mode 100644 index 00000000..af5dc70d --- /dev/null +++ b/src/adapter/service-worker/index.ts @@ -0,0 +1,5 @@ +/** + * Cloudflare Workers Adapter for Hono. + * @module + */ +export { handle } from './handler' diff --git a/src/adapter/service-worker/types.ts b/src/adapter/service-worker/types.ts new file mode 100644 index 00000000..37ce64a0 --- /dev/null +++ b/src/adapter/service-worker/types.ts @@ -0,0 +1,14 @@ +interface ExtendableEvent extends Event { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + waitUntil(f: Promise): void +} + +export interface FetchEvent extends ExtendableEvent { + readonly clientId: string + readonly handled: Promise + // eslint-disable-next-line @typescript-eslint/no-explicit-any + readonly preloadResponse: Promise + readonly request: Request + readonly resultingClientId: string + respondWith(r: Response | PromiseLike): void +}