h3 1.1.0 → 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 +7 -3
- package/dist/index.cjs +182 -90
- package/dist/index.d.ts +29 -2
- package/dist/index.mjs +179 -91
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ const router = createRouter()
|
|
|
90
90
|
app.use(router);
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
**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.
|
|
94
94
|
|
|
95
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).
|
|
96
96
|
|
|
@@ -119,7 +119,7 @@ app.use('/big', () => import('./big-handler'), { lazy: true })
|
|
|
119
119
|
|
|
120
120
|
## Utilities
|
|
121
121
|
|
|
122
|
-
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.
|
|
123
123
|
|
|
124
124
|
### Built-in
|
|
125
125
|
|
|
@@ -153,12 +153,16 @@ H3 has concept of compasable utilities that accept `event` (from `eventHandler((
|
|
|
153
153
|
- `getResponseStatus(event)`
|
|
154
154
|
- `getResponseStatusText(event)`
|
|
155
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? }))`
|
|
156
160
|
|
|
157
161
|
👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).
|
|
158
162
|
|
|
159
163
|
## Community Packages
|
|
160
164
|
|
|
161
|
-
You can use more
|
|
165
|
+
You can use more H3 event utilities made by the community.
|
|
162
166
|
|
|
163
167
|
Please check their READMEs for more details.
|
|
164
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);
|
|
@@ -351,6 +353,111 @@ const MIMES = {
|
|
|
351
353
|
json: "application/json"
|
|
352
354
|
};
|
|
353
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
|
+
|
|
354
461
|
const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
|
|
355
462
|
function send(event, data, type) {
|
|
356
463
|
if (type) {
|
|
@@ -483,101 +590,82 @@ ${header}: ${value}`;
|
|
|
483
590
|
}
|
|
484
591
|
}
|
|
485
592
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
function
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
"connection",
|
|
510
|
-
"keep-alive",
|
|
511
|
-
"upgrade",
|
|
512
|
-
"expect"
|
|
513
|
-
]);
|
|
514
|
-
async function proxyRequest(event, target, opts = {}) {
|
|
515
|
-
const method = getMethod(event);
|
|
516
|
-
let body;
|
|
517
|
-
if (PayloadMethods.has(method)) {
|
|
518
|
-
body = await readRawBody(event).catch(() => void 0);
|
|
519
|
-
}
|
|
520
|
-
const headers = /* @__PURE__ */ Object.create(null);
|
|
521
|
-
const reqHeaders = getRequestHeaders(event);
|
|
522
|
-
for (const name in reqHeaders) {
|
|
523
|
-
if (!ignoredHeaders.has(name)) {
|
|
524
|
-
headers[name] = reqHeaders[name];
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
if (opts.fetchOptions?.headers) {
|
|
528
|
-
Object.assign(headers, opts.fetchOptions.headers);
|
|
529
|
-
}
|
|
530
|
-
if (opts.headers) {
|
|
531
|
-
Object.assign(headers, opts.headers);
|
|
532
|
-
}
|
|
533
|
-
return sendProxy(event, target, {
|
|
534
|
-
...opts,
|
|
535
|
-
fetchOptions: {
|
|
536
|
-
headers,
|
|
537
|
-
method,
|
|
538
|
-
body,
|
|
539
|
-
...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;
|
|
540
616
|
}
|
|
541
|
-
}
|
|
617
|
+
};
|
|
618
|
+
return sessionManager;
|
|
542
619
|
}
|
|
543
|
-
async function
|
|
544
|
-
const
|
|
545
|
-
if (!
|
|
546
|
-
|
|
547
|
-
|
|
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
|
|
548
640
|
);
|
|
641
|
+
Object.assign(session, unsealed);
|
|
549
642
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
event.
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
event.node.res.end();
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
} catch (error) {
|
|
578
|
-
event.node.res.end();
|
|
579
|
-
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];
|
|
580
667
|
}
|
|
668
|
+
await setCookie(event, sessionName, "", config.cookie || DEFAULT_COOKIE);
|
|
581
669
|
}
|
|
582
670
|
|
|
583
671
|
class H3Headers {
|
|
@@ -1040,6 +1128,7 @@ exports.appendResponseHeader = appendResponseHeader;
|
|
|
1040
1128
|
exports.appendResponseHeaders = appendResponseHeaders;
|
|
1041
1129
|
exports.assertMethod = assertMethod;
|
|
1042
1130
|
exports.callNodeListener = callNodeListener;
|
|
1131
|
+
exports.clearSession = clearSession;
|
|
1043
1132
|
exports.createApp = createApp;
|
|
1044
1133
|
exports.createAppEventHandler = createAppEventHandler;
|
|
1045
1134
|
exports.createError = createError;
|
|
@@ -1067,6 +1156,7 @@ exports.getResponseStatus = getResponseStatus;
|
|
|
1067
1156
|
exports.getResponseStatusText = getResponseStatusText;
|
|
1068
1157
|
exports.getRouterParam = getRouterParam;
|
|
1069
1158
|
exports.getRouterParams = getRouterParams;
|
|
1159
|
+
exports.getSession = getSession;
|
|
1070
1160
|
exports.handleCacheHeaders = handleCacheHeaders;
|
|
1071
1161
|
exports.isError = isError;
|
|
1072
1162
|
exports.isEvent = isEvent;
|
|
@@ -1094,6 +1184,8 @@ exports.setResponseHeaders = setResponseHeaders;
|
|
|
1094
1184
|
exports.setResponseStatus = setResponseStatus;
|
|
1095
1185
|
exports.toEventHandler = toEventHandler;
|
|
1096
1186
|
exports.toNodeListener = toNodeListener;
|
|
1187
|
+
exports.updateSession = updateSession;
|
|
1097
1188
|
exports.use = use;
|
|
1098
1189
|
exports.useBase = useBase;
|
|
1190
|
+
exports.useSession = useSession;
|
|
1099
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> {
|
|
@@ -322,4 +349,4 @@ interface CreateRouterOptions {
|
|
|
322
349
|
}
|
|
323
350
|
declare function createRouter(opts?: CreateRouterOptions): Router;
|
|
324
351
|
|
|
325
|
-
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, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, 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, 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
|
@@ -2,6 +2,8 @@ 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
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);
|
|
@@ -349,6 +351,111 @@ const MIMES = {
|
|
|
349
351
|
json: "application/json"
|
|
350
352
|
};
|
|
351
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
|
+
|
|
352
459
|
const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
|
|
353
460
|
function send(event, data, type) {
|
|
354
461
|
if (type) {
|
|
@@ -481,101 +588,82 @@ ${header}: ${value}`;
|
|
|
481
588
|
}
|
|
482
589
|
}
|
|
483
590
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
function
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
"connection",
|
|
508
|
-
"keep-alive",
|
|
509
|
-
"upgrade",
|
|
510
|
-
"expect"
|
|
511
|
-
]);
|
|
512
|
-
async function proxyRequest(event, target, opts = {}) {
|
|
513
|
-
const method = getMethod(event);
|
|
514
|
-
let body;
|
|
515
|
-
if (PayloadMethods.has(method)) {
|
|
516
|
-
body = await readRawBody(event).catch(() => void 0);
|
|
517
|
-
}
|
|
518
|
-
const headers = /* @__PURE__ */ Object.create(null);
|
|
519
|
-
const reqHeaders = getRequestHeaders(event);
|
|
520
|
-
for (const name in reqHeaders) {
|
|
521
|
-
if (!ignoredHeaders.has(name)) {
|
|
522
|
-
headers[name] = reqHeaders[name];
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (opts.fetchOptions?.headers) {
|
|
526
|
-
Object.assign(headers, opts.fetchOptions.headers);
|
|
527
|
-
}
|
|
528
|
-
if (opts.headers) {
|
|
529
|
-
Object.assign(headers, opts.headers);
|
|
530
|
-
}
|
|
531
|
-
return sendProxy(event, target, {
|
|
532
|
-
...opts,
|
|
533
|
-
fetchOptions: {
|
|
534
|
-
headers,
|
|
535
|
-
method,
|
|
536
|
-
body,
|
|
537
|
-
...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;
|
|
538
614
|
}
|
|
539
|
-
}
|
|
615
|
+
};
|
|
616
|
+
return sessionManager;
|
|
540
617
|
}
|
|
541
|
-
async function
|
|
542
|
-
const
|
|
543
|
-
if (!
|
|
544
|
-
|
|
545
|
-
|
|
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
|
|
546
638
|
);
|
|
639
|
+
Object.assign(session, unsealed);
|
|
547
640
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
event.
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
event.node.res.end();
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
} catch (error) {
|
|
576
|
-
event.node.res.end();
|
|
577
|
-
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];
|
|
578
665
|
}
|
|
666
|
+
await setCookie(event, sessionName, "", config.cookie || DEFAULT_COOKIE);
|
|
579
667
|
}
|
|
580
668
|
|
|
581
669
|
class H3Headers {
|
|
@@ -1027,4 +1115,4 @@ function createRouter(opts = {}) {
|
|
|
1027
1115
|
return router;
|
|
1028
1116
|
}
|
|
1029
1117
|
|
|
1030
|
-
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, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, 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, 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.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Tiny JavaScript Server",
|
|
5
5
|
"repository": "unjs/h3",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,19 +22,21 @@
|
|
|
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
32
|
"@types/express": "^4.17.16",
|
|
31
33
|
"@types/node": "^18.11.18",
|
|
32
34
|
"@types/supertest": "^2.0.12",
|
|
33
|
-
"@vitest/coverage-c8": "^0.28.
|
|
35
|
+
"@vitest/coverage-c8": "^0.28.3",
|
|
34
36
|
"autocannon": "^7.10.0",
|
|
35
37
|
"changelogen": "^0.4.1",
|
|
36
38
|
"connect": "^3.7.0",
|
|
37
|
-
"eslint": "^8.
|
|
39
|
+
"eslint": "^8.33.0",
|
|
38
40
|
"eslint-config-unjs": "^0.1.0",
|
|
39
41
|
"express": "^4.18.2",
|
|
40
42
|
"get-port": "^6.1.2",
|
|
@@ -43,11 +45,11 @@
|
|
|
43
45
|
"node-fetch-native": "^1.0.1",
|
|
44
46
|
"prettier": "^2.8.3",
|
|
45
47
|
"supertest": "^6.3.3",
|
|
46
|
-
"typescript": "^4.9.
|
|
48
|
+
"typescript": "^4.9.5",
|
|
47
49
|
"unbuild": "^1.1.1",
|
|
48
|
-
"vitest": "^0.28.
|
|
50
|
+
"vitest": "^0.28.3"
|
|
49
51
|
},
|
|
50
|
-
"packageManager": "pnpm@7.26.
|
|
52
|
+
"packageManager": "pnpm@7.26.3",
|
|
51
53
|
"scripts": {
|
|
52
54
|
"build": "unbuild",
|
|
53
55
|
"dev": "vitest",
|