h3 1.0.2 → 1.2.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 +41 -22
- package/dist/index.cjs +304 -90
- package/dist/index.d.ts +49 -3
- package/dist/index.mjs +297 -92
- package/package.json +17 -15
package/README.md
CHANGED
|
@@ -39,28 +39,32 @@ pnpm add h3
|
|
|
39
39
|
## Usage
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
|
-
import { createServer } from
|
|
43
|
-
import { createApp, eventHandler, toNodeListener } from
|
|
42
|
+
import { createServer } from "node:http";
|
|
43
|
+
import { createApp, eventHandler, toNodeListener } from "h3";
|
|
44
44
|
|
|
45
|
-
const app = createApp()
|
|
46
|
-
app.use(
|
|
45
|
+
const app = createApp();
|
|
46
|
+
app.use(
|
|
47
|
+
"/",
|
|
48
|
+
eventHandler(() => "Hello world!")
|
|
49
|
+
);
|
|
47
50
|
|
|
48
|
-
createServer(toNodeListener(app)).listen(process.env.PORT || 3000)
|
|
51
|
+
createServer(toNodeListener(app)).listen(process.env.PORT || 3000);
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
<
|
|
52
|
-
<summary>Example using <a href="https://github.com/unjs/listhen">listhen</a> for an elegant listener.</summary>
|
|
54
|
+
Example using <a href="https://github.com/unjs/listhen">listhen</a> for an elegant listener:
|
|
53
55
|
|
|
54
56
|
```ts
|
|
55
|
-
import { createApp, toNodeListener } from
|
|
56
|
-
import { listen } from
|
|
57
|
+
import { createApp, eventHandler, toNodeListener } from "h3";
|
|
58
|
+
import { listen } from "listhen";
|
|
57
59
|
|
|
58
|
-
const app = createApp()
|
|
59
|
-
app.use(
|
|
60
|
+
const app = createApp();
|
|
61
|
+
app.use(
|
|
62
|
+
"/",
|
|
63
|
+
eventHandler(() => "Hello world!")
|
|
64
|
+
);
|
|
60
65
|
|
|
61
|
-
listen(toNodeListener(app))
|
|
66
|
+
listen(toNodeListener(app));
|
|
62
67
|
```
|
|
63
|
-
</details>
|
|
64
68
|
|
|
65
69
|
## Router
|
|
66
70
|
|
|
@@ -69,18 +73,24 @@ The `app` instance created by `h3` uses a middleware stack (see [how it works](#
|
|
|
69
73
|
To opt-in using a more advanced and convenient routing system, we can create a router instance and register it to app instance.
|
|
70
74
|
|
|
71
75
|
```ts
|
|
72
|
-
import { createApp, eventHandler, createRouter } from
|
|
76
|
+
import { createApp, eventHandler, createRouter } from "h3";
|
|
73
77
|
|
|
74
|
-
const app = createApp()
|
|
78
|
+
const app = createApp();
|
|
75
79
|
|
|
76
80
|
const router = createRouter()
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
.get(
|
|
82
|
+
"/",
|
|
83
|
+
eventHandler(() => "Hello World!")
|
|
84
|
+
)
|
|
85
|
+
.get(
|
|
86
|
+
"/hello/:name",
|
|
87
|
+
eventHandler((event) => `Hello ${event.context.params.name}!`)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
app.use(router);
|
|
81
91
|
```
|
|
82
92
|
|
|
83
|
-
**Tip:** We can register same route more than once with different methods.
|
|
93
|
+
**Tip:** We can register the same route more than once with different methods.
|
|
84
94
|
|
|
85
95
|
Routes are internally stored in a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) and matched using [unjs/radix3](https://github.com/unjs/radix3).
|
|
86
96
|
|
|
@@ -109,7 +119,7 @@ app.use('/big', () => import('./big-handler'), { lazy: true })
|
|
|
109
119
|
|
|
110
120
|
## Utilities
|
|
111
121
|
|
|
112
|
-
H3 has concept of
|
|
122
|
+
H3 has a concept of composable utilities that accept `event` (from `eventHandler((event) => {})`) as their first argument. This has several performance benefits over injecting them to `event` or `app` instances in global middleware commonly used in Node.js frameworks, such as Express. This concept means only required code is evaluated and bundled, and the rest of the utilities can be tree-shaken when not used.
|
|
113
123
|
|
|
114
124
|
### Built-in
|
|
115
125
|
|
|
@@ -138,12 +148,21 @@ H3 has concept of compasable utilities that accept `event` (from `eventHandler((
|
|
|
138
148
|
- `createError({ statusCode, statusMessage, data? })`
|
|
139
149
|
- `sendProxy(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
|
|
140
150
|
- `proxyRequest(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
|
|
151
|
+
- `sendNoContent(event, code = 204)`
|
|
152
|
+
- `setResponseStatus(event, status)`
|
|
153
|
+
- `getResponseStatus(event)`
|
|
154
|
+
- `getResponseStatusText(event)`
|
|
155
|
+
- `readMultipartFormData(event)`
|
|
156
|
+
- `useSession(event, { password, name?, cookie?, seal?, crypto? })`
|
|
157
|
+
- `getSession(event, { password, name?, cookie?, seal?, crypto? })`
|
|
158
|
+
- `updateSession(event, { password, name?, cookie?, seal?, crypto? }), update)`
|
|
159
|
+
- `clearSession(event, { password, name?, cookie?, seal?, crypto? }))`
|
|
141
160
|
|
|
142
161
|
👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).
|
|
143
162
|
|
|
144
163
|
## Community Packages
|
|
145
164
|
|
|
146
|
-
You can use more
|
|
165
|
+
You can use more H3 event utilities made by the community.
|
|
147
166
|
|
|
148
167
|
Please check their READMEs for more details.
|
|
149
168
|
|
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,8 @@ const ufo = require('ufo');
|
|
|
4
4
|
const radix3 = require('radix3');
|
|
5
5
|
const destr = require('destr');
|
|
6
6
|
const cookieEs = require('cookie-es');
|
|
7
|
+
const ironWebcrypto = require('iron-webcrypto');
|
|
8
|
+
const crypto = require('uncrypto');
|
|
7
9
|
|
|
8
10
|
function useBase(base, handler) {
|
|
9
11
|
base = ufo.withoutTrailingSlash(base);
|
|
@@ -17,6 +19,83 @@ function useBase(base, handler) {
|
|
|
17
19
|
});
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
function parse(multipartBodyBuffer, boundary) {
|
|
23
|
+
let lastline = "";
|
|
24
|
+
let state = 0 /* INIT */;
|
|
25
|
+
let buffer = [];
|
|
26
|
+
const allParts = [];
|
|
27
|
+
let currentPartHeaders = [];
|
|
28
|
+
for (let i = 0; i < multipartBodyBuffer.length; i++) {
|
|
29
|
+
const prevByte = i > 0 ? multipartBodyBuffer[i - 1] : null;
|
|
30
|
+
const currByte = multipartBodyBuffer[i];
|
|
31
|
+
const newLineChar = currByte === 10 || currByte === 13;
|
|
32
|
+
if (!newLineChar) {
|
|
33
|
+
lastline += String.fromCodePoint(currByte);
|
|
34
|
+
}
|
|
35
|
+
const newLineDetected = currByte === 10 && prevByte === 13;
|
|
36
|
+
if (0 /* INIT */ === state && newLineDetected) {
|
|
37
|
+
if ("--" + boundary === lastline) {
|
|
38
|
+
state = 1 /* READING_HEADERS */;
|
|
39
|
+
}
|
|
40
|
+
lastline = "";
|
|
41
|
+
} else if (1 /* READING_HEADERS */ === state && newLineDetected) {
|
|
42
|
+
if (lastline.length > 0) {
|
|
43
|
+
const i2 = lastline.indexOf(":");
|
|
44
|
+
if (i2 > 0) {
|
|
45
|
+
const name = lastline.slice(0, i2).toLowerCase();
|
|
46
|
+
const value = lastline.slice(i2 + 1).trim();
|
|
47
|
+
currentPartHeaders.push([name, value]);
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
state = 2 /* READING_DATA */;
|
|
51
|
+
buffer = [];
|
|
52
|
+
}
|
|
53
|
+
lastline = "";
|
|
54
|
+
} else if (2 /* READING_DATA */ === state) {
|
|
55
|
+
if (lastline.length > boundary.length + 4) {
|
|
56
|
+
lastline = "";
|
|
57
|
+
}
|
|
58
|
+
if ("--" + boundary === lastline) {
|
|
59
|
+
const j = buffer.length - lastline.length;
|
|
60
|
+
const part = buffer.slice(0, j - 1);
|
|
61
|
+
allParts.push(process(part, currentPartHeaders));
|
|
62
|
+
buffer = [];
|
|
63
|
+
currentPartHeaders = [];
|
|
64
|
+
lastline = "";
|
|
65
|
+
state = 3 /* READING_PART_SEPARATOR */;
|
|
66
|
+
} else {
|
|
67
|
+
buffer.push(currByte);
|
|
68
|
+
}
|
|
69
|
+
if (newLineDetected) {
|
|
70
|
+
lastline = "";
|
|
71
|
+
}
|
|
72
|
+
} else if (3 /* READING_PART_SEPARATOR */ === state && newLineDetected) {
|
|
73
|
+
state = 1 /* READING_HEADERS */;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return allParts;
|
|
77
|
+
}
|
|
78
|
+
function process(data, headers) {
|
|
79
|
+
const dataObj = {};
|
|
80
|
+
const contentDispositionHeader = headers.find((h) => h[0] === "content-disposition")?.[1] || "";
|
|
81
|
+
for (const i of contentDispositionHeader.split(";")) {
|
|
82
|
+
const s = i.split("=");
|
|
83
|
+
if (s.length !== 2) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const key = (s[0] || "").trim();
|
|
87
|
+
if (key === "name" || key === "filename") {
|
|
88
|
+
dataObj[key] = (s[1] || "").trim().replace(/"/g, "");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const contentType = headers.find((h) => h[0] === "content-type")?.[1] || "";
|
|
92
|
+
if (contentType) {
|
|
93
|
+
dataObj.type = contentType;
|
|
94
|
+
}
|
|
95
|
+
dataObj.data = Buffer.from(data);
|
|
96
|
+
return dataObj;
|
|
97
|
+
}
|
|
98
|
+
|
|
20
99
|
class H3Error extends Error {
|
|
21
100
|
constructor() {
|
|
22
101
|
super(...arguments);
|
|
@@ -49,6 +128,7 @@ function createError(input) {
|
|
|
49
128
|
}
|
|
50
129
|
const err = new H3Error(
|
|
51
130
|
input.message ?? input.statusMessage,
|
|
131
|
+
// @ts-ignore
|
|
52
132
|
input.cause ? { cause: input.cause } : void 0
|
|
53
133
|
);
|
|
54
134
|
if ("stack" in input) {
|
|
@@ -222,6 +302,21 @@ async function readBody(event) {
|
|
|
222
302
|
event.node.req[ParsedBodySymbol] = json;
|
|
223
303
|
return json;
|
|
224
304
|
}
|
|
305
|
+
async function readMultipartFormData(event) {
|
|
306
|
+
const contentType = getRequestHeader(event, "content-type");
|
|
307
|
+
if (!contentType || !contentType.startsWith("multipart/form-data")) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const boundary = contentType.match(/boundary=([^;]*)(;|$)/i)?.[1];
|
|
311
|
+
if (!boundary) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const body = await readRawBody(event, false);
|
|
315
|
+
if (!body) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
return parse(body, boundary);
|
|
319
|
+
}
|
|
225
320
|
|
|
226
321
|
function handleCacheHeaders(event, opts) {
|
|
227
322
|
const cacheControls = ["public", ...opts.cacheControls || []];
|
|
@@ -258,6 +353,111 @@ const MIMES = {
|
|
|
258
353
|
json: "application/json"
|
|
259
354
|
};
|
|
260
355
|
|
|
356
|
+
function parseCookies(event) {
|
|
357
|
+
return cookieEs.parse(event.node.req.headers.cookie || "");
|
|
358
|
+
}
|
|
359
|
+
function getCookie(event, name) {
|
|
360
|
+
return parseCookies(event)[name];
|
|
361
|
+
}
|
|
362
|
+
function setCookie(event, name, value, serializeOptions) {
|
|
363
|
+
const cookieStr = cookieEs.serialize(name, value, {
|
|
364
|
+
path: "/",
|
|
365
|
+
...serializeOptions
|
|
366
|
+
});
|
|
367
|
+
let setCookies = event.node.res.getHeader("set-cookie");
|
|
368
|
+
if (!Array.isArray(setCookies)) {
|
|
369
|
+
setCookies = [setCookies];
|
|
370
|
+
}
|
|
371
|
+
setCookies = setCookies.filter((cookieValue) => {
|
|
372
|
+
return cookieValue && !cookieValue.startsWith(name + "=");
|
|
373
|
+
});
|
|
374
|
+
event.node.res.setHeader("set-cookie", [...setCookies, cookieStr]);
|
|
375
|
+
}
|
|
376
|
+
function deleteCookie(event, name, serializeOptions) {
|
|
377
|
+
setCookie(event, name, "", {
|
|
378
|
+
...serializeOptions,
|
|
379
|
+
maxAge: 0
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
|
|
384
|
+
const ignoredHeaders = /* @__PURE__ */ new Set([
|
|
385
|
+
"transfer-encoding",
|
|
386
|
+
"connection",
|
|
387
|
+
"keep-alive",
|
|
388
|
+
"upgrade",
|
|
389
|
+
"expect",
|
|
390
|
+
"host"
|
|
391
|
+
]);
|
|
392
|
+
async function proxyRequest(event, target, opts = {}) {
|
|
393
|
+
const method = getMethod(event);
|
|
394
|
+
let body;
|
|
395
|
+
if (PayloadMethods.has(method)) {
|
|
396
|
+
body = await readRawBody(event).catch(() => void 0);
|
|
397
|
+
}
|
|
398
|
+
const headers = /* @__PURE__ */ Object.create(null);
|
|
399
|
+
const reqHeaders = getRequestHeaders(event);
|
|
400
|
+
for (const name in reqHeaders) {
|
|
401
|
+
if (!ignoredHeaders.has(name)) {
|
|
402
|
+
headers[name] = reqHeaders[name];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (opts.fetchOptions?.headers) {
|
|
406
|
+
Object.assign(headers, opts.fetchOptions.headers);
|
|
407
|
+
}
|
|
408
|
+
if (opts.headers) {
|
|
409
|
+
Object.assign(headers, opts.headers);
|
|
410
|
+
}
|
|
411
|
+
return sendProxy(event, target, {
|
|
412
|
+
...opts,
|
|
413
|
+
fetchOptions: {
|
|
414
|
+
headers,
|
|
415
|
+
method,
|
|
416
|
+
body,
|
|
417
|
+
...opts.fetchOptions
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
async function sendProxy(event, target, opts = {}) {
|
|
422
|
+
const _fetch = opts.fetch || globalThis.fetch;
|
|
423
|
+
if (!_fetch) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
"fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
const response = await _fetch(target, {
|
|
429
|
+
headers: opts.headers,
|
|
430
|
+
...opts.fetchOptions
|
|
431
|
+
});
|
|
432
|
+
event.node.res.statusCode = response.status;
|
|
433
|
+
event.node.res.statusMessage = response.statusText;
|
|
434
|
+
for (const [key, value] of response.headers.entries()) {
|
|
435
|
+
if (key === "content-encoding") {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (key === "content-length") {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
event.node.res.setHeader(key, value);
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
if (response.body) {
|
|
445
|
+
if (opts.sendStream === false) {
|
|
446
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
447
|
+
event.node.res.end(data);
|
|
448
|
+
} else {
|
|
449
|
+
for await (const chunk of response.body) {
|
|
450
|
+
event.node.res.write(chunk);
|
|
451
|
+
}
|
|
452
|
+
event.node.res.end();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
} catch (error) {
|
|
456
|
+
event.node.res.end();
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
261
461
|
const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
|
|
262
462
|
function send(event, data, type) {
|
|
263
463
|
if (type) {
|
|
@@ -270,6 +470,25 @@ function send(event, data, type) {
|
|
|
270
470
|
});
|
|
271
471
|
});
|
|
272
472
|
}
|
|
473
|
+
function sendNoContent(event, code = 204) {
|
|
474
|
+
event.node.res.statusCode = code;
|
|
475
|
+
if (event.node.res.statusCode === 204) {
|
|
476
|
+
event.node.res.removeHeader("content-length");
|
|
477
|
+
}
|
|
478
|
+
event.node.res.end();
|
|
479
|
+
}
|
|
480
|
+
function setResponseStatus(event, code, text) {
|
|
481
|
+
event.node.res.statusCode = code;
|
|
482
|
+
if (text) {
|
|
483
|
+
event.node.res.statusMessage = text;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function getResponseStatus(event) {
|
|
487
|
+
return event.node.res.statusCode;
|
|
488
|
+
}
|
|
489
|
+
function getResponseStatusText(event) {
|
|
490
|
+
return event.node.res.statusMessage;
|
|
491
|
+
}
|
|
273
492
|
function defaultContentType(event, type) {
|
|
274
493
|
if (type && !event.node.res.getHeader("content-type")) {
|
|
275
494
|
event.node.res.setHeader("content-type", type);
|
|
@@ -371,101 +590,82 @@ ${header}: ${value}`;
|
|
|
371
590
|
}
|
|
372
591
|
}
|
|
373
592
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
function
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
"connection",
|
|
398
|
-
"keep-alive",
|
|
399
|
-
"upgrade",
|
|
400
|
-
"expect"
|
|
401
|
-
]);
|
|
402
|
-
async function proxyRequest(event, target, opts = {}) {
|
|
403
|
-
const method = getMethod(event);
|
|
404
|
-
let body;
|
|
405
|
-
if (PayloadMethods.has(method)) {
|
|
406
|
-
body = await readRawBody(event).catch(() => void 0);
|
|
407
|
-
}
|
|
408
|
-
const headers = /* @__PURE__ */ Object.create(null);
|
|
409
|
-
const reqHeaders = getRequestHeaders(event);
|
|
410
|
-
for (const name in reqHeaders) {
|
|
411
|
-
if (!ignoredHeaders.has(name)) {
|
|
412
|
-
headers[name] = reqHeaders[name];
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (opts.fetchOptions?.headers) {
|
|
416
|
-
Object.assign(headers, opts.fetchOptions.headers);
|
|
417
|
-
}
|
|
418
|
-
if (opts.headers) {
|
|
419
|
-
Object.assign(headers, opts.headers);
|
|
420
|
-
}
|
|
421
|
-
return sendProxy(event, target, {
|
|
422
|
-
...opts,
|
|
423
|
-
fetchOptions: {
|
|
424
|
-
headers,
|
|
425
|
-
method,
|
|
426
|
-
body,
|
|
427
|
-
...opts.fetchOptions
|
|
593
|
+
const DEFAULT_NAME = "h3";
|
|
594
|
+
const DEFAULT_COOKIE = {
|
|
595
|
+
path: "/",
|
|
596
|
+
secure: true,
|
|
597
|
+
httpOnly: true
|
|
598
|
+
};
|
|
599
|
+
async function useSession(event, config) {
|
|
600
|
+
const sessionName = config.name || DEFAULT_NAME;
|
|
601
|
+
await getSession(event, config);
|
|
602
|
+
const sessionManager = {
|
|
603
|
+
get id() {
|
|
604
|
+
return event.context.sessions?.[sessionName]?.id;
|
|
605
|
+
},
|
|
606
|
+
get data() {
|
|
607
|
+
return event.context.sessions?.[sessionName]?.data || {};
|
|
608
|
+
},
|
|
609
|
+
update: async (update) => {
|
|
610
|
+
await updateSession(event, config, update);
|
|
611
|
+
return sessionManager;
|
|
612
|
+
},
|
|
613
|
+
clear: async () => {
|
|
614
|
+
await clearSession(event, config);
|
|
615
|
+
return sessionManager;
|
|
428
616
|
}
|
|
429
|
-
}
|
|
617
|
+
};
|
|
618
|
+
return sessionManager;
|
|
430
619
|
}
|
|
431
|
-
async function
|
|
432
|
-
const
|
|
433
|
-
if (!
|
|
434
|
-
|
|
435
|
-
|
|
620
|
+
async function getSession(event, config) {
|
|
621
|
+
const sessionName = config.name || DEFAULT_NAME;
|
|
622
|
+
if (!event.context.sessions) {
|
|
623
|
+
event.context.sessions = /* @__PURE__ */ Object.create(null);
|
|
624
|
+
}
|
|
625
|
+
if (event.context.sessions[sessionName]) {
|
|
626
|
+
return event.context.sessions[sessionName];
|
|
627
|
+
}
|
|
628
|
+
const session = { id: "", data: /* @__PURE__ */ Object.create(null) };
|
|
629
|
+
event.context.sessions[sessionName] = session;
|
|
630
|
+
const reqCookie = getCookie(event, sessionName);
|
|
631
|
+
if (!reqCookie) {
|
|
632
|
+
session.id = (config.crypto || crypto).randomUUID();
|
|
633
|
+
await updateSession(event, config);
|
|
634
|
+
} else {
|
|
635
|
+
const unsealed = await ironWebcrypto.unseal(
|
|
636
|
+
config.crypto || crypto,
|
|
637
|
+
reqCookie,
|
|
638
|
+
config.password,
|
|
639
|
+
config.seal || ironWebcrypto.defaults
|
|
436
640
|
);
|
|
641
|
+
Object.assign(session, unsealed);
|
|
437
642
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
event.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
event.node.res.end();
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
} catch (error) {
|
|
466
|
-
event.node.res.end();
|
|
467
|
-
throw error;
|
|
643
|
+
return session;
|
|
644
|
+
}
|
|
645
|
+
async function updateSession(event, config, update) {
|
|
646
|
+
const sessionName = config.name || DEFAULT_NAME;
|
|
647
|
+
const session = event.context.sessions?.[sessionName] || await getSession(event, config);
|
|
648
|
+
if (typeof update === "function") {
|
|
649
|
+
update = update(session.data);
|
|
650
|
+
}
|
|
651
|
+
if (update) {
|
|
652
|
+
Object.assign(session.data, update);
|
|
653
|
+
}
|
|
654
|
+
const sealed = await ironWebcrypto.seal(
|
|
655
|
+
config.crypto || crypto,
|
|
656
|
+
session,
|
|
657
|
+
config.password,
|
|
658
|
+
config.seal || ironWebcrypto.defaults
|
|
659
|
+
);
|
|
660
|
+
setCookie(event, sessionName, sealed, config.cookie || DEFAULT_COOKIE);
|
|
661
|
+
return session;
|
|
662
|
+
}
|
|
663
|
+
async function clearSession(event, config) {
|
|
664
|
+
const sessionName = config.name || DEFAULT_NAME;
|
|
665
|
+
if (event.context.sessions?.[sessionName]) {
|
|
666
|
+
delete event.context.sessions[sessionName];
|
|
468
667
|
}
|
|
668
|
+
await setCookie(event, sessionName, "", config.cookie || DEFAULT_COOKIE);
|
|
469
669
|
}
|
|
470
670
|
|
|
471
671
|
class H3Headers {
|
|
@@ -521,6 +721,7 @@ class H3Headers {
|
|
|
521
721
|
|
|
522
722
|
class H3Response {
|
|
523
723
|
constructor(body = null, init = {}) {
|
|
724
|
+
// TODO: yet to implement
|
|
524
725
|
this.body = null;
|
|
525
726
|
this.type = "default";
|
|
526
727
|
this.bodyUsed = false;
|
|
@@ -565,12 +766,15 @@ class H3Event {
|
|
|
565
766
|
get path() {
|
|
566
767
|
return this.req.url;
|
|
567
768
|
}
|
|
769
|
+
/** @deprecated Please use `event.node.req` instead. **/
|
|
568
770
|
get req() {
|
|
569
771
|
return this.node.req;
|
|
570
772
|
}
|
|
773
|
+
/** @deprecated Please use `event.node.res` instead. **/
|
|
571
774
|
get res() {
|
|
572
775
|
return this.node.res;
|
|
573
776
|
}
|
|
777
|
+
// Implementation of FetchEvent
|
|
574
778
|
respondWith(r) {
|
|
575
779
|
Promise.resolve(r).then((_response) => {
|
|
576
780
|
if (this.res.writableEnded) {
|
|
@@ -676,6 +880,7 @@ function createApp(options = {}) {
|
|
|
676
880
|
const stack = [];
|
|
677
881
|
const handler = createAppEventHandler(stack, options);
|
|
678
882
|
const app = {
|
|
883
|
+
// @ts-ignore
|
|
679
884
|
use: (arg1, arg2, arg3) => use(app, arg1, arg2, arg3),
|
|
680
885
|
handler,
|
|
681
886
|
stack,
|
|
@@ -923,6 +1128,7 @@ exports.appendResponseHeader = appendResponseHeader;
|
|
|
923
1128
|
exports.appendResponseHeaders = appendResponseHeaders;
|
|
924
1129
|
exports.assertMethod = assertMethod;
|
|
925
1130
|
exports.callNodeListener = callNodeListener;
|
|
1131
|
+
exports.clearSession = clearSession;
|
|
926
1132
|
exports.createApp = createApp;
|
|
927
1133
|
exports.createAppEventHandler = createAppEventHandler;
|
|
928
1134
|
exports.createError = createError;
|
|
@@ -946,8 +1152,11 @@ exports.getRequestHeader = getRequestHeader;
|
|
|
946
1152
|
exports.getRequestHeaders = getRequestHeaders;
|
|
947
1153
|
exports.getResponseHeader = getResponseHeader;
|
|
948
1154
|
exports.getResponseHeaders = getResponseHeaders;
|
|
1155
|
+
exports.getResponseStatus = getResponseStatus;
|
|
1156
|
+
exports.getResponseStatusText = getResponseStatusText;
|
|
949
1157
|
exports.getRouterParam = getRouterParam;
|
|
950
1158
|
exports.getRouterParams = getRouterParams;
|
|
1159
|
+
exports.getSession = getSession;
|
|
951
1160
|
exports.handleCacheHeaders = handleCacheHeaders;
|
|
952
1161
|
exports.isError = isError;
|
|
953
1162
|
exports.isEvent = isEvent;
|
|
@@ -959,9 +1168,11 @@ exports.parseCookies = parseCookies;
|
|
|
959
1168
|
exports.promisifyNodeListener = promisifyNodeListener;
|
|
960
1169
|
exports.proxyRequest = proxyRequest;
|
|
961
1170
|
exports.readBody = readBody;
|
|
1171
|
+
exports.readMultipartFormData = readMultipartFormData;
|
|
962
1172
|
exports.readRawBody = readRawBody;
|
|
963
1173
|
exports.send = send;
|
|
964
1174
|
exports.sendError = sendError;
|
|
1175
|
+
exports.sendNoContent = sendNoContent;
|
|
965
1176
|
exports.sendProxy = sendProxy;
|
|
966
1177
|
exports.sendRedirect = sendRedirect;
|
|
967
1178
|
exports.sendStream = sendStream;
|
|
@@ -970,8 +1181,11 @@ exports.setHeader = setHeader;
|
|
|
970
1181
|
exports.setHeaders = setHeaders;
|
|
971
1182
|
exports.setResponseHeader = setResponseHeader;
|
|
972
1183
|
exports.setResponseHeaders = setResponseHeaders;
|
|
1184
|
+
exports.setResponseStatus = setResponseStatus;
|
|
973
1185
|
exports.toEventHandler = toEventHandler;
|
|
974
1186
|
exports.toNodeListener = toNodeListener;
|
|
1187
|
+
exports.updateSession = updateSession;
|
|
975
1188
|
exports.use = use;
|
|
976
1189
|
exports.useBase = useBase;
|
|
1190
|
+
exports.useSession = useSession;
|
|
977
1191
|
exports.writeEarlyHints = writeEarlyHints;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,38 @@
|
|
|
1
|
+
import { SealOptions } from 'iron-webcrypto';
|
|
2
|
+
import { CookieSerializeOptions } from 'cookie-es';
|
|
1
3
|
import { IncomingMessage, ServerResponse, OutgoingMessage } from 'node:http';
|
|
2
4
|
export { IncomingMessage as NodeIncomingMessage, ServerResponse as NodeServerResponse } from 'node:http';
|
|
3
|
-
import { CookieSerializeOptions } from 'cookie-es';
|
|
4
5
|
import * as ufo from 'ufo';
|
|
5
6
|
|
|
7
|
+
type SessionDataT = Record<string, string | number | boolean>;
|
|
8
|
+
type SessionData<T extends SessionDataT = SessionDataT> = T;
|
|
9
|
+
interface Session<T extends SessionDataT = SessionDataT> {
|
|
10
|
+
id: string;
|
|
11
|
+
data: SessionData<T>;
|
|
12
|
+
}
|
|
13
|
+
interface SessionConfig {
|
|
14
|
+
password: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
cookie?: CookieSerializeOptions;
|
|
17
|
+
seal?: SealOptions;
|
|
18
|
+
crypto?: Crypto;
|
|
19
|
+
}
|
|
20
|
+
declare function useSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfig): Promise<{
|
|
21
|
+
readonly id: string | undefined;
|
|
22
|
+
readonly data: SessionDataT;
|
|
23
|
+
update: (update: SessionUpdate<T>) => Promise<any>;
|
|
24
|
+
clear: () => Promise<any>;
|
|
25
|
+
}>;
|
|
26
|
+
declare function getSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfig): Promise<Session<T>>;
|
|
27
|
+
type SessionUpdate<T extends SessionDataT = SessionDataT> = Partial<SessionData<T>> | ((oldData: SessionData<T>) => Partial<SessionData<T>> | undefined);
|
|
28
|
+
declare function updateSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfig, update?: SessionUpdate<T>): Promise<Session<T>>;
|
|
29
|
+
declare function clearSession(event: H3Event, config: SessionConfig): Promise<void>;
|
|
30
|
+
|
|
6
31
|
type HTTPMethod = "GET" | "HEAD" | "PATCH" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE";
|
|
7
32
|
type Encoding = false | "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex";
|
|
8
33
|
interface H3EventContext extends Record<string, any> {
|
|
34
|
+
params?: Record<string, string>;
|
|
35
|
+
sessions?: Record<string, Session>;
|
|
9
36
|
}
|
|
10
37
|
type EventHandlerResponse<T = any> = T | Promise<T>;
|
|
11
38
|
interface EventHandler<T = any> {
|
|
@@ -139,7 +166,7 @@ declare function createAppEventHandler(stack: Stack, options: AppOptions): Event
|
|
|
139
166
|
*/
|
|
140
167
|
declare class H3Error extends Error {
|
|
141
168
|
static __h3_error__: boolean;
|
|
142
|
-
toJSON(): Pick<H3Error, "
|
|
169
|
+
toJSON(): Pick<H3Error, "data" | "statusCode" | "statusMessage" | "message">;
|
|
143
170
|
statusCode: number;
|
|
144
171
|
fatal: boolean;
|
|
145
172
|
unhandled: boolean;
|
|
@@ -171,6 +198,13 @@ declare function isError(input: any): input is H3Error;
|
|
|
171
198
|
|
|
172
199
|
declare function useBase(base: string, handler: EventHandler): EventHandler;
|
|
173
200
|
|
|
201
|
+
interface MultiPartData {
|
|
202
|
+
data: Buffer;
|
|
203
|
+
name?: string;
|
|
204
|
+
filename?: string;
|
|
205
|
+
type?: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
174
208
|
/**
|
|
175
209
|
* Reads body of the request and returns encoded raw string (default) or `Buffer` if encoding if falsy.
|
|
176
210
|
* @param event {H3Event} H3 event or req passed by h3 handler
|
|
@@ -191,6 +225,7 @@ declare function readRawBody<E extends Encoding = "utf8">(event: H3Event, encodi
|
|
|
191
225
|
* ```
|
|
192
226
|
*/
|
|
193
227
|
declare function readBody<T = any>(event: H3Event): Promise<T>;
|
|
228
|
+
declare function readMultipartFormData(event: H3Event): Promise<MultiPartData[] | undefined>;
|
|
194
229
|
|
|
195
230
|
interface CacheConditions {
|
|
196
231
|
modifiedTime?: string | Date;
|
|
@@ -272,6 +307,17 @@ declare function getRequestHeader(event: H3Event, name: string): RequestHeaders[
|
|
|
272
307
|
declare const getHeader: typeof getRequestHeader;
|
|
273
308
|
|
|
274
309
|
declare function send(event: H3Event, data?: any, type?: string): Promise<void>;
|
|
310
|
+
/**
|
|
311
|
+
* Respond with an empty payload.<br>
|
|
312
|
+
* Note that calling this function will close the connection and no other data can be sent to the client afterwards.
|
|
313
|
+
*
|
|
314
|
+
* @param event H3 event
|
|
315
|
+
* @param code status code to be send. By default, it is `204 No Content`.
|
|
316
|
+
*/
|
|
317
|
+
declare function sendNoContent(event: H3Event, code?: number): void;
|
|
318
|
+
declare function setResponseStatus(event: H3Event, code: number, text?: string): void;
|
|
319
|
+
declare function getResponseStatus(event: H3Event): number;
|
|
320
|
+
declare function getResponseStatusText(event: H3Event): string;
|
|
275
321
|
declare function defaultContentType(event: H3Event, type?: string): void;
|
|
276
322
|
declare function sendRedirect(event: H3Event, location: string, code?: number): Promise<void>;
|
|
277
323
|
declare function getResponseHeaders(event: H3Event): ReturnType<H3Event["res"]["getHeaders"]>;
|
|
@@ -303,4 +349,4 @@ interface CreateRouterOptions {
|
|
|
303
349
|
}
|
|
304
350
|
declare function createRouter(opts?: CreateRouterOptions): Router;
|
|
305
351
|
|
|
306
|
-
export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Stack, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getRouterParam, getRouterParams, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readRawBody, send, sendError, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, toEventHandler, toNodeListener, use, useBase, writeEarlyHints };
|
|
352
|
+
export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, updateSession, use, useBase, useSession, writeEarlyHints };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { withoutTrailingSlash, withoutBase, getQuery as getQuery$1 } from 'ufo';
|
|
2
2
|
import { createRouter as createRouter$1 } from 'radix3';
|
|
3
3
|
import destr from 'destr';
|
|
4
|
-
import { parse, serialize } from 'cookie-es';
|
|
4
|
+
import { parse as parse$1, serialize } from 'cookie-es';
|
|
5
|
+
import { unseal, defaults, seal } from 'iron-webcrypto';
|
|
6
|
+
import crypto from 'uncrypto';
|
|
5
7
|
|
|
6
8
|
function useBase(base, handler) {
|
|
7
9
|
base = withoutTrailingSlash(base);
|
|
@@ -15,6 +17,83 @@ function useBase(base, handler) {
|
|
|
15
17
|
});
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
function parse(multipartBodyBuffer, boundary) {
|
|
21
|
+
let lastline = "";
|
|
22
|
+
let state = 0 /* INIT */;
|
|
23
|
+
let buffer = [];
|
|
24
|
+
const allParts = [];
|
|
25
|
+
let currentPartHeaders = [];
|
|
26
|
+
for (let i = 0; i < multipartBodyBuffer.length; i++) {
|
|
27
|
+
const prevByte = i > 0 ? multipartBodyBuffer[i - 1] : null;
|
|
28
|
+
const currByte = multipartBodyBuffer[i];
|
|
29
|
+
const newLineChar = currByte === 10 || currByte === 13;
|
|
30
|
+
if (!newLineChar) {
|
|
31
|
+
lastline += String.fromCodePoint(currByte);
|
|
32
|
+
}
|
|
33
|
+
const newLineDetected = currByte === 10 && prevByte === 13;
|
|
34
|
+
if (0 /* INIT */ === state && newLineDetected) {
|
|
35
|
+
if ("--" + boundary === lastline) {
|
|
36
|
+
state = 1 /* READING_HEADERS */;
|
|
37
|
+
}
|
|
38
|
+
lastline = "";
|
|
39
|
+
} else if (1 /* READING_HEADERS */ === state && newLineDetected) {
|
|
40
|
+
if (lastline.length > 0) {
|
|
41
|
+
const i2 = lastline.indexOf(":");
|
|
42
|
+
if (i2 > 0) {
|
|
43
|
+
const name = lastline.slice(0, i2).toLowerCase();
|
|
44
|
+
const value = lastline.slice(i2 + 1).trim();
|
|
45
|
+
currentPartHeaders.push([name, value]);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
state = 2 /* READING_DATA */;
|
|
49
|
+
buffer = [];
|
|
50
|
+
}
|
|
51
|
+
lastline = "";
|
|
52
|
+
} else if (2 /* READING_DATA */ === state) {
|
|
53
|
+
if (lastline.length > boundary.length + 4) {
|
|
54
|
+
lastline = "";
|
|
55
|
+
}
|
|
56
|
+
if ("--" + boundary === lastline) {
|
|
57
|
+
const j = buffer.length - lastline.length;
|
|
58
|
+
const part = buffer.slice(0, j - 1);
|
|
59
|
+
allParts.push(process(part, currentPartHeaders));
|
|
60
|
+
buffer = [];
|
|
61
|
+
currentPartHeaders = [];
|
|
62
|
+
lastline = "";
|
|
63
|
+
state = 3 /* READING_PART_SEPARATOR */;
|
|
64
|
+
} else {
|
|
65
|
+
buffer.push(currByte);
|
|
66
|
+
}
|
|
67
|
+
if (newLineDetected) {
|
|
68
|
+
lastline = "";
|
|
69
|
+
}
|
|
70
|
+
} else if (3 /* READING_PART_SEPARATOR */ === state && newLineDetected) {
|
|
71
|
+
state = 1 /* READING_HEADERS */;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return allParts;
|
|
75
|
+
}
|
|
76
|
+
function process(data, headers) {
|
|
77
|
+
const dataObj = {};
|
|
78
|
+
const contentDispositionHeader = headers.find((h) => h[0] === "content-disposition")?.[1] || "";
|
|
79
|
+
for (const i of contentDispositionHeader.split(";")) {
|
|
80
|
+
const s = i.split("=");
|
|
81
|
+
if (s.length !== 2) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const key = (s[0] || "").trim();
|
|
85
|
+
if (key === "name" || key === "filename") {
|
|
86
|
+
dataObj[key] = (s[1] || "").trim().replace(/"/g, "");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const contentType = headers.find((h) => h[0] === "content-type")?.[1] || "";
|
|
90
|
+
if (contentType) {
|
|
91
|
+
dataObj.type = contentType;
|
|
92
|
+
}
|
|
93
|
+
dataObj.data = Buffer.from(data);
|
|
94
|
+
return dataObj;
|
|
95
|
+
}
|
|
96
|
+
|
|
18
97
|
class H3Error extends Error {
|
|
19
98
|
constructor() {
|
|
20
99
|
super(...arguments);
|
|
@@ -47,6 +126,7 @@ function createError(input) {
|
|
|
47
126
|
}
|
|
48
127
|
const err = new H3Error(
|
|
49
128
|
input.message ?? input.statusMessage,
|
|
129
|
+
// @ts-ignore
|
|
50
130
|
input.cause ? { cause: input.cause } : void 0
|
|
51
131
|
);
|
|
52
132
|
if ("stack" in input) {
|
|
@@ -220,6 +300,21 @@ async function readBody(event) {
|
|
|
220
300
|
event.node.req[ParsedBodySymbol] = json;
|
|
221
301
|
return json;
|
|
222
302
|
}
|
|
303
|
+
async function readMultipartFormData(event) {
|
|
304
|
+
const contentType = getRequestHeader(event, "content-type");
|
|
305
|
+
if (!contentType || !contentType.startsWith("multipart/form-data")) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const boundary = contentType.match(/boundary=([^;]*)(;|$)/i)?.[1];
|
|
309
|
+
if (!boundary) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const body = await readRawBody(event, false);
|
|
313
|
+
if (!body) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
return parse(body, boundary);
|
|
317
|
+
}
|
|
223
318
|
|
|
224
319
|
function handleCacheHeaders(event, opts) {
|
|
225
320
|
const cacheControls = ["public", ...opts.cacheControls || []];
|
|
@@ -256,6 +351,111 @@ const MIMES = {
|
|
|
256
351
|
json: "application/json"
|
|
257
352
|
};
|
|
258
353
|
|
|
354
|
+
function parseCookies(event) {
|
|
355
|
+
return parse$1(event.node.req.headers.cookie || "");
|
|
356
|
+
}
|
|
357
|
+
function getCookie(event, name) {
|
|
358
|
+
return parseCookies(event)[name];
|
|
359
|
+
}
|
|
360
|
+
function setCookie(event, name, value, serializeOptions) {
|
|
361
|
+
const cookieStr = serialize(name, value, {
|
|
362
|
+
path: "/",
|
|
363
|
+
...serializeOptions
|
|
364
|
+
});
|
|
365
|
+
let setCookies = event.node.res.getHeader("set-cookie");
|
|
366
|
+
if (!Array.isArray(setCookies)) {
|
|
367
|
+
setCookies = [setCookies];
|
|
368
|
+
}
|
|
369
|
+
setCookies = setCookies.filter((cookieValue) => {
|
|
370
|
+
return cookieValue && !cookieValue.startsWith(name + "=");
|
|
371
|
+
});
|
|
372
|
+
event.node.res.setHeader("set-cookie", [...setCookies, cookieStr]);
|
|
373
|
+
}
|
|
374
|
+
function deleteCookie(event, name, serializeOptions) {
|
|
375
|
+
setCookie(event, name, "", {
|
|
376
|
+
...serializeOptions,
|
|
377
|
+
maxAge: 0
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
|
|
382
|
+
const ignoredHeaders = /* @__PURE__ */ new Set([
|
|
383
|
+
"transfer-encoding",
|
|
384
|
+
"connection",
|
|
385
|
+
"keep-alive",
|
|
386
|
+
"upgrade",
|
|
387
|
+
"expect",
|
|
388
|
+
"host"
|
|
389
|
+
]);
|
|
390
|
+
async function proxyRequest(event, target, opts = {}) {
|
|
391
|
+
const method = getMethod(event);
|
|
392
|
+
let body;
|
|
393
|
+
if (PayloadMethods.has(method)) {
|
|
394
|
+
body = await readRawBody(event).catch(() => void 0);
|
|
395
|
+
}
|
|
396
|
+
const headers = /* @__PURE__ */ Object.create(null);
|
|
397
|
+
const reqHeaders = getRequestHeaders(event);
|
|
398
|
+
for (const name in reqHeaders) {
|
|
399
|
+
if (!ignoredHeaders.has(name)) {
|
|
400
|
+
headers[name] = reqHeaders[name];
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (opts.fetchOptions?.headers) {
|
|
404
|
+
Object.assign(headers, opts.fetchOptions.headers);
|
|
405
|
+
}
|
|
406
|
+
if (opts.headers) {
|
|
407
|
+
Object.assign(headers, opts.headers);
|
|
408
|
+
}
|
|
409
|
+
return sendProxy(event, target, {
|
|
410
|
+
...opts,
|
|
411
|
+
fetchOptions: {
|
|
412
|
+
headers,
|
|
413
|
+
method,
|
|
414
|
+
body,
|
|
415
|
+
...opts.fetchOptions
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
async function sendProxy(event, target, opts = {}) {
|
|
420
|
+
const _fetch = opts.fetch || globalThis.fetch;
|
|
421
|
+
if (!_fetch) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
"fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
const response = await _fetch(target, {
|
|
427
|
+
headers: opts.headers,
|
|
428
|
+
...opts.fetchOptions
|
|
429
|
+
});
|
|
430
|
+
event.node.res.statusCode = response.status;
|
|
431
|
+
event.node.res.statusMessage = response.statusText;
|
|
432
|
+
for (const [key, value] of response.headers.entries()) {
|
|
433
|
+
if (key === "content-encoding") {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (key === "content-length") {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
event.node.res.setHeader(key, value);
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
if (response.body) {
|
|
443
|
+
if (opts.sendStream === false) {
|
|
444
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
445
|
+
event.node.res.end(data);
|
|
446
|
+
} else {
|
|
447
|
+
for await (const chunk of response.body) {
|
|
448
|
+
event.node.res.write(chunk);
|
|
449
|
+
}
|
|
450
|
+
event.node.res.end();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} catch (error) {
|
|
454
|
+
event.node.res.end();
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
259
459
|
const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
|
|
260
460
|
function send(event, data, type) {
|
|
261
461
|
if (type) {
|
|
@@ -268,6 +468,25 @@ function send(event, data, type) {
|
|
|
268
468
|
});
|
|
269
469
|
});
|
|
270
470
|
}
|
|
471
|
+
function sendNoContent(event, code = 204) {
|
|
472
|
+
event.node.res.statusCode = code;
|
|
473
|
+
if (event.node.res.statusCode === 204) {
|
|
474
|
+
event.node.res.removeHeader("content-length");
|
|
475
|
+
}
|
|
476
|
+
event.node.res.end();
|
|
477
|
+
}
|
|
478
|
+
function setResponseStatus(event, code, text) {
|
|
479
|
+
event.node.res.statusCode = code;
|
|
480
|
+
if (text) {
|
|
481
|
+
event.node.res.statusMessage = text;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function getResponseStatus(event) {
|
|
485
|
+
return event.node.res.statusCode;
|
|
486
|
+
}
|
|
487
|
+
function getResponseStatusText(event) {
|
|
488
|
+
return event.node.res.statusMessage;
|
|
489
|
+
}
|
|
271
490
|
function defaultContentType(event, type) {
|
|
272
491
|
if (type && !event.node.res.getHeader("content-type")) {
|
|
273
492
|
event.node.res.setHeader("content-type", type);
|
|
@@ -369,101 +588,82 @@ ${header}: ${value}`;
|
|
|
369
588
|
}
|
|
370
589
|
}
|
|
371
590
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
function
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
"connection",
|
|
396
|
-
"keep-alive",
|
|
397
|
-
"upgrade",
|
|
398
|
-
"expect"
|
|
399
|
-
]);
|
|
400
|
-
async function proxyRequest(event, target, opts = {}) {
|
|
401
|
-
const method = getMethod(event);
|
|
402
|
-
let body;
|
|
403
|
-
if (PayloadMethods.has(method)) {
|
|
404
|
-
body = await readRawBody(event).catch(() => void 0);
|
|
405
|
-
}
|
|
406
|
-
const headers = /* @__PURE__ */ Object.create(null);
|
|
407
|
-
const reqHeaders = getRequestHeaders(event);
|
|
408
|
-
for (const name in reqHeaders) {
|
|
409
|
-
if (!ignoredHeaders.has(name)) {
|
|
410
|
-
headers[name] = reqHeaders[name];
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (opts.fetchOptions?.headers) {
|
|
414
|
-
Object.assign(headers, opts.fetchOptions.headers);
|
|
415
|
-
}
|
|
416
|
-
if (opts.headers) {
|
|
417
|
-
Object.assign(headers, opts.headers);
|
|
418
|
-
}
|
|
419
|
-
return sendProxy(event, target, {
|
|
420
|
-
...opts,
|
|
421
|
-
fetchOptions: {
|
|
422
|
-
headers,
|
|
423
|
-
method,
|
|
424
|
-
body,
|
|
425
|
-
...opts.fetchOptions
|
|
591
|
+
const DEFAULT_NAME = "h3";
|
|
592
|
+
const DEFAULT_COOKIE = {
|
|
593
|
+
path: "/",
|
|
594
|
+
secure: true,
|
|
595
|
+
httpOnly: true
|
|
596
|
+
};
|
|
597
|
+
async function useSession(event, config) {
|
|
598
|
+
const sessionName = config.name || DEFAULT_NAME;
|
|
599
|
+
await getSession(event, config);
|
|
600
|
+
const sessionManager = {
|
|
601
|
+
get id() {
|
|
602
|
+
return event.context.sessions?.[sessionName]?.id;
|
|
603
|
+
},
|
|
604
|
+
get data() {
|
|
605
|
+
return event.context.sessions?.[sessionName]?.data || {};
|
|
606
|
+
},
|
|
607
|
+
update: async (update) => {
|
|
608
|
+
await updateSession(event, config, update);
|
|
609
|
+
return sessionManager;
|
|
610
|
+
},
|
|
611
|
+
clear: async () => {
|
|
612
|
+
await clearSession(event, config);
|
|
613
|
+
return sessionManager;
|
|
426
614
|
}
|
|
427
|
-
}
|
|
615
|
+
};
|
|
616
|
+
return sessionManager;
|
|
428
617
|
}
|
|
429
|
-
async function
|
|
430
|
-
const
|
|
431
|
-
if (!
|
|
432
|
-
|
|
433
|
-
|
|
618
|
+
async function getSession(event, config) {
|
|
619
|
+
const sessionName = config.name || DEFAULT_NAME;
|
|
620
|
+
if (!event.context.sessions) {
|
|
621
|
+
event.context.sessions = /* @__PURE__ */ Object.create(null);
|
|
622
|
+
}
|
|
623
|
+
if (event.context.sessions[sessionName]) {
|
|
624
|
+
return event.context.sessions[sessionName];
|
|
625
|
+
}
|
|
626
|
+
const session = { id: "", data: /* @__PURE__ */ Object.create(null) };
|
|
627
|
+
event.context.sessions[sessionName] = session;
|
|
628
|
+
const reqCookie = getCookie(event, sessionName);
|
|
629
|
+
if (!reqCookie) {
|
|
630
|
+
session.id = (config.crypto || crypto).randomUUID();
|
|
631
|
+
await updateSession(event, config);
|
|
632
|
+
} else {
|
|
633
|
+
const unsealed = await unseal(
|
|
634
|
+
config.crypto || crypto,
|
|
635
|
+
reqCookie,
|
|
636
|
+
config.password,
|
|
637
|
+
config.seal || defaults
|
|
434
638
|
);
|
|
639
|
+
Object.assign(session, unsealed);
|
|
435
640
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
event.
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
event.node.res.end();
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
} catch (error) {
|
|
464
|
-
event.node.res.end();
|
|
465
|
-
throw error;
|
|
641
|
+
return session;
|
|
642
|
+
}
|
|
643
|
+
async function updateSession(event, config, update) {
|
|
644
|
+
const sessionName = config.name || DEFAULT_NAME;
|
|
645
|
+
const session = event.context.sessions?.[sessionName] || await getSession(event, config);
|
|
646
|
+
if (typeof update === "function") {
|
|
647
|
+
update = update(session.data);
|
|
648
|
+
}
|
|
649
|
+
if (update) {
|
|
650
|
+
Object.assign(session.data, update);
|
|
651
|
+
}
|
|
652
|
+
const sealed = await seal(
|
|
653
|
+
config.crypto || crypto,
|
|
654
|
+
session,
|
|
655
|
+
config.password,
|
|
656
|
+
config.seal || defaults
|
|
657
|
+
);
|
|
658
|
+
setCookie(event, sessionName, sealed, config.cookie || DEFAULT_COOKIE);
|
|
659
|
+
return session;
|
|
660
|
+
}
|
|
661
|
+
async function clearSession(event, config) {
|
|
662
|
+
const sessionName = config.name || DEFAULT_NAME;
|
|
663
|
+
if (event.context.sessions?.[sessionName]) {
|
|
664
|
+
delete event.context.sessions[sessionName];
|
|
466
665
|
}
|
|
666
|
+
await setCookie(event, sessionName, "", config.cookie || DEFAULT_COOKIE);
|
|
467
667
|
}
|
|
468
668
|
|
|
469
669
|
class H3Headers {
|
|
@@ -519,6 +719,7 @@ class H3Headers {
|
|
|
519
719
|
|
|
520
720
|
class H3Response {
|
|
521
721
|
constructor(body = null, init = {}) {
|
|
722
|
+
// TODO: yet to implement
|
|
522
723
|
this.body = null;
|
|
523
724
|
this.type = "default";
|
|
524
725
|
this.bodyUsed = false;
|
|
@@ -563,12 +764,15 @@ class H3Event {
|
|
|
563
764
|
get path() {
|
|
564
765
|
return this.req.url;
|
|
565
766
|
}
|
|
767
|
+
/** @deprecated Please use `event.node.req` instead. **/
|
|
566
768
|
get req() {
|
|
567
769
|
return this.node.req;
|
|
568
770
|
}
|
|
771
|
+
/** @deprecated Please use `event.node.res` instead. **/
|
|
569
772
|
get res() {
|
|
570
773
|
return this.node.res;
|
|
571
774
|
}
|
|
775
|
+
// Implementation of FetchEvent
|
|
572
776
|
respondWith(r) {
|
|
573
777
|
Promise.resolve(r).then((_response) => {
|
|
574
778
|
if (this.res.writableEnded) {
|
|
@@ -674,6 +878,7 @@ function createApp(options = {}) {
|
|
|
674
878
|
const stack = [];
|
|
675
879
|
const handler = createAppEventHandler(stack, options);
|
|
676
880
|
const app = {
|
|
881
|
+
// @ts-ignore
|
|
677
882
|
use: (arg1, arg2, arg3) => use(app, arg1, arg2, arg3),
|
|
678
883
|
handler,
|
|
679
884
|
stack,
|
|
@@ -910,4 +1115,4 @@ function createRouter(opts = {}) {
|
|
|
910
1115
|
return router;
|
|
911
1116
|
}
|
|
912
1117
|
|
|
913
|
-
export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getRouterParam, getRouterParams, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readRawBody, send, sendError, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, toEventHandler, toNodeListener, use, useBase, writeEarlyHints };
|
|
1118
|
+
export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, updateSession, use, useBase, useSession, writeEarlyHints };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "h3",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Tiny JavaScript Server",
|
|
5
5
|
"repository": "unjs/h3",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,32 +22,34 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"cookie-es": "^0.5.0",
|
|
24
24
|
"destr": "^1.2.2",
|
|
25
|
+
"iron-webcrypto": "^0.2.7",
|
|
25
26
|
"radix3": "^1.0.0",
|
|
26
|
-
"ufo": "^1.0.1"
|
|
27
|
+
"ufo": "^1.0.1",
|
|
28
|
+
"uncrypto": "^0.1.2"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
31
|
"0x": "^5.4.1",
|
|
30
|
-
"@types/express": "^4.17.
|
|
31
|
-
"@types/node": "^18.11.
|
|
32
|
+
"@types/express": "^4.17.16",
|
|
33
|
+
"@types/node": "^18.11.18",
|
|
32
34
|
"@types/supertest": "^2.0.12",
|
|
33
|
-
"@vitest/coverage-c8": "^0.
|
|
35
|
+
"@vitest/coverage-c8": "^0.28.3",
|
|
34
36
|
"autocannon": "^7.10.0",
|
|
35
|
-
"changelogen": "^0.4.
|
|
37
|
+
"changelogen": "^0.4.1",
|
|
36
38
|
"connect": "^3.7.0",
|
|
37
|
-
"eslint": "^8.
|
|
38
|
-
"eslint-config-unjs": "^0.0
|
|
39
|
+
"eslint": "^8.33.0",
|
|
40
|
+
"eslint-config-unjs": "^0.1.0",
|
|
39
41
|
"express": "^4.18.2",
|
|
40
42
|
"get-port": "^6.1.2",
|
|
41
|
-
"jiti": "^1.16.
|
|
42
|
-
"listhen": "^1.0.
|
|
43
|
+
"jiti": "^1.16.2",
|
|
44
|
+
"listhen": "^1.0.2",
|
|
43
45
|
"node-fetch-native": "^1.0.1",
|
|
44
|
-
"prettier": "^2.8.
|
|
46
|
+
"prettier": "^2.8.3",
|
|
45
47
|
"supertest": "^6.3.3",
|
|
46
|
-
"typescript": "^4.9.
|
|
47
|
-
"unbuild": "^1.
|
|
48
|
-
"vitest": "^0.
|
|
48
|
+
"typescript": "^4.9.5",
|
|
49
|
+
"unbuild": "^1.1.1",
|
|
50
|
+
"vitest": "^0.28.3"
|
|
49
51
|
},
|
|
50
|
-
"packageManager": "pnpm@7.
|
|
52
|
+
"packageManager": "pnpm@7.26.3",
|
|
51
53
|
"scripts": {
|
|
52
54
|
"build": "unbuild",
|
|
53
55
|
"dev": "vitest",
|