@scayle/h3-session 0.4.2 → 0.5.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @scayle/h3-session
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - When a sessionID exists, but there is no session data, re-use the sessionID instead of generating both new data and a new ID. This enables proper functioning of the `saveUnitialized` configuration.
8
+ - Fix the `clear()` method on `UnstorageSessionStore` not clearing sessions
9
+
10
+ ## 0.5.0
11
+
12
+ ### Minor Changes
13
+
14
+ - Cookie metadata is no longer saved in the session store. Previously, `h3-session` stored a copy of the cookie settings in the session store alongside the session data. This has been changed and now only the session data itself is stored. The cookie settings can still be read and manipulated from the `Session` object.
15
+
16
+ ### Patch Changes
17
+
18
+ - Removed dependency `zod@^3.23.8`
19
+ - Replace regex used for cookie parsing
20
+
3
21
  ## 0.4.2
4
22
 
5
23
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -41,7 +41,7 @@ declare class UnstorageSessionStore implements SessionStore {
41
41
  * Get the session with the specified session ID
42
42
  * @param sid the un-prefixed session ID
43
43
  */
44
- get(sid: string): Promise<RawSession | undefined>;
44
+ get(sid: string): Promise<SessionDataT | undefined>;
45
45
  /**
46
46
  * Save the session with the specified session ID
47
47
  * If the session has expired (has a TTL <= 0) it is deleted.
@@ -62,7 +62,7 @@ declare class UnstorageSessionStore implements SessionStore {
62
62
  /**
63
63
  * Fetch all saved sessions.
64
64
  */
65
- all(): Promise<RawSession[]>;
65
+ all(): Promise<SessionDataT[]>;
66
66
  /**
67
67
  * Returns the number of saved sessions
68
68
  */
@@ -84,9 +84,7 @@ type SessionCookie = SessionCookieOptions & {
84
84
  };
85
85
  interface SessionDataT {
86
86
  }
87
- type RawSession = SessionDataT & {
88
- cookie?: SessionCookieOptions;
89
- };
87
+ type RawSession = SessionDataT;
90
88
  interface SessionStore {
91
89
  all?: () => Promise<RawSession[]>;
92
90
  destroy: (sid: string) => Promise<void>;
@@ -128,6 +126,13 @@ declare module 'h3' {
128
126
  * @param secrets an array of secret strings to verify with
129
127
  */
130
128
  declare function unsignCookie(value: string, secrets: string[]): Promise<string | false>;
129
+ /**
130
+ * Creates a signed cookie value in the format `s:[value].[signature]`
131
+ * @param value
132
+ * @param secret
133
+ */
134
+ declare function signCookie(value: string, secret: string): Promise<string>;
135
+ declare function validateConfig(config: H3SessionOptions): void;
131
136
  /**
132
137
  * Attach a session to an H3Event
133
138
  * @param event
@@ -135,4 +140,4 @@ declare function unsignCookie(value: string, secrets: string[]): Promise<string
135
140
  */
136
141
  declare function useSession(event: H3Event, config: H3SessionOptions): Promise<void>;
137
142
 
138
- export { type RawSession, Session, type SessionCookie, type SessionCookieOptions, type SessionDataT, type SessionMethods, type SessionStore, UnstorageSessionStore, unsignCookie, useSession };
143
+ export { type H3SessionOptions, type RawSession, Session, type SessionCookie, type SessionCookieOptions, type SessionDataT, type SessionMethods, type SessionStore, UnstorageSessionStore, signCookie, unsignCookie, useSession, validateConfig };
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ declare class UnstorageSessionStore implements SessionStore {
41
41
  * Get the session with the specified session ID
42
42
  * @param sid the un-prefixed session ID
43
43
  */
44
- get(sid: string): Promise<RawSession | undefined>;
44
+ get(sid: string): Promise<SessionDataT | undefined>;
45
45
  /**
46
46
  * Save the session with the specified session ID
47
47
  * If the session has expired (has a TTL <= 0) it is deleted.
@@ -62,7 +62,7 @@ declare class UnstorageSessionStore implements SessionStore {
62
62
  /**
63
63
  * Fetch all saved sessions.
64
64
  */
65
- all(): Promise<RawSession[]>;
65
+ all(): Promise<SessionDataT[]>;
66
66
  /**
67
67
  * Returns the number of saved sessions
68
68
  */
@@ -84,9 +84,7 @@ type SessionCookie = SessionCookieOptions & {
84
84
  };
85
85
  interface SessionDataT {
86
86
  }
87
- type RawSession = SessionDataT & {
88
- cookie?: SessionCookieOptions;
89
- };
87
+ type RawSession = SessionDataT;
90
88
  interface SessionStore {
91
89
  all?: () => Promise<RawSession[]>;
92
90
  destroy: (sid: string) => Promise<void>;
@@ -128,6 +126,13 @@ declare module 'h3' {
128
126
  * @param secrets an array of secret strings to verify with
129
127
  */
130
128
  declare function unsignCookie(value: string, secrets: string[]): Promise<string | false>;
129
+ /**
130
+ * Creates a signed cookie value in the format `s:[value].[signature]`
131
+ * @param value
132
+ * @param secret
133
+ */
134
+ declare function signCookie(value: string, secret: string): Promise<string>;
135
+ declare function validateConfig(config: H3SessionOptions): void;
131
136
  /**
132
137
  * Attach a session to an H3Event
133
138
  * @param event
@@ -135,4 +140,4 @@ declare function unsignCookie(value: string, secrets: string[]): Promise<string
135
140
  */
136
141
  declare function useSession(event: H3Event, config: H3SessionOptions): Promise<void>;
137
142
 
138
- export { type RawSession, Session, type SessionCookie, type SessionCookieOptions, type SessionDataT, type SessionMethods, type SessionStore, UnstorageSessionStore, unsignCookie, useSession };
143
+ export { type H3SessionOptions, type RawSession, Session, type SessionCookie, type SessionCookieOptions, type SessionDataT, type SessionMethods, type SessionStore, UnstorageSessionStore, signCookie, unsignCookie, useSession, validateConfig };
package/dist/index.mjs CHANGED
@@ -2,7 +2,6 @@ import { Buffer } from 'node:buffer';
2
2
  import { getCookie, setCookie } from 'h3';
3
3
  import { defu } from 'defu';
4
4
  import { subtle, randomUUID } from 'uncrypto';
5
- import { z } from 'zod';
6
5
 
7
6
  class Session {
8
7
  #id;
@@ -21,7 +20,7 @@ class Session {
21
20
  return this.#id;
22
21
  }
23
22
  async save() {
24
- await this.#store.set(this.#id, { ...this.data, cookie: this.cookie });
23
+ await this.#store.set(this.#id, this.data);
25
24
  }
26
25
  async reload() {
27
26
  this.data = await this.#store.get(this.#id) ?? (await this.#generate()).data;
@@ -39,15 +38,6 @@ class Session {
39
38
  }
40
39
  }
41
40
 
42
- const CookieSchema = z.object({
43
- domain: z.string().optional(),
44
- expires: z.coerce.date().optional(),
45
- httpOnly: z.boolean().optional(),
46
- maxAge: z.number().optional(),
47
- path: z.string().optional(),
48
- sameSite: z.enum(["lax", "none", "strict"]).or(z.boolean()).optional(),
49
- secure: z.boolean().optional()
50
- });
51
41
  class UnstorageSessionStore {
52
42
  storage;
53
43
  prefix;
@@ -70,13 +60,6 @@ class UnstorageSessionStore {
70
60
  */
71
61
  async get(sid) {
72
62
  const item = await this.storage.getItem(this.getKey(sid));
73
- if (item && item.cookie) {
74
- try {
75
- item.cookie = CookieSchema.parse(item.cookie);
76
- } catch {
77
- delete item.cookie;
78
- }
79
- }
80
63
  return item ?? void 0;
81
64
  }
82
65
  /**
@@ -105,7 +88,8 @@ class UnstorageSessionStore {
105
88
  * Remove all saved sessions
106
89
  */
107
90
  async clear() {
108
- return await this.storage.clear(this.prefix);
91
+ const keys = await this.getAllKeys();
92
+ await Promise.all(keys.map((k) => this.storage.removeItem(k)));
109
93
  }
110
94
  /**
111
95
  * Fetch all saved sessions.
@@ -144,12 +128,26 @@ class UnstorageSessionStore {
144
128
  }
145
129
  }
146
130
 
131
+ const signatureLength = 43;
132
+ function parseCookieValue(value) {
133
+ if (value.length < signatureLength + 3) {
134
+ return void 0;
135
+ }
136
+ const separatorIndex = value.length - signatureLength - 1;
137
+ if (value.charAt(0) !== "s" || value.charAt(1) !== ":" || value.charAt(separatorIndex) !== ".") {
138
+ return void 0;
139
+ }
140
+ return [
141
+ value.substring(2, separatorIndex),
142
+ value.substring(separatorIndex + 1)
143
+ ];
144
+ }
147
145
  async function unsignCookie(value, secrets) {
148
- const matches = /s:([^.]*)\.(.*)/.exec(value);
146
+ const matches = parseCookieValue(value);
149
147
  if (!matches) {
150
148
  return false;
151
149
  }
152
- const [, message, signature] = matches;
150
+ const [message, signature] = matches;
153
151
  const encoder = new TextEncoder();
154
152
  const messageUint8Array = encoder.encode(message);
155
153
  const signatureUint8Array = Uint8Array.from(Buffer.from(signature, "base64"));
@@ -175,6 +173,12 @@ async function unsignCookie(value, secrets) {
175
173
  return false;
176
174
  }
177
175
  async function signCookie(value, secret) {
176
+ if (!value) {
177
+ throw new Error("[h3-session] No value was provided!");
178
+ }
179
+ if (!secret) {
180
+ throw new Error("[h3-session] No secret was provided!");
181
+ }
178
182
  const encoder = new TextEncoder();
179
183
  const messageUint8Array = encoder.encode(value);
180
184
  const keyUint8Array = encoder.encode(secret);
@@ -259,18 +263,26 @@ async function useSession(event, config) {
259
263
  }
260
264
  async function createExistingSession(id, data) {
261
265
  const cookie = await createSessionCookie(id);
262
- if (store.touch) {
266
+ if (store.touch && data) {
263
267
  await store.touch(id, data);
264
268
  }
265
- event.context.session = new Session(id, data, store, generate, cookie);
269
+ event.context.session = new Session(
270
+ id,
271
+ data ?? sessionConfig.generate(),
272
+ store,
273
+ generate,
274
+ cookie
275
+ );
266
276
  event.context.sessionId = id;
267
277
  }
268
- if (!sessionId || !sessionData) {
278
+ if (!sessionId) {
269
279
  await createNewSession();
280
+ } else if (!sessionData) {
281
+ await createExistingSession(sessionId);
270
282
  } else {
271
283
  await createExistingSession(sessionId, sessionData);
272
284
  }
273
285
  event.context.sessionStore = store;
274
286
  }
275
287
 
276
- export { Session, UnstorageSessionStore, unsignCookie, useSession };
288
+ export { Session, UnstorageSessionStore, signCookie, unsignCookie, useSession, validateConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/h3-session",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "description": "Persistent sessions for h3",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -22,7 +22,11 @@
22
22
  "format": "dprint check",
23
23
  "format:fix": "dprint fmt",
24
24
  "lint": "eslint . --format gitlab",
25
- "lint:fix": "eslint . --fix"
25
+ "lint:fix": "eslint . --fix",
26
+ "test": "vitest run",
27
+ "test:ci": "vitest --run --coverage --reporter=default --reporter=junit --outputFile=./junit.xml",
28
+ "test:watch": "vitest watch",
29
+ "typecheck": "tsc --noEmit -p tsconfig.json"
26
30
  },
27
31
  "peerDependencies": {
28
32
  "h3": "^1.10.0"
@@ -30,18 +34,19 @@
30
34
  "dependencies": {
31
35
  "defu": "^6.1.4",
32
36
  "uncrypto": "^0.1.3",
33
- "unstorage": "^1.10.2",
34
- "zod": "^3.23.8"
37
+ "unstorage": "^1.10.2"
35
38
  },
36
39
  "devDependencies": {
37
- "@arethetypeswrong/cli": "0.17.1",
40
+ "@arethetypeswrong/cli": "0.17.2",
38
41
  "@scayle/eslint-config-storefront": "4.4.0",
39
- "dprint": "0.47.6",
42
+ "cookie-es": "1.2.2",
43
+ "dprint": "0.48.0",
40
44
  "eslint": "9.17.0",
41
45
  "eslint-formatter-gitlab": "5.1.0",
42
46
  "h3": "1.13.0",
43
- "typescript": "5.6.3",
44
- "unbuild": "3.0.1"
47
+ "typescript": "5.7.2",
48
+ "unbuild": "3.2.0",
49
+ "vitest": "2.1.8"
45
50
  },
46
51
  "volta": {
47
52
  "node": "22.12.0"