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 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 compasable utilities that accept `event` (from `eventHandler((event) => {})`) as their first argument. This has several performance benefits over injecting them to `event` or `app` instances and global middleware commonly used in Node.js frameworks such as Express, which Only required code is evaluated and bundled and rest of utils can be tree-shaken when not used.
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 h3 event utilities made by the community.
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
- function parseCookies(event) {
487
- return cookieEs.parse(event.node.req.headers.cookie || "");
488
- }
489
- function getCookie(event, name) {
490
- return parseCookies(event)[name];
491
- }
492
- function setCookie(event, name, value, serializeOptions) {
493
- const cookieStr = cookieEs.serialize(name, value, {
494
- path: "/",
495
- ...serializeOptions
496
- });
497
- appendHeader(event, "Set-Cookie", cookieStr);
498
- }
499
- function deleteCookie(event, name, serializeOptions) {
500
- setCookie(event, name, "", {
501
- ...serializeOptions,
502
- maxAge: 0
503
- });
504
- }
505
-
506
- const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
507
- const ignoredHeaders = /* @__PURE__ */ new Set([
508
- "transfer-encoding",
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 sendProxy(event, target, opts = {}) {
544
- const _fetch = opts.fetch || globalThis.fetch;
545
- if (!_fetch) {
546
- throw new Error(
547
- "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
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
- const response = await _fetch(target, {
551
- headers: opts.headers,
552
- ...opts.fetchOptions
553
- });
554
- event.node.res.statusCode = response.status;
555
- event.node.res.statusMessage = response.statusText;
556
- for (const [key, value] of response.headers.entries()) {
557
- if (key === "content-encoding") {
558
- continue;
559
- }
560
- if (key === "content-length") {
561
- continue;
562
- }
563
- event.node.res.setHeader(key, value);
564
- }
565
- try {
566
- if (response.body) {
567
- if (opts.sendStream === false) {
568
- const data = new Uint8Array(await response.arrayBuffer());
569
- event.node.res.end(data);
570
- } else {
571
- for await (const chunk of response.body) {
572
- event.node.res.write(chunk);
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
- function parseCookies(event) {
485
- return parse$1(event.node.req.headers.cookie || "");
486
- }
487
- function getCookie(event, name) {
488
- return parseCookies(event)[name];
489
- }
490
- function setCookie(event, name, value, serializeOptions) {
491
- const cookieStr = serialize(name, value, {
492
- path: "/",
493
- ...serializeOptions
494
- });
495
- appendHeader(event, "Set-Cookie", cookieStr);
496
- }
497
- function deleteCookie(event, name, serializeOptions) {
498
- setCookie(event, name, "", {
499
- ...serializeOptions,
500
- maxAge: 0
501
- });
502
- }
503
-
504
- const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
505
- const ignoredHeaders = /* @__PURE__ */ new Set([
506
- "transfer-encoding",
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 sendProxy(event, target, opts = {}) {
542
- const _fetch = opts.fetch || globalThis.fetch;
543
- if (!_fetch) {
544
- throw new Error(
545
- "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
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
- const response = await _fetch(target, {
549
- headers: opts.headers,
550
- ...opts.fetchOptions
551
- });
552
- event.node.res.statusCode = response.status;
553
- event.node.res.statusMessage = response.statusText;
554
- for (const [key, value] of response.headers.entries()) {
555
- if (key === "content-encoding") {
556
- continue;
557
- }
558
- if (key === "content-length") {
559
- continue;
560
- }
561
- event.node.res.setHeader(key, value);
562
- }
563
- try {
564
- if (response.body) {
565
- if (opts.sendStream === false) {
566
- const data = new Uint8Array(await response.arrayBuffer());
567
- event.node.res.end(data);
568
- } else {
569
- for await (const chunk of response.body) {
570
- event.node.res.write(chunk);
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.1.0",
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.2",
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.32.0",
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.4",
48
+ "typescript": "^4.9.5",
47
49
  "unbuild": "^1.1.1",
48
- "vitest": "^0.28.2"
50
+ "vitest": "^0.28.3"
49
51
  },
50
- "packageManager": "pnpm@7.26.0",
52
+ "packageManager": "pnpm@7.26.3",
51
53
  "scripts": {
52
54
  "build": "unbuild",
53
55
  "dev": "vitest",