diff --git a/.vitepress/config.ts b/.vitepress/config.ts index d6720bc9..d8154878 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -155,6 +155,7 @@ const sidebars = (): DefaultTheme.SidebarItem[] => [ { text: 'Cache', link: '/docs/middleware/builtin/cache' }, { text: 'Combine', link: '/docs/middleware/builtin/combine' }, { text: 'Compress', link: '/docs/middleware/builtin/compress' }, + { text: 'Context Storage', link: '/docs/middleware/builtin/context-storage' }, { text: 'CORS', link: '/docs/middleware/builtin/cors' }, { text: 'CSRF Protection', diff --git a/docs/getting-started/bun.md b/docs/getting-started/bun.md index e4efc8d2..760ce7c6 100644 --- a/docs/getting-started/bun.md +++ b/docs/getting-started/bun.md @@ -135,6 +135,22 @@ app.get( ) ``` +### `onFound` + +You can specify handling when the requested file is found with `onFound`: + +```ts +app.get( + '/static/*', + serveStatic({ + // ... + onFound: (_path, c) => { + c.header('Cache-Control', `public, immutable, max-age=31536000`) + }, + }) +) +``` + ### `onNotFound` You can specify handling when the requested file is not found with `onNotFound`: @@ -150,6 +166,19 @@ app.get( ) ``` +### `precompressed` + +The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file. + +```ts +app.get( + '/static/*', + serveStatic({ + precompressed: true, + }) +) +``` + ## Testing You can use `bun:test` for testing on Bun. diff --git a/docs/getting-started/cloudflare-pages.md b/docs/getting-started/cloudflare-pages.md index 1dfa1d34..e19cd1be 100644 --- a/docs/getting-started/cloudflare-pages.md +++ b/docs/getting-started/cloudflare-pages.md @@ -361,3 +361,45 @@ export const onRequest = [ handleMiddleware(middleware3), ] ``` + +### Accessing `EventContext` + +You can access [`EventContext`](https://developers.cloudflare.com/pages/functions/api-reference/#eventcontext) object via `c.env` in `handleMiddleware`. + +```ts +// functions/_middleware.ts +import { handleMiddleware } from 'hono/cloudflare-pages' + +export const onRequest = [ + handleMiddleware(async (c, next) => { + c.env.eventContext.data.user = 'Joe' + await next() + }), +] +``` + +Then, you can access the data value in via `c.env.eventContext` in the handler: + +```ts +// functions/api/[[route]].ts +import type { EventContext } from 'hono/cloudflare-pages' +import { handle } from 'hono/cloudflare-pages' + +// ... + +type Env = { + Bindings: { + eventContext: EventContext + } +} + +const app = new Hono() + +app.get('/hello', (c) => { + return c.json({ + message: `Hello, ${c.env.eventContext.data.user}!`, // 'Joe' + }) +}) + +export const onRequest = handle(app) +``` diff --git a/docs/getting-started/deno.md b/docs/getting-started/deno.md index a5cf602a..a098ae0a 100644 --- a/docs/getting-started/deno.md +++ b/docs/getting-started/deno.md @@ -122,6 +122,22 @@ app.get( ) ``` +### `onFound` + +You can specify handling when the requested file is found with `onFound`: + +```ts +app.get( + '/static/*', + serveStatic({ + // ... + onFound: (_path, c) => { + c.header('Cache-Control', `public, immutable, max-age=31536000`) + }, + }) +) +``` + ### `onNotFound` You can specify handling when the requested file is not found with `onNotFound`: @@ -137,6 +153,19 @@ app.get( ) ``` +### `precompressed` + +The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file. + +```ts +app.get( + '/static/*', + serveStatic({ + precompressed: true, + }) +) +``` + ## Deno Deploy Deno Deploy is an edge runtime platform for Deno. diff --git a/docs/helpers/websocket.md b/docs/helpers/websocket.md index ec4bc23a..ca084513 100644 --- a/docs/helpers/websocket.md +++ b/docs/helpers/websocket.md @@ -20,8 +20,10 @@ import { upgradeWebSocket } from 'hono/deno' ```ts [Bun] import { Hono } from 'hono' import { createBunWebSocket } from 'hono/bun' +import type { ServerWebSocket } from 'bun' -const { upgradeWebSocket, websocket } = createBunWebSocket() +const { upgradeWebSocket, websocket } = + createBunWebSocket() // ... diff --git a/docs/index.md b/docs/index.md index ee8366bc..0bdafe32 100644 --- a/docs/index.md +++ b/docs/index.md @@ -159,6 +159,7 @@ Out of the box, Hono provides middleware and helpers for: - [Body Limit](/docs/middleware/builtin/body-limit) - [Cache](/docs/middleware/builtin/cache) - [Compress](/docs/middleware/builtin/compress) +- [Context Storage](/docs/middleware/builtin/context-storage) - [Cookie](/docs/helpers/cookie) - [CORS](/docs/middleware/builtin/cors) - [ETag](/docs/middleware/builtin/etag) diff --git a/docs/middleware/builtin/basic-auth.md b/docs/middleware/builtin/basic-auth.md index d463c974..b1808060 100644 --- a/docs/middleware/builtin/basic-auth.md +++ b/docs/middleware/builtin/basic-auth.md @@ -87,6 +87,10 @@ A function to handle hashing for safe comparison of passwords. The function to verify the user. +### invalidUserMessage: `string | object | MessageFunction` + +`MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if the user is invalid. + ## More Options ### ...users: `{ username: string, password: string }[]` diff --git a/docs/middleware/builtin/bearer-auth.md b/docs/middleware/builtin/bearer-auth.md index 9387c722..c1c82ab8 100644 --- a/docs/middleware/builtin/bearer-auth.md +++ b/docs/middleware/builtin/bearer-auth.md @@ -110,3 +110,15 @@ A function to handle hashing for safe comparison of authentication tokens. ### verifyToken: `(token: string, c: Context) => boolean | Promise` The function to verify the token. + +### noAuthenticationHeaderMessage: `string | object | MessageFunction` + +`MessageFunction` is `(c: Context) => string | object | Promise`. The custom message if it does not have an authentication header. + +### invalidAuthenticationHeaderMessage: `string | object | MessageFunction` + +The custom message if the authentication header is invalid. + +### invalidTokenMessage: `string | object | MessageFunction` + +The custom message if the token is invalid. diff --git a/docs/middleware/builtin/context-storage.md b/docs/middleware/builtin/context-storage.md new file mode 100644 index 00000000..b36a4d8f --- /dev/null +++ b/docs/middleware/builtin/context-storage.md @@ -0,0 +1,64 @@ +# Context Storage Middleware + +The Context Storage Middleware stores the Hono `Context` in the `AsyncLocalStorage`, to make it globally accessible. + +::: info +**Note** This middleware uses `AsyncLocalStorage`. The runtime should support it. + +**Cloudflare Workers**: To enable `AsyncLocalStorage`, add the [`nodejs_compat` or `nodejs_als` flag](https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag) to your `wrangler.toml` file. +::: + +## Import + +```ts +import { Hono } from 'hono' +import { contextStorage, getContext } from 'hono/context-storage' +``` + +## Usage + +The `getContext()` will return the current Context object if the `contextStorage()` is applied as a middleware. + +```ts +type Env = { + Variables: { + message: string + } +} + +const app = new Hono() + +app.use(contextStorage()) + +app.use(async (c, next) => { + c.set('message', 'Hello!') + await next() +}) + +// You can access the variable outside the handler. +const getMessage = () => { + return getContext().var.message +} + +app.get('/', (c) => { + return c.text(getMessage()) +}) +``` + +On Cloudflare Workers, you can access the bindings outside the handler. + +```ts +type Env = { + Bindings: { + KV: KVNamespace + } +} + +const app = new Hono() + +app.use(contextStorage()) + +const setKV = (value: string) => { + return getContext().env.KV.put('key', value) +} +``` diff --git a/docs/middleware/builtin/jsx-renderer.md b/docs/middleware/builtin/jsx-renderer.md index adc50344..42ff62b8 100644 --- a/docs/middleware/builtin/jsx-renderer.md +++ b/docs/middleware/builtin/jsx-renderer.md @@ -117,7 +117,8 @@ If `true` is set, the following headers are added: ```ts { 'Transfer-Encoding': 'chunked', - 'Content-Type': 'text/html; charset=UTF-8' + 'Content-Type': 'text/html; charset=UTF-8', + 'Content-Encoding': 'Identity' } ``` diff --git a/docs/middleware/builtin/secure-headers.md b/docs/middleware/builtin/secure-headers.md index 71eeac6a..35dd31a9 100644 --- a/docs/middleware/builtin/secure-headers.md +++ b/docs/middleware/builtin/secure-headers.md @@ -68,6 +68,7 @@ Each option corresponds to the following Header Key-Value pairs. | xFrameOptions | [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) | SAMEORIGIN | True | | xPermittedCrossDomainPolicies | [X-Permitted-Cross-Domain-Policies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Permitted-Cross-Domain-Policies) | none | True | | xXssProtection | [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) | 0 | True | +| permissionPolicy | [Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy) | Usage: [Setting Permission-Policy](#setting-permission-policy) | No Setting | ## Middleware Conflict @@ -211,3 +212,32 @@ app.get('/', (c) => { ) }) ``` + +## Setting Permission-Policy + +The Permission-Policy header allows you to control which features and APIs can be used in the browser. Here's an example of how to set it: + +```ts +const app = new Hono() +app.use( + '*', + secureHeaders({ + permissionsPolicy: { + fullscreen: ['self'], // fullscreen=(self) + bluetooth: ['none'], // bluetooth=(none) + payment: ['self', 'https://example.com'], // payment=(self "https://example.com") + syncXhr: [], // sync-xhr=() + camera: false, // camera=none + microphone: true, // microphone=* + geolocation: ['*'], // geolocation=* + usb: ['self', 'https://a.example.com', 'https://b.example.com'], // usb=(self "https://a.example.com" "https://b.example.com") + accelerometer: ['https://*.example.com'], // accelerometer=("https://*.example.com") + gyroscope: ['src'], // gyroscope=(src) + magnetometer: [ + 'https://a.example.com', + 'https://b.example.com', + ], // magnetometer=("https://a.example.com" "https://b.example.com") + }, + }) +) +```