memorio 5.0.0 → 5.1.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 +80 -449
- package/SECURITY.md +152 -42
- package/SUMMARY.md +1 -1
- package/adr/001-state-proxy-model.md +95 -96
- package/adr/002-observer-semantics.md +179 -180
- package/adr/003-deep-mutation-semantics.md +7 -8
- package/adr/004-array-mutation-semantics.md +127 -128
- package/adr/005-scheduler-contract.md +148 -149
- package/adr/006-context-isolation.md +91 -92
- package/adr/007-mutation-records.md +5 -6
- package/adr/008-transactions.md +6 -7
- package/adr/009-history-model.md +6 -7
- package/adr/README.md +46 -46
- package/adr/template.md +48 -49
- package/bin/cli.js +68 -0
- package/global.cjs +1462 -323
- package/global.js +1459 -324
- package/index.cjs +1462 -323
- package/index.d.ts +1 -0
- package/index.js +1459 -324
- package/llms.txt +42 -5
- package/markdown/AUDIT-REPORT.md +7 -8
- package/markdown/CACHE.md +190 -99
- package/markdown/DEVTOOLS.md +0 -1
- package/markdown/DISPATCH.md +0 -1
- package/markdown/HISTORY.md +0 -1
- package/markdown/IDB.md +0 -1
- package/markdown/IMPORT.md +0 -1
- package/markdown/INSPECT.md +0 -1
- package/markdown/LOGGER.md +0 -1
- package/markdown/MEMORY-ATTACHMENT.md +0 -1
- package/markdown/MEMORY.md +0 -1
- package/markdown/OBSERVER.md +0 -1
- package/markdown/PLATFORM.md +277 -271
- package/markdown/REDUX.md +54 -0
- package/markdown/SCHEMA.md +0 -1
- package/markdown/SESSION.md +0 -1
- package/markdown/SQLITE.md +0 -1
- package/markdown/STATE.md +0 -1
- package/markdown/STORE.md +0 -1
- package/markdown/SYNC.md +0 -1
- package/markdown/TYPED.md +0 -1
- package/markdown/USEOBSERVER.md +0 -1
- package/modules/redux.cjs +381 -10
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +381 -10
- package/modules/redux.js.map +1 -1
- package/package.json +14 -2
- package/types/broadcast.d.ts +61 -0
- package/types/computed.d.ts +96 -0
- package/types/encryption.d.ts +129 -0
- package/types/exports.d.ts +9 -0
- package/types/memorio.d.ts +19 -12
- package/types/security.d.ts +67 -0
- package/types/session.d.ts +23 -5
- package/types/store.d.ts +19 -3
- package/vsix/memorio.vsix +0 -0
- package/markdown/CHANGELOG.md +0 -243
- package/markdown/PROJECT.md +0 -311
- package/markdown/SECURITY.md +0 -330
package/modules/redux.js
CHANGED
|
@@ -200,6 +200,241 @@ var init_message = __esm({
|
|
|
200
200
|
}
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
+
// functions/encryption/index.ts
|
|
204
|
+
function _hasWebCrypto() {
|
|
205
|
+
const g2 = globalThis;
|
|
206
|
+
return typeof g2.crypto !== "undefined" && typeof g2.crypto.subtle !== "undefined" && typeof g2.crypto.subtle.encrypt === "function";
|
|
207
|
+
}
|
|
208
|
+
function _b64encode(buf) {
|
|
209
|
+
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
210
|
+
if (typeof btoa === "function") {
|
|
211
|
+
let binary = "";
|
|
212
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
213
|
+
return btoa(binary);
|
|
214
|
+
}
|
|
215
|
+
if (typeof Buffer !== "undefined") {
|
|
216
|
+
return Buffer.from(bytes).toString("base64");
|
|
217
|
+
}
|
|
218
|
+
return "";
|
|
219
|
+
}
|
|
220
|
+
function _b64decode(b64) {
|
|
221
|
+
if (typeof Buffer !== "undefined") {
|
|
222
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
223
|
+
}
|
|
224
|
+
if (typeof atob === "function") {
|
|
225
|
+
const binary = atob(b64);
|
|
226
|
+
const out = new Uint8Array(binary.length);
|
|
227
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
throw new Error("memorio encryption: no base64 decoder available");
|
|
231
|
+
}
|
|
232
|
+
function _isEncrypted(value) {
|
|
233
|
+
return typeof value === "object" && value !== null && value.__memorio_encrypted === true && typeof value.alg === "string";
|
|
234
|
+
}
|
|
235
|
+
var AES_GCM, PBKDF2_ITERATIONS, encryption;
|
|
236
|
+
var init_encryption = __esm({
|
|
237
|
+
"functions/encryption/index.ts"() {
|
|
238
|
+
init_esm_shims();
|
|
239
|
+
init_env();
|
|
240
|
+
init_message();
|
|
241
|
+
__name(_hasWebCrypto, "_hasWebCrypto");
|
|
242
|
+
__name(_b64encode, "_b64encode");
|
|
243
|
+
__name(_b64decode, "_b64decode");
|
|
244
|
+
__name(_isEncrypted, "_isEncrypted");
|
|
245
|
+
AES_GCM = { name: "AES-GCM", length: 256 };
|
|
246
|
+
PBKDF2_ITERATIONS = 6e5;
|
|
247
|
+
encryption = {
|
|
248
|
+
///
|
|
249
|
+
// Capability detection
|
|
250
|
+
///
|
|
251
|
+
isAvailable() {
|
|
252
|
+
return _hasWebCrypto();
|
|
253
|
+
},
|
|
254
|
+
///
|
|
255
|
+
// Derive a CryptoKey from a password using PBKDF2.
|
|
256
|
+
//
|
|
257
|
+
// @param password The password string.
|
|
258
|
+
// @param salt Salt (string or Uint8Array). Defaults to a static value.
|
|
259
|
+
// @param alg 'AES-GCM' (default) or 'AES-CBC'.
|
|
260
|
+
// @returns A Promise<CryptoKey>.
|
|
261
|
+
///
|
|
262
|
+
async deriveKey(password, salt, alg = "AES-GCM", iterations = PBKDF2_ITERATIONS) {
|
|
263
|
+
if (!_hasWebCrypto()) {
|
|
264
|
+
throw new Error("memorio encryption: Web Crypto API is not available in this environment");
|
|
265
|
+
}
|
|
266
|
+
const enc = new TextEncoder();
|
|
267
|
+
const saltBytes = typeof salt === "string" ? enc.encode(salt) : salt ?? enc.encode("memorio:salt");
|
|
268
|
+
const keyMaterial = await globalThis.crypto.subtle.importKey(
|
|
269
|
+
"raw",
|
|
270
|
+
enc.encode(password),
|
|
271
|
+
{ name: "PBKDF2" },
|
|
272
|
+
false,
|
|
273
|
+
["deriveKey"]
|
|
274
|
+
);
|
|
275
|
+
return globalThis.crypto.subtle.deriveKey(
|
|
276
|
+
{ name: "PBKDF2", salt: saltBytes, iterations, hash: "SHA-256" },
|
|
277
|
+
keyMaterial,
|
|
278
|
+
{ name: alg, length: 256 },
|
|
279
|
+
false,
|
|
280
|
+
["encrypt", "decrypt"]
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
///
|
|
284
|
+
// Generate a fresh random CryptoKey (AES-GCM, 256-bit).
|
|
285
|
+
///
|
|
286
|
+
async generateKey(alg = "AES-GCM") {
|
|
287
|
+
if (!_hasWebCrypto()) {
|
|
288
|
+
throw new Error("memorio encryption: Web Crypto API is not available in this environment");
|
|
289
|
+
}
|
|
290
|
+
return globalThis.crypto.subtle.generateKey(
|
|
291
|
+
{ name: alg, length: 256 },
|
|
292
|
+
true,
|
|
293
|
+
["encrypt", "decrypt"]
|
|
294
|
+
);
|
|
295
|
+
},
|
|
296
|
+
///
|
|
297
|
+
// Generate a random salt for PBKDF2 key derivation.
|
|
298
|
+
///
|
|
299
|
+
generateSalt(bytes = 16) {
|
|
300
|
+
const arr = new Uint8Array(bytes);
|
|
301
|
+
globalThis.crypto?.getRandomValues?.(arr);
|
|
302
|
+
return _b64encode(arr);
|
|
303
|
+
},
|
|
304
|
+
///
|
|
305
|
+
// Export a CryptoKey to portable base64 strings so it can be stored
|
|
306
|
+
// (e.g. in an httpOnly cookie) and re-imported later with importKey().
|
|
307
|
+
//
|
|
308
|
+
// @param cryptoKey A raw CryptoKey (not a password string).
|
|
309
|
+
// @returns { key: string, iv: string } base64-encoded.
|
|
310
|
+
///
|
|
311
|
+
async exportKey(cryptoKey) {
|
|
312
|
+
if (!_hasWebCrypto()) {
|
|
313
|
+
throw new Error("memorio encryption: Web Crypto API is not available in this environment");
|
|
314
|
+
}
|
|
315
|
+
const exported = await globalThis.crypto.subtle.exportKey("raw", cryptoKey);
|
|
316
|
+
return {
|
|
317
|
+
key: _b64encode(exported),
|
|
318
|
+
iv: ""
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
///
|
|
322
|
+
// Import a CryptoKey from base64 strings produced by exportKey().
|
|
323
|
+
//
|
|
324
|
+
// @param keyData Base64-encoded raw key bytes.
|
|
325
|
+
// @param _ivData Ignored (kept for API compatibility with exportKey return).
|
|
326
|
+
// @returns A Promise<CryptoKey>.
|
|
327
|
+
///
|
|
328
|
+
async importKey(keyData, _ivData) {
|
|
329
|
+
if (!_hasWebCrypto()) {
|
|
330
|
+
throw new Error("memorio encryption: Web Crypto API is not available in this environment");
|
|
331
|
+
}
|
|
332
|
+
const keyBytes = _b64decode(keyData);
|
|
333
|
+
if (keyBytes.length !== 32) {
|
|
334
|
+
throw new Error(`memorio encryption: AES-256 key must be exactly 32 bytes, got ${keyBytes.length}`);
|
|
335
|
+
}
|
|
336
|
+
return globalThis.crypto.subtle.importKey(
|
|
337
|
+
"raw",
|
|
338
|
+
keyBytes,
|
|
339
|
+
AES_GCM,
|
|
340
|
+
true,
|
|
341
|
+
["encrypt", "decrypt"]
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
///
|
|
345
|
+
// Encrypt a JavaScript value.
|
|
346
|
+
//
|
|
347
|
+
// @param value The value to encrypt (any JSON-serializable object).
|
|
348
|
+
// @param key A CryptoKey or a password string (password is derived via PBKDF2).
|
|
349
|
+
// @param keyId Optional label, stored in the envelope for key rotation.
|
|
350
|
+
// @returns An EncryptedEnvelope object.
|
|
351
|
+
///
|
|
352
|
+
async encrypt(value, key, keyId) {
|
|
353
|
+
const cryptoKey = typeof key === "string" ? await this.deriveKey(key, void 0, "AES-GCM") : key;
|
|
354
|
+
const data = new TextEncoder().encode(JSON.stringify(value));
|
|
355
|
+
const iv = new Uint8Array(12);
|
|
356
|
+
globalThis.crypto.getRandomValues(iv);
|
|
357
|
+
const ciphertext = await globalThis.crypto.subtle.encrypt(
|
|
358
|
+
{ name: "AES-GCM", iv },
|
|
359
|
+
cryptoKey,
|
|
360
|
+
data
|
|
361
|
+
);
|
|
362
|
+
const envelope = {
|
|
363
|
+
__memorio_encrypted: true,
|
|
364
|
+
v: 1,
|
|
365
|
+
alg: "AES-GCM",
|
|
366
|
+
iv: _b64encode(iv),
|
|
367
|
+
data: _b64encode(ciphertext)
|
|
368
|
+
};
|
|
369
|
+
if (keyId) envelope.keyId = keyId;
|
|
370
|
+
if (DEV) message_default(`encryption: encrypted value (alg=${envelope.alg})`);
|
|
371
|
+
return envelope;
|
|
372
|
+
},
|
|
373
|
+
///
|
|
374
|
+
// Decrypt an EncryptedEnvelope.
|
|
375
|
+
//
|
|
376
|
+
// @param envelope The envelope object returned by encrypt().
|
|
377
|
+
// @param key A CryptoKey or a password string.
|
|
378
|
+
// @returns The original value (parsed from JSON).
|
|
379
|
+
///
|
|
380
|
+
async decrypt(envelope, key) {
|
|
381
|
+
if (!_isEncrypted(envelope)) {
|
|
382
|
+
throw new Error("memorio encryption: value is not a valid encrypted envelope");
|
|
383
|
+
}
|
|
384
|
+
const cryptoKey = typeof key === "string" ? await this.deriveKey(key, void 0, "AES-GCM") : key;
|
|
385
|
+
const iv = _b64decode(envelope.iv);
|
|
386
|
+
const data = _b64decode(envelope.data);
|
|
387
|
+
const decrypted = await globalThis.crypto.subtle.decrypt(
|
|
388
|
+
{ name: "AES-GCM", iv },
|
|
389
|
+
cryptoKey,
|
|
390
|
+
data
|
|
391
|
+
);
|
|
392
|
+
return JSON.parse(new TextDecoder().decode(new Uint8Array(decrypted)));
|
|
393
|
+
},
|
|
394
|
+
///
|
|
395
|
+
// Convenience: encrypt with a password (derives the key internally).
|
|
396
|
+
// Returns a JSON string you can store anywhere.
|
|
397
|
+
///
|
|
398
|
+
async encryptWithPassword(value, password, salt, keyId) {
|
|
399
|
+
const env = await this.encrypt(value, password, keyId);
|
|
400
|
+
return JSON.stringify(env);
|
|
401
|
+
},
|
|
402
|
+
///
|
|
403
|
+
// Convenience: decrypt a JSON envelope string with a password.
|
|
404
|
+
///
|
|
405
|
+
async decryptWithPassword(envelopeJson, password, _salt) {
|
|
406
|
+
const envelope = JSON.parse(envelopeJson);
|
|
407
|
+
return this.decrypt(envelope, password);
|
|
408
|
+
},
|
|
409
|
+
///
|
|
410
|
+
// Type guard: check whether a value is an encrypted envelope.
|
|
411
|
+
///
|
|
412
|
+
isEncrypted(value) {
|
|
413
|
+
return _isEncrypted(value);
|
|
414
|
+
},
|
|
415
|
+
///
|
|
416
|
+
// Configure a default encryption key for auto-encrypt mode.
|
|
417
|
+
// When set on `store` or `session`, all writes are encrypted and all
|
|
418
|
+
// reads attempt decryption automatically.
|
|
419
|
+
///
|
|
420
|
+
config(opts) {
|
|
421
|
+
if (opts.key !== void 0) this._defaultKey = opts.key;
|
|
422
|
+
if (opts.keyId !== void 0) this._defaultKeyId = opts.keyId;
|
|
423
|
+
return this;
|
|
424
|
+
},
|
|
425
|
+
///
|
|
426
|
+
// Internal: resolve the configured default key.
|
|
427
|
+
///
|
|
428
|
+
get _key() {
|
|
429
|
+
return this._defaultKey ?? null;
|
|
430
|
+
},
|
|
431
|
+
get _keyId() {
|
|
432
|
+
return this._defaultKeyId;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
203
438
|
// core/platform.ts
|
|
204
439
|
function getSessionStorage() {
|
|
205
440
|
try {
|
|
@@ -236,6 +471,7 @@ var init_platform = __esm({
|
|
|
236
471
|
"core/platform.ts"() {
|
|
237
472
|
init_esm_shims();
|
|
238
473
|
init_internal();
|
|
474
|
+
init_encryption();
|
|
239
475
|
__name(getSessionStorage, "getSessionStorage");
|
|
240
476
|
__name(getLocalStorage, "getLocalStorage");
|
|
241
477
|
__name(hasIndexedDB, "hasIndexedDB");
|
|
@@ -966,6 +1202,7 @@ init_esm_shims();
|
|
|
966
1202
|
init_platform();
|
|
967
1203
|
init_internal();
|
|
968
1204
|
init_message();
|
|
1205
|
+
init_encryption();
|
|
969
1206
|
var _localStorage = getLocalStorage();
|
|
970
1207
|
var _fallbackStorageByContext = /* @__PURE__ */ new Map();
|
|
971
1208
|
function _getFallbackStorage() {
|
|
@@ -978,6 +1215,14 @@ function _getFallbackStorage() {
|
|
|
978
1215
|
}
|
|
979
1216
|
__name(_getFallbackStorage, "_getFallbackStorage");
|
|
980
1217
|
var _hasStorage = _localStorage !== null;
|
|
1218
|
+
var _encryptionKey = null;
|
|
1219
|
+
var _onError = null;
|
|
1220
|
+
var _maxObjectSize = 0;
|
|
1221
|
+
var _maxTotalSize = 0;
|
|
1222
|
+
function _isEncryptedEnvelope(value) {
|
|
1223
|
+
return encryption.isEncrypted(value);
|
|
1224
|
+
}
|
|
1225
|
+
__name(_isEncryptedEnvelope, "_isEncryptedEnvelope");
|
|
981
1226
|
function _getPrefix() {
|
|
982
1227
|
const contextName = internal_default.currentContext || "";
|
|
983
1228
|
return contextName ? `${contextName}-` : `memorio_store_`;
|
|
@@ -1031,16 +1276,24 @@ if (globalThis[STORE_INSTANCE_KEY]) {
|
|
|
1031
1276
|
/**
|
|
1032
1277
|
* Gets a value from storage.
|
|
1033
1278
|
* @param name The key to retrieve.
|
|
1034
|
-
* @
|
|
1279
|
+
* @param opts Optional { decrypt: key } to decrypt an encrypted envelope.
|
|
1280
|
+
* When no key is passed, encrypted envelopes are returned as-is.
|
|
1281
|
+
* If a default key was configured via store.config(), it is used automatically.
|
|
1282
|
+
* @returns The parsed value, a Promise<parsed value> when decrypting, or null if not found.
|
|
1035
1283
|
*/
|
|
1036
1284
|
get: {
|
|
1037
|
-
value: /* @__PURE__ */ __name((name) => {
|
|
1285
|
+
value: /* @__PURE__ */ __name((name, opts) => {
|
|
1038
1286
|
if (!name) {
|
|
1039
1287
|
console.warn("[memorio store] store.get() called with empty or null key - returning null");
|
|
1040
1288
|
return null;
|
|
1041
1289
|
}
|
|
1042
1290
|
try {
|
|
1043
|
-
|
|
1291
|
+
const raw = _read(_prefixKey(name));
|
|
1292
|
+
const key = opts?.decrypt ?? _encryptionKey;
|
|
1293
|
+
if (key && _isEncryptedEnvelope(raw)) {
|
|
1294
|
+
return encryption.decrypt(raw, key);
|
|
1295
|
+
}
|
|
1296
|
+
return raw;
|
|
1044
1297
|
} catch (err) {
|
|
1045
1298
|
message(`Error parsing store item '${name}': ${err}`);
|
|
1046
1299
|
}
|
|
@@ -1065,10 +1318,13 @@ if (globalThis[STORE_INSTANCE_KEY]) {
|
|
|
1065
1318
|
* Sets a value in storage.
|
|
1066
1319
|
* @param name The key to set.
|
|
1067
1320
|
* @param value The value to store (objects, arrays, primitives).
|
|
1068
|
-
* @
|
|
1321
|
+
* @param opts Optional { encrypt: key } to encrypt the value before storage.
|
|
1322
|
+
* If a default key was configured via store.config(), it is used automatically.
|
|
1323
|
+
* When encryption is used, returns a Promise<boolean>.
|
|
1324
|
+
* @returns true if written, false if rejected or error.
|
|
1069
1325
|
*/
|
|
1070
1326
|
set: {
|
|
1071
|
-
value: /* @__PURE__ */ __name((name, value) => {
|
|
1327
|
+
value: /* @__PURE__ */ __name((name, value, opts) => {
|
|
1072
1328
|
if (!name) {
|
|
1073
1329
|
console.warn("[memorio store] store.set() called with empty or null key - write aborted");
|
|
1074
1330
|
return false;
|
|
@@ -1080,15 +1336,66 @@ if (globalThis[STORE_INSTANCE_KEY]) {
|
|
|
1080
1336
|
message("Storing functions is not secure and is blocked for safety.");
|
|
1081
1337
|
return false;
|
|
1082
1338
|
}
|
|
1339
|
+
if (_maxObjectSize > 0 || _maxTotalSize > 0) {
|
|
1340
|
+
let serializedSize = 0;
|
|
1341
|
+
try {
|
|
1342
|
+
serializedSize = new TextEncoder().encode(JSON.stringify(value)).length;
|
|
1343
|
+
} catch {
|
|
1344
|
+
serializedSize = 0;
|
|
1345
|
+
}
|
|
1346
|
+
if (_maxObjectSize > 0 && serializedSize > _maxObjectSize) {
|
|
1347
|
+
const err = new Error(`Object size (${serializedSize} bytes) exceeds maxObjectSize (${_maxObjectSize} bytes)`);
|
|
1348
|
+
_onError?.(err, { operation: "set" });
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
if (_maxTotalSize > 0) {
|
|
1352
|
+
const currentTotal = store.size() * 1024;
|
|
1353
|
+
const existingItemSize = _hasStorage ? _localStorage.getItem(prefixedName)?.length ?? 0 : _getFallbackStorage().get(prefixedName)?.length ?? 0;
|
|
1354
|
+
const projected = currentTotal * 1024 - existingItemSize + serializedSize;
|
|
1355
|
+
if (projected > _maxTotalSize) {
|
|
1356
|
+
const err = new Error(`Total store size (${projected} bytes) exceeds maxTotalSize (${_maxTotalSize} bytes)`);
|
|
1357
|
+
_onError?.(err, { operation: "set" });
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
const key = opts?.encrypt ?? _encryptionKey;
|
|
1363
|
+
if (key) {
|
|
1364
|
+
return encryption.encrypt(value, key).then((env) => {
|
|
1365
|
+
_write(prefixedName, env);
|
|
1366
|
+
return true;
|
|
1367
|
+
}).catch((err) => {
|
|
1368
|
+
message(`Error encrypting store item '${name}': ${err}`);
|
|
1369
|
+
_onError?.(err, { operation: "set" });
|
|
1370
|
+
return false;
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1083
1373
|
try {
|
|
1084
1374
|
_write(prefixedName, value);
|
|
1085
1375
|
} catch (err) {
|
|
1086
1376
|
message(`Error setting store item '${name}': ${err}`);
|
|
1377
|
+
_onError?.(err, { operation: "set" });
|
|
1087
1378
|
return false;
|
|
1088
1379
|
}
|
|
1089
1380
|
return true;
|
|
1090
1381
|
}, "value")
|
|
1091
1382
|
},
|
|
1383
|
+
/**
|
|
1384
|
+
* Configure store-wide behavior.
|
|
1385
|
+
* @param opts Optional { encryptionKey } to enable automatic encryption
|
|
1386
|
+
* for all set/get operations. When set, values are encrypted
|
|
1387
|
+
* on write and decrypted on read (if the value is an envelope).
|
|
1388
|
+
* @returns The store instance for chaining.
|
|
1389
|
+
*/
|
|
1390
|
+
config: {
|
|
1391
|
+
value: /* @__PURE__ */ __name((opts) => {
|
|
1392
|
+
if (opts.encryptionKey !== void 0) _encryptionKey = opts.encryptionKey;
|
|
1393
|
+
if (opts.onError !== void 0) _onError = opts.onError;
|
|
1394
|
+
if (opts.maxObjectSize !== void 0) _maxObjectSize = opts.maxObjectSize;
|
|
1395
|
+
if (opts.maxTotalSize !== void 0) _maxTotalSize = opts.maxTotalSize;
|
|
1396
|
+
return store;
|
|
1397
|
+
}, "value")
|
|
1398
|
+
},
|
|
1092
1399
|
/**
|
|
1093
1400
|
* Removes a value from storage.
|
|
1094
1401
|
* @param name The key to remove.
|
|
@@ -1760,6 +2067,7 @@ init_esm_shims();
|
|
|
1760
2067
|
init_platform();
|
|
1761
2068
|
init_internal();
|
|
1762
2069
|
init_message();
|
|
2070
|
+
init_encryption();
|
|
1763
2071
|
var _sessionStorage = getSessionStorage();
|
|
1764
2072
|
var _fallbackStorageByContext2 = /* @__PURE__ */ new Map();
|
|
1765
2073
|
function _getFallbackStorage2() {
|
|
@@ -1772,6 +2080,14 @@ function _getFallbackStorage2() {
|
|
|
1772
2080
|
}
|
|
1773
2081
|
__name(_getFallbackStorage2, "_getFallbackStorage");
|
|
1774
2082
|
var _hasStorage2 = _sessionStorage !== null;
|
|
2083
|
+
var _encryptionKey2 = null;
|
|
2084
|
+
var _onError2 = null;
|
|
2085
|
+
var _maxObjectSize2 = 0;
|
|
2086
|
+
var _maxTotalSize2 = 0;
|
|
2087
|
+
function _isEncryptedEnvelope2(value) {
|
|
2088
|
+
return encryption.isEncrypted(value);
|
|
2089
|
+
}
|
|
2090
|
+
__name(_isEncryptedEnvelope2, "_isEncryptedEnvelope");
|
|
1775
2091
|
function _getPrefix2() {
|
|
1776
2092
|
const contextName = internal_default.currentContext || "";
|
|
1777
2093
|
return contextName ? `${contextName}-` : `memorio_session_`;
|
|
@@ -1825,16 +2141,22 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
|
|
|
1825
2141
|
/**
|
|
1826
2142
|
* Gets a value from storage.
|
|
1827
2143
|
* @param name The key to retrieve.
|
|
1828
|
-
* @
|
|
2144
|
+
* @param opts Optional { decrypt: key } to decrypt an encrypted envelope.
|
|
2145
|
+
* @returns The parsed value, a Promise when decrypting, or null if not found.
|
|
1829
2146
|
*/
|
|
1830
2147
|
get: {
|
|
1831
|
-
value: /* @__PURE__ */ __name((name) => {
|
|
2148
|
+
value: /* @__PURE__ */ __name((name, opts) => {
|
|
1832
2149
|
if (!name) {
|
|
1833
2150
|
console.warn("[memorio session] session.get() called with empty or null key - returning null");
|
|
1834
2151
|
return null;
|
|
1835
2152
|
}
|
|
1836
2153
|
try {
|
|
1837
|
-
|
|
2154
|
+
const raw = _read2(_prefixKey2(name));
|
|
2155
|
+
const key = opts?.decrypt ?? _encryptionKey2;
|
|
2156
|
+
if (key && _isEncryptedEnvelope2(raw)) {
|
|
2157
|
+
return encryption.decrypt(raw, key);
|
|
2158
|
+
}
|
|
2159
|
+
return raw;
|
|
1838
2160
|
} catch (err) {
|
|
1839
2161
|
message(`Error parsing session item '${name}':${err}`);
|
|
1840
2162
|
}
|
|
@@ -1845,10 +2167,11 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
|
|
|
1845
2167
|
* Sets a value in storage.
|
|
1846
2168
|
* @param name The key to set.
|
|
1847
2169
|
* @param value The value to store.
|
|
1848
|
-
* @
|
|
2170
|
+
* @param opts Optional { encrypt: key } to encrypt before storage.
|
|
2171
|
+
* @returns true if written, false if rejected. Returns a Promise when encrypting.
|
|
1849
2172
|
*/
|
|
1850
2173
|
set: {
|
|
1851
|
-
value: /* @__PURE__ */ __name((name, value) => {
|
|
2174
|
+
value: /* @__PURE__ */ __name((name, value, opts) => {
|
|
1852
2175
|
if (!name) {
|
|
1853
2176
|
console.warn("[memorio session] session.set() called with empty or null key - write aborted");
|
|
1854
2177
|
return false;
|
|
@@ -1860,15 +2183,63 @@ if (globalThis[SESSION_INSTANCE_KEY]) {
|
|
|
1860
2183
|
message("It's not secure to session functions.");
|
|
1861
2184
|
return false;
|
|
1862
2185
|
}
|
|
2186
|
+
if (_maxObjectSize2 > 0 || _maxTotalSize2 > 0) {
|
|
2187
|
+
let serializedSize = 0;
|
|
2188
|
+
try {
|
|
2189
|
+
serializedSize = new TextEncoder().encode(JSON.stringify(value)).length;
|
|
2190
|
+
} catch {
|
|
2191
|
+
serializedSize = 0;
|
|
2192
|
+
}
|
|
2193
|
+
if (_maxObjectSize2 > 0 && serializedSize > _maxObjectSize2) {
|
|
2194
|
+
const err = new Error(`Object size (${serializedSize} bytes) exceeds maxObjectSize (${_maxObjectSize2} bytes)`);
|
|
2195
|
+
_onError2?.(err, { operation: "set" });
|
|
2196
|
+
return false;
|
|
2197
|
+
}
|
|
2198
|
+
if (_maxTotalSize2 > 0) {
|
|
2199
|
+
const currentTotal = session.size() * 1024;
|
|
2200
|
+
const existingItemSize = _sessionStorage ? _sessionStorage.getItem(prefixedName)?.length ?? 0 : _getFallbackStorage2().get(prefixedName)?.length ?? 0;
|
|
2201
|
+
const projected = currentTotal - existingItemSize + serializedSize;
|
|
2202
|
+
if (projected > _maxTotalSize2) {
|
|
2203
|
+
const err = new Error(`Total session size (${projected} bytes) exceeds maxTotalSize (${_maxTotalSize2} bytes)`);
|
|
2204
|
+
_onError2?.(err, { operation: "set" });
|
|
2205
|
+
return false;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
const key = opts?.encrypt ?? _encryptionKey2;
|
|
2210
|
+
if (key) {
|
|
2211
|
+
return encryption.encrypt(value, key).then((env) => {
|
|
2212
|
+
_write2(prefixedName, env);
|
|
2213
|
+
return true;
|
|
2214
|
+
}).catch((err) => {
|
|
2215
|
+
message(`Error encrypting session item '${name}': ${err}`);
|
|
2216
|
+
_onError2?.(err, { operation: "set" });
|
|
2217
|
+
return false;
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
1863
2220
|
try {
|
|
1864
2221
|
_write2(prefixedName, value);
|
|
1865
2222
|
} catch (err) {
|
|
1866
2223
|
message(`Error setting session item '${name}': ${err}`);
|
|
2224
|
+
_onError2?.(err, { operation: "set" });
|
|
1867
2225
|
return false;
|
|
1868
2226
|
}
|
|
1869
2227
|
return true;
|
|
1870
2228
|
}, "value")
|
|
1871
2229
|
},
|
|
2230
|
+
/**
|
|
2231
|
+
* Configure session-wide behavior.
|
|
2232
|
+
* @param opts { encryptionKey, onError, maxObjectSize, maxTotalSize }
|
|
2233
|
+
*/
|
|
2234
|
+
config: {
|
|
2235
|
+
value: /* @__PURE__ */ __name((opts) => {
|
|
2236
|
+
if (opts.encryptionKey !== void 0) _encryptionKey2 = opts.encryptionKey;
|
|
2237
|
+
if (opts.onError !== void 0) _onError2 = opts.onError;
|
|
2238
|
+
if (opts.maxObjectSize !== void 0) _maxObjectSize2 = opts.maxObjectSize;
|
|
2239
|
+
if (opts.maxTotalSize !== void 0) _maxTotalSize2 = opts.maxTotalSize;
|
|
2240
|
+
return session;
|
|
2241
|
+
}, "value")
|
|
2242
|
+
},
|
|
1872
2243
|
/**
|
|
1873
2244
|
* Removes a value from storage.
|
|
1874
2245
|
* @param name The key to remove.
|