hono 4.5.11 → 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/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/middleware/basic-auth/index.js +13 -4
- 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/html.js +8 -0
- package/dist/context.js +25 -16
- package/dist/helper/streaming/sse.js +4 -2
- package/dist/middleware/basic-auth/index.js +13 -4
- 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 +1 -1
- 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/html.d.ts +1 -1
- package/dist/utils/html.js +8 -0
- package/package.json +9 -1
|
@@ -14,7 +14,7 @@ var handle = (app) => (eventContext) => {
|
|
|
14
14
|
function handleMiddleware(middleware) {
|
|
15
15
|
return async (executionCtx) => {
|
|
16
16
|
const context = new Context(executionCtx.request, {
|
|
17
|
-
env: executionCtx.env,
|
|
17
|
+
env: { ...executionCtx.env, eventContext: executionCtx },
|
|
18
18
|
executionCtx
|
|
19
19
|
});
|
|
20
20
|
let response = void 0;
|
|
@@ -38,7 +38,7 @@ const handle = (app) => (eventContext) => {
|
|
|
38
38
|
function handleMiddleware(middleware) {
|
|
39
39
|
return async (executionCtx) => {
|
|
40
40
|
const context = new import_context.Context(executionCtx.request, {
|
|
41
|
-
env: executionCtx.env,
|
|
41
|
+
env: { ...executionCtx.env, eventContext: executionCtx },
|
|
42
42
|
executionCtx
|
|
43
43
|
});
|
|
44
44
|
let response = void 0;
|
package/dist/cjs/context.js
CHANGED
|
@@ -82,16 +82,30 @@ class Context {
|
|
|
82
82
|
set res(_res) {
|
|
83
83
|
this.#isFresh = false;
|
|
84
84
|
if (this.#res && _res) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
_res.headers.delete("set-cookie");
|
|
90
|
-
for (const cookie of cookies) {
|
|
91
|
-
_res.headers.append("set-cookie", cookie);
|
|
85
|
+
try {
|
|
86
|
+
for (const [k, v] of this.#res.headers.entries()) {
|
|
87
|
+
if (k === "content-type") {
|
|
88
|
+
continue;
|
|
92
89
|
}
|
|
90
|
+
if (k === "set-cookie") {
|
|
91
|
+
const cookies = this.#res.headers.getSetCookie();
|
|
92
|
+
_res.headers.delete("set-cookie");
|
|
93
|
+
for (const cookie of cookies) {
|
|
94
|
+
_res.headers.append("set-cookie", cookie);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
_res.headers.set(k, v);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch (e) {
|
|
101
|
+
if (e instanceof TypeError && e.message.includes("immutable")) {
|
|
102
|
+
this.res = new Response(_res.body, {
|
|
103
|
+
headers: _res.headers,
|
|
104
|
+
status: _res.status
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
93
107
|
} else {
|
|
94
|
-
|
|
108
|
+
throw e;
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
}
|
|
@@ -235,14 +249,9 @@ class Context {
|
|
|
235
249
|
this.#preparedHeaders ??= {};
|
|
236
250
|
this.#preparedHeaders["content-type"] = "text/html; charset=UTF-8";
|
|
237
251
|
if (typeof html === "object") {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
if (html instanceof Promise) {
|
|
242
|
-
return html.then((html2) => (0, import_html.resolveCallback)(html2, import_html.HtmlEscapedCallbackPhase.Stringify, false, {})).then((html2) => {
|
|
243
|
-
return typeof arg === "number" ? this.newResponse(html2, arg, headers) : this.newResponse(html2, arg);
|
|
244
|
-
});
|
|
245
|
-
}
|
|
252
|
+
return (0, import_html.resolveCallback)(html, import_html.HtmlEscapedCallbackPhase.Stringify, false, {}).then((html2) => {
|
|
253
|
+
return typeof arg === "number" ? this.newResponse(html2, arg, headers) : this.newResponse(html2, arg);
|
|
254
|
+
});
|
|
246
255
|
}
|
|
247
256
|
return typeof arg === "number" ? this.newResponse(html, arg, headers) : this.newResponse(html, arg);
|
|
248
257
|
};
|
|
@@ -23,17 +23,19 @@ __export(sse_exports, {
|
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(sse_exports);
|
|
25
25
|
var import_stream = require("../../utils/stream");
|
|
26
|
+
var import_html = require("../../utils/html");
|
|
26
27
|
class SSEStreamingApi extends import_stream.StreamingApi {
|
|
27
28
|
constructor(writable, readable) {
|
|
28
29
|
super(writable, readable);
|
|
29
30
|
}
|
|
30
31
|
async writeSSE(message) {
|
|
31
|
-
const data = message.data.
|
|
32
|
+
const data = await (0, import_html.resolveCallback)(message.data, import_html.HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
33
|
+
const dataLines = data.split("\n").map((line) => {
|
|
32
34
|
return `data: ${line}`;
|
|
33
35
|
}).join("\n");
|
|
34
36
|
const sseData = [
|
|
35
37
|
message.event && `event: ${message.event}`,
|
|
36
|
-
|
|
38
|
+
dataLines,
|
|
37
39
|
message.id && `id: ${message.id}`,
|
|
38
40
|
message.retry && `retry: ${message.retry}`
|
|
39
41
|
].filter(Boolean).join("\n") + "\n\n";
|
|
@@ -35,6 +35,9 @@ const basicAuth = (options, ...users) => {
|
|
|
35
35
|
if (!options.realm) {
|
|
36
36
|
options.realm = "Secure Area";
|
|
37
37
|
}
|
|
38
|
+
if (!options.invalidUserMessage) {
|
|
39
|
+
options.invalidUserMessage = "Unauthorized";
|
|
40
|
+
}
|
|
38
41
|
if (usernamePasswordInOptions) {
|
|
39
42
|
users.unshift({ username: options.username, password: options.password });
|
|
40
43
|
}
|
|
@@ -59,13 +62,19 @@ const basicAuth = (options, ...users) => {
|
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
65
|
+
const status = 401;
|
|
66
|
+
const headers = {
|
|
67
|
+
"WWW-Authenticate": 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"'
|
|
68
|
+
};
|
|
69
|
+
const responseMessage = typeof options.invalidUserMessage === "function" ? await options.invalidUserMessage(ctx) : options.invalidUserMessage;
|
|
70
|
+
const res = typeof responseMessage === "string" ? new Response(responseMessage, { status, headers }) : new Response(JSON.stringify(responseMessage), {
|
|
71
|
+
status,
|
|
64
72
|
headers: {
|
|
65
|
-
|
|
73
|
+
...headers,
|
|
74
|
+
"content-type": "application/json; charset=UTF-8"
|
|
66
75
|
}
|
|
67
76
|
});
|
|
68
|
-
throw new import_http_exception.HTTPException(
|
|
77
|
+
throw new import_http_exception.HTTPException(status, { res });
|
|
69
78
|
};
|
|
70
79
|
};
|
|
71
80
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -40,26 +40,38 @@ const bearerAuth = (options) => {
|
|
|
40
40
|
const prefixRegexStr = options.prefix === "" ? "" : `${options.prefix} +`;
|
|
41
41
|
const regexp = new RegExp(`^${prefixRegexStr}(${TOKEN_STRINGS}) *$`);
|
|
42
42
|
const wwwAuthenticatePrefix = options.prefix === "" ? "" : `${options.prefix} `;
|
|
43
|
+
const throwHTTPException = async (c, status, wwwAuthenticateHeader, messageOption) => {
|
|
44
|
+
const headers = {
|
|
45
|
+
"WWW-Authenticate": wwwAuthenticateHeader
|
|
46
|
+
};
|
|
47
|
+
const responseMessage = typeof messageOption === "function" ? await messageOption(c) : messageOption;
|
|
48
|
+
const res = typeof responseMessage === "string" ? new Response(responseMessage, { status, headers }) : new Response(JSON.stringify(responseMessage), {
|
|
49
|
+
status,
|
|
50
|
+
headers: {
|
|
51
|
+
...headers,
|
|
52
|
+
"content-type": "application/json; charset=UTF-8"
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
throw new import_http_exception.HTTPException(status, { res });
|
|
56
|
+
};
|
|
43
57
|
return async function bearerAuth2(c, next) {
|
|
44
58
|
const headerToken = c.req.header(options.headerName || HEADER);
|
|
45
59
|
if (!headerToken) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
throw new import_http_exception.HTTPException(401, { res });
|
|
60
|
+
await throwHTTPException(
|
|
61
|
+
c,
|
|
62
|
+
401,
|
|
63
|
+
`${wwwAuthenticatePrefix}realm="${realm}"`,
|
|
64
|
+
options.noAuthenticationHeaderMessage || "Unauthorized"
|
|
65
|
+
);
|
|
53
66
|
} else {
|
|
54
67
|
const match = regexp.exec(headerToken);
|
|
55
68
|
if (!match) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
throw new import_http_exception.HTTPException(400, { res });
|
|
69
|
+
await throwHTTPException(
|
|
70
|
+
c,
|
|
71
|
+
400,
|
|
72
|
+
`${wwwAuthenticatePrefix}error="invalid_request"`,
|
|
73
|
+
options.invalidAuthenticationHeaderMessage || "Bad Request"
|
|
74
|
+
);
|
|
63
75
|
} else {
|
|
64
76
|
let equal = false;
|
|
65
77
|
if ("verifyToken" in options) {
|
|
@@ -75,13 +87,12 @@ const bearerAuth = (options) => {
|
|
|
75
87
|
}
|
|
76
88
|
}
|
|
77
89
|
if (!equal) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
throw new import_http_exception.HTTPException(401, { res });
|
|
90
|
+
await throwHTTPException(
|
|
91
|
+
c,
|
|
92
|
+
401,
|
|
93
|
+
`${wwwAuthenticatePrefix}error="invalid_token"`,
|
|
94
|
+
options.invalidTokenMessage || "Unauthorized"
|
|
95
|
+
);
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var context_storage_exports = {};
|
|
20
|
+
__export(context_storage_exports, {
|
|
21
|
+
contextStorage: () => contextStorage,
|
|
22
|
+
getContext: () => getContext
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(context_storage_exports);
|
|
25
|
+
var import_node_async_hooks = require("node:async_hooks");
|
|
26
|
+
const asyncLocalStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
27
|
+
const contextStorage = () => {
|
|
28
|
+
return async function contextStorage2(c, next) {
|
|
29
|
+
await asyncLocalStorage.run(c, next);
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const getContext = () => {
|
|
33
|
+
const context = asyncLocalStorage.getStore();
|
|
34
|
+
if (!context) {
|
|
35
|
+
throw new Error("Context is not available");
|
|
36
|
+
}
|
|
37
|
+
return context;
|
|
38
|
+
};
|
|
39
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
40
|
+
0 && (module.exports = {
|
|
41
|
+
contextStorage,
|
|
42
|
+
getContext
|
|
43
|
+
});
|
|
@@ -45,6 +45,7 @@ const createRenderer = (c, Layout, component, options) => (children, props) => {
|
|
|
45
45
|
if (options.stream === true) {
|
|
46
46
|
c.header("Transfer-Encoding", "chunked");
|
|
47
47
|
c.header("Content-Type", "text/html; charset=UTF-8");
|
|
48
|
+
c.header("Content-Encoding", "Identity");
|
|
48
49
|
} else {
|
|
49
50
|
for (const [key, value] of Object.entries(options.stream)) {
|
|
50
51
|
c.header(key, value);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var permissions_policy_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(permissions_policy_exports);
|
|
@@ -50,7 +50,8 @@ const DEFAULT_OPTIONS = {
|
|
|
50
50
|
xFrameOptions: true,
|
|
51
51
|
xPermittedCrossDomainPolicies: true,
|
|
52
52
|
xXssProtection: true,
|
|
53
|
-
removePoweredBy: true
|
|
53
|
+
removePoweredBy: true,
|
|
54
|
+
permissionsPolicy: {}
|
|
54
55
|
};
|
|
55
56
|
const generateNonce = () => {
|
|
56
57
|
const buffer = new Uint8Array(16);
|
|
@@ -76,6 +77,12 @@ const secureHeaders = (customOptions) => {
|
|
|
76
77
|
}
|
|
77
78
|
headersToSet.push(["Content-Security-Policy", value]);
|
|
78
79
|
}
|
|
80
|
+
if (options.permissionsPolicy && Object.keys(options.permissionsPolicy).length > 0) {
|
|
81
|
+
headersToSet.push([
|
|
82
|
+
"Permissions-Policy",
|
|
83
|
+
getPermissionsPolicyDirectives(options.permissionsPolicy)
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
79
86
|
if (options.reportingEndpoints) {
|
|
80
87
|
headersToSet.push(["Reporting-Endpoints", getReportingEndpoints(options.reportingEndpoints)]);
|
|
81
88
|
}
|
|
@@ -135,6 +142,28 @@ function getCSPDirectives(contentSecurityPolicy) {
|
|
|
135
142
|
resultValues
|
|
136
143
|
];
|
|
137
144
|
}
|
|
145
|
+
function getPermissionsPolicyDirectives(policy) {
|
|
146
|
+
return Object.entries(policy).map(([directive, value]) => {
|
|
147
|
+
const kebabDirective = camelToKebab(directive);
|
|
148
|
+
if (typeof value === "boolean") {
|
|
149
|
+
return `${kebabDirective}=${value ? "*" : "none"}`;
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(value)) {
|
|
152
|
+
if (value.length === 0) {
|
|
153
|
+
return `${kebabDirective}=()`;
|
|
154
|
+
}
|
|
155
|
+
if (value.length === 1 && (value[0] === "*" || value[0] === "none")) {
|
|
156
|
+
return `${kebabDirective}=${value[0]}`;
|
|
157
|
+
}
|
|
158
|
+
const allowlist = value.map((item) => ["self", "src"].includes(item) ? item : `"${item}"`);
|
|
159
|
+
return `${kebabDirective}=(${allowlist.join(" ")})`;
|
|
160
|
+
}
|
|
161
|
+
return "";
|
|
162
|
+
}).filter(Boolean).join(", ");
|
|
163
|
+
}
|
|
164
|
+
function camelToKebab(str) {
|
|
165
|
+
return str.replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
|
|
166
|
+
}
|
|
138
167
|
function getReportingEndpoints(reportingEndpoints = []) {
|
|
139
168
|
return reportingEndpoints.map((endpoint) => `${endpoint.name}="${endpoint.url}"`).join(", ");
|
|
140
169
|
}
|
|
@@ -23,6 +23,11 @@ __export(serve_static_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(serve_static_exports);
|
|
24
24
|
var import_filepath = require("../../utils/filepath");
|
|
25
25
|
var import_mime = require("../../utils/mime");
|
|
26
|
+
const ENCODINGS = {
|
|
27
|
+
br: ".br",
|
|
28
|
+
zstd: ".zst",
|
|
29
|
+
gzip: ".gz"
|
|
30
|
+
};
|
|
26
31
|
const DEFAULT_DOCUMENT = "index.html";
|
|
27
32
|
const defaultPathResolve = (path) => path;
|
|
28
33
|
const serveStatic = (options) => {
|
|
@@ -40,7 +45,7 @@ const serveStatic = (options) => {
|
|
|
40
45
|
root
|
|
41
46
|
});
|
|
42
47
|
if (path2 && await options.isDir(path2)) {
|
|
43
|
-
filename
|
|
48
|
+
filename += "/";
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
let path = (0, import_filepath.getFilePath)({
|
|
@@ -56,34 +61,46 @@ const serveStatic = (options) => {
|
|
|
56
61
|
path = pathResolve(path);
|
|
57
62
|
let content = await getContent(path, c);
|
|
58
63
|
if (!content) {
|
|
59
|
-
let
|
|
64
|
+
let pathWithoutDefaultDocument = (0, import_filepath.getFilePathWithoutDefaultDocument)({
|
|
60
65
|
filename,
|
|
61
66
|
root
|
|
62
67
|
});
|
|
63
|
-
if (!
|
|
68
|
+
if (!pathWithoutDefaultDocument) {
|
|
64
69
|
return await next();
|
|
65
70
|
}
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
content = await getContent(
|
|
71
|
+
pathWithoutDefaultDocument = pathResolve(pathWithoutDefaultDocument);
|
|
72
|
+
if (pathWithoutDefaultDocument !== path) {
|
|
73
|
+
content = await getContent(pathWithoutDefaultDocument, c);
|
|
69
74
|
if (content) {
|
|
70
|
-
path =
|
|
75
|
+
path = pathWithoutDefaultDocument;
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
if (content instanceof Response) {
|
|
75
80
|
return c.newResponse(content.body, content);
|
|
76
81
|
}
|
|
82
|
+
const mimeType = options.mimes ? (0, import_mime.getMimeType)(path, options.mimes) ?? (0, import_mime.getMimeType)(path) : (0, import_mime.getMimeType)(path);
|
|
83
|
+
if (mimeType) {
|
|
84
|
+
c.header("Content-Type", mimeType);
|
|
85
|
+
}
|
|
77
86
|
if (content) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
if (options.precompressed) {
|
|
88
|
+
const acceptEncodings = c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()).filter(
|
|
89
|
+
(encoding) => Object.hasOwn(ENCODINGS, encoding)
|
|
90
|
+
).sort(
|
|
91
|
+
(a, b) => Object.keys(ENCODINGS).indexOf(a) - Object.keys(ENCODINGS).indexOf(b)
|
|
92
|
+
) ?? [];
|
|
93
|
+
for (const encoding of acceptEncodings) {
|
|
94
|
+
const compressedContent = await getContent(path + ENCODINGS[encoding], c);
|
|
95
|
+
if (compressedContent) {
|
|
96
|
+
content = compressedContent;
|
|
97
|
+
c.header("Content-Encoding", encoding);
|
|
98
|
+
c.header("Vary", "Accept-Encoding", { append: true });
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
86
102
|
}
|
|
103
|
+
await options.onFound?.(path, c);
|
|
87
104
|
return c.body(content);
|
|
88
105
|
}
|
|
89
106
|
await options.onNotFound?.(path, c);
|
package/dist/cjs/utils/html.js
CHANGED
|
@@ -112,6 +112,14 @@ const resolveCallbackSync = (str) => {
|
|
|
112
112
|
return buffer[0];
|
|
113
113
|
};
|
|
114
114
|
const resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
|
|
115
|
+
if (typeof str === "object" && !(str instanceof String)) {
|
|
116
|
+
if (!(str instanceof Promise)) {
|
|
117
|
+
str = str.toString();
|
|
118
|
+
}
|
|
119
|
+
if (str instanceof Promise) {
|
|
120
|
+
str = await str;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
115
123
|
const callbacks = str.callbacks;
|
|
116
124
|
if (!callbacks?.length) {
|
|
117
125
|
return Promise.resolve(str);
|
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";
|
|
@@ -13,6 +13,9 @@ var basicAuth = (options, ...users) => {
|
|
|
13
13
|
if (!options.realm) {
|
|
14
14
|
options.realm = "Secure Area";
|
|
15
15
|
}
|
|
16
|
+
if (!options.invalidUserMessage) {
|
|
17
|
+
options.invalidUserMessage = "Unauthorized";
|
|
18
|
+
}
|
|
16
19
|
if (usernamePasswordInOptions) {
|
|
17
20
|
users.unshift({ username: options.username, password: options.password });
|
|
18
21
|
}
|
|
@@ -37,13 +40,19 @@ var basicAuth = (options, ...users) => {
|
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
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,
|
|
42
50
|
headers: {
|
|
43
|
-
|
|
51
|
+
...headers,
|
|
52
|
+
"content-type": "application/json; charset=UTF-8"
|
|
44
53
|
}
|
|
45
54
|
});
|
|
46
|
-
throw new HTTPException(
|
|
55
|
+
throw new HTTPException(status, { res });
|
|
47
56
|
};
|
|
48
57
|
};
|
|
49
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.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Context Storage Middleware for Hono.
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '../../context';
|
|
6
|
+
import type { Env, MiddlewareHandler } from '../../types';
|
|
7
|
+
/**
|
|
8
|
+
* Context Storage Middleware for Hono.
|
|
9
|
+
*
|
|
10
|
+
* @see {@link https://hono.dev/docs/middleware/builtin/context-storage}
|
|
11
|
+
*
|
|
12
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* type Env = {
|
|
17
|
+
* Variables: {
|
|
18
|
+
* message: string
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* const app = new Hono<Env>()
|
|
23
|
+
*
|
|
24
|
+
* app.use(contextStorage())
|
|
25
|
+
*
|
|
26
|
+
* app.use(async (c, next) => {
|
|
27
|
+
* c.set('message', 'Hono is cool!!)
|
|
28
|
+
* await next()
|
|
29
|
+
* })
|
|
30
|
+
*
|
|
31
|
+
* app.get('/', async (c) => { c.text(getMessage()) })
|
|
32
|
+
*
|
|
33
|
+
* const getMessage = () => {
|
|
34
|
+
* return getContext<Env>().var.message
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare const contextStorage: () => MiddlewareHandler;
|
|
39
|
+
export declare const getContext: <E extends Env = Env>() => Context<E, any, {}>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type PermissionsPolicyDirective = StandardizedFeatures | ProposedFeatures | ExperimentalFeatures;
|
|
2
|
+
/**
|
|
3
|
+
* These features have been declared in a published version of the respective specification.
|
|
4
|
+
*/
|
|
5
|
+
type StandardizedFeatures = 'accelerometer' | 'ambientLightSensor' | 'attributionReporting' | 'autoplay' | 'battery' | 'bluetooth' | 'camera' | 'chUa' | 'chUaArch' | 'chUaBitness' | 'chUaFullVersion' | 'chUaFullVersionList' | 'chUaMobile' | 'chUaModel' | 'chUaPlatform' | 'chUaPlatformVersion' | 'chUaWow64' | 'computePressure' | 'crossOriginIsolated' | 'directSockets' | 'displayCapture' | 'encryptedMedia' | 'executionWhileNotRendered' | 'executionWhileOutOfViewport' | 'fullscreen' | 'geolocation' | 'gyroscope' | 'hid' | 'identityCredentialsGet' | 'idleDetection' | 'keyboardMap' | 'magnetometer' | 'microphone' | 'midi' | 'navigationOverride' | 'payment' | 'pictureInPicture' | 'publickeyCredentialsGet' | 'screenWakeLock' | 'serial' | 'storageAccess' | 'syncXhr' | 'usb' | 'webShare' | 'windowManagement' | 'xrSpatialTracking';
|
|
6
|
+
/**
|
|
7
|
+
* These features have been proposed, but the definitions have not yet been integrated into their respective specs.
|
|
8
|
+
*/
|
|
9
|
+
type ProposedFeatures = 'clipboardRead' | 'clipboardWrite' | 'gemepad' | 'sharedAutofill' | 'speakerSelection';
|
|
10
|
+
/**
|
|
11
|
+
* These features generally have an explainer only, but may be available for experimentation by web developers.
|
|
12
|
+
*/
|
|
13
|
+
type ExperimentalFeatures = 'allScreensCapture' | 'browsingTopics' | 'capturedSurfaceControl' | 'conversionMeasurement' | 'digitalCredentialsGet' | 'focusWithoutUserActivation' | 'joinAdInterestGroup' | 'localFonts' | 'runAdAuction' | 'smartCard' | 'syncScript' | 'trustTokenRedemption' | 'unload' | 'verticalScroll';
|
|
14
|
+
export {};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Context } from '../../context';
|
|
6
6
|
import type { MiddlewareHandler } from '../../types';
|
|
7
|
+
import type { PermissionsPolicyDirective } from './permissions-policy';
|
|
7
8
|
export type SecureHeadersVariables = {
|
|
8
9
|
secureHeadersNonce?: string;
|
|
9
10
|
};
|
|
@@ -45,6 +46,8 @@ interface ReportingEndpointOptions {
|
|
|
45
46
|
name: string;
|
|
46
47
|
url: string;
|
|
47
48
|
}
|
|
49
|
+
type PermissionsPolicyValue = '*' | 'self' | 'src' | 'none' | string;
|
|
50
|
+
type PermissionsPolicyOptions = Partial<Record<PermissionsPolicyDirective, PermissionsPolicyValue[] | boolean>>;
|
|
48
51
|
type overridableHeader = boolean | string;
|
|
49
52
|
interface SecureHeadersOptions {
|
|
50
53
|
contentSecurityPolicy?: ContentSecurityPolicyOptions;
|
|
@@ -63,6 +66,7 @@ interface SecureHeadersOptions {
|
|
|
63
66
|
xPermittedCrossDomainPolicies?: overridableHeader;
|
|
64
67
|
xXssProtection?: overridableHeader;
|
|
65
68
|
removePoweredBy?: boolean;
|
|
69
|
+
permissionsPolicy?: PermissionsPolicyOptions;
|
|
66
70
|
}
|
|
67
71
|
export declare const NONCE: ContentSecurityPolicyOptionHandler;
|
|
68
72
|
/**
|
|
@@ -87,6 +91,7 @@ export declare const NONCE: ContentSecurityPolicyOptionHandler;
|
|
|
87
91
|
* @param {overridableHeader} [customOptions.xPermittedCrossDomainPolicies=true] - Settings for the X-Permitted-Cross-Domain-Policies header.
|
|
88
92
|
* @param {overridableHeader} [customOptions.xXssProtection=true] - Settings for the X-XSS-Protection header.
|
|
89
93
|
* @param {boolean} [customOptions.removePoweredBy=true] - Settings for remove X-Powered-By header.
|
|
94
|
+
* @param {PermissionsPolicyOptions} [customOptions.permissionsPolicy] - Settings for the Permissions-Policy header.
|
|
90
95
|
* @returns {MiddlewareHandler} The middleware handler function.
|
|
91
96
|
*
|
|
92
97
|
* @example
|
|
@@ -7,8 +7,10 @@ import type { Env, MiddlewareHandler } from '../../types';
|
|
|
7
7
|
export type ServeStaticOptions<E extends Env = Env> = {
|
|
8
8
|
root?: string;
|
|
9
9
|
path?: string;
|
|
10
|
+
precompressed?: boolean;
|
|
10
11
|
mimes?: Record<string, string>;
|
|
11
12
|
rewriteRequestPath?: (path: string) => string;
|
|
13
|
+
onFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
|
12
14
|
onNotFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
@@ -40,5 +40,5 @@ export declare const raw: (value: unknown, callbacks?: HtmlEscapedCallback[]) =>
|
|
|
40
40
|
export declare const stringBufferToString: (buffer: StringBuffer, callbacks: HtmlEscapedCallback[] | undefined) => Promise<HtmlEscapedString>;
|
|
41
41
|
export declare const escapeToBuffer: (str: string, buffer: StringBuffer) => void;
|
|
42
42
|
export declare const resolveCallbackSync: (str: string | HtmlEscapedString) => string;
|
|
43
|
-
export declare const resolveCallback: (str: string | HtmlEscapedString
|
|
43
|
+
export declare const resolveCallback: (str: string | HtmlEscapedString | Promise<string>, phase: (typeof HtmlEscapedCallbackPhase)[keyof typeof HtmlEscapedCallbackPhase], preserveCallbacks: boolean, context: object, buffer?: [string]) => Promise<string>;
|
|
44
44
|
export {};
|
package/dist/utils/html.js
CHANGED
|
@@ -85,6 +85,14 @@ var resolveCallbackSync = (str) => {
|
|
|
85
85
|
return buffer[0];
|
|
86
86
|
};
|
|
87
87
|
var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
|
|
88
|
+
if (typeof str === "object" && !(str instanceof String)) {
|
|
89
|
+
if (!(str instanceof Promise)) {
|
|
90
|
+
str = str.toString();
|
|
91
|
+
}
|
|
92
|
+
if (str instanceof Promise) {
|
|
93
|
+
str = await str;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
88
96
|
const callbacks = str.callbacks;
|
|
89
97
|
if (!callbacks?.length) {
|
|
90
98
|
return Promise.resolve(str);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "Web framework built on Web Standards",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -104,6 +104,11 @@
|
|
|
104
104
|
"import": "./dist/middleware/compress/index.js",
|
|
105
105
|
"require": "./dist/cjs/middleware/compress/index.js"
|
|
106
106
|
},
|
|
107
|
+
"./context-storage": {
|
|
108
|
+
"types": "./dist/types/middleware/context-storage/index.d.ts",
|
|
109
|
+
"import": "./dist/middleware/context-storage/index.js",
|
|
110
|
+
"require": "./dist/cjs/middleware/context-storage/index.js"
|
|
111
|
+
},
|
|
107
112
|
"./cors": {
|
|
108
113
|
"types": "./dist/types/middleware/cors/index.d.ts",
|
|
109
114
|
"import": "./dist/middleware/cors/index.js",
|
|
@@ -421,6 +426,9 @@
|
|
|
421
426
|
"compress": [
|
|
422
427
|
"./dist/types/middleware/compress"
|
|
423
428
|
],
|
|
429
|
+
"context-storage": [
|
|
430
|
+
"./dist/types/middleware/context-storage"
|
|
431
|
+
],
|
|
424
432
|
"cors": [
|
|
425
433
|
"./dist/types/middleware/cors"
|
|
426
434
|
],
|