ngx-sp-auth 4.5.0 → 4.5.2

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.
@@ -230,11 +230,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
230
230
  args: [LIB_CUSTOM_ENVIRONMENT_SERVICE]
231
231
  }] }] });
232
232
 
233
+ /**
234
+ * Mecanismo simples de lock para evitar race conditions
235
+ * Garante que apenas uma operação crítica (init/delete) execute por vez
236
+ */
237
+ class DatabaseLock {
238
+ constructor() {
239
+ this.isLocked = false;
240
+ this.queue = [];
241
+ }
242
+ async acquire(operation) {
243
+ while (this.isLocked) {
244
+ // Aguarda até que a lock seja liberada
245
+ await new Promise(resolve => setTimeout(resolve, 10));
246
+ }
247
+ this.isLocked = true;
248
+ try {
249
+ return await operation();
250
+ }
251
+ finally {
252
+ this.isLocked = false;
253
+ }
254
+ }
255
+ }
233
256
  class IndexedDBService {
234
257
  // #endregion ==========> PROPERTIES <==========
235
258
  constructor(_customEnvironment) {
236
259
  this._customEnvironment = _customEnvironment;
237
260
  this._dbName = "Sp_Filtros_";
261
+ this._lock = new DatabaseLock();
262
+ this._isInitialized = false;
238
263
  if (!window.indexedDB) {
239
264
  alert("Seu navegador não suporta uma versão estável do IndexedDB. Salvamento de filtros em sessão não estará disponível.");
240
265
  }
@@ -249,15 +274,16 @@ class IndexedDBService {
249
274
  * @param value Valor a ser inserido
250
275
  */
251
276
  async add(value) {
252
- const db = await openDB(this._dbName, 1);
277
+ await this._ensureInitialized();
278
+ if (!this.request) {
279
+ throw new Error('Database not initialized. Call initializeDatabase() first.');
280
+ }
253
281
  try {
254
- await db.add('filters', value);
282
+ await this.request.add('filters', value);
255
283
  }
256
- finally {
257
- try {
258
- db.close();
259
- }
260
- catch (e) { /* não faz nada */ }
284
+ catch (error) {
285
+ console.error('Error adding value to IndexedDB:', error);
286
+ throw error;
261
287
  }
262
288
  }
263
289
  // #endregion ADD
@@ -265,20 +291,20 @@ class IndexedDBService {
265
291
  /**
266
292
  * Busca um valor na base dentro de um objectStore.
267
293
  *
268
- * @param storeName Nome do objectStore
269
294
  * @param key Valor da chave única
270
295
  * @returns Promise<> com o valor encontrado ou undefined se não encontrar
271
296
  */
272
297
  async get(key) {
273
- const db = await openDB(this._dbName, 1);
298
+ await this._ensureInitialized();
299
+ if (!this.request) {
300
+ throw new Error('Database not initialized. Call initializeDatabase() first.');
301
+ }
274
302
  try {
275
- return await db.get('filters', key);
303
+ return await this.request.get('filters', key);
276
304
  }
277
- finally {
278
- try {
279
- db.close();
280
- }
281
- catch (e) { /* não faz nada */ }
305
+ catch (error) {
306
+ console.error('Error getting value from IndexedDB:', error);
307
+ throw error;
282
308
  }
283
309
  }
284
310
  // #endregion GET
@@ -289,15 +315,16 @@ class IndexedDBService {
289
315
  * @param value Valor atualizado
290
316
  */
291
317
  async update(value) {
292
- const db = await openDB(this._dbName, 1);
318
+ await this._ensureInitialized();
319
+ if (!this.request) {
320
+ throw new Error('Database not initialized. Call initializeDatabase() first.');
321
+ }
293
322
  try {
294
- await db.put('filters', value);
323
+ await this.request.put('filters', value);
295
324
  }
296
- finally {
297
- try {
298
- db.close();
299
- }
300
- catch (e) { /* não faz nada */ }
325
+ catch (error) {
326
+ console.error('Error updating value in IndexedDB:', error);
327
+ throw error;
301
328
  }
302
329
  }
303
330
  // #endregion UPDATE
@@ -308,82 +335,154 @@ class IndexedDBService {
308
335
  * @param key Valor da chave única
309
336
  */
310
337
  async delete(key) {
311
- const db = await openDB(this._dbName, 1);
338
+ await this._ensureInitialized();
339
+ if (!this.request) {
340
+ throw new Error('Database not initialized. Call initializeDatabase() first.');
341
+ }
312
342
  try {
313
- await db.delete('filters', key);
343
+ await this.request.delete('filters', key);
314
344
  }
315
- finally {
316
- try {
317
- db.close();
318
- }
319
- catch (e) { /* não faz nada */ }
345
+ catch (error) {
346
+ console.error('Error deleting value from IndexedDB:', error);
347
+ throw error;
320
348
  }
321
349
  }
322
350
  // #endregion DELETE
323
351
  // #endregion ==========> ACTIONS <==========
324
352
  // #region ==========> UTILS <==========
353
+ /**
354
+ * Garante que a database foi inicializada antes de usar qualquer operação.
355
+ * Se `initializeDatabase()` foi chamado mas ainda não completou, aguarda a promise.
356
+ * Previne race conditions ao tentar usar operações durante a inicialização.
357
+ *
358
+ * @private
359
+ */
360
+ async _ensureInitialized() {
361
+ // Se já está inicializando, aguarda a promise existente
362
+ if (this._initPromise) {
363
+ await this._initPromise;
364
+ return;
365
+ }
366
+ // Se já foi inicializado com sucesso, retorna imediatamente
367
+ if (this._isInitialized && this.request) {
368
+ return;
369
+ }
370
+ // Se chegou aqui e não está inicializado, significa que initializeDatabase() não foi chamado
371
+ throw new Error('IndexedDB not initialized. Call initializeDatabase() and await it before using any operations.');
372
+ }
325
373
  /**
326
374
  * Inicializa as configurações iniciais do IndexedDB e já cria o objectStore que será utilizado caso não exista.
327
375
  *
328
- * O object store que será criado terá sua chave inline na propriedade `key` e o índice será a propriedade `context`, portanto todos os valores que forem inseridos **DEVEM** ser um objeto com pelo menos a propriedade `key` e `context`.
329
- * Deve ser chamado no constructor do seu componente para garantir inicialização correta pelo componente
376
+ * ⚠️ IMPORTANTE: Deve ser chamado com `await` no ngOnInit() do seu componente, não no constructor!
377
+ *
378
+ * O object store que será criado terá sua chave inline na propriedade `key` e o índice será a propriedade `context`,
379
+ * portanto todos os valores que forem inseridos **DEVEM** ser um objeto com pelo menos a propriedade `key` e `context`.
330
380
  *
331
- * @param dbName Nome da base de dados que será usada
381
+ * @example
382
+ * async ngOnInit() {
383
+ * await this._indexedDB.initializeDatabase();
384
+ * const restored = await this._indexedDB.get('minha-chave');
385
+ * }
332
386
  */
333
387
  async initializeDatabase() {
334
- this.request = await openDB(this._dbName, 1, {
335
- upgrade(db) {
336
- // Criar objectStore se não houver um mesmo com este nome
337
- if (!db.objectStoreNames.contains('filters')) {
338
- const store = db.createObjectStore('filters', { keyPath: 'key' });
339
- store.createIndex('context', 'context');
340
- }
341
- },
342
- blocked() {
343
- // notify user / log: another tab has old version open
344
- console.warn('IndexedDB blocked — feche outras abas');
345
- },
346
- blocking() {
347
- // this instance is blocking a future version request
348
- console.warn('IndexedDB blocking — considere fechar esta conexão');
388
+ return this._lock.acquire(async () => {
389
+ // Se já está inicializado, não refaz
390
+ if (this._isInitialized && this.request) {
391
+ return;
392
+ }
393
+ // Marca que está inicializando
394
+ const initPromise = this._performInitialization();
395
+ this._initPromise = initPromise;
396
+ try {
397
+ await initPromise;
398
+ this._isInitialized = true;
399
+ }
400
+ finally {
401
+ this._initPromise = undefined;
349
402
  }
350
403
  });
351
404
  }
405
+ /**
406
+ * Executa a inicialização real da database.
407
+ * @private
408
+ */
409
+ async _performInitialization() {
410
+ try {
411
+ this.request = await openDB(this._dbName, 1, {
412
+ upgrade(db) {
413
+ // Criar objectStore se não houver um mesmo com este nome
414
+ if (!db.objectStoreNames.contains('filters')) {
415
+ const store = db.createObjectStore('filters', { keyPath: 'key' });
416
+ store.createIndex('context', 'context');
417
+ }
418
+ },
419
+ blocked() {
420
+ console.warn('IndexedDB blocked — feche outras abas com esta aplicação');
421
+ },
422
+ blocking() {
423
+ console.warn('IndexedDB blocking — recarregue a página se persistir');
424
+ }
425
+ });
426
+ }
427
+ catch (error) {
428
+ console.error('Error initializing IndexedDB:', error);
429
+ throw error;
430
+ }
431
+ }
352
432
  /**
353
433
  * Exclui uma database do IndexedDB com base no nome.
354
434
  *
355
- * @param name Nome da database
435
+ * Deve ser chamado durante o logout para limpar dados do usuário.
436
+ *
437
+ * @example
438
+ * await this._indexedDB.closeOpenConnection();
439
+ * await this._indexedDB.deleteDatabase();
356
440
  */
357
441
  async deleteDatabase() {
358
- // Fecha a conexão persistente local, se existir, antes de tentar excluir a DB
359
- if (this.request) {
442
+ return this._lock.acquire(async () => {
443
+ // Fecha a conexão persistente local, se existir, antes de tentar excluir a DB
444
+ await this._closeConnection();
360
445
  try {
361
- this.request.close();
446
+ await deleteDB(this._dbName);
447
+ this._isInitialized = false;
362
448
  }
363
449
  catch (err) {
364
- console.warn('deleteDatabase() => erro ao fechar conexão local', err);
450
+ console.warn('Error deleting IndexedDB:', err);
451
+ throw err;
365
452
  }
366
- this.request = undefined;
367
- }
368
- return await deleteDB(this._dbName);
453
+ });
369
454
  }
370
455
  /**
371
456
  * Fecha a conexão persistente (se existir) sem excluir a database.
372
457
  * Útil para cenários onde se precisa liberar a conexão antes de chamar `deleteDatabase()`.
458
+ *
459
+ * @example
460
+ * await this._indexedDB.closeOpenConnection();
373
461
  */
374
462
  async closeOpenConnection() {
463
+ return this._lock.acquire(async () => {
464
+ await this._closeConnection();
465
+ });
466
+ }
467
+ /**
468
+ * Executa o fechamento real da conexão.
469
+ * @private
470
+ */
471
+ async _closeConnection() {
375
472
  if (this.request) {
376
473
  try {
377
474
  this.request.close();
378
475
  }
379
476
  catch (err) {
380
- console.warn('closeOpenConnection() => erro ao fechar conexão', err);
477
+ console.warn('Error closing IndexedDB connection:', err);
381
478
  }
382
479
  this.request = undefined;
480
+ this._isInitialized = false;
383
481
  }
384
482
  }
385
483
  /**
386
- * Valida se já existe um valor cadastrado na base com a chave-única que foi informada, se houver retorna ele, caso contrário cria um registro placeholder para poder atualizar depois.
484
+ * Valida se já existe um valor cadastrado na base com a chave-única que foi informada,
485
+ * se houver retorna ele, caso contrário cria um registro placeholder para poder atualizar depois.
387
486
  *
388
487
  * @param key Valor da chave única
389
488
  * @param value (opcional) Valor placeholder caso não exista um valor previamente criado
@@ -396,6 +495,7 @@ class IndexedDBService {
396
495
  if (!this._restored && value) {
397
496
  // Se não existir nada, inicializa um registro placeholder dentro do objectStore existente
398
497
  await this.add(value);
498
+ return value?.payload;
399
499
  }
400
500
  else {
401
501
  // Se encontrar valor retorna apenas o payload com os dados que serão usados na tela