h3 1.3.0 → 1.4.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
@@ -155,10 +155,12 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler
155
155
  - `getResponseStatus(event)`
156
156
  - `getResponseStatusText(event)`
157
157
  - `readMultipartFormData(event)`
158
- - `useSession(event, { password, name?, cookie?, seal?, crypto? })`
159
- - `getSession(event, { password, name?, cookie?, seal?, crypto? })`
160
- - `updateSession(event, { password, name?, cookie?, seal?, crypto? }), update)`
161
- - `clearSession(event, { password, name?, cookie?, seal?, crypto? }))`
158
+ - `useSession(event, config = { password, maxAge?, name?, cookie?, seal?, crypto? })`
159
+ - `getSession(event, config)`
160
+ - `updateSession(event, config, update)`
161
+ - `clearSession(event, config)`
162
+ - `sealSession(event, config)`
163
+ - `unsealSession(event, config, sealed)`
162
164
 
163
165
  👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).
164
166
 
package/dist/index.cjs CHANGED
@@ -640,21 +640,35 @@ async function getSession(event, config) {
640
640
  if (event.context.sessions[sessionName]) {
641
641
  return event.context.sessions[sessionName];
642
642
  }
643
- const session = { id: "", data: /* @__PURE__ */ Object.create(null) };
643
+ const session = {
644
+ id: "",
645
+ createdAt: 0,
646
+ data: /* @__PURE__ */ Object.create(null)
647
+ };
644
648
  event.context.sessions[sessionName] = session;
645
- const reqCookie = getCookie(event, sessionName);
646
- if (!reqCookie) {
647
- session.id = (config.crypto || crypto).randomUUID();
648
- await updateSession(event, config);
649
- } else {
650
- const unsealed = await ironWebcrypto.unseal(
651
- config.crypto || crypto,
652
- reqCookie,
653
- config.password,
654
- config.seal || ironWebcrypto.defaults
649
+ let sealedSession;
650
+ if (config.sessionHeader !== false) {
651
+ const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
652
+ const headerValue = event.node.req.headers[headerName];
653
+ if (typeof headerValue === "string") {
654
+ sealedSession = headerValue;
655
+ }
656
+ }
657
+ if (!sealedSession) {
658
+ sealedSession = getCookie(event, sessionName);
659
+ }
660
+ if (sealedSession) {
661
+ const unsealed = await unsealSession(event, config, sealedSession).catch(
662
+ () => {
663
+ }
655
664
  );
656
665
  Object.assign(session, unsealed);
657
666
  }
667
+ if (!session.id) {
668
+ session.id = (config.crypto || crypto).randomUUID();
669
+ session.createdAt = Date.now();
670
+ await updateSession(event, config);
671
+ }
658
672
  return session;
659
673
  }
660
674
  async function updateSession(event, config, update) {
@@ -666,21 +680,54 @@ async function updateSession(event, config, update) {
666
680
  if (update) {
667
681
  Object.assign(session.data, update);
668
682
  }
669
- const sealed = await ironWebcrypto.seal(
683
+ if (config.cookie !== false) {
684
+ const sealed = await sealSession(event, config);
685
+ setCookie(event, sessionName, sealed, {
686
+ ...DEFAULT_COOKIE,
687
+ expires: config.maxAge ? new Date(session.createdAt + config.maxAge * 1e3) : void 0,
688
+ ...config.cookie
689
+ });
690
+ }
691
+ return session;
692
+ }
693
+ async function sealSession(event, config) {
694
+ const sessionName = config.name || DEFAULT_NAME;
695
+ const session = event.context.sessions?.[sessionName] || await getSession(event, config);
696
+ const sealed = await ironWebcrypto.seal(config.crypto || crypto, session, config.password, {
697
+ ...ironWebcrypto.defaults,
698
+ ttl: config.maxAge ? config.maxAge * 1e3 : 0,
699
+ ...config.seal
700
+ });
701
+ return sealed;
702
+ }
703
+ async function unsealSession(_event, config, sealed) {
704
+ const unsealed = await ironWebcrypto.unseal(
670
705
  config.crypto || crypto,
671
- session,
706
+ sealed,
672
707
  config.password,
673
- config.seal || ironWebcrypto.defaults
708
+ {
709
+ ...ironWebcrypto.defaults,
710
+ ttl: config.maxAge ? config.maxAge * 1e3 : 0,
711
+ ...config.seal
712
+ }
674
713
  );
675
- setCookie(event, sessionName, sealed, config.cookie || DEFAULT_COOKIE);
676
- return session;
714
+ if (config.maxAge) {
715
+ const age = Date.now() - (unsealed.createdAt || Number.NEGATIVE_INFINITY);
716
+ if (age > config.maxAge * 1e3) {
717
+ throw new Error("Session expired!");
718
+ }
719
+ }
720
+ return unsealed;
677
721
  }
678
722
  async function clearSession(event, config) {
679
723
  const sessionName = config.name || DEFAULT_NAME;
680
724
  if (event.context.sessions?.[sessionName]) {
681
725
  delete event.context.sessions[sessionName];
682
726
  }
683
- await setCookie(event, sessionName, "", config.cookie || DEFAULT_COOKIE);
727
+ await setCookie(event, sessionName, "", {
728
+ ...DEFAULT_COOKIE,
729
+ ...config.cookie
730
+ });
684
731
  }
685
732
 
686
733
  class H3Headers {
@@ -1187,6 +1234,7 @@ exports.proxyRequest = proxyRequest;
1187
1234
  exports.readBody = readBody;
1188
1235
  exports.readMultipartFormData = readMultipartFormData;
1189
1236
  exports.readRawBody = readRawBody;
1237
+ exports.sealSession = sealSession;
1190
1238
  exports.send = send;
1191
1239
  exports.sendError = sendError;
1192
1240
  exports.sendNoContent = sendNoContent;
@@ -1201,6 +1249,7 @@ exports.setResponseHeaders = setResponseHeaders;
1201
1249
  exports.setResponseStatus = setResponseStatus;
1202
1250
  exports.toEventHandler = toEventHandler;
1203
1251
  exports.toNodeListener = toNodeListener;
1252
+ exports.unsealSession = unsealSession;
1204
1253
  exports.updateSession = updateSession;
1205
1254
  exports.use = use;
1206
1255
  exports.useBase = useBase;
package/dist/index.d.ts CHANGED
@@ -8,12 +8,20 @@ type SessionDataT = Record<string, any>;
8
8
  type SessionData<T extends SessionDataT = SessionDataT> = T;
9
9
  interface Session<T extends SessionDataT = SessionDataT> {
10
10
  id: string;
11
+ createdAt: number;
11
12
  data: SessionData<T>;
12
13
  }
13
14
  interface SessionConfig {
15
+ /** Private key used to encrypt session tokens */
14
16
  password: string;
17
+ /** Session expiration time in seconds */
18
+ maxAge?: number;
19
+ /** default is h3 */
15
20
  name?: string;
16
- cookie?: CookieSerializeOptions;
21
+ /** Default is secure, httpOnly, / */
22
+ cookie?: false | CookieSerializeOptions;
23
+ /** Default is x-h3-session / x-{name}-session */
24
+ sessionHeader?: false | string;
17
25
  seal?: SealOptions;
18
26
  crypto?: Crypto;
19
27
  }
@@ -26,6 +34,8 @@ declare function useSession<T extends SessionDataT = SessionDataT>(event: H3Even
26
34
  declare function getSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfig): Promise<Session<T>>;
27
35
  type SessionUpdate<T extends SessionDataT = SessionDataT> = Partial<SessionData<T>> | ((oldData: SessionData<T>) => Partial<SessionData<T>> | undefined);
28
36
  declare function updateSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfig, update?: SessionUpdate<T>): Promise<Session<T>>;
37
+ declare function sealSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfig): Promise<string>;
38
+ declare function unsealSession(_event: H3Event, config: SessionConfig, sealed: string): Promise<Partial<Session<SessionDataT>>>;
29
39
  declare function clearSession(event: H3Event, config: SessionConfig): Promise<void>;
30
40
 
31
41
  type HTTPMethod = "GET" | "HEAD" | "PATCH" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE";
@@ -353,4 +363,4 @@ interface CreateRouterOptions {
353
363
  }
354
364
  declare function createRouter(opts?: CreateRouterOptions): Router;
355
365
 
356
- 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, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, 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 };
366
+ 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, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ 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
5
  import crypto from 'uncrypto';
6
- import { unseal, defaults, seal } from 'iron-webcrypto';
6
+ import { seal, defaults, unseal } from 'iron-webcrypto';
7
7
 
8
8
  function useBase(base, handler) {
9
9
  base = withoutTrailingSlash(base);
@@ -638,21 +638,35 @@ async function getSession(event, config) {
638
638
  if (event.context.sessions[sessionName]) {
639
639
  return event.context.sessions[sessionName];
640
640
  }
641
- const session = { id: "", data: /* @__PURE__ */ Object.create(null) };
641
+ const session = {
642
+ id: "",
643
+ createdAt: 0,
644
+ data: /* @__PURE__ */ Object.create(null)
645
+ };
642
646
  event.context.sessions[sessionName] = session;
643
- const reqCookie = getCookie(event, sessionName);
644
- if (!reqCookie) {
645
- session.id = (config.crypto || crypto).randomUUID();
646
- await updateSession(event, config);
647
- } else {
648
- const unsealed = await unseal(
649
- config.crypto || crypto,
650
- reqCookie,
651
- config.password,
652
- config.seal || defaults
647
+ let sealedSession;
648
+ if (config.sessionHeader !== false) {
649
+ const headerName = typeof config.sessionHeader === "string" ? config.sessionHeader.toLowerCase() : `x-${sessionName.toLowerCase()}-session`;
650
+ const headerValue = event.node.req.headers[headerName];
651
+ if (typeof headerValue === "string") {
652
+ sealedSession = headerValue;
653
+ }
654
+ }
655
+ if (!sealedSession) {
656
+ sealedSession = getCookie(event, sessionName);
657
+ }
658
+ if (sealedSession) {
659
+ const unsealed = await unsealSession(event, config, sealedSession).catch(
660
+ () => {
661
+ }
653
662
  );
654
663
  Object.assign(session, unsealed);
655
664
  }
665
+ if (!session.id) {
666
+ session.id = (config.crypto || crypto).randomUUID();
667
+ session.createdAt = Date.now();
668
+ await updateSession(event, config);
669
+ }
656
670
  return session;
657
671
  }
658
672
  async function updateSession(event, config, update) {
@@ -664,21 +678,54 @@ async function updateSession(event, config, update) {
664
678
  if (update) {
665
679
  Object.assign(session.data, update);
666
680
  }
667
- const sealed = await seal(
681
+ if (config.cookie !== false) {
682
+ const sealed = await sealSession(event, config);
683
+ setCookie(event, sessionName, sealed, {
684
+ ...DEFAULT_COOKIE,
685
+ expires: config.maxAge ? new Date(session.createdAt + config.maxAge * 1e3) : void 0,
686
+ ...config.cookie
687
+ });
688
+ }
689
+ return session;
690
+ }
691
+ async function sealSession(event, config) {
692
+ const sessionName = config.name || DEFAULT_NAME;
693
+ const session = event.context.sessions?.[sessionName] || await getSession(event, config);
694
+ const sealed = await seal(config.crypto || crypto, session, config.password, {
695
+ ...defaults,
696
+ ttl: config.maxAge ? config.maxAge * 1e3 : 0,
697
+ ...config.seal
698
+ });
699
+ return sealed;
700
+ }
701
+ async function unsealSession(_event, config, sealed) {
702
+ const unsealed = await unseal(
668
703
  config.crypto || crypto,
669
- session,
704
+ sealed,
670
705
  config.password,
671
- config.seal || defaults
706
+ {
707
+ ...defaults,
708
+ ttl: config.maxAge ? config.maxAge * 1e3 : 0,
709
+ ...config.seal
710
+ }
672
711
  );
673
- setCookie(event, sessionName, sealed, config.cookie || DEFAULT_COOKIE);
674
- return session;
712
+ if (config.maxAge) {
713
+ const age = Date.now() - (unsealed.createdAt || Number.NEGATIVE_INFINITY);
714
+ if (age > config.maxAge * 1e3) {
715
+ throw new Error("Session expired!");
716
+ }
717
+ }
718
+ return unsealed;
675
719
  }
676
720
  async function clearSession(event, config) {
677
721
  const sessionName = config.name || DEFAULT_NAME;
678
722
  if (event.context.sessions?.[sessionName]) {
679
723
  delete event.context.sessions[sessionName];
680
724
  }
681
- await setCookie(event, sessionName, "", config.cookie || DEFAULT_COOKIE);
725
+ await setCookie(event, sessionName, "", {
726
+ ...DEFAULT_COOKIE,
727
+ ...config.cookie
728
+ });
682
729
  }
683
730
 
684
731
  class H3Headers {
@@ -1130,4 +1177,4 @@ function createRouter(opts = {}) {
1130
1177
  return router;
1131
1178
  }
1132
1179
 
1133
- 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, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, 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 };
1180
+ 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, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h3",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Tiny JavaScript Server",
5
5
  "repository": "unjs/h3",
6
6
  "license": "MIT",