@ngx-pwa/local-storage 14.0.0 → 16.0.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.
Files changed (58) hide show
  1. package/{esm2020 → esm2022}/lib/databases/exceptions.mjs +2 -2
  2. package/esm2022/lib/databases/indexeddb-database.mjs +305 -0
  3. package/esm2022/lib/databases/local-database.mjs +67 -0
  4. package/esm2022/lib/databases/localstorage-database.mjs +158 -0
  5. package/esm2022/lib/databases/memory-database.mjs +84 -0
  6. package/{esm2020 → esm2022}/lib/providers.mjs +3 -3
  7. package/esm2022/lib/storage.module.mjs +40 -0
  8. package/esm2022/lib/storages/local-storage.service.mjs +81 -0
  9. package/esm2022/lib/storages/storage-map.service.mjs +293 -0
  10. package/esm2022/lib/tokens.mjs +59 -0
  11. package/esm2022/lib/validation/json-schema.mjs +5 -0
  12. package/esm2022/lib/validation/json-validator.mjs +244 -0
  13. package/esm2022/public-api.mjs +12 -0
  14. package/{fesm2020 → fesm2022}/ngx-pwa-local-storage.mjs +543 -553
  15. package/fesm2022/ngx-pwa-local-storage.mjs.map +1 -0
  16. package/lib/databases/indexeddb-database.d.ts +2 -2
  17. package/lib/databases/local-database.d.ts +1 -1
  18. package/lib/databases/localstorage-database.d.ts +3 -3
  19. package/lib/databases/memory-database.d.ts +2 -2
  20. package/lib/providers.d.ts +1 -1
  21. package/lib/storage.module.d.ts +2 -2
  22. package/lib/storages/local-storage.service.d.ts +3 -3
  23. package/lib/storages/storage-map.service.d.ts +6 -7
  24. package/lib/tokens.d.ts +1 -1
  25. package/lib/validation/json-schema.d.ts +8 -8
  26. package/lib/validation/json-validator.d.ts +1 -1
  27. package/package.json +10 -16
  28. package/public-api.d.ts +10 -10
  29. package/schematics/collection.json +1 -1
  30. package/schematics/migration.json +1 -1
  31. package/schematics/ng-add/index.d.ts +1 -1
  32. package/schematics/ng-add/index.js +7 -7
  33. package/schematics/ng-add/index.js.map +1 -1
  34. package/schematics/ng-add/schema.json +2 -2
  35. package/schematics/ng-update/index.d.ts +1 -1
  36. package/schematics/ng-update/index.js +3 -3
  37. package/schematics/ng-update/index.js.map +1 -1
  38. package/schematics/utility/config.d.ts +1 -1
  39. package/schematics/utility/config.js +7 -7
  40. package/schematics/utility/module.d.ts +1 -1
  41. package/schematics/utility/module.js +6 -6
  42. package/schematics/utility/module.js.map +1 -1
  43. package/esm2020/lib/databases/indexeddb-database.mjs +0 -304
  44. package/esm2020/lib/databases/local-database.mjs +0 -66
  45. package/esm2020/lib/databases/localstorage-database.mjs +0 -160
  46. package/esm2020/lib/databases/memory-database.mjs +0 -83
  47. package/esm2020/lib/storage.module.mjs +0 -39
  48. package/esm2020/lib/storages/local-storage.service.mjs +0 -80
  49. package/esm2020/lib/storages/storage-map.service.mjs +0 -299
  50. package/esm2020/lib/tokens.mjs +0 -59
  51. package/esm2020/lib/validation/json-schema.mjs +0 -5
  52. package/esm2020/lib/validation/json-validator.mjs +0 -243
  53. package/esm2020/public-api.mjs +0 -12
  54. package/fesm2015/ngx-pwa-local-storage.mjs +0 -1432
  55. package/fesm2015/ngx-pwa-local-storage.mjs.map +0 -1
  56. package/fesm2020/ngx-pwa-local-storage.mjs.map +0 -1
  57. /package/{esm2020 → esm2022}/lib/storages/exceptions.mjs +0 -0
  58. /package/{esm2020 → esm2022}/ngx-pwa-local-storage.mjs +0 -0
