hono 4.5.10 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/dist/adapter/cloudflare-pages/handler.js +1 -1
  3. package/dist/cjs/adapter/cloudflare-pages/handler.js +1 -1
  4. package/dist/cjs/context.js +25 -16
  5. package/dist/cjs/helper/streaming/sse.js +4 -2
  6. package/dist/cjs/jsx/base.js +1 -1
  7. package/dist/cjs/jsx/components.js +10 -5
  8. package/dist/cjs/jsx/intrinsic-element/components.js +15 -3
  9. package/dist/cjs/middleware/basic-auth/index.js +15 -24
  10. package/dist/cjs/middleware/bearer-auth/index.js +32 -21
  11. package/dist/cjs/middleware/context-storage/index.js +43 -0
  12. package/dist/cjs/middleware/jsx-renderer/index.js +1 -0
  13. package/dist/cjs/middleware/secure-headers/permissions-policy.js +16 -0
  14. package/dist/cjs/middleware/secure-headers/secure-headers.js +30 -1
  15. package/dist/cjs/middleware/serve-static/index.js +32 -15
  16. package/dist/cjs/utils/basic-auth.js +46 -0
  17. package/dist/cjs/utils/body.js +1 -1
  18. package/dist/cjs/utils/html.js +8 -0
  19. package/dist/context.js +25 -16
  20. package/dist/helper/streaming/sse.js +4 -2
  21. package/dist/jsx/base.js +1 -1
  22. package/dist/jsx/components.js +10 -5
  23. package/dist/jsx/intrinsic-element/components.js +15 -3
  24. package/dist/middleware/basic-auth/index.js +15 -24
  25. package/dist/middleware/bearer-auth/index.js +32 -21
  26. package/dist/middleware/context-storage/index.js +19 -0
  27. package/dist/middleware/jsx-renderer/index.js +1 -0
  28. package/dist/middleware/secure-headers/permissions-policy.js +0 -0
  29. package/dist/middleware/secure-headers/secure-headers.js +30 -1
  30. package/dist/middleware/serve-static/index.js +32 -15
  31. package/dist/types/adapter/bun/websocket.d.ts +4 -6
  32. package/dist/types/adapter/cloudflare-pages/handler.d.ts +6 -2
  33. package/dist/types/adapter/cloudflare-workers/websocket.d.ts +1 -1
  34. package/dist/types/adapter/deno/websocket.d.ts +1 -1
  35. package/dist/types/helper/streaming/sse.d.ts +1 -1
  36. package/dist/types/helper/websocket/index.d.ts +8 -8
  37. package/dist/types/middleware/basic-auth/index.d.ts +4 -0
  38. package/dist/types/middleware/bearer-auth/index.d.ts +10 -0
  39. package/dist/types/middleware/context-storage/index.d.ts +39 -0
  40. package/dist/types/middleware/jsx-renderer/index.d.ts +2 -2
  41. package/dist/types/middleware/secure-headers/permissions-policy.d.ts +14 -0
  42. package/dist/types/middleware/secure-headers/secure-headers.d.ts +5 -0
  43. package/dist/types/middleware/serve-static/index.d.ts +2 -0
  44. package/dist/types/utils/basic-auth.d.ts +5 -0
  45. package/dist/types/utils/html.d.ts +1 -1
  46. package/dist/utils/basic-auth.js +23 -0
  47. package/dist/utils/body.js +1 -1
  48. package/dist/utils/html.js +8 -0
  49. package/package.json +9 -1
