@kedataindo/docflow-core 0.0.95 → 0.0.97

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/dist/ai/index.cjs CHANGED
@@ -22,12 +22,14 @@ var ai_exports = {};
22
22
  __export(ai_exports, {
23
23
  CONTEXT_CHAR_CAP: () => CONTEXT_CHAR_CAP,
24
24
  LOCAL_STORAGE_KEY: () => LOCAL_STORAGE_KEY,
25
+ SESSION_STORAGE_KEY: () => SESSION_STORAGE_KEY,
25
26
  buildAIPrompt: () => buildAIPrompt,
26
27
  claudeProvider: () => claudeProvider,
27
28
  httpKeyStorage: () => httpKeyStorage,
28
29
  localStorageKeyStorage: () => localStorageKeyStorage,
29
30
  memoryKeyStorage: () => memoryKeyStorage,
30
31
  openaiCompatibleProvider: () => openaiCompatibleProvider,
32
+ sessionStorageKeyStorage: () => sessionStorageKeyStorage,
31
33
  toAIStreamFn: () => toAIStreamFn,
32
34
  trimContextAfter: () => trimContextAfter,
33
35
  trimContextBefore: () => trimContextBefore
@@ -144,30 +146,65 @@ function memoryKeyStorage(initial = null) {
144
146
  };
145
147
  }
146
148
  var LOCAL_STORAGE_KEY = "docflow:ai-config";
147
- function localStorageKeyStorage(key = LOCAL_STORAGE_KEY, storage) {
148
- const store = () => {
149
- const s = storage ?? globalThis.localStorage;
150
- if (!s) throw new Error("localStorageKeyStorage: no Storage available (SSR?)");
151
- return s;
152
- };
149
+ var SESSION_STORAGE_KEY = "docflow:ai-config";
150
+ function isStoredAIConfig(value) {
151
+ if (typeof value !== "object" || value === null) return false;
152
+ const candidate = value;
153
+ return candidate.__docflowKeyStorage === 1 && typeof candidate.config === "object" && candidate.config !== null;
154
+ }
155
+ function webStorageKeyStorage(resolve, key = LOCAL_STORAGE_KEY, options = {}) {
156
+ const { ttlMs } = options;
157
+ if (ttlMs !== void 0 && (!Number.isFinite(ttlMs) || ttlMs <= 0)) {
158
+ throw new Error(
159
+ `webStorageKeyStorage: ttlMs must be a positive number of milliseconds, got ${String(ttlMs)}`
160
+ );
161
+ }
153
162
  return {
154
163
  async get() {
155
- const raw = store().getItem(key);
164
+ const raw = resolve().getItem(key);
156
165
  if (!raw) return null;
166
+ let parsed;
157
167
  try {
158
- return JSON.parse(raw);
168
+ parsed = JSON.parse(raw);
159
169
  } catch {
160
170
  return null;
161
171
  }
172
+ if (!isStoredAIConfig(parsed)) {
173
+ return parsed;
174
+ }
175
+ if (parsed.expiresAt !== void 0 && Date.now() >= parsed.expiresAt) {
176
+ resolve().removeItem(key);
177
+ return null;
178
+ }
179
+ return parsed.config;
162
180
  },
163
181
  async set(value) {
164
- store().setItem(key, JSON.stringify(value));
182
+ const stored = {
183
+ __docflowKeyStorage: 1,
184
+ config: value,
185
+ ...ttlMs === void 0 ? {} : { expiresAt: Date.now() + ttlMs }
186
+ };
187
+ resolve().setItem(key, JSON.stringify(stored));
165
188
  },
166
189
  async clear() {
167
- store().removeItem(key);
190
+ resolve().removeItem(key);
168
191
  }
169
192
  };
170
193
  }
194
+ function localStorageKeyStorage(key = LOCAL_STORAGE_KEY, storage, options = {}) {
195
+ return webStorageKeyStorage(() => {
196
+ const s = storage ?? globalThis.localStorage;
197
+ if (!s) throw new Error("localStorageKeyStorage: no localStorage available (SSR?)");
198
+ return s;
199
+ }, key, options);
200
+ }
201
+ function sessionStorageKeyStorage(key = SESSION_STORAGE_KEY, storage, options = {}) {
202
+ return webStorageKeyStorage(() => {
203
+ const s = storage ?? globalThis.sessionStorage;
204
+ if (!s) throw new Error("sessionStorageKeyStorage: no sessionStorage available (SSR?)");
205
+ return s;
206
+ }, key, options);
207
+ }
171
208
  function httpKeyStorage(urls) {
172
209
  const doFetch = urls.fetchImpl ?? ((...args) => fetch(...args));
173
210
  return {
@@ -444,12 +481,14 @@ function claudeProvider(config) {
444
481
  0 && (module.exports = {
445
482
  CONTEXT_CHAR_CAP,
446
483
  LOCAL_STORAGE_KEY,
484
+ SESSION_STORAGE_KEY,
447
485
  buildAIPrompt,
448
486
  claudeProvider,
449
487
  httpKeyStorage,
450
488
  localStorageKeyStorage,
451
489
  memoryKeyStorage,
452
490
  openaiCompatibleProvider,
491
+ sessionStorageKeyStorage,
453
492
  toAIStreamFn,
454
493
  trimContextAfter,
455
494
  trimContextBefore
@@ -1 +1 @@
1
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, a as AIDraftFn, h as AIProvider, A as AIStreamFn, j as Auth, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, q as buildAIPrompt, r as claudeProvider, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from '../index-DRhAaVy1.cjs';
1
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, a as AIDraftFn, h as AIProvider, A as AIStreamFn, j as Auth, B as BrowserKeyStorageOptions, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as SESSION_STORAGE_KEY, r as StreamEvent, s as buildAIPrompt, t as claudeProvider, u as httpKeyStorage, v as localStorageKeyStorage, w as memoryKeyStorage, x as openaiCompatibleProvider, y as sessionStorageKeyStorage, z as toAIStreamFn, D as trimContextAfter, E as trimContextBefore } from '../index-Br6IhSDL.cjs';
@@ -1 +1 @@
1
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, a as AIDraftFn, h as AIProvider, A as AIStreamFn, j as Auth, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, q as buildAIPrompt, r as claudeProvider, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from '../index-DRhAaVy1.js';
1
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, a as AIDraftFn, h as AIProvider, A as AIStreamFn, j as Auth, B as BrowserKeyStorageOptions, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as SESSION_STORAGE_KEY, r as StreamEvent, s as buildAIPrompt, t as claudeProvider, u as httpKeyStorage, v as localStorageKeyStorage, w as memoryKeyStorage, x as openaiCompatibleProvider, y as sessionStorageKeyStorage, z as toAIStreamFn, D as trimContextAfter, E as trimContextBefore } from '../index-Br6IhSDL.js';
package/dist/ai/index.js CHANGED
@@ -1,25 +1,29 @@
1
1
  import {
2
2
  CONTEXT_CHAR_CAP,
3
3
  LOCAL_STORAGE_KEY,
4
+ SESSION_STORAGE_KEY,
4
5
  buildAIPrompt,
5
6
  claudeProvider,
6
7
  httpKeyStorage,
7
8
  localStorageKeyStorage,
8
9
  memoryKeyStorage,
9
10
  openaiCompatibleProvider,
11
+ sessionStorageKeyStorage,
10
12
  toAIStreamFn,
11
13
  trimContextAfter,
12
14
  trimContextBefore
13
- } from "../chunk-6RIBBYIP.js";
15
+ } from "../chunk-ADA2T4CE.js";
14
16
  export {
15
17
  CONTEXT_CHAR_CAP,
16
18
  LOCAL_STORAGE_KEY,
19
+ SESSION_STORAGE_KEY,
17
20
  buildAIPrompt,
18
21
  claudeProvider,
19
22
  httpKeyStorage,
20
23
  localStorageKeyStorage,
21
24
  memoryKeyStorage,
22
25
  openaiCompatibleProvider,
26
+ sessionStorageKeyStorage,
23
27
  toAIStreamFn,
24
28
  trimContextAfter,
25
29
  trimContextBefore
@@ -108,30 +108,65 @@ function memoryKeyStorage(initial = null) {
108
108
  };
109
109
  }
110
110
  var LOCAL_STORAGE_KEY = "docflow:ai-config";
111
- function localStorageKeyStorage(key = LOCAL_STORAGE_KEY, storage) {
112
- const store = () => {
113
- const s = storage ?? globalThis.localStorage;
114
- if (!s) throw new Error("localStorageKeyStorage: no Storage available (SSR?)");
115
- return s;
116
- };
111
+ var SESSION_STORAGE_KEY = "docflow:ai-config";
112
+ function isStoredAIConfig(value) {
113
+ if (typeof value !== "object" || value === null) return false;
114
+ const candidate = value;
115
+ return candidate.__docflowKeyStorage === 1 && typeof candidate.config === "object" && candidate.config !== null;
116
+ }
117
+ function webStorageKeyStorage(resolve, key = LOCAL_STORAGE_KEY, options = {}) {
118
+ const { ttlMs } = options;
119
+ if (ttlMs !== void 0 && (!Number.isFinite(ttlMs) || ttlMs <= 0)) {
120
+ throw new Error(
121
+ `webStorageKeyStorage: ttlMs must be a positive number of milliseconds, got ${String(ttlMs)}`
122
+ );
123
+ }
117
124
  return {
118
125
  async get() {
119
- const raw = store().getItem(key);
126
+ const raw = resolve().getItem(key);
120
127
  if (!raw) return null;
128
+ let parsed;
121
129
  try {
122
- return JSON.parse(raw);
130
+ parsed = JSON.parse(raw);
123
131
  } catch {
124
132
  return null;
125
133
  }
134
+ if (!isStoredAIConfig(parsed)) {
135
+ return parsed;
136
+ }
137
+ if (parsed.expiresAt !== void 0 && Date.now() >= parsed.expiresAt) {
138
+ resolve().removeItem(key);
139
+ return null;
140
+ }
141
+ return parsed.config;
126
142
  },
127
143
  async set(value) {
128
- store().setItem(key, JSON.stringify(value));
144
+ const stored = {
145
+ __docflowKeyStorage: 1,
146
+ config: value,
147
+ ...ttlMs === void 0 ? {} : { expiresAt: Date.now() + ttlMs }
148
+ };
149
+ resolve().setItem(key, JSON.stringify(stored));
129
150
  },
130
151
  async clear() {
131
- store().removeItem(key);
152
+ resolve().removeItem(key);
132
153
  }
133
154
  };
134
155
  }
156
+ function localStorageKeyStorage(key = LOCAL_STORAGE_KEY, storage, options = {}) {
157
+ return webStorageKeyStorage(() => {
158
+ const s = storage ?? globalThis.localStorage;
159
+ if (!s) throw new Error("localStorageKeyStorage: no localStorage available (SSR?)");
160
+ return s;
161
+ }, key, options);
162
+ }
163
+ function sessionStorageKeyStorage(key = SESSION_STORAGE_KEY, storage, options = {}) {
164
+ return webStorageKeyStorage(() => {
165
+ const s = storage ?? globalThis.sessionStorage;
166
+ if (!s) throw new Error("sessionStorageKeyStorage: no sessionStorage available (SSR?)");
167
+ return s;
168
+ }, key, options);
169
+ }
135
170
  function httpKeyStorage(urls) {
136
171
  const doFetch = urls.fetchImpl ?? ((...args) => fetch(...args));
137
172
  return {
@@ -413,7 +448,9 @@ export {
413
448
  toAIStreamFn,
414
449
  memoryKeyStorage,
415
450
  LOCAL_STORAGE_KEY,
451
+ SESSION_STORAGE_KEY,
416
452
  localStorageKeyStorage,
453
+ sessionStorageKeyStorage,
417
454
  httpKeyStorage,
418
455
  openaiCompatibleProvider,
419
456
  claudeProvider
@@ -156,6 +156,19 @@ interface CslItemData {
156
156
  abstract?: string;
157
157
  [k: string]: unknown;
158
158
  }
159
+ /**
160
+ * A CSL style the citation engine can render with (issue #180). The library
161
+ * bundles four; a host may inject more (an institutional or Harvard/IEEE style)
162
+ * through `CitationPort.styles` — without modifying the library.
163
+ */
164
+ interface CslStyleInfo {
165
+ id: string;
166
+ name: string;
167
+ /** 'note' styles render citations as footnotes; 'in-text' render inline. */
168
+ category: 'note' | 'in-text';
169
+ /** The CSL XML itself (the `<style>` document, as a string). */
170
+ xml: string;
171
+ }
159
172
  /**
160
173
  * Host-supplied citation port. The library never fetches sources itself — the
161
174
  * host owns the reference library and injects CSL-JSON through this port,
@@ -166,6 +179,13 @@ interface CitationPort {
166
179
  sources: CslItemData[] | (() => CslItemData[]);
167
180
  /** Active CSL style id (e.g. 'chicago-notes-bibliography'). */
168
181
  style?: string;
182
+ /**
183
+ * Extra CSL styles for this document's engine (issue #180). A style whose
184
+ * `id` matches a bundled one overrides it; every other id is added alongside
185
+ * the four defaults. Omit it and the engine behaves exactly as before — the
186
+ * bundled styles keep the editor offline-capable.
187
+ */
188
+ styles?: CslStyleInfo[];
169
189
  /**
170
190
  * Called when the user inserts a citation: the host opens its source
171
191
  * picker and resolves with the chosen source id (null = cancelled).
@@ -236,11 +256,16 @@ declare function buildAIPrompt(req: AIActionRequest): {
236
256
  * is the overridable `DEFAULT_BASE_URL` default in `claudeProvider.ts`
237
257
  * (contract rule 4 exception; see §5 of docs/LIBRARY_CONTRACT.md).
238
258
  *
239
- * ⚠️ Secrets note: `localStorageKeyStorage` keeps an API key in
240
- * `localStorage` in plaintext — any script running on the same origin can read
241
- * it. Prefer a non-persistent storage (or `httpKeyStorage`, which keeps the key
242
- * on the host backend) when the embedding app is not fully trusted
243
- * (issue #179).
259
+ * ⚠️ Secrets note (issue #179): `localStorageKeyStorage` **and**
260
+ * `sessionStorageKeyStorage` keep an API key in the browser in plaintext — any
261
+ * script running on the same origin can read either one. `sessionStorage` only
262
+ * shortens the *lifetime* (closing the tab drops it); it is **not** an XSS
263
+ * mitigation. The only option that keeps the key out of the browser entirely is
264
+ * `httpKeyStorage` — with `AIConfig.visibility: 'admin-only'` plus the host's
265
+ * completion proxy, the key never reaches the client. That is the recommended
266
+ * production path. `ttlMs` (both browser storages) bounds how long a stored key
267
+ * stays readable, and hosts should call `clear()` on sign-out.
268
+ * See docs/AI_PROVIDERS.md §5.
244
269
  */
245
270
  type Auth = {
246
271
  type: 'bearer';
@@ -274,12 +299,41 @@ interface KeyStorage {
274
299
  /** In-memory storage — for tests and SSR (no persistence). */
275
300
  declare function memoryKeyStorage(initial?: AIConfig | null): KeyStorage;
276
301
  declare const LOCAL_STORAGE_KEY = "docflow:ai-config";
302
+ /** Same logical key, kept in `sessionStorage` instead (issue #179). */
303
+ declare const SESSION_STORAGE_KEY = "docflow:ai-config";
304
+ interface BrowserKeyStorageOptions {
305
+ /**
306
+ * Drop the credentials once the entry is older than this many milliseconds:
307
+ * `get()` then returns null and removes the entry. Rotating a key is the
308
+ * host's job, but a TTL bounds how long a key the user forgot about stays
309
+ * readable.
310
+ */
311
+ ttlMs?: number;
312
+ }
277
313
  /**
278
314
  * Browser `localStorage` persistence — for embedded apps that want the config
279
315
  * kept client-side. Note: this stores AI *credentials*, not document data, so
280
316
  * it is the host's deliberate choice, not a library persistence leak.
317
+ *
318
+ * The key survives browser restarts and is readable by any script on the
319
+ * origin — see the module secrets note, and prefer `sessionStorageKeyStorage`
320
+ * (shorter lifetime) or `httpKeyStorage` (key never reaches the client) when the
321
+ * embedding app is not fully trusted.
322
+ */
323
+ declare function localStorageKeyStorage(key?: string, storage?: Storage, options?: BrowserKeyStorageOptions): KeyStorage;
324
+ /**
325
+ * Browser `sessionStorage` persistence (issue #179) — the same key/value pair
326
+ * as `localStorageKeyStorage`, scoped to the tab: closing it (or the browser)
327
+ * drops the credentials, and a reload keeps them.
328
+ *
329
+ * What this does and does not do: it **reduces the lifetime** of a key the user
330
+ * typed into an embedded app (no "I logged out but the key is still there"
331
+ * surprise after the tab closes). It is **not** a defence against XSS — a
332
+ * script running on the origin reads `sessionStorage` as easily as
333
+ * `localStorage`. Only `httpKeyStorage` (plus `visibility: 'admin-only'` and the
334
+ * host's completion proxy) keeps the key out of the browser.
281
335
  */
282
- declare function localStorageKeyStorage(key?: string, storage?: Storage): KeyStorage;
336
+ declare function sessionStorageKeyStorage(key?: string, storage?: Storage, options?: BrowserKeyStorageOptions): KeyStorage;
283
337
  interface HttpKeyStorageUrls {
284
338
  /** GET → 200 with the AIConfig JSON body, or 404 when unset. */
285
339
  getUrl: string;
@@ -349,5 +403,5 @@ interface ClaudeConfig {
349
403
  }
350
404
  declare function claudeProvider(config: ClaudeConfig): AIProvider;
351
405
 
352
- export { LOCAL_STORAGE_KEY as L, CONTEXT_CHAR_CAP as k, buildAIPrompt as q, claudeProvider as r, httpKeyStorage as s, localStorageKeyStorage as t, memoryKeyStorage as u, openaiCompatibleProvider as v, toAIStreamFn as w, trimContextAfter as x, trimContextBefore as y };
353
- export type { AIStreamFn as A, CitationPort as C, HttpKeyStorageUrls as H, ImageUploadHandler as I, KeyStorage as K, OpenAICompatibleConfig as O, StreamEvent as S, AIDraftFn as a, AIAction as b, AIActionRequest as c, AICompleteRequest as d, AIConfig as e, AIDraftCitation as f, AIDraftEvent as g, AIProvider as h, AIProviderFactory as i, Auth as j, ClaudeConfig as l, CslDate as m, CslItemData as n, CslName as o, ImageUploadResult as p };
406
+ export { trimContextAfter as D, trimContextBefore as E, LOCAL_STORAGE_KEY as L, SESSION_STORAGE_KEY as S, CONTEXT_CHAR_CAP as k, buildAIPrompt as s, claudeProvider as t, httpKeyStorage as u, localStorageKeyStorage as v, memoryKeyStorage as w, openaiCompatibleProvider as x, sessionStorageKeyStorage as y, toAIStreamFn as z };
407
+ export type { AIStreamFn as A, BrowserKeyStorageOptions as B, CitationPort as C, HttpKeyStorageUrls as H, ImageUploadHandler as I, KeyStorage as K, OpenAICompatibleConfig as O, AIDraftFn as a, AIAction as b, AIActionRequest as c, AICompleteRequest as d, AIConfig as e, AIDraftCitation as f, AIDraftEvent as g, AIProvider as h, AIProviderFactory as i, Auth as j, ClaudeConfig as l, CslDate as m, CslItemData as n, CslName as o, CslStyleInfo as p, ImageUploadResult as q, StreamEvent as r };
@@ -156,6 +156,19 @@ interface CslItemData {
156
156
  abstract?: string;
157
157
  [k: string]: unknown;
158
158
  }
159
+ /**
160
+ * A CSL style the citation engine can render with (issue #180). The library
161
+ * bundles four; a host may inject more (an institutional or Harvard/IEEE style)
162
+ * through `CitationPort.styles` — without modifying the library.
163
+ */
164
+ interface CslStyleInfo {
165
+ id: string;
166
+ name: string;
167
+ /** 'note' styles render citations as footnotes; 'in-text' render inline. */
168
+ category: 'note' | 'in-text';
169
+ /** The CSL XML itself (the `<style>` document, as a string). */
170
+ xml: string;
171
+ }
159
172
  /**
160
173
  * Host-supplied citation port. The library never fetches sources itself — the
161
174
  * host owns the reference library and injects CSL-JSON through this port,
@@ -166,6 +179,13 @@ interface CitationPort {
166
179
  sources: CslItemData[] | (() => CslItemData[]);
167
180
  /** Active CSL style id (e.g. 'chicago-notes-bibliography'). */
168
181
  style?: string;
182
+ /**
183
+ * Extra CSL styles for this document's engine (issue #180). A style whose
184
+ * `id` matches a bundled one overrides it; every other id is added alongside
185
+ * the four defaults. Omit it and the engine behaves exactly as before — the
186
+ * bundled styles keep the editor offline-capable.
187
+ */
188
+ styles?: CslStyleInfo[];
169
189
  /**
170
190
  * Called when the user inserts a citation: the host opens its source
171
191
  * picker and resolves with the chosen source id (null = cancelled).
@@ -236,11 +256,16 @@ declare function buildAIPrompt(req: AIActionRequest): {
236
256
  * is the overridable `DEFAULT_BASE_URL` default in `claudeProvider.ts`
237
257
  * (contract rule 4 exception; see §5 of docs/LIBRARY_CONTRACT.md).
238
258
  *
239
- * ⚠️ Secrets note: `localStorageKeyStorage` keeps an API key in
240
- * `localStorage` in plaintext — any script running on the same origin can read
241
- * it. Prefer a non-persistent storage (or `httpKeyStorage`, which keeps the key
242
- * on the host backend) when the embedding app is not fully trusted
243
- * (issue #179).
259
+ * ⚠️ Secrets note (issue #179): `localStorageKeyStorage` **and**
260
+ * `sessionStorageKeyStorage` keep an API key in the browser in plaintext — any
261
+ * script running on the same origin can read either one. `sessionStorage` only
262
+ * shortens the *lifetime* (closing the tab drops it); it is **not** an XSS
263
+ * mitigation. The only option that keeps the key out of the browser entirely is
264
+ * `httpKeyStorage` — with `AIConfig.visibility: 'admin-only'` plus the host's
265
+ * completion proxy, the key never reaches the client. That is the recommended
266
+ * production path. `ttlMs` (both browser storages) bounds how long a stored key
267
+ * stays readable, and hosts should call `clear()` on sign-out.
268
+ * See docs/AI_PROVIDERS.md §5.
244
269
  */
245
270
  type Auth = {
246
271
  type: 'bearer';
@@ -274,12 +299,41 @@ interface KeyStorage {
274
299
  /** In-memory storage — for tests and SSR (no persistence). */
275
300
  declare function memoryKeyStorage(initial?: AIConfig | null): KeyStorage;
276
301
  declare const LOCAL_STORAGE_KEY = "docflow:ai-config";
302
+ /** Same logical key, kept in `sessionStorage` instead (issue #179). */
303
+ declare const SESSION_STORAGE_KEY = "docflow:ai-config";
304
+ interface BrowserKeyStorageOptions {
305
+ /**
306
+ * Drop the credentials once the entry is older than this many milliseconds:
307
+ * `get()` then returns null and removes the entry. Rotating a key is the
308
+ * host's job, but a TTL bounds how long a key the user forgot about stays
309
+ * readable.
310
+ */
311
+ ttlMs?: number;
312
+ }
277
313
  /**
278
314
  * Browser `localStorage` persistence — for embedded apps that want the config
279
315
  * kept client-side. Note: this stores AI *credentials*, not document data, so
280
316
  * it is the host's deliberate choice, not a library persistence leak.
317
+ *
318
+ * The key survives browser restarts and is readable by any script on the
319
+ * origin — see the module secrets note, and prefer `sessionStorageKeyStorage`
320
+ * (shorter lifetime) or `httpKeyStorage` (key never reaches the client) when the
321
+ * embedding app is not fully trusted.
322
+ */
323
+ declare function localStorageKeyStorage(key?: string, storage?: Storage, options?: BrowserKeyStorageOptions): KeyStorage;
324
+ /**
325
+ * Browser `sessionStorage` persistence (issue #179) — the same key/value pair
326
+ * as `localStorageKeyStorage`, scoped to the tab: closing it (or the browser)
327
+ * drops the credentials, and a reload keeps them.
328
+ *
329
+ * What this does and does not do: it **reduces the lifetime** of a key the user
330
+ * typed into an embedded app (no "I logged out but the key is still there"
331
+ * surprise after the tab closes). It is **not** a defence against XSS — a
332
+ * script running on the origin reads `sessionStorage` as easily as
333
+ * `localStorage`. Only `httpKeyStorage` (plus `visibility: 'admin-only'` and the
334
+ * host's completion proxy) keeps the key out of the browser.
281
335
  */
282
- declare function localStorageKeyStorage(key?: string, storage?: Storage): KeyStorage;
336
+ declare function sessionStorageKeyStorage(key?: string, storage?: Storage, options?: BrowserKeyStorageOptions): KeyStorage;
283
337
  interface HttpKeyStorageUrls {
284
338
  /** GET → 200 with the AIConfig JSON body, or 404 when unset. */
285
339
  getUrl: string;
@@ -349,5 +403,5 @@ interface ClaudeConfig {
349
403
  }
350
404
  declare function claudeProvider(config: ClaudeConfig): AIProvider;
351
405
 
352
- export { LOCAL_STORAGE_KEY as L, CONTEXT_CHAR_CAP as k, buildAIPrompt as q, claudeProvider as r, httpKeyStorage as s, localStorageKeyStorage as t, memoryKeyStorage as u, openaiCompatibleProvider as v, toAIStreamFn as w, trimContextAfter as x, trimContextBefore as y };
353
- export type { AIStreamFn as A, CitationPort as C, HttpKeyStorageUrls as H, ImageUploadHandler as I, KeyStorage as K, OpenAICompatibleConfig as O, StreamEvent as S, AIDraftFn as a, AIAction as b, AIActionRequest as c, AICompleteRequest as d, AIConfig as e, AIDraftCitation as f, AIDraftEvent as g, AIProvider as h, AIProviderFactory as i, Auth as j, ClaudeConfig as l, CslDate as m, CslItemData as n, CslName as o, ImageUploadResult as p };
406
+ export { trimContextAfter as D, trimContextBefore as E, LOCAL_STORAGE_KEY as L, SESSION_STORAGE_KEY as S, CONTEXT_CHAR_CAP as k, buildAIPrompt as s, claudeProvider as t, httpKeyStorage as u, localStorageKeyStorage as v, memoryKeyStorage as w, openaiCompatibleProvider as x, sessionStorageKeyStorage as y, toAIStreamFn as z };
407
+ export type { AIStreamFn as A, BrowserKeyStorageOptions as B, CitationPort as C, HttpKeyStorageUrls as H, ImageUploadHandler as I, KeyStorage as K, OpenAICompatibleConfig as O, AIDraftFn as a, AIAction as b, AIActionRequest as c, AICompleteRequest as d, AIConfig as e, AIDraftCitation as f, AIDraftEvent as g, AIProvider as h, AIProviderFactory as i, Auth as j, ClaudeConfig as l, CslDate as m, CslItemData as n, CslName as o, CslStyleInfo as p, ImageUploadResult as q, StreamEvent as r };
package/dist/index.cjs CHANGED
@@ -45,6 +45,7 @@ __export(index_exports, {
45
45
  PAGE_SIZES: () => PAGE_SIZES,
46
46
  PaginationPlus: () => PaginationPlus,
47
47
  PerformanceMonitor: () => PerformanceMonitor,
48
+ SESSION_STORAGE_KEY: () => SESSION_STORAGE_KEY,
48
49
  SearchAndReplaceExtension: () => SearchAndReplaceExtension,
49
50
  SubdocumentProvider: () => SubdocumentProvider,
50
51
  buildAIPrompt: () => buildAIPrompt,
@@ -75,6 +76,7 @@ __export(index_exports, {
75
76
  searchAndReplaceKey: () => searchAndReplaceKey,
76
77
  searchNext: () => searchNext,
77
78
  searchPrev: () => searchPrev,
79
+ sessionStorageKeyStorage: () => sessionStorageKeyStorage,
78
80
  setSearchQuery: () => setSearchQuery,
79
81
  toAIStreamFn: () => toAIStreamFn,
80
82
  trimContextAfter: () => trimContextAfter,
@@ -2801,30 +2803,65 @@ function memoryKeyStorage(initial = null) {
2801
2803
  };
2802
2804
  }
2803
2805
  var LOCAL_STORAGE_KEY = "docflow:ai-config";
2804
- function localStorageKeyStorage(key2 = LOCAL_STORAGE_KEY, storage) {
2805
- const store = () => {
2806
- const s = storage ?? globalThis.localStorage;
2807
- if (!s) throw new Error("localStorageKeyStorage: no Storage available (SSR?)");
2808
- return s;
2809
- };
2806
+ var SESSION_STORAGE_KEY = "docflow:ai-config";
2807
+ function isStoredAIConfig(value) {
2808
+ if (typeof value !== "object" || value === null) return false;
2809
+ const candidate = value;
2810
+ return candidate.__docflowKeyStorage === 1 && typeof candidate.config === "object" && candidate.config !== null;
2811
+ }
2812
+ function webStorageKeyStorage(resolve, key2 = LOCAL_STORAGE_KEY, options = {}) {
2813
+ const { ttlMs } = options;
2814
+ if (ttlMs !== void 0 && (!Number.isFinite(ttlMs) || ttlMs <= 0)) {
2815
+ throw new Error(
2816
+ `webStorageKeyStorage: ttlMs must be a positive number of milliseconds, got ${String(ttlMs)}`
2817
+ );
2818
+ }
2810
2819
  return {
2811
2820
  async get() {
2812
- const raw = store().getItem(key2);
2821
+ const raw = resolve().getItem(key2);
2813
2822
  if (!raw) return null;
2823
+ let parsed;
2814
2824
  try {
2815
- return JSON.parse(raw);
2825
+ parsed = JSON.parse(raw);
2816
2826
  } catch {
2817
2827
  return null;
2818
2828
  }
2829
+ if (!isStoredAIConfig(parsed)) {
2830
+ return parsed;
2831
+ }
2832
+ if (parsed.expiresAt !== void 0 && Date.now() >= parsed.expiresAt) {
2833
+ resolve().removeItem(key2);
2834
+ return null;
2835
+ }
2836
+ return parsed.config;
2819
2837
  },
2820
2838
  async set(value) {
2821
- store().setItem(key2, JSON.stringify(value));
2839
+ const stored = {
2840
+ __docflowKeyStorage: 1,
2841
+ config: value,
2842
+ ...ttlMs === void 0 ? {} : { expiresAt: Date.now() + ttlMs }
2843
+ };
2844
+ resolve().setItem(key2, JSON.stringify(stored));
2822
2845
  },
2823
2846
  async clear() {
2824
- store().removeItem(key2);
2847
+ resolve().removeItem(key2);
2825
2848
  }
2826
2849
  };
2827
2850
  }
2851
+ function localStorageKeyStorage(key2 = LOCAL_STORAGE_KEY, storage, options = {}) {
2852
+ return webStorageKeyStorage(() => {
2853
+ const s = storage ?? globalThis.localStorage;
2854
+ if (!s) throw new Error("localStorageKeyStorage: no localStorage available (SSR?)");
2855
+ return s;
2856
+ }, key2, options);
2857
+ }
2858
+ function sessionStorageKeyStorage(key2 = SESSION_STORAGE_KEY, storage, options = {}) {
2859
+ return webStorageKeyStorage(() => {
2860
+ const s = storage ?? globalThis.sessionStorage;
2861
+ if (!s) throw new Error("sessionStorageKeyStorage: no sessionStorage available (SSR?)");
2862
+ return s;
2863
+ }, key2, options);
2864
+ }
2828
2865
  function httpKeyStorage(urls) {
2829
2866
  const doFetch = urls.fetchImpl ?? ((...args) => fetch(...args));
2830
2867
  return {
@@ -3114,6 +3151,7 @@ function claudeProvider(config) {
3114
3151
  PAGE_SIZES,
3115
3152
  PaginationPlus,
3116
3153
  PerformanceMonitor,
3154
+ SESSION_STORAGE_KEY,
3117
3155
  SearchAndReplaceExtension,
3118
3156
  SubdocumentProvider,
3119
3157
  buildAIPrompt,
@@ -3144,6 +3182,7 @@ function claudeProvider(config) {
3144
3182
  searchAndReplaceKey,
3145
3183
  searchNext,
3146
3184
  searchPrev,
3185
+ sessionStorageKeyStorage,
3147
3186
  setSearchQuery,
3148
3187
  toAIStreamFn,
3149
3188
  trimContextAfter,
package/dist/index.d.cts CHANGED
@@ -4,8 +4,8 @@ import { IndexeddbPersistence } from 'y-indexeddb';
4
4
  import { WebrtcProvider } from 'y-webrtc';
5
5
  import { WebsocketProvider } from 'y-websocket';
6
6
  import * as Y from 'yjs';
7
- import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-DRhAaVy1.cjs';
8
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, p as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, q as buildAIPrompt, r as claudeProvider, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from './index-DRhAaVy1.cjs';
7
+ import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-Br6IhSDL.cjs';
8
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, B as BrowserKeyStorageOptions, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, p as CslStyleInfo, H as HttpKeyStorageUrls, q as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as SESSION_STORAGE_KEY, r as StreamEvent, s as buildAIPrompt, t as claudeProvider, u as httpKeyStorage, v as localStorageKeyStorage, w as memoryKeyStorage, x as openaiCompatibleProvider, y as sessionStorageKeyStorage, z as toAIStreamFn, D as trimContextAfter, E as trimContextBefore } from './index-Br6IhSDL.cjs';
9
9
  import { PaginationPlusOptions } from 'tiptap-pagination-plus';
10
10
  export { PAGE_SIZES, PageSize, PaginationPlus, PaginationPlusOptions } from 'tiptap-pagination-plus';
11
11
  import { Node } from '@tiptap/pm/model';
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ import { IndexeddbPersistence } from 'y-indexeddb';
4
4
  import { WebrtcProvider } from 'y-webrtc';
5
5
  import { WebsocketProvider } from 'y-websocket';
6
6
  import * as Y from 'yjs';
7
- import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-DRhAaVy1.js';
8
- export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, H as HttpKeyStorageUrls, p as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as StreamEvent, q as buildAIPrompt, r as claudeProvider, s as httpKeyStorage, t as localStorageKeyStorage, u as memoryKeyStorage, v as openaiCompatibleProvider, w as toAIStreamFn, x as trimContextAfter, y as trimContextBefore } from './index-DRhAaVy1.js';
7
+ import { I as ImageUploadHandler, C as CitationPort, A as AIStreamFn, a as AIDraftFn } from './index-Br6IhSDL.js';
8
+ export { b as AIAction, c as AIActionRequest, d as AICompleteRequest, e as AIConfig, f as AIDraftCitation, g as AIDraftEvent, h as AIProvider, i as AIProviderFactory, j as Auth, B as BrowserKeyStorageOptions, k as CONTEXT_CHAR_CAP, l as ClaudeConfig, m as CslDate, n as CslItemData, o as CslName, p as CslStyleInfo, H as HttpKeyStorageUrls, q as ImageUploadResult, K as KeyStorage, L as LOCAL_STORAGE_KEY, O as OpenAICompatibleConfig, S as SESSION_STORAGE_KEY, r as StreamEvent, s as buildAIPrompt, t as claudeProvider, u as httpKeyStorage, v as localStorageKeyStorage, w as memoryKeyStorage, x as openaiCompatibleProvider, y as sessionStorageKeyStorage, z as toAIStreamFn, D as trimContextAfter, E as trimContextBefore } from './index-Br6IhSDL.js';
9
9
  import { PaginationPlusOptions } from 'tiptap-pagination-plus';
10
10
  export { PAGE_SIZES, PageSize, PaginationPlus, PaginationPlusOptions } from 'tiptap-pagination-plus';
11
11
  import { Node } from '@tiptap/pm/model';
package/dist/index.js CHANGED
@@ -1,16 +1,18 @@
1
1
  import {
2
2
  CONTEXT_CHAR_CAP,
3
3
  LOCAL_STORAGE_KEY,
4
+ SESSION_STORAGE_KEY,
4
5
  buildAIPrompt,
5
6
  claudeProvider,
6
7
  httpKeyStorage,
7
8
  localStorageKeyStorage,
8
9
  memoryKeyStorage,
9
10
  openaiCompatibleProvider,
11
+ sessionStorageKeyStorage,
10
12
  toAIStreamFn,
11
13
  trimContextAfter,
12
14
  trimContextBefore
13
- } from "./chunk-6RIBBYIP.js";
15
+ } from "./chunk-ADA2T4CE.js";
14
16
 
15
17
  // src/Editor.ts
16
18
  import { Editor as TiptapEditor } from "@tiptap/core";
@@ -2636,6 +2638,7 @@ export {
2636
2638
  PAGE_SIZES,
2637
2639
  PaginationPlus,
2638
2640
  PerformanceMonitor,
2641
+ SESSION_STORAGE_KEY,
2639
2642
  SearchAndReplaceExtension,
2640
2643
  SubdocumentProvider,
2641
2644
  buildAIPrompt,
@@ -2666,6 +2669,7 @@ export {
2666
2669
  searchAndReplaceKey,
2667
2670
  searchNext,
2668
2671
  searchPrev,
2672
+ sessionStorageKeyStorage,
2669
2673
  setSearchQuery,
2670
2674
  toAIStreamFn,
2671
2675
  trimContextAfter,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kedataindo/docflow-core",
3
3
  "license": "Apache-2.0",
4
- "version": "0.0.95",
4
+ "version": "0.0.97",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",