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.
- package/README.md +1 -1
- package/dist/adapter/cloudflare-pages/handler.js +1 -1
- package/dist/cjs/adapter/cloudflare-pages/handler.js +1 -1
- package/dist/cjs/context.js +25 -16
- package/dist/cjs/helper/streaming/sse.js +4 -2
- package/dist/cjs/jsx/base.js +1 -1
- package/dist/cjs/jsx/components.js +10 -5
- package/dist/cjs/jsx/intrinsic-element/components.js +15 -3
- package/dist/cjs/middleware/basic-auth/index.js +15 -24
- package/dist/cjs/middleware/bearer-auth/index.js +32 -21
- package/dist/cjs/middleware/context-storage/index.js +43 -0
- package/dist/cjs/middleware/jsx-renderer/index.js +1 -0
- package/dist/cjs/middleware/secure-headers/permissions-policy.js +16 -0
- package/dist/cjs/middleware/secure-headers/secure-headers.js +30 -1
- package/dist/cjs/middleware/serve-static/index.js +32 -15
- package/dist/cjs/utils/basic-auth.js +46 -0
- package/dist/cjs/utils/body.js +1 -1
- package/dist/cjs/utils/html.js +8 -0
- package/dist/context.js +25 -16
- package/dist/helper/streaming/sse.js +4 -2
- package/dist/jsx/base.js +1 -1
- package/dist/jsx/components.js +10 -5
- package/dist/jsx/intrinsic-element/components.js +15 -3
- package/dist/middleware/basic-auth/index.js +15 -24
- package/dist/middleware/bearer-auth/index.js +32 -21
- package/dist/middleware/context-storage/index.js +19 -0
- package/dist/middleware/jsx-renderer/index.js +1 -0
- package/dist/middleware/secure-headers/permissions-policy.js +0 -0
- package/dist/middleware/secure-headers/secure-headers.js +30 -1
- package/dist/middleware/serve-static/index.js +32 -15
- package/dist/types/adapter/bun/websocket.d.ts +4 -6
- package/dist/types/adapter/cloudflare-pages/handler.d.ts +6 -2
- package/dist/types/adapter/cloudflare-workers/websocket.d.ts +1 -1
- package/dist/types/adapter/deno/websocket.d.ts +1 -1
- package/dist/types/helper/streaming/sse.d.ts +1 -1
- package/dist/types/helper/websocket/index.d.ts +8 -8
- package/dist/types/middleware/basic-auth/index.d.ts +4 -0
- package/dist/types/middleware/bearer-auth/index.d.ts +10 -0
- package/dist/types/middleware/context-storage/index.d.ts +39 -0
- package/dist/types/middleware/jsx-renderer/index.d.ts +2 -2
- package/dist/types/middleware/secure-headers/permissions-policy.d.ts +14 -0
- package/dist/types/middleware/secure-headers/secure-headers.d.ts +5 -0
- package/dist/types/middleware/serve-static/index.d.ts +2 -0
- package/dist/types/utils/basic-auth.d.ts +5 -0
- package/dist/types/utils/html.d.ts +1 -1
- package/dist/utils/basic-auth.js +23 -0
- package/dist/utils/body.js +1 -1
- package/dist/utils/html.js +8 -0
- 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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
package/dist/jsx/components.js
CHANGED
|
@@ -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(
|
|
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
|
|
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((
|
|
135
|
+
}).catch((error2) => catchCallback({ error: error2, buffer }))
|
|
131
136
|
);
|
|
132
137
|
return raw(html, promises);
|
|
133
|
-
}).catch((
|
|
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
|
|
82
|
-
|
|
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
|
-
|
|
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
|
|
59
|
-
|
|
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
|
-
|
|
51
|
+
...headers,
|
|
52
|
+
"content-type": "application/json; charset=UTF-8"
|
|
62
53
|
}
|
|
63
54
|
});
|
|
64
|
-
throw new HTTPException(
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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);
|
|
File without changes
|
|
@@ -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
|
|
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
|
|
42
|
+
let pathWithoutDefaultDocument = getFilePathWithoutDefaultDocument({
|
|
38
43
|
filename,
|
|
39
44
|
root
|
|
40
45
|
});
|
|
41
|
-
if (!
|
|
46
|
+
if (!pathWithoutDefaultDocument) {
|
|
42
47
|
return await next();
|
|
43
48
|
}
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
content = await getContent(
|
|
49
|
+
pathWithoutDefaultDocument = pathResolve(pathWithoutDefaultDocument);
|
|
50
|
+
if (pathWithoutDefaultDocument !== path) {
|
|
51
|
+
content = await getContent(pathWithoutDefaultDocument, c);
|
|
47
52
|
if (content) {
|
|
48
|
-
path =
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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 =
|
|
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>;
|
|
@@ -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
|
|
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?:
|
|
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.
|