package/dist/context.js CHANGED
@@ -59,16 +59,30 @@ var Context = class {
59
59
  set res(_res) {
60
60
  this.#isFresh = false;
61
61
  if (this.#res && _res) {
62
- this.#res.headers.delete("content-type");
63
- for (const [k, v] of this.#res.headers.entries()) {
64
- if (k === "set-cookie") {
65
- const cookies = this.#res.headers.getSetCookie();
66
- _res.headers.delete("set-cookie");
67
- for (const cookie of cookies) {
68
- _res.headers.append("set-cookie", cookie);
62
+ try {
63
+ for (const [k, v] of this.#res.headers.entries()) {
64
+ if (k === "content-type") {
65
+ continue;
69
66
  }
67
+ if (k === "set-cookie") {
68
+ const cookies = this.#res.headers.getSetCookie();
69
+ _res.headers.delete("set-cookie");
70
+ for (const cookie of cookies) {
71
+ _res.headers.append("set-cookie", cookie);
72
+ }
73
+ } else {
74
+ _res.headers.set(k, v);
75
+ }
76
+ }
77
+ } catch (e) {
78
+ if (e instanceof TypeError && e.message.includes("immutable")) {
79
+ this.res = new Response(_res.body, {
80
+ headers: _res.headers,
81
+ status: _res.status
82
+ });
83
+ return;
70
84
  } else {
71
- _res.headers.set(k, v);
85
+ throw e;
72
86
  }
73
87
  }
74
88
  }
@@ -212,14 +226,9 @@ var Context = class {
212
226
  this.#preparedHeaders ??= {};
213
227
  this.#preparedHeaders["content-type"] = "text/html; charset=UTF-8";
214
228
  if (typeof html === "object") {
215
- if (!(html instanceof Promise)) {
216
- html = html.toString();
217
- }
218
- if (html instanceof Promise) {
219
- return html.then((html2) => resolveCallback(html2, HtmlEscapedCallbackPhase.Stringify, false, {})).then((html2) => {
220
- return typeof arg === "number" ? this.newResponse(html2, arg, headers) : this.newResponse(html2, arg);
221
- });
222
- }
229
+ return resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then((html2) => {
230
+ return typeof arg === "number" ? this.newResponse(html2, arg, headers) : this.newResponse(html2, arg);
231
+ });
223
232
  }
224
233
  return typeof arg === "number" ? this.newResponse(html, arg, headers) : this.newResponse(html, arg);
225
234
  };
@@ -1,16 +1,18 @@
1
1
  // src/helper/streaming/sse.ts
2
2
  import { StreamingApi } from "../../utils/stream.js";
3
+ import { HtmlEscapedCallbackPhase, resolveCallback } from "../../utils/html.js";
3
4
  var SSEStreamingApi = class extends StreamingApi {
4
5
  constructor(writable, readable) {
5
6
  super(writable, readable);
6
7
  }
7
8
  async writeSSE(message) {
8
- const data = message.data.split("\n").map((line) => {
9
+ const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
10
+ const dataLines = data.split("\n").map((line) => {
9
11
  return `data: ${line}`;
10
12
  }).join("\n");
11
13
  const sseData = [
12
14
  message.event && `event: ${message.event}`,
13
- data,
15
+ dataLines,
14
16
  message.id && `id: ${message.id}`,
15
17
  message.retry && `retry: ${message.retry}`
16
18
  ].filter(Boolean).join("\n") + "\n\n";
package/dist/jsx/base.js CHANGED
@@ -234,7 +234,7 @@ var jsxFn = (tag, props, children) => {
234
234
  props,
235
235
  children
236
236
  );
237
- } else if (tag === "svg") {
237
+ } else if (tag === "svg" || tag === "head") {
238
238
  nameSpaceContext ||= createContext("");
239
239
  return new JSXNode(tag, props, [
240
240
  new JSXFunctionNode(
@@ -48,11 +48,11 @@ var ErrorBoundary = async ({ children, fallback, fallbackRender, onError }) => {
48
48
  const index = errorBoundaryCounter++;
49
49
  const replaceRe = RegExp(`(<template id="E:${index}"></template>.*?)(.*?)(<!--E:${index}-->)`);
50
50
  const caught = false;
51
- const catchCallback = ({ error, buffer }) => {
51
+ const catchCallback = ({ error: error2, buffer }) => {
52
52
  if (caught) {
53
53
  return "";
54
54
  }
55
- const fallbackResString = fallbackRes(error);
55
+ const fallbackResString = fallbackRes(error2);
56
56
  if (buffer) {
57
57
  buffer[0] = buffer[0].replace(replaceRe, fallbackResString);
58
58
  }
@@ -66,12 +66,17 @@ d.replaceWith(c.content)
66
66
  })(document)
67
67
  <\/script>`;
68
68
  };
69
+ let error;
70
+ const promiseAll = Promise.all(resArray).catch((e) => error = e);
69
71
  return raw(`<template id="E:${index}"></template><!--E:${index}-->`, [
70
72
  ({ phase, buffer, context }) => {
71
73
  if (phase === HtmlEscapedCallbackPhase.BeforeStream) {
72
74
  return;
73
75
  }
74
- return Promise.all(resArray).then(async (htmlArray) => {
76
+ return promiseAll.then(async (htmlArray) => {
77
+ if (error) {
78
+ throw error;
79
+ }
75
80
  htmlArray = htmlArray.flat();
76
81
  const content = htmlArray.join("");
77
82
  let html = buffer ? "" : `<template data-hono-target="E:${index}">${content}</template><script>
@@ -127,10 +132,10 @@ d.remove()
127
132
  <\/script>`),
128
133
  content2.callbacks
129
134
  );
130
- }).catch((error) => catchCallback({ error, buffer }))
135
+ }).catch((error2) => catchCallback({ error: error2, buffer }))
131
136
  );
132
137
  return raw(html, promises);
133
- }).catch((error) => catchCallback({ error, buffer }));
138
+ }).catch((error2) => catchCallback({ error: error2, buffer }));
134
139
  }
135
140
  ]);
136
141
  } else {
@@ -78,8 +78,15 @@ var documentMetadataTag = (tag, children, props, sort) => {
78
78
  };
79
79
  var title = ({ children, ...props }) => {
80
80
  const nameSpaceContext = getNameSpaceContext();
81
- if (nameSpaceContext && useContext(nameSpaceContext) === "svg") {
82
- new JSXNode("title", props, toArray(children ?? []));
81
+ if (nameSpaceContext) {
82
+ const context = useContext(nameSpaceContext);
83
+ if (context === "svg" || context === "head") {
84
+ return new JSXNode(
85
+ "title",
86
+ props,
87
+ toArray(children ?? [])
88
+ );
89
+ }
83
90
  }
84
91
  return documentMetadataTag("title", children, props, false);
85
92
  };
@@ -87,7 +94,8 @@ var script = ({
87
94
  children,
88
95
  ...props
89
96
  }) => {
90
- if (["src", "async"].some((k) => !props[k])) {
97
+ const nameSpaceContext = getNameSpaceContext();
98
+ if (["src", "async"].some((k) => !props[k]) || nameSpaceContext && useContext(nameSpaceContext) === "head") {
91
99
  return returnWithoutSpecialBehavior("script", children, props);
92
100
  }
93
101
  return documentMetadataTag("script", children, props, false);
@@ -110,6 +118,10 @@ var link = ({ children, ...props }) => {
110
118
  return documentMetadataTag("link", children, props, "precedence" in props);
111
119
  };
112
120
  var meta = ({ children, ...props }) => {
121
+ const nameSpaceContext = getNameSpaceContext();
122
+ if (nameSpaceContext && useContext(nameSpaceContext) === "head") {
123
+ return returnWithoutSpecialBehavior("meta", children, props);
124
+ }
113
125
  return documentMetadataTag("meta", children, props, false);
114
126
  };
115
127
  var newJSXNode = (tag, { children, ...props }) => new JSXNode(tag, props, toArray(children ?? []));
@@ -1,25 +1,7 @@
1
1
  // src/middleware/basic-auth/index.ts
2
2
  import { HTTPException } from "../../http-exception.js";
3
+ import { auth } from "../../utils/basic-auth.js";
3
4
  import { timingSafeEqual } from "../../utils/buffer.js";
4
- import { decodeBase64 } from "../../utils/encode.js";
5
- var CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/;
6
- var USER_PASS_REGEXP = /^([^:]*):(.*)$/;
7
- var utf8Decoder = new TextDecoder();
8
- var auth = (req) => {
9
- const match = CREDENTIALS_REGEXP.exec(req.header("Authorization") || "");
10
- if (!match) {
11
- return void 0;
12
- }
13
- let userPass = void 0;
14
- try {
15
- userPass = USER_PASS_REGEXP.exec(utf8Decoder.decode(decodeBase64(match[1])));
16
- } catch {
17
- }
18
- if (!userPass) {
19
- return void 0;
20
- }
21
- return { username: userPass[1], password: userPass[2] };
22
- };
23
5
  var basicAuth = (options, ...users) => {
24
6
  const usernamePasswordInOptions = "username" in options && "password" in options;
25
7
  const verifyUserInOptions = "verifyUser" in options;
@@ -31,11 +13,14 @@ var basicAuth = (options, ...users) => {
31
13
  if (!options.realm) {
32
14
  options.realm = "Secure Area";
33
15
  }
16
+ if (!options.invalidUserMessage) {
17
+ options.invalidUserMessage = "Unauthorized";
18
+ }
34
19
  if (usernamePasswordInOptions) {
35
20
  users.unshift({ username: options.username, password: options.password });
36
21
  }
37
22
  return async function basicAuth2(ctx, next) {
38
- const requestUser = auth(ctx.req);
23
+ const requestUser = auth(ctx.req.raw);
39
24
  if (requestUser) {
40
25
  if (verifyUserInOptions) {
41
26
  if (await options.verifyUser(requestUser.username, requestUser.password, ctx)) {
@@ -55,13 +40,19 @@ var basicAuth = (options, ...users) => {
55
40
  }
56
41
  }
57
42
  }
58
- const res = new Response("Unauthorized", {
59
- status: 401,
43
+ const status = 401;
44
+ const headers = {
45
+ "WWW-Authenticate": 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"'
46
+ };
47
+ const responseMessage = typeof options.invalidUserMessage === "function" ? await options.invalidUserMessage(ctx) : options.invalidUserMessage;
48
+ const res = typeof responseMessage === "string" ? new Response(responseMessage, { status, headers }) : new Response(JSON.stringify(responseMessage), {
49
+ status,
60
50
  headers: {
61
- "WWW-Authenticate": 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"'
51
+ ...headers,
52
+ "content-type": "application/json; charset=UTF-8"
62
53
  }
63
54
  });
64
- throw new HTTPException(401, { res });
55
+ throw new HTTPException(status, { res });
65
56
  };
66
57
  };
67
58
  export {
@@ -18,26 +18,38 @@ var bearerAuth = (options) => {
18
18
  const prefixRegexStr = options.prefix === "" ? "" : `${options.prefix} +`;
19
19
  const regexp = new RegExp(`^${prefixRegexStr}(${TOKEN_STRINGS}) *$`);
20
20
  const wwwAuthenticatePrefix = options.prefix === "" ? "" : `${options.prefix} `;
21
+ const throwHTTPException = async (c, status, wwwAuthenticateHeader, messageOption) => {
22
+ const headers = {
23
+ "WWW-Authenticate": wwwAuthenticateHeader
24
+ };
25
+ const responseMessage = typeof messageOption === "function" ? await messageOption(c) : messageOption;
26
+ const res = typeof responseMessage === "string" ? new Response(responseMessage, { status, headers }) : new Response(JSON.stringify(responseMessage), {
27
+ status,
28
+ headers: {
29
+ ...headers,
30
+ "content-type": "application/json; charset=UTF-8"
31
+ }
32
+ });
33
+ throw new HTTPException(status, { res });
34
+ };
21
35
  return async function bearerAuth2(c, next) {
22
36
  const headerToken = c.req.header(options.headerName || HEADER);
23
37
  if (!headerToken) {
24
- const res = new Response("Unauthorized", {
25
- status: 401,
26
- headers: {
27
- "WWW-Authenticate": `${wwwAuthenticatePrefix}realm="` + realm + '"'
28
- }
29
- });
30
- throw new HTTPException(401, { res });
38
+ await throwHTTPException(
39
+ c,
40
+ 401,
41
+ `${wwwAuthenticatePrefix}realm="${realm}"`,
42
+ options.noAuthenticationHeaderMessage || "Unauthorized"
43
+ );
31
44
  } else {
32
45
  const match = regexp.exec(headerToken);
33
46
  if (!match) {
34
- const res = new Response("Bad Request", {
35
- status: 400,
36
- headers: {
37
- "WWW-Authenticate": `${wwwAuthenticatePrefix}error="invalid_request"`
38
- }
39
- });
40
- throw new HTTPException(400, { res });
47
+ await throwHTTPException(
48
+ c,
49
+ 400,
50
+ `${wwwAuthenticatePrefix}error="invalid_request"`,
51
+ options.invalidAuthenticationHeaderMessage || "Bad Request"
52
+ );
41
53
  } else {
42
54
  let equal = false;
43
55
  if ("verifyToken" in options) {
@@ -53,13 +65,12 @@ var bearerAuth = (options) => {
53
65
  }
54
66
  }
55
67
  if (!equal) {
56
- const res = new Response("Unauthorized", {
57
- status: 401,
58
- headers: {
59
- "WWW-Authenticate": `${wwwAuthenticatePrefix}error="invalid_token"`
60
- }
61
- });
62
- throw new HTTPException(401, { res });
68
+ await throwHTTPException(
69
+ c,
70
+ 401,
71
+ `${wwwAuthenticatePrefix}error="invalid_token"`,
72
+ options.invalidTokenMessage || "Unauthorized"
73
+ );
63
74
  }
64
75
  }
65
76
  }
@@ -0,0 +1,19 @@
1
+ // src/middleware/context-storage/index.ts
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ var asyncLocalStorage = new AsyncLocalStorage();
4
+ var contextStorage = () => {
5
+ return async function contextStorage2(c, next) {
6
+ await asyncLocalStorage.run(c, next);
7
+ };
8
+ };
9
+ var getContext = () => {
10
+ const context = asyncLocalStorage.getStore();
11
+ if (!context) {
12
+ throw new Error("Context is not available");
13
+ }
14
+ return context;
15
+ };
16
+ export {
17
+ contextStorage,
18
+ getContext
19
+ };
@@ -21,6 +21,7 @@ var createRenderer = (c, Layout, component, options) => (children, props) => {
21
21
  if (options.stream === true) {
22
22
  c.header("Transfer-Encoding", "chunked");
23
23
  c.header("Content-Type", "text/html; charset=UTF-8");
24
+ c.header("Content-Encoding", "Identity");
24
25
  } else {
25
26
  for (const [key, value] of Object.entries(options.stream)) {
26
27
  c.header(key, value);
@@ -27,7 +27,8 @@ var DEFAULT_OPTIONS = {
27
27
  xFrameOptions: true,
28
28
  xPermittedCrossDomainPolicies: true,
29
29
  xXssProtection: true,
30
- removePoweredBy: true
30
+ removePoweredBy: true,
31
+ permissionsPolicy: {}
31
32
  };
32
33
  var generateNonce = () => {
33
34
  const buffer = new Uint8Array(16);
@@ -53,6 +54,12 @@ var secureHeaders = (customOptions) => {
53
54
  }
54
55
  headersToSet.push(["Content-Security-Policy", value]);
55
56
  }
57
+ if (options.permissionsPolicy && Object.keys(options.permissionsPolicy).length > 0) {
58
+ headersToSet.push([
59
+ "Permissions-Policy",
60
+ getPermissionsPolicyDirectives(options.permissionsPolicy)
61
+ ]);
62
+ }
56
63
  if (options.reportingEndpoints) {
57
64
  headersToSet.push(["Reporting-Endpoints", getReportingEndpoints(options.reportingEndpoints)]);
58
65
  }
@@ -112,6 +119,28 @@ function getCSPDirectives(contentSecurityPolicy) {
112
119
  resultValues
113
120
  ];
114
121
  }
122
+ function getPermissionsPolicyDirectives(policy) {
123
+ return Object.entries(policy).map(([directive, value]) => {
124
+ const kebabDirective = camelToKebab(directive);
125
+ if (typeof value === "boolean") {
126
+ return `${kebabDirective}=${value ? "*" : "none"}`;
127
+ }
128
+ if (Array.isArray(value)) {
129
+ if (value.length === 0) {
130
+ return `${kebabDirective}=()`;
131
+ }
132
+ if (value.length === 1 && (value[0] === "*" || value[0] === "none")) {
133
+ return `${kebabDirective}=${value[0]}`;
134
+ }
135
+ const allowlist = value.map((item) => ["self", "src"].includes(item) ? item : `"${item}"`);
136
+ return `${kebabDirective}=(${allowlist.join(" ")})`;
137
+ }
138
+ return "";
139
+ }).filter(Boolean).join(", ");
140
+ }
141
+ function camelToKebab(str) {
142
+ return str.replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
143
+ }
115
144
  function getReportingEndpoints(reportingEndpoints = []) {
116
145
  return reportingEndpoints.map((endpoint) => `${endpoint.name}="${endpoint.url}"`).join(", ");
117
146
  }
@@ -1,6 +1,11 @@
1
1
  // src/middleware/serve-static/index.ts
2
2
  import { getFilePath, getFilePathWithoutDefaultDocument } from "../../utils/filepath.js";
3
3
  import { getMimeType } from "../../utils/mime.js";
4
+ var ENCODINGS = {
5
+ br: ".br",
6
+ zstd: ".zst",
7
+ gzip: ".gz"
8
+ };
4
9
  var DEFAULT_DOCUMENT = "index.html";
5
10
  var defaultPathResolve = (path) => path;
6
11
  var serveStatic = (options) => {
@@ -18,7 +23,7 @@ var serveStatic = (options) => {
18
23
  root
19
24
  });
20
25
  if (path2 && await options.isDir(path2)) {
21
- filename = filename + "/";
26
+ filename += "/";
22
27
  }
23
28
  }
24
29
  let path = getFilePath({
@@ -34,34 +39,46 @@ var serveStatic = (options) => {
34
39
  path = pathResolve(path);
35
40
  let content = await getContent(path, c);
36
41
  if (!content) {
37
- let pathWithOutDefaultDocument = getFilePathWithoutDefaultDocument({
42
+ let pathWithoutDefaultDocument = getFilePathWithoutDefaultDocument({
38
43
  filename,
39
44
  root
40
45
  });
41
- if (!pathWithOutDefaultDocument) {
46
+ if (!pathWithoutDefaultDocument) {
42
47
  return await next();
43
48
  }
44
- pathWithOutDefaultDocument = pathResolve(pathWithOutDefaultDocument);
45
- if (pathWithOutDefaultDocument !== path) {
46
- content = await getContent(pathWithOutDefaultDocument, c);
49
+ pathWithoutDefaultDocument = pathResolve(pathWithoutDefaultDocument);
50
+ if (pathWithoutDefaultDocument !== path) {
51
+ content = await getContent(pathWithoutDefaultDocument, c);
47
52
  if (content) {
48
- path = pathWithOutDefaultDocument;
53
+ path = pathWithoutDefaultDocument;
49
54
  }
50
55
  }
51
56
  }
52
57
  if (content instanceof Response) {
53
58
  return c.newResponse(content.body, content);
54
59
  }
60
+ const mimeType = options.mimes ? getMimeType(path, options.mimes) ?? getMimeType(path) : getMimeType(path);
61
+ if (mimeType) {
62
+ c.header("Content-Type", mimeType);
63
+ }
55
64
  if (content) {
56
- let mimeType;
57
- if (options.mimes) {
58
- mimeType = getMimeType(path, options.mimes) ?? getMimeType(path);
59
- } else {
60
- mimeType = getMimeType(path);
61
- }
62
- if (mimeType) {
63
- c.header("Content-Type", mimeType);
65
+ if (options.precompressed) {
66
+ const acceptEncodings = c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()).filter(
67
+ (encoding) => Object.hasOwn(ENCODINGS, encoding)
68
+ ).sort(
69
+ (a, b) => Object.keys(ENCODINGS).indexOf(a) - Object.keys(ENCODINGS).indexOf(b)
70
+ ) ?? [];
71
+ for (const encoding of acceptEncodings) {
72
+ const compressedContent = await getContent(path + ENCODINGS[encoding], c);
73
+ if (compressedContent) {
74
+ content = compressedContent;
75
+ c.header("Content-Encoding", encoding);
76
+ c.header("Vary", "Accept-Encoding", { append: true });
77
+ break;
78
+ }
79
+ }
64
80
  }
81
+ await options.onFound?.(path, c);
65
82
  return c.body(content);
66
83
  }
67
84
  await options.onNotFound?.(path, c);
@@ -10,16 +10,14 @@ interface BunWebSocketHandler<T> {
10
10
  close(ws: BunServerWebSocket<T>, code?: number, reason?: string): void;
11
11
  message(ws: BunServerWebSocket<T>, message: string | Uint8Array): void;
12
12
  }
13
- interface CreateWebSocket {
14
- (): {
15
- upgradeWebSocket: UpgradeWebSocket;
16
- websocket: BunWebSocketHandler<BunWebSocketData>;
17
- };
13
+ interface CreateWebSocket<T> {
14
+ upgradeWebSocket: UpgradeWebSocket<T>;
15
+ websocket: BunWebSocketHandler<BunWebSocketData>;
18
16
  }
19
17
  export interface BunWebSocketData {
20
18
  connId: number;
21
19
  url: URL;
22
20
  protocol: string;
23
21
  }
24
- export declare const createBunWebSocket: CreateWebSocket;
22
+ export declare const createBunWebSocket: <T>() => CreateWebSocket<T>;
25
23
  export {};
@@ -1,7 +1,7 @@
1
1
  import type { Hono } from '../../hono';
2
2
  import type { BlankSchema, Env, Input, MiddlewareHandler, Schema } from '../../types';
3
3
  type Params<P extends string = any> = Record<P, string | string[]>;
4
- export type EventContext<Env = {}, P extends string = any, Data = {}> = {
4
+ export type EventContext<Env = {}, P extends string = any, Data = Record<string, unknown>> = {
5
5
  request: Request;
6
6
  functionPath: string;
7
7
  waitUntil: (promise: Promise<unknown>) => void;
@@ -17,7 +17,11 @@ export type EventContext<Env = {}, P extends string = any, Data = {}> = {
17
17
  };
18
18
  declare type PagesFunction<Env = unknown, Params extends string = any, Data extends Record<string, unknown> = Record<string, unknown>> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>;
19
19
  export declare const handle: <E extends Env = Env, S extends Schema = BlankSchema, BasePath extends string = "/">(app: Hono<E, S, BasePath>) => PagesFunction<E["Bindings"], any, Record<string, unknown>>;
20
- export declare function handleMiddleware<E extends Env = any, P extends string = any, I extends Input = {}>(middleware: MiddlewareHandler<E, P, I>): PagesFunction<E['Bindings']>;
20
+ export declare function handleMiddleware<E extends Env = {}, P extends string = any, I extends Input = {}>(middleware: MiddlewareHandler<E & {
21
+ Bindings: {
22
+ eventContext: EventContext;
23
+ };
24
+ }, P, I>): PagesFunction<E['Bindings']>;
21
25
  /**
22
26
  *
23
27
  * @description `serveStatic()` is for advanced mode:
@@ -1,2 +1,2 @@
1
1
  import type { UpgradeWebSocket } from '../../helper/websocket';
2
- export declare const upgradeWebSocket: UpgradeWebSocket;
2
+ export declare const upgradeWebSocket: UpgradeWebSocket<WebSocket>;
@@ -18,4 +18,4 @@ export interface UpgradeWebSocketOptions {
18
18
  */
19
19
  idleTimeout?: number;
20
20
  }
21
- export declare const upgradeWebSocket: UpgradeWebSocket<UpgradeWebSocketOptions>;
21
+ export declare const upgradeWebSocket: UpgradeWebSocket<WebSocket, UpgradeWebSocketOptions>;
@@ -1,7 +1,7 @@
1
1
  import type { Context } from '../../context';
2
2
  import { StreamingApi } from '../../utils/stream';
3
3
  export interface SSEMessage {
4
- data: string;
4
+ data: string | Promise<string>;
5
5
  event?: string;
6
6
  id?: string;
7
7
  retry?: number;
@@ -7,24 +7,24 @@ import type { MiddlewareHandler } from '../../types';
7
7
  /**
8
8
  * WebSocket Event Listeners type
9
9
  */
10
- export interface WSEvents {
11
- onOpen?: (evt: Event, ws: WSContext) => void;
12
- onMessage?: (evt: MessageEvent<WSMessageReceive>, ws: WSContext) => void;
13
- onClose?: (evt: CloseEvent, ws: WSContext) => void;
14
- onError?: (evt: Event, ws: WSContext) => void;
10
+ export interface WSEvents<T = unknown> {
11
+ onOpen?: (evt: Event, ws: WSContext<T>) => void;
12
+ onMessage?: (evt: MessageEvent<WSMessageReceive>, ws: WSContext<T>) => void;
13
+ onClose?: (evt: CloseEvent, ws: WSContext<T>) => void;
14
+ onError?: (evt: Event, ws: WSContext<T>) => void;
15
15
  }
16
16
  /**
17
17
  * Upgrade WebSocket Type
18
18
  */
19
- export type UpgradeWebSocket<T = any> = (createEvents: (c: Context) => WSEvents | Promise<WSEvents>, options?: T) => MiddlewareHandler<any, string, {
19
+ export type UpgradeWebSocket<T = unknown, U = any> = (createEvents: (c: Context) => WSEvents<T> | Promise<WSEvents<T>>, options?: U) => MiddlewareHandler<any, string, {
20
20
  outputFormat: 'ws';
21
21
  }>;
22
22
  export type WSReadyState = 0 | 1 | 2 | 3;
23
- export type WSContext = {
23
+ export type WSContext<T = unknown> = {
24
24
  send(source: string | ArrayBuffer | Uint8Array, options?: {
25
25
  compress: boolean;
26
26
  }): void;
27
- raw?: unknown;
27
+ raw?: T;
28
28
  binaryType: BinaryType;
29
29
  readyState: WSReadyState;
30
30
  url: URL | null;
@@ -4,15 +4,18 @@
4
4
  */
5
5
  import type { Context } from '../../context';
6
6
  import type { MiddlewareHandler } from '../../types';
7
+ type MessageFunction = (c: Context) => string | object | Promise<string | object>;
7
8
  type BasicAuthOptions = {
8
9
  username: string;
9
10
  password: string;
10
11
  realm?: string;
11
12
  hashFunction?: Function;
13
+ invalidUserMessage?: string | object | MessageFunction;
12
14
  } | {
13
15
  verifyUser: (username: string, password: string, c: Context) => boolean | Promise<boolean>;
14
16
  realm?: string;
15
17
  hashFunction?: Function;
18
+ invalidUserMessage?: string | object | MessageFunction;
16
19
  };
17
20
  /**
18
21
  * Basic Auth Middleware for Hono.
@@ -25,6 +28,7 @@ type BasicAuthOptions = {
25
28
  * @param {string} [options.realm="Secure Area"] - The realm attribute for the WWW-Authenticate header.
26
29
  * @param {Function} [options.hashFunction] - The hash function used for secure comparison.
27
30
  * @param {Function} [options.verifyUser] - The function to verify user credentials.
31
+ * @param {string | object | MessageFunction} [options.invalidUserMessage="Unauthorized"] - The invalid user message.
28
32
  * @returns {MiddlewareHandler} The middleware handler function.
29
33
  * @throws {HTTPException} If neither "username and password" nor "verifyUser" options are provided.
30
34
  *
@@ -4,18 +4,25 @@
4
4
  */
5
5
  import type { Context } from '../../context';
6
6
  import type { MiddlewareHandler } from '../../types';
7
+ type MessageFunction = (c: Context) => string | object | Promise<string | object>;
7
8
  type BearerAuthOptions = {
8
9
  token: string | string[];
9
10
  realm?: string;
10
11
  prefix?: string;
11
12
  headerName?: string;
12
13
  hashFunction?: Function;
14
+ noAuthenticationHeaderMessage?: string | object | MessageFunction;
15
+ invalidAuthenticationHeaderMessage?: string | object | MessageFunction;
16
+ invalidTokenMessage?: string | object | MessageFunction;
13
17
  } | {
14
18
  realm?: string;
15
19
  prefix?: string;
16
20
  headerName?: string;
17
21
  verifyToken: (token: string, c: Context) => boolean | Promise<boolean>;
18
22
  hashFunction?: Function;
23
+ noAuthenticationHeaderMessage?: string | object | MessageFunction;
24
+ invalidAuthenticationHeaderMessage?: string | object | MessageFunction;
25
+ invalidTokenMessage?: string | object | MessageFunction;
19
26
  };
20
27
  /**
21
28
  * Bearer Auth Middleware for Hono.
@@ -29,6 +36,9 @@ type BearerAuthOptions = {
29
36
  * @param {string} [options.prefix="Bearer"] - The prefix (or known as `schema`) for the Authorization header value. If set to the empty string, no prefix is expected.
30
37
  * @param {string} [options.headerName=Authorization] - The header name.
31
38
  * @param {Function} [options.hashFunction] - A function to handle hashing for safe comparison of authentication tokens.
39
+ * @param {string | object | MessageFunction} [options.noAuthenticationHeaderMessage="Unauthorized"] - The no authentication header message.
40
+ * @param {string | object | MessageFunction} [options.invalidAuthenticationHeaderMeasage="Bad Request"] - The invalid authentication header message.
41
+ * @param {string | object | MessageFunction} [options.invalidTokenMessage="Unauthorized"] - The invalid token message.
32
42
  * @returns {MiddlewareHandler} The middleware handler function.
33
43
  * @throws {Error} If neither "token" nor "verifyToken" options are provided.
34
44
  * @throws {HTTPException} If authentication fails, with 401 status code for missing or invalid token, or 400 status code for invalid request.