@@ -1,1432 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, inject, Inject, PLATFORM_ID, NgModule, makeEnvironmentProviders } from '@angular/core';
3
- import { isPlatformBrowser } from '@angular/common';
4
- import { ReplaySubject, fromEvent, race, throwError, of, Observable, asyncScheduler, from } from 'rxjs';
5
- import { mergeMap, map, first, takeWhile, tap, observeOn, catchError } from 'rxjs/operators';
6
-
7
- class JSONValidator {
8
- /**
9
- * Validate a JSON data against a Jsubset of the JSON Schema standard.
10
- * Types are enforced to validate everything: each schema must
11
- * @param data JSON data to validate
12
- * @param schema Subset of JSON Schema. Must have a `type`.
13
- * @returns If data is valid: `true`, if it is invalid: `false`
14
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/VALIDATION.md}
15
- */
16
- validate(data, schema) {
17
- switch (schema.type) {
18
- case 'string':
19
- return this.validateString(data, schema);
20
- case 'number':
21
- case 'integer':
22
- return this.validateNumber(data, schema);
23
- case 'boolean':
24
- return this.validateBoolean(data, schema);
25
- case 'array':
26
- return this.validateArray(data, schema);
27
- case 'object':
28
- return this.validateObject(data, schema);
29
- }
30
- }
31
- /**
32
- * Validate a string
33
- * @param data Data to validate
34
- * @param schema Schema describing the string
35
- * @returns If data is valid: `true`, if it is invalid: `false`
36
- */
37
- validateString(data, schema) {
38
- if (typeof data !== 'string') {
39
- return false;
40
- }
41
- if (!this.validateConst(data, schema)) {
42
- return false;
43
- }
44
- if (!this.validateEnum(data, schema)) {
45
- return false;
46
- }
47
- if ((schema.maxLength !== undefined) && (data.length > schema.maxLength)) {
48
- return false;
49
- }
50
- if ((schema.minLength !== undefined) && (data.length < schema.minLength)) {
51
- return false;
52
- }
53
- if (schema.pattern) {
54
- let regularExpression = null;
55
- try {
56
- regularExpression = new RegExp(schema.pattern);
57
- }
58
- catch (_a) {
59
- // Nothing to do
60
- }
61
- if (regularExpression && !regularExpression.test(data)) {
62
- return false;
63
- }
64
- }
65
- return true;
66
- }
67
- /**
68
- * Validate a number or an integer
69
- * @param data Data to validate
70
- * @param schema Schema describing the number or integer
71
- * @returns If data is valid: `true`, if it is invalid: `false`
72
- */
73
- validateNumber(data, schema) {
74
- if (typeof data !== 'number') {
75
- return false;
76
- }
77
- if ((schema.type === 'integer') && !Number.isInteger(data)) {
78
- return false;
79
- }
80
- if (!this.validateConst(data, schema)) {
81
- return false;
82
- }
83
- if (!this.validateEnum(data, schema)) {
84
- return false;
85
- }
86
- /* Test is done this way to not divide by 0 */
87
- if (schema.multipleOf && !Number.isInteger(data / schema.multipleOf)) {
88
- return false;
89
- }
90
- if ((schema.maximum !== undefined) && (data > schema.maximum)) {
91
- return false;
92
- }
93
- if ((schema.exclusiveMaximum !== undefined) && (data >= schema.exclusiveMaximum)) {
94
- return false;
95
- }
96
- if ((schema.minimum !== undefined) && (data < schema.minimum)) {
97
- return false;
98
- }
99
- if ((schema.exclusiveMinimum !== undefined) && (data <= schema.exclusiveMinimum)) {
100
- return false;
101
- }
102
- return true;
103
- }
104
- /**
105
- * Validate a boolean
106
- * @param data Data to validate
107
- * @param schema Schema describing the boolean
108
- * @returns If data is valid: `true`, if it is invalid: `false`
109
- */
110
- validateBoolean(data, schema) {
111
- if (typeof data !== 'boolean') {
112
- return false;
113
- }
114
- if (!this.validateConst(data, schema)) {
115
- return false;
116
- }
117
- return true;
118
- }
119
- /**
120
- * Validate an array
121
- * @param data Data to validate
122
- * @param schema Schema describing the array
123
- * @returns If data is valid: `true`, if it is invalid: `false`
124
- */
125
- validateArray(data, schema) {
126
- if (!Array.isArray(data)) {
127
- return false;
128
- }
129
- if ((schema.maxItems !== undefined) && (data.length > schema.maxItems)) {
130
- return false;
131
- }
132
- if ((schema.minItems !== undefined) && (data.length < schema.minItems)) {
133
- return false;
134
- }
135
- if (schema.uniqueItems) {
136
- /* Create a set to eliminate values with multiple occurences */
137
- const dataSet = new Set(data);
138
- if (data.length !== dataSet.size) {
139
- return false;
140
- }
141
- }
142
- /* Specific test for tuples */
143
- if (Array.isArray(schema.items)) {
144
- // TODO: cast should not be needed here
145
- return this.validateTuple(data, schema.items);
146
- }
147
- /* Validate all the values in array */
148
- for (const value of data) {
149
- // TODO: remove when TypeScript 4.1 is available
150
- // (currently the narrowed type from `Array.isArray()` is lost on readonly arrays)
151
- if (!this.validate(value, schema.items)) {
152
- return false;
153
- }
154
- }
155
- return true;
156
- }
157
- /**
158
- * Validate a tuple (array with fixed length and multiple types)
159
- * @param data Data to validate
160
- * @param schemas Schemas describing the tuple
161
- * @returns If data is valid: `true`, if it is invalid: `false`
162
- */
163
- validateTuple(data, schemas) {
164
- /* Tuples have a fixed length */
165
- if (data.length !== schemas.length) {
166
- return false;
167
- }
168
- for (let i = 0; i < schemas.length; i += 1) {
169
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
170
- if (!this.validate(data[i], schemas[i])) {
171
- return false;
172
- }
173
- }
174
- return true;
175
- }
176
- /**
177
- * Validate an object
178
- * @param data Data to validate
179
- * @param schema JSON schema describing the object
180
- * @returns If data is valid: `true`, if it is invalid: `false`
181
- */
182
- validateObject(data, schema) {
183
- /* Check the type and if not `null` as `null` also have the type `object` in old browsers */
184
- if ((typeof data !== 'object') || (data === null)) {
185
- return false;
186
- }
187
- /* Check if the object doesn't have more properties than expected
188
- * Equivalent of `additionalProperties: false`
189
- */
190
- if (Object.keys(schema.properties).length < Object.keys(data).length) {
191
- return false;
192
- }
193
- /* Validate required properties */
194
- if (schema.required) {
195
- for (const requiredProp of schema.required) {
196
- if (!Object.prototype.hasOwnProperty.call(data, requiredProp)) {
197
- return false;
198
- }
199
- }
200
- }
201
- /* Recursively validate all properties */
202
- for (const property in schema.properties) {
203
- /* Filter to keep only real properties (no internal JS stuff) and check if the data has the property too */
204
- if (Object.prototype.hasOwnProperty.call(schema.properties, property) && Object.prototype.hasOwnProperty.call(data, property)) {
205
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
206
- if (!this.validate(data[property], schema.properties[property])) {
207
- return false;
208
- }
209
- }
210
- }
211
- return true;
212
- }
213
- /**
214
- * Validate a constant
215
- * @param data Data ta validate
216
- * @param schema JSON schema describing the constant
217
- * @returns If data is valid: `true`, if it is invalid: `false`
218
- */
219
- validateConst(data, schema) {
220
- if (!schema.const) {
221
- return true;
222
- }
223
- return (data === schema.const);
224
- }
225
- /**
226
- * Validate an enum
227
- * @param data Data ta validate
228
- * @param schema JSON schema describing the enum
229
- * @returns If data is valid: `true`, if it is invalid: `false`
230
- */
231
- validateEnum(data, schema) {
232
- if (!schema.enum) {
233
- return true;
234
- }
235
- /* Cast as the data can be of multiple types, and so TypeScript is lost */
236
- return (schema.enum.includes(data));
237
- }
238
- }
239
- JSONValidator.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: JSONValidator, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
240
- JSONValidator.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: JSONValidator, providedIn: 'root' });
241
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: JSONValidator, decorators: [{
242
- type: Injectable,
243
- args: [{
244
- providedIn: 'root'
245
- }]
246
- }] });
247
-
248
- /**
249
- * Token to provide a prefix to `localStorage` keys.
250
- */
251
- const LS_PREFIX = new InjectionToken('localStoragePrefix', {
252
- providedIn: 'root',
253
- factory: () => ''
254
- });
255
- /**
256
- * Default name used for `indexedDB` database.
257
- */
258
- const DEFAULT_IDB_DB_NAME = 'ngStorage';
259
- /**
260
- * Token to provide `indexedDB` database name.
261
- */
262
- const IDB_DB_NAME = new InjectionToken('localStorageIDBDBName', {
263
- providedIn: 'root',
264
- factory: () => DEFAULT_IDB_DB_NAME
265
- });
266
- /**
267
- * Default version used for `indexedDB` database.
268
- */
269
- const DEFAULT_IDB_DB_VERSION = 1;
270
- /**
271
- * Token to provide `indexedDB` database version.
272
- * Must be an unsigned **integer**.
273
- */
274
- const IDB_DB_VERSION = new InjectionToken('localStorageIDBDBVersion', {
275
- providedIn: 'root',
276
- factory: () => DEFAULT_IDB_DB_VERSION
277
- });
278
- /**
279
- * Default name used for `indexedDB` object store.
280
- */
281
- const DEFAULT_IDB_STORE_NAME = 'localStorage';
282
- /**
283
- * Token to provide `indexedDB` store name.
284
- * For backward compatibility, the default can't be set now, `IndexedDBDatabase` will do it at runtime.
285
- */
286
- const IDB_STORE_NAME = new InjectionToken('localStorageIDBStoreName', {
287
- providedIn: 'root',
288
- factory: () => DEFAULT_IDB_STORE_NAME
289
- });
290
- /**
291
- * Default value for interoperability with native `indexedDB` and other storage libs,
292
- * by changing how values are stored in `indexedDB` database.
293
- */
294
- const DEFAULT_IDB_NO_WRAP = true;
295
- /**
296
- * Token to allow interoperability with native `indexedDB` and other storage libs,
297
- * by changing how values are stored in `indexedDB` database.
298
- * Defaults to `true`. Change to `false` for backward compatiblity in existing applications.
299
- * **DO NOT CHANGE THIS BEHAVIOR ONCE IN PRODUCTION**, as it would break with existing data.
300
- */
301
- const IDB_NO_WRAP = new InjectionToken('localStorageIDBWrap', {
302
- providedIn: 'root',
303
- factory: () => DEFAULT_IDB_NO_WRAP
304
- });
305
-
306
- /**
307
- * Exception message when `indexedDB` is not working
308
- */
309
- const IDB_BROKEN_ERROR = 'indexedDB is not working';
310
- /**
311
- * Exception raised when `indexedDB` is not working
312
- */
313
- class IDBBrokenError extends Error {
314
- constructor() {
315
- super(...arguments);
316
- this.message = IDB_BROKEN_ERROR;
317
- }
318
- }
319
- /**
320
- * Exception message when a value can't be serialized for `localStorage`
321
- */
322
- const SERIALIZATION_ERROR = `The storage is currently localStorage,
323
- where data must be serialized, and the provided data can't be serialized.`;
324
- /**
325
- * Exception raised when a value can't be serialized for `localStorage`
326
- */
327
- class SerializationError extends Error {
328
- constructor() {
329
- super(...arguments);
330
- this.message = SERIALIZATION_ERROR;
331
- }
332
- }
333
-
334
- class IndexedDBDatabase {
335
- constructor() {
336
- /**
337
- * `indexedDB` database connection, wrapped in a RxJS `ReplaySubject` to be able to access the connection
338
- * even after the connection success event happened
339
- */
340
- this.database = new ReplaySubject(1);
341
- /**
342
- * Index used when wrapping value. *For backward compatibility only.*
343
- */
344
- this.wrapIndex = 'value';
345
- this.dbName = inject(IDB_DB_NAME);
346
- this.storeName = inject(IDB_STORE_NAME);
347
- this.dbVersion = inject(IDB_DB_VERSION);
348
- this.noWrap = inject(IDB_NO_WRAP);
349
- /* Connect to `indexedDB`, with prefix if provided by the user */
350
- this.connect();
351
- }
352
- /**
353
- * Information about `indexedDB` connection. *Only useful for interoperability.*
354
- * @returns `indexedDB` database name, store name and database version
355
- */
356
- get backingStore() {
357
- return {
358
- database: this.dbName,
359
- store: this.storeName,
360
- version: this.dbVersion,
361
- };
362
- }
363
- /**
364
- * Number of items in our `indexedDB` database and object store
365
- */
366
- get size() {
367
- /* Open a transaction in read-only mode */
368
- return this.transaction('readonly').pipe(mergeMap((transactionData) => {
369
- const { store, events } = transactionData;
370
- /* Request to know the number of items */
371
- const request = store.count();
372
- /* Return the result */
373
- return events.pipe(map(() => request.result));
374
- }),
375
- /* The observable will complete after the first value */
376
- first());
377
- }
378
- /**
379
- * Gets an item value in our `indexedDB` store
380
- * @param key The item's key
381
- * @returns The item's value if the key exists, `undefined` otherwise, wrapped in an RxJS `Observable`
382
- */
383
- get(key) {
384
- /* Open a transaction in read-only mode */
385
- return this.transaction('readonly').pipe(mergeMap((transactionData) => {
386
- const { store, events } = transactionData;
387
- /* Request the value with the key provided by the user */
388
- const request = store.get(key);
389
- /* Listen events and return the result */
390
- return events.pipe(map(() => {
391
- if ((request.result !== undefined) && (request.result !== null)) {
392
- /* Prior to v8, the value was wrapped in an `{ value: ...}` object */
393
- if (!this.noWrap && (typeof request.result === 'object') && (this.wrapIndex in request.result) &&
394
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
395
- (request.result[this.wrapIndex] !== undefined) && (request.result[this.wrapIndex] !== null)) {
396
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
397
- return request.result[this.wrapIndex];
398
- }
399
- else {
400
- /* Cast to the wanted type */
401
- return request.result;
402
- }
403
- }
404
- /* Return `undefined` if the value is empty */
405
- return undefined;
406
- }));
407
- }),
408
- /* The observable will complete after the first value */
409
- first());
410
- }
411
- /**
412
- * Sets an item in our `indexedDB` store
413
- * @param key The item's key
414
- * @param data The item's value
415
- * @returns An RxJS `Observable` to wait the end of the operation
416
- */
417
- set(key, data) {
418
- /* Storing `undefined` in `indexedDb` can cause issues in some browsers so removing item instead */
419
- if (data === undefined) {
420
- return this.delete(key);
421
- }
422
- /* Open a transaction in write mode */
423
- return this.transaction('readwrite').pipe(mergeMap((transactionData) => {
424
- const { store, events } = transactionData;
425
- /* Prior to v8, data was wrapped in a `{ value: ... }` object */
426
- const dataToStore = this.noWrap ? data : { [this.wrapIndex]: data };
427
- /* Add if the item is not existing yet, or update otherwise */
428
- store.put(dataToStore, key);
429
- /* Listen to events and return `undefined` as no value is expected */
430
- return events.pipe(map(() => undefined));
431
- }),
432
- /* The observable will complete after the first value */
433
- first());
434
- }
435
- /**
436
- * Deletes an item in our `indexedDB` store
437
- * @param key The item's key
438
- * @returns An RxJS `Observable` to wait the end of the operation
439
- */
440
- delete(key) {
441
- /* Open a transaction in write mode */
442
- return this.transaction('readwrite').pipe(mergeMap((transactionData) => {
443
- const { store, events } = transactionData;
444
- /* Delete the item in store */
445
- store.delete(key);
446
- /* Listen to events and return `undefined` as no data is expected here */
447
- return events.pipe(map(() => undefined));
448
- }),
449
- /* The observable will complete after the first value */
450
- first());
451
- }
452
- /**
453
- * Deletes all items from our `indexedDB` objet store
454
- * @returns An RxJS `Observable` to wait the end of the operation
455
- */
456
- clear() {
457
- /* Open a transaction in write mode */
458
- return this.transaction('readwrite').pipe(mergeMap((transactionData) => {
459
- const { store, events } = transactionData;
460
- /* Delete all items in object store */
461
- store.clear();
462
- /* Listen to events and return `undefined` as no data is expected here */
463
- return events.pipe(map(() => undefined));
464
- }),
465
- /* The observable will complete */
466
- first());
467
- }
468
- /**
469
- * Get all the keys in our `indexedDB` store
470
- * @returns An RxJS `Observable` iterating on each key
471
- */
472
- keys() {
473
- /* Open a transaction in read-only mode */
474
- return this.transaction('readonly').pipe(
475
- /* `first()` is used as the final operator in other methods to complete the `Observable`
476
- * (as it all starts from a `ReplaySubject` which never ends),
477
- * but as this method is iterating over multiple values, `first()` **must** be used here */
478
- first(), mergeMap((transactionData) => {
479
- const { store } = transactionData;
480
- /* Open a cursor on the store
481
- * Avoid issues like https://github.com/cyrilletuzi/angular-async-local-storage/issues/69 */
482
- const request = store.openKeyCursor();
483
- /* Listen to success event */
484
- const success$ = fromEvent(request, 'success').pipe(
485
- /* Stop the `Observable` when the cursor is `null` */
486
- takeWhile(() => (request.result !== null)),
487
- /* This lib only allows string keys, but user could have added other types of keys from outside
488
- * It's OK to cast as the cursor as been tested in the previous operator */
489
- map(() => request.result.key.toString()),
490
- /* Iterate on the cursor */
491
- tap(() => { request.result.continue(); }));
492
- /* Listen to error event and if so, throw an error */
493
- const error$ = this.listenError(request);
494
- /* Choose the first event to occur */
495
- return race([success$, error$]);
496
- }));
497
- }
498
- /**
499
- * Check if a key exists in our `indexedDB` store
500
- * @returns An RxJS `Observable` telling if the key exists or not
501
- */
502
- has(key) {
503
- /* Open a transaction in read-only mode */
504
- return this.transaction('readonly').pipe(mergeMap((transactionData) => {
505
- const { store, events } = transactionData;
506
- /* Check if the key exists in the store
507
- * Fixes https://github.com/cyrilletuzi/angular-async-local-storage/issues/69
508
- */
509
- const request = store.getKey(key);
510
- /* Listen to events and return `true` or `false` */
511
- return events.pipe(map(() => (request.result !== undefined) ? true : false));
512
- }),
513
- /* The observable will complete */
514
- first());
515
- }
516
- /**
517
- * Connects to `indexedDB` and creates the object store on first time
518
- */
519
- connect() {
520
- let request;
521
- /* Connect to `indexedDB`
522
- * Will fail in Safari cross-origin iframes
523
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/issues/42} */
524
- try {
525
- /* Do NOT explicit `window` here, as `indexedDB` could be used from a web worker too */
526
- request = indexedDB.open(this.dbName, this.dbVersion);
527
- }
528
- catch (_a) {
529
- this.database.error(new IDBBrokenError());
530
- return;
531
- }
532
- /* Create store on first connection */
533
- this.createStore(request);
534
- /* Listen to success and error events */
535
- const success$ = fromEvent(request, 'success');
536
- const error$ = this.listenError(request);
537
- /* Choose the first to occur */
538
- race([success$, error$])
539
- /* The observable will complete */
540
- .pipe(first())
541
- .subscribe({
542
- next: () => {
543
- /* Register the database connection in the `ReplaySubject` for further access */
544
- this.database.next(request.result);
545
- },
546
- error: () => {
547
- /* Firefox private mode issue: fallback storage if IndexedDb connection is failing
548
- * @see {@link https://bugzilla.mozilla.org/show_bug.cgi?id=781982}
549
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/issues/26} */
550
- this.database.error(new IDBBrokenError());
551
- },
552
- });
553
- }
554
- /**
555
- * Create store on first use of `indexedDB`
556
- * @param request `indexedDB` database opening request
557
- */
558
- createStore(request) {
559
- /* Listen to the event fired on first connection */
560
- fromEvent(request, 'upgradeneeded')
561
- /* The observable will complete */
562
- .pipe(first())
563
- .subscribe({
564
- next: () => {
565
- /* Check if the store already exists, to avoid error */
566
- if (!request.result.objectStoreNames.contains(this.storeName)) {
567
- /* Create the object store */
568
- request.result.createObjectStore(this.storeName);
569
- }
570
- }
571
- });
572
- }
573
- /**
574
- * Open an `indexedDB` transaction and get our store
575
- * @param mode `readonly` or `readwrite`
576
- * @returns An `indexedDB` transaction store and events, wrapped in an RxJS `Observable`
577
- */
578
- transaction(mode) {
579
- /* From the `indexedDB` connection, open a transaction and get the store */
580
- return this.database
581
- .pipe(mergeMap((database) => {
582
- let transaction;
583
- try {
584
- transaction = database.transaction([this.storeName], mode);
585
- }
586
- catch (error) {
587
- /* The store could have been deleted from outside */
588
- return throwError(() => error);
589
- }
590
- /* Get the store from the transaction */
591
- const store = transaction.objectStore(this.storeName);
592
- /* Listen transaction `complete` and `error` events */
593
- const events = this.listenTransactionEvents(transaction);
594
- return of({ store, events });
595
- }));
596
- }
597
- /**
598
- * Listen errors on a transaction or request, and throw if trigerred
599
- * @param transactionOrRequest `indexedDb` transaction or request to listen
600
- * @returns An `Observable` listening to errors
601
- */
602
- listenError(transactionOrRequest) {
603
- return fromEvent(transactionOrRequest, 'error').pipe(
604
- /* Throw on error to be able to catch errors in RxJS way.
605
- * Here `event.target` must be used, as `transactionOrRequest.error` will be `null`
606
- * if we are on the request and the error is only triggered later by the transaction */
607
- mergeMap((event) => throwError(() => event.target.error)));
608
- }
609
- /**
610
- * Listen transaction `complete` and `error` events
611
- * @param transaction Transaction to listen
612
- * @returns An `Observable` listening to transaction `complete` and `error` events
613
- */
614
- listenTransactionEvents(transaction) {
615
- /* Listen to the `complete` event */
616
- const complete$ = fromEvent(transaction, 'complete');
617
- /* Listen to the `error` event */
618
- const error$ = this.listenError(transaction);
619
- /* Choose the first event to occur */
620
- return race([complete$, error$]);
621
- }
622
- }
623
- IndexedDBDatabase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IndexedDBDatabase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
624
- IndexedDBDatabase.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IndexedDBDatabase, providedIn: 'root' });
625
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: IndexedDBDatabase, decorators: [{
626
- type: Injectable,
627
- args: [{
628
- providedIn: 'root'
629
- }]
630
- }], ctorParameters: function () { return []; } });
631
-
632
- class LocalStorageDatabase {
633
- // `inject()` cannot be used here, as `LocalStorageDatabase` is instanciated at runtime in some scenarios
634
- constructor(prefix = '') {
635
- /* Prefix if asked, or no prefix otherwise */
636
- this.prefix = prefix || '';
637
- }
638
- /**
639
- * Number of items in `localStorage`
640
- */
641
- get size() {
642
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
643
- return of(localStorage.length);
644
- }
645
- /**
646
- * Gets an item value in `localStorage`
647
- * @param key The item's key
648
- * @returns The item's value if the key exists, `undefined` otherwise, wrapped in a RxJS `Observable`
649
- */
650
- get(key) {
651
- /* Get raw data */
652
- const unparsedData = localStorage.getItem(this.prefixKey(key));
653
- let parsedData;
654
- /* No need to parse if data is `null` or `undefined` */
655
- if ((unparsedData !== undefined) && (unparsedData !== null)) {
656
- /* Try to parse */
657
- try {
658
- parsedData = JSON.parse(unparsedData);
659
- }
660
- catch (error) {
661
- return throwError(() => error);
662
- }
663
- }
664
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
665
- return of(parsedData);
666
- }
667
- /**
668
- * Store an item in `localStorage`
669
- * @param key The item's key
670
- * @param data The item's value
671
- * @returns A RxJS `Observable` to wait the end of the operation
672
- */
673
- set(key, data) {
674
- let serializedData = null;
675
- /* Check if data can be serialized */
676
- const dataPrototype = Object.getPrototypeOf(data);
677
- if ((typeof data === 'object') && (data !== null) && !Array.isArray(data) &&
678
- !((dataPrototype === Object.prototype) || (dataPrototype === null))) {
679
- return throwError(() => new SerializationError());
680
- }
681
- /* Try to stringify (can fail on circular references) */
682
- try {
683
- serializedData = JSON.stringify(data);
684
- }
685
- catch (error) {
686
- return throwError(() => error);
687
- }
688
- /* Can fail if storage quota is exceeded */
689
- try {
690
- localStorage.setItem(this.prefixKey(key), serializedData);
691
- }
692
- catch (error) {
693
- return throwError(() => error);
694
- }
695
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
696
- return of(undefined);
697
- }
698
- /**
699
- * Deletes an item in `localStorage`
700
- * @param key The item's key
701
- * @returns A RxJS `Observable` to wait the end of the operation
702
- */
703
- delete(key) {
704
- localStorage.removeItem(this.prefixKey(key));
705
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
706
- return of(undefined);
707
- }
708
- /**
709
- * Deletes all items in `localStorage`
710
- * @returns A RxJS `Observable` to wait the end of the operation
711
- */
712
- clear() {
713
- localStorage.clear();
714
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
715
- return of(undefined);
716
- }
717
- /**
718
- * Get all keys in `localStorage`
719
- * Note the order of the keys may be inconsistent in Firefox
720
- * @returns A RxJS `Observable` iterating on keys
721
- */
722
- keys() {
723
- /* Create an `Observable` from keys */
724
- return new Observable((subscriber) => {
725
- /* Iteretate over all the indexes */
726
- for (let index = 0; index < localStorage.length; index += 1) {
727
- /* Cast as we are sure in this case the key is not `null` */
728
- subscriber.next(this.getUnprefixedKey(index));
729
- }
730
- subscriber.complete();
731
- }).pipe(
732
- /* Required to work like other databases which are asynchronous */
733
- observeOn(asyncScheduler));
734
- }
735
- /**
736
- * Check if a key exists in `localStorage`
737
- * @param key The item's key
738
- * @returns A RxJS `Observable` telling if the key exists or not
739
- */
740
- has(key) {
741
- /* Itérate over all indexes in storage */
742
- for (let index = 0; index < localStorage.length; index += 1) {
743
- if (key === this.getUnprefixedKey(index)) {
744
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
745
- return of(true);
746
- }
747
- }
748
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
749
- return of(false);
750
- }
751
- /**
752
- * Get an unprefixed key
753
- * @param index Index of the key
754
- * @returns The unprefixed key name if exists, `null` otherwise
755
- */
756
- getUnprefixedKey(index) {
757
- /* Get the key in storage: may have a prefix */
758
- const prefixedKey = localStorage.key(index);
759
- if (prefixedKey !== null) {
760
- /* If no prefix, the key is already good, otherwrite strip the prefix */
761
- return !this.prefix ? prefixedKey : prefixedKey.substr(this.prefix.length);
762
- }
763
- return null;
764
- }
765
- /**
766
- * Add the prefix to a key
767
- * @param key The key name
768
- * @returns The prefixed key name
769
- */
770
- prefixKey(key) {
771
- return `${this.prefix}${key}`;
772
- }
773
- }
774
- LocalStorageDatabase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalStorageDatabase, deps: [{ token: LS_PREFIX }], target: i0.ɵɵFactoryTarget.Injectable });
775
- LocalStorageDatabase.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalStorageDatabase, providedIn: 'root' });
776
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalStorageDatabase, decorators: [{
777
- type: Injectable,
778
- args: [{
779
- providedIn: 'root'
780
- }]
781
- }], ctorParameters: function () {
782
- return [{ type: undefined, decorators: [{
783
- type: Inject,
784
- args: [LS_PREFIX]
785
- }] }];
786
- } });
787
-
788
- class MemoryDatabase {
789
- constructor() {
790
- /**
791
- * Memory storage
792
- */
793
- this.memoryStorage = new Map();
794
- }
795
- /**
796
- * Number of items in memory
797
- */
798
- get size() {
799
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
800
- return of(this.memoryStorage.size);
801
- }
802
- /**
803
- * Gets an item value in memory
804
- * @param key The item's key
805
- * @returns The item's value if the key exists, `undefined` otherwise, wrapped in a RxJS `Observable`
806
- */
807
- get(key) {
808
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
809
- return of(this.memoryStorage.get(key));
810
- }
811
- /**
812
- * Sets an item in memory
813
- * @param key The item's key
814
- * @param data The item's value
815
- * @returns A RxJS `Observable` to wait the end of the operation
816
- */
817
- set(key, data) {
818
- this.memoryStorage.set(key, data);
819
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
820
- return of(undefined);
821
- }
822
- /**
823
- * Deletes an item in memory
824
- * @param key The item's key
825
- * @returns A RxJS `Observable` to wait the end of the operation
826
- */
827
- delete(key) {
828
- this.memoryStorage.delete(key);
829
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
830
- return of(undefined);
831
- }
832
- /**
833
- * Deletes all items in memory
834
- * @returns A RxJS `Observable` to wait the end of the operation
835
- */
836
- clear() {
837
- this.memoryStorage.clear();
838
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
839
- return of(undefined);
840
- }
841
- /**
842
- * Get all keys in memory
843
- * @returns A RxJS `Observable` iterating on keys
844
- */
845
- keys() {
846
- /* Create an `Observable` from keys */
847
- return from(this.memoryStorage.keys());
848
- }
849
- /**
850
- * Check if a key exists in memory
851
- * @param key Key name
852
- * @returns a RxJS `Observable` telling if the key exists or not
853
- */
854
- has(key) {
855
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
856
- return of(this.memoryStorage.has(key));
857
- }
858
- }
859
- MemoryDatabase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MemoryDatabase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
860
- MemoryDatabase.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MemoryDatabase, providedIn: 'root' });
861
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: MemoryDatabase, decorators: [{
862
- type: Injectable,
863
- args: [{
864
- providedIn: 'root'
865
- }]
866
- }] });
867
-
868
- /**
869
- * Factory to create a storage according to browser support
870
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/BROWSERS_SUPPORT.md}
871
- */
872
- function localDatabaseFactory() {
873
- const platformId = inject(PLATFORM_ID);
874
- /* When storage is fully disabled in browser (via the "Block all cookies" option),
875
- * just trying to check `indexedDB` or `localStorage` variables causes a security exception.
876
- * Prevents https://github.com/cyrilletuzi/angular-async-local-storage/issues/118
877
- */
878
- try {
879
- // Do not explicit `window` here, as the global object is not the same in web workers
880
- if (isPlatformBrowser(platformId) && (indexedDB !== undefined) && (indexedDB !== null) && ('open' in indexedDB)) {
881
- /* Check:
882
- * - if we are in a browser context (issue: server-side rendering)
883
- * - it could exist but be `undefined` or `null`
884
- * - it could exists but not having a working API
885
- * Will be the case for:
886
- * - All other browsers in normal mode
887
- * - Chromium / Safari private mode, but in this case, data will be swiped when the user leaves the app */
888
- return new IndexedDBDatabase();
889
- }
890
- else if (isPlatformBrowser(platformId)
891
- && (localStorage !== undefined) && (localStorage !== null) && ('getItem' in localStorage)) {
892
- /* Check:
893
- * - if we are in a browser context (issue: server-side rendering)
894
- * - if `localStorage` exists (to be sure)
895
- * - it could exists but not having a working API
896
- * Will be the case for:
897
- * - Safari cross-origin iframes, detected later in `IndexedDBDatabase.connect()`
898
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/issues/42}
899
- * - Firefox private mode, but in this case, data will be swiped when the user leaves the app
900
- * For Firefox, can only be detected later in `IndexedDBDatabase.connect()`
901
- * @see {@link https://bugzilla.mozilla.org/show_bug.cgi?id=781982}
902
- */
903
- return new LocalStorageDatabase();
904
- }
905
- }
906
- catch (_a) {
907
- // Nothing to do
908
- }
909
- /* Will be the case for:
910
- * - In browsers if storage has been fully disabled (via the "Block all cookies" option)
911
- * - Server-side rendering
912
- * - All other non-browser context
913
- */
914
- return new MemoryDatabase();
915
- }
916
- class LocalDatabase {
917
- }
918
- LocalDatabase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalDatabase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
919
- LocalDatabase.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalDatabase, providedIn: 'root', useFactory: localDatabaseFactory });
920
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalDatabase, decorators: [{
921
- type: Injectable,
922
- args: [{
923
- providedIn: 'root',
924
- useFactory: localDatabaseFactory,
925
- }]
926
- }] });
927
-
928
- /**
929
- * Exception message when a value is not valid against the JSON schema
930
- */
931
- const VALIDATION_ERROR = `Data stored is not valid against the provided JSON schema.
932
- Check your JSON schema, otherwise it means data has been corrupted.`;
933
- /**
934
- * Exception raised when a value is not valid against the JSON schema
935
- */
936
- class ValidationError extends Error {
937
- constructor() {
938
- super(...arguments);
939
- this.message = VALIDATION_ERROR;
940
- }
941
- }
942
-
943
- class StorageMap {
944
- /**
945
- * Constructor params are provided by Angular (but can also be passed manually in tests)
946
- * @param database Storage to use
947
- * @param jsonValidator Validator service
948
- * @param LSPrefix Prefix for `localStorage` keys to avoid collision for multiple apps on the same subdomain or for interoperability
949
- */
950
- constructor(database, jsonValidator = new JSONValidator(),
951
- // `inject()` cannot be used here, as `StorageMap` needs other manual params
952
- LSPrefix = '') {
953
- this.database = database;
954
- this.jsonValidator = jsonValidator;
955
- this.LSPrefix = LSPrefix;
956
- this.notifiers = new Map();
957
- }
958
- /**
959
- * **Number of items** in storage, wrapped in an `Observable`.
960
- *
961
- * @example
962
- * this.storageMap.size.subscribe((size) => {
963
- * console.log(size);
964
- * });
965
- */
966
- get size() {
967
- return this.database.size
968
- /* Catch if `indexedDb` is broken */
969
- .pipe(this.catchIDBBroken(() => this.database.size));
970
- }
971
- /**
972
- * Tells you which storage engine is used. *Only useful for interoperability.*
973
- * Note that due to some browsers issues in some special contexts
974
- * (Firefox private mode and Safari cross-origin iframes),
975
- * **this information may be wrong at initialization,**
976
- * as the storage could fallback from `indexedDB` to `localStorage`
977
- * only after a first read or write operation.
978
- * @returns Storage engine used
979
- *
980
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/INTEROPERABILITY.md}
981
- *
982
- * @example
983
- * if (this.storageMap.backingEngine === 'indexedDB') {}
984
- */
985
- get backingEngine() {
986
- if (this.database instanceof IndexedDBDatabase) {
987
- return 'indexedDB';
988
- }
989
- else if (this.database instanceof LocalStorageDatabase) {
990
- return 'localStorage';
991
- }
992
- else if (this.database instanceof MemoryDatabase) {
993
- return 'memory';
994
- }
995
- else {
996
- return 'unknown';
997
- }
998
- }
999
- /**
1000
- * Info about `indexedDB` database. *Only useful for interoperability.*
1001
- * @returns `indexedDB` database name, store name and database version.
1002
- * **Values will be empty if the storage is not `indexedDB`,**
1003
- * **so it should be used after an engine check**.
1004
- *
1005
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/INTEROPERABILITY.md}
1006
- *
1007
- * @example
1008
- * if (this.storageMap.backingEngine === 'indexedDB') {
1009
- * const { database, store, version } = this.storageMap.backingStore;
1010
- * }
1011
- */
1012
- get backingStore() {
1013
- return (this.database instanceof IndexedDBDatabase) ?
1014
- this.database.backingStore :
1015
- { database: '', store: '', version: 0 };
1016
- }
1017
- /**
1018
- * Info about `localStorage` fallback storage. *Only useful for interoperability.*
1019
- * @returns `localStorage` prefix.
1020
- * **Values will be empty if the storage is not `localStorage`,**
1021
- * **so it should be used after an engine check**.
1022
- *
1023
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/INTEROPERABILITY.md}
1024
- *
1025
- * @example
1026
- * if (this.storageMap.backingEngine === 'localStorage') {
1027
- * const { prefix } = this.storageMap.fallbackBackingStore;
1028
- * }
1029
- */
1030
- get fallbackBackingStore() {
1031
- return (this.database instanceof LocalStorageDatabase) ?
1032
- { prefix: this.database.prefix } :
1033
- { prefix: '' };
1034
- }
1035
- get(key, schema) {
1036
- /* Get the data in storage */
1037
- return this.database.get(key).pipe(
1038
- /* Check if `indexedDb` is broken */
1039
- this.catchIDBBroken(() => this.database.get(key)), mergeMap((data) => {
1040
- /* No need to validate if the data is empty */
1041
- if ((data === undefined) || (data === null)) {
1042
- return of(undefined);
1043
- }
1044
- else if (schema) {
1045
- /* Validate data against a JSON schema if provided */
1046
- if (!this.jsonValidator.validate(data, schema)) {
1047
- return throwError(() => new ValidationError());
1048
- }
1049
- /* Data have been checked, so it's OK to cast */
1050
- return of(data);
1051
- }
1052
- /* Cast to unknown as the data wasn't checked */
1053
- return of(data);
1054
- }));
1055
- }
1056
- /**
1057
- * Set an item in storage.
1058
- * Note that setting `null` or `undefined` will remove the item to avoid some browsers issues.
1059
- * @param key The item's key
1060
- * @param data The item's value
1061
- * @param schema Optional JSON schema to validate the data
1062
- * @returns A RxJS `Observable` to wait the end of the operation
1063
- *
1064
- * @example
1065
- * this.storageMap.set('key', 'value').subscribe(() => {});
1066
- */
1067
- set(key, data, schema) {
1068
- /* Storing `undefined` or `null` is useless and can cause issues in `indexedDb` in some browsers,
1069
- * so removing item instead for all storages to have a consistent API */
1070
- if ((data === undefined) || (data === null)) {
1071
- return this.delete(key);
1072
- }
1073
- /* Validate data against a JSON schema if provided */
1074
- if (schema && !this.jsonValidator.validate(data, schema)) {
1075
- return throwError(() => new ValidationError());
1076
- }
1077
- return this.database.set(key, data).pipe(
1078
- /* Catch if `indexedDb` is broken */
1079
- this.catchIDBBroken(() => this.database.set(key, data)),
1080
- /* Notify watchers (must be last because it should only happen if the operation succeeds) */
1081
- tap(() => { this.notify(key, data); }));
1082
- }
1083
- /**
1084
- * Delete an item in storage
1085
- * @param key The item's key
1086
- * @returns A RxJS `Observable` to wait the end of the operation
1087
- *
1088
- * @example
1089
- * this.storageMap.delete('key').subscribe(() => {});
1090
- */
1091
- delete(key) {
1092
- return this.database.delete(key).pipe(
1093
- /* Catch if `indexedDb` is broken */
1094
- this.catchIDBBroken(() => this.database.delete(key)),
1095
- /* Notify watchers (must be last because it should only happen if the operation succeeds) */
1096
- tap(() => { this.notify(key, undefined); }));
1097
- }
1098
- /**
1099
- * Delete all items in storage
1100
- * @returns A RxJS `Observable` to wait the end of the operation
1101
- *
1102
- * @example
1103
- * this.storageMap.clear().subscribe(() => {});
1104
- */
1105
- clear() {
1106
- return this.database.clear().pipe(
1107
- /* Catch if `indexedDb` is broken */
1108
- this.catchIDBBroken(() => this.database.clear()),
1109
- /* Notify watchers (must be last because it should only happen if the operation succeeds) */
1110
- tap(() => {
1111
- for (const key of this.notifiers.keys()) {
1112
- this.notify(key, undefined);
1113
- }
1114
- }));
1115
- }
1116
- /**
1117
- * Get all keys stored in storage. Note **this is an *iterating* `Observable`**:
1118
- * * if there is no key, the `next` callback will not be invoked,
1119
- * * if you need to wait the whole operation to end, be sure to act in the `complete` callback,
1120
- * as this `Observable` can emit several values and so will invoke the `next` callback several times.
1121
- * @returns A list of the keys wrapped in a RxJS `Observable`
1122
- *
1123
- * @example
1124
- * this.storageMap.keys().subscribe({
1125
- * next: (key) => { console.log(key); },
1126
- * complete: () => { console.log('Done'); },
1127
- * });
1128
- */
1129
- keys() {
1130
- return this.database.keys()
1131
- /* Catch if `indexedDb` is broken */
1132
- .pipe(this.catchIDBBroken(() => this.database.keys()));
1133
- }
1134
- /**
1135
- * Tells if a key exists in storage
1136
- * @returns A RxJS `Observable` telling if the key exists
1137
- *
1138
- * @example
1139
- * this.storageMap.has('key').subscribe((hasKey) => {
1140
- * if (hasKey) {}
1141
- * });
1142
- */
1143
- has(key) {
1144
- return this.database.has(key)
1145
- /* Catch if `indexedDb` is broken */
1146
- .pipe(this.catchIDBBroken(() => this.database.has(key)));
1147
- }
1148
- watch(key, schema) {
1149
- /* Check if there is already a notifier */
1150
- if (!this.notifiers.has(key)) {
1151
- this.notifiers.set(key, new ReplaySubject(1));
1152
- }
1153
- /* Non-null assertion is required because TypeScript doesn't narrow `.has()` yet */
1154
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1155
- const notifier = this.notifiers.get(key);
1156
- /* Get the current item value */
1157
- (schema ? this.get(key, schema) : this.get(key)).subscribe({
1158
- next: (result) => notifier.next(result),
1159
- error: (error) => notifier.error(error),
1160
- });
1161
- /* Only the public API of the `Observable` should be returned */
1162
- return (schema ?
1163
- notifier.asObservable() :
1164
- notifier.asObservable());
1165
- }
1166
- /**
1167
- * Notify when a value changes
1168
- * @param key The item's key
1169
- * @param data The new value
1170
- */
1171
- notify(key, value) {
1172
- const notifier = this.notifiers.get(key);
1173
- if (notifier) {
1174
- notifier.next(value);
1175
- }
1176
- }
1177
- /**
1178
- * RxJS operator to catch if `indexedDB` is broken
1179
- * @param operationCallback Callback with the operation to redo
1180
- */
1181
- catchIDBBroken(operationCallback) {
1182
- return catchError((error) => {
1183
- /* Check if `indexedDB` is broken based on error message (the specific error class seems to be lost in the process) */
1184
- if ((error !== undefined) && (error !== null)
1185
- && (typeof error === 'object') && ('message' in error)
1186
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1187
- && (error.message === IDB_BROKEN_ERROR)) {
1188
- /* When storage is fully disabled in browser (via the "Block all cookies" option),
1189
- * just trying to check `localStorage` variable causes a security exception.
1190
- * Prevents https://github.com/cyrilletuzi/angular-async-local-storage/issues/118
1191
- */
1192
- try {
1193
- if ('getItem' in localStorage) {
1194
- /* Fallback to `localStorage` if available */
1195
- this.database = new LocalStorageDatabase(this.LSPrefix);
1196
- }
1197
- else {
1198
- /* Fallback to memory storage otherwise */
1199
- this.database = new MemoryDatabase();
1200
- }
1201
- }
1202
- catch (_a) {
1203
- /* Fallback to memory storage otherwise */
1204
- this.database = new MemoryDatabase();
1205
- }
1206
- /* Redo the operation */
1207
- return operationCallback();
1208
- }
1209
- else {
1210
- /* Otherwise, rethrow the error */
1211
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
1212
- return throwError(() => error);
1213
- }
1214
- });
1215
- }
1216
- }
1217
- StorageMap.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StorageMap, deps: [{ token: LocalDatabase }, { token: JSONValidator }, { token: LS_PREFIX }], target: i0.ɵɵFactoryTarget.Injectable });
1218
- StorageMap.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StorageMap, providedIn: 'root' });
1219
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StorageMap, decorators: [{
1220
- type: Injectable,
1221
- args: [{
1222
- providedIn: 'root'
1223
- }]
1224
- }], ctorParameters: function () {
1225
- return [{ type: LocalDatabase }, { type: JSONValidator }, { type: undefined, decorators: [{
1226
- type: Inject,
1227
- args: [LS_PREFIX]
1228
- }] }];
1229
- } });
1230
-
1231
- class LocalStorage {
1232
- /* Use the `StorageMap` service to avoid code duplication */
1233
- constructor(storageMap) {
1234
- this.storageMap = storageMap;
1235
- }
1236
- /**
1237
- * Number of items in storage wrapped in an `Observable`
1238
- *
1239
- * @example
1240
- * this.localStorage.length.subscribe((length) => {
1241
- * console.log(length);
1242
- * });
1243
- */
1244
- get length() {
1245
- return this.storageMap.size;
1246
- }
1247
- getItem(key, schema) {
1248
- if (schema) {
1249
- /* Backward compatibility with version <= 7 */
1250
- schema = ('schema' in schema) ? schema.schema : schema;
1251
- }
1252
- return (schema ? this.storageMap.get(key, schema) : this.storageMap.get(key)).pipe(
1253
- /* Transform `undefined` into `null` to align with `localStorage` API */
1254
- map((value) => (value !== undefined) ? value : null));
1255
- }
1256
- /**
1257
- * Set an item in storage.
1258
- * Note that setting `null` or `undefined` will remove the item to avoid some browsers issues.
1259
- * @param key The item's key
1260
- * @param data The item's value
1261
- * @param schema Optional JSON schema to validate the data
1262
- * @returns A RxJS `Observable` to wait the end of the operation
1263
- *
1264
- * @example
1265
- * this.localStorage.set('key', 'value').subscribe(() => {});
1266
- */
1267
- setItem(key, data, schema) {
1268
- return this.storageMap.set(key, data, schema).pipe(
1269
- /* Transform `undefined` into `true` for backward compatibility with v7 */
1270
- map(() => true));
1271
- }
1272
- /**
1273
- * Delete an item in storage
1274
- * @param key The item's key
1275
- * @returns A RxJS `Observable` to wait the end of the operation
1276
- *
1277
- * @example
1278
- * this.localStorage.delete('key').subscribe(() => {});
1279
- */
1280
- removeItem(key) {
1281
- return this.storageMap.delete(key).pipe(
1282
- /* Transform `undefined` into `true` for backward compatibility with v7 */
1283
- map(() => true));
1284
- }
1285
- /**
1286
- * Delete all items in storage
1287
- * @returns A RxJS `Observable` to wait the end of the operation
1288
- *
1289
- * @example
1290
- * this.localStorage.clear().subscribe(() => {});
1291
- */
1292
- clear() {
1293
- return this.storageMap.clear().pipe(
1294
- /* Transform `undefined` into `true` for backward compatibility with v7 */
1295
- map(() => true));
1296
- }
1297
- }
1298
- LocalStorage.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalStorage, deps: [{ token: StorageMap }], target: i0.ɵɵFactoryTarget.Injectable });
1299
- LocalStorage.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalStorage, providedIn: 'root' });
1300
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: LocalStorage, decorators: [{
1301
- type: Injectable,
1302
- args: [{
1303
- providedIn: 'root'
1304
- }]
1305
- }], ctorParameters: function () { return [{ type: StorageMap }]; } });
1306
-
1307
- /**
1308
- * This module does not contain anything, it's only useful to provide options via `.forRoot()`.
1309
- */
1310
- class StorageModule {
1311
- /**
1312
- * Only useful to provide options, otherwise it does nothing.
1313
- * **Must be used at initialization, ie. in `AppModule`, and must not be loaded again in another module.**
1314
- *
1315
- * @example
1316
- * NgModule({
1317
- * imports: [StorageModule.forRoot({
1318
- * LSPrefix: 'custom_',
1319
- * })]
1320
- * })
1321
- * export class AppModule
1322
- */
1323
- static forRoot(config) {
1324
- return {
1325
- ngModule: StorageModule,
1326
- providers: [
1327
- config.LSPrefix ? { provide: LS_PREFIX, useValue: config.LSPrefix } : [],
1328
- config.IDBDBName ? { provide: IDB_DB_NAME, useValue: config.IDBDBName } : [],
1329
- config.IDBStoreName ? { provide: IDB_STORE_NAME, useValue: config.IDBStoreName } : [],
1330
- config.IDBDBVersion ? { provide: IDB_DB_VERSION, useValue: config.IDBDBVersion } : [],
1331
- (config.IDBNoWrap === false) ? { provide: IDB_NO_WRAP, useValue: config.IDBNoWrap } : [],
1332
- ],
1333
- };
1334
- }
1335
- }
1336
- StorageModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StorageModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1337
- StorageModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.4", ngImport: i0, type: StorageModule });
1338
- StorageModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StorageModule });
1339
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.4", ngImport: i0, type: StorageModule, decorators: [{
1340
- type: NgModule
1341
- }] });
1342
-
1343
- /**
1344
- * Allows to add a prefix before `localStorage` keys.
1345
- * *Use only* for interoperability with other APIs or to avoid collision for multiple apps on the same subdomain.
1346
- * **WARNING: do not change this option in an app already deployed in production, as previously stored data would be lost.**
1347
- *
1348
- * @example
1349
- * bootstrapApplication(AppComponent, {
1350
- * providers: [provideLocalStoragePrefix('custom_')]
1351
- * });
1352
- *
1353
- * @example
1354
- * NgModule({
1355
- * providers: [provideLocalStoragePrefix('custom_')]
1356
- * })
1357
- * export class AppModule
1358
- */
1359
- function provideLocalStoragePrefix(prefix) {
1360
- return makeEnvironmentProviders([{ provide: LS_PREFIX, useValue: prefix }]);
1361
- }
1362
- /**
1363
- * Allows to change the name used for `indexedDB` database.
1364
- * *Use only* for interoperability with other APIs or to avoid collision for multiple apps on the same subdomain.
1365
- * **WARNING: do not change this option in an app already deployed in production, as previously stored data would be lost.**
1366
- *
1367
- * @example
1368
- * bootstrapApplication(AppComponent, {
1369
- * providers: [provideIndexedDBDataBaseName('custom')]
1370
- * });
1371
- *
1372
- * @example
1373
- * NgModule({
1374
- * providers: [provideIndexedDBDataBaseName('custom')]
1375
- * })
1376
- * export class AppModule
1377
- */
1378
- function provideIndexedDBDataBaseName(name) {
1379
- return makeEnvironmentProviders([{ provide: IDB_DB_NAME, useValue: name }]);
1380
- }
1381
- /**
1382
- * Allows to change the database version used for `indexedDB` database.
1383
- * Must be an unsigned **integer**.
1384
- * **Use with caution as the creation of the store depends on the version.**
1385
- * *Use only* for interoperability with other APIs or to avoid collision for multiple apps on the same subdomain.
1386
- * **WARNING: do not change this option in an app already deployed in production, as previously stored data would be lost.**
1387
- *
1388
- * @example
1389
- * bootstrapApplication(AppComponent, {
1390
- * providers: [provideIndexedDBDataBaseVersion(2)]
1391
- * });
1392
- *
1393
- * @example
1394
- * NgModule({
1395
- * providers: [provideIndexedDBDataBaseVersion(2)]
1396
- * })
1397
- * export class AppModule
1398
- */
1399
- function provideIndexedDBDataBaseVersion(version) {
1400
- return makeEnvironmentProviders([{ provide: IDB_DB_VERSION, useValue: version }]);
1401
- }
1402
- /**
1403
- * Allows to change the name used for `indexedDB` object store.
1404
- * *Use only* for interoperability with other APIs.
1405
- * **WARNING: do not change this option in an app already deployed in production, as previously stored data would be lost.**
1406
- *
1407
- * @example
1408
- * bootstrapApplication(AppComponent, {
1409
- * providers: [provideIndexedDBStoreName('custom')]
1410
- * });
1411
- *
1412
- * @example
1413
- * NgModule({
1414
- * providers: [provideIndexedDBStoreName('custom')]
1415
- * })
1416
- * export class AppModule
1417
- */
1418
- function provideIndexedDBStoreName(name) {
1419
- return makeEnvironmentProviders([{ provide: IDB_STORE_NAME, useValue: name }]);
1420
- }
1421
-
1422
- /*
1423
- * Public API Surface of local-storage
1424
- */
1425
-
1426
- /**
1427
- * Generated bundle index. Do not edit.
1428
- */
1429
-
1430
- export { JSONValidator, LocalDatabase, LocalStorage, SERIALIZATION_ERROR, SerializationError, StorageMap, StorageModule, VALIDATION_ERROR, ValidationError, provideIndexedDBDataBaseName, provideIndexedDBDataBaseVersion, provideIndexedDBStoreName, provideLocalStoragePrefix };
1431
- //# sourceMappingURL=ngx-pwa-local-storage.mjs.map
1432
- //# sourceMappingURL=ngx-pwa-local-storage.mjs.map