valtech-components 2.0.798 → 2.0.799

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.
@@ -52,7 +52,7 @@ import 'prismjs/components/prism-json';
52
52
  * Current version of valtech-components.
53
53
  * This is automatically updated during the publish process.
54
54
  */
55
- const VERSION = '2.0.798';
55
+ const VERSION = '2.0.799';
56
56
 
57
57
  /**
58
58
  * Servicio para gestionar presets de componentes.
@@ -22598,6 +22598,15 @@ class MessagingService {
22598
22598
  });
22599
22599
  /** Key para localStorage de mensajes FCM (debugging) */
22600
22600
  this.DEBUG_STORAGE_KEY = 'fcm_debug_messages';
22601
+ /**
22602
+ * Key de localStorage donde se persiste el token FCM.
22603
+ *
22604
+ * El token vive en un `BehaviorSubject` en memoria y se pierde en cada
22605
+ * recarga / suspensión de la PWA (iOS reinicia PWAs con frecuencia). Persistir
22606
+ * el token permite hidratar el estado en cold start y que la UI no parpadee.
22607
+ * Es un *optimistic hint* — la verdad la confirma el siguiente `getToken()`.
22608
+ */
22609
+ this.TOKEN_STORAGE_KEY = 'valtech_fcm_token';
22601
22610
  this.debugPersistence = this.config?.debugMessagePersistence ?? false;
22602
22611
  this.initializeMessaging();
22603
22612
  }
@@ -22637,6 +22646,10 @@ class MessagingService {
22637
22646
  if (!isPlatformBrowser(this.platformId)) {
22638
22647
  return;
22639
22648
  }
22649
+ // Hidratar el token desde localStorage ANTES del check de soporte: el token
22650
+ // cacheado es un hint optimista para la UI tras recargas / suspensiones de
22651
+ // la PWA. La validez real se confirma en el próximo getToken().
22652
+ this.hydrateTokenFromStorage();
22640
22653
  const supported = await this.checkSupport();
22641
22654
  const permission = this.getPermissionState();
22642
22655
  this.stateSubject.next({
@@ -22770,10 +22783,11 @@ class MessagingService {
22770
22783
  return null;
22771
22784
  }
22772
22785
  try {
22773
- // Registrar SW personalizado antes de obtener token
22774
- // Esto es necesario cuando usamos un SW custom (firebase-messaging-sw.js)
22775
- const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js');
22776
- console.log('[Messaging] SW registered, waiting ready...');
22786
+ // Reutilizar el SW ya registrado (en config.ts durante el bootstrap) en
22787
+ // lugar de re-registrarlo en cada llamada re-registrar dispara
22788
+ // revalidaciones del SW en iOS PWA. Solo registramos si no existe aún.
22789
+ const registration = await this.resolveServiceWorkerRegistration();
22790
+ console.log('[Messaging] SW resolved, waiting ready...');
22777
22791
  // Esperar a que el SW esté activo
22778
22792
  await navigator.serviceWorker.ready;
22779
22793
  console.log('[Messaging] SW ready, calling Firebase getToken()...');
@@ -22786,13 +22800,68 @@ class MessagingService {
22786
22800
  ...this.stateSubject.value,
22787
22801
  token,
22788
22802
  });
22803
+ // Persistir el token para hidratar el estado en futuros cold starts.
22804
+ this.persistToken(token);
22789
22805
  return token;
22790
22806
  }
22791
22807
  catch (error) {
22792
- console.error('[Messaging] getToken error:', error);
22808
+ // No tragamos la causa: logueamos el detalle para diagnóstico. La firma
22809
+ // sigue siendo Promise<string | null> para no romper callers.
22810
+ const reason = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
22811
+ console.error('[Messaging] getToken error:', reason, error);
22793
22812
  return null;
22794
22813
  }
22795
22814
  }
22815
+ /**
22816
+ * Resuelve el `ServiceWorkerRegistration` del SW de FCM.
22817
+ *
22818
+ * El SW `/firebase-messaging-sw.js` ya se registra una vez en el bootstrap de
22819
+ * la app (`config.ts`). Reutilizamos ese registro en lugar de re-registrarlo
22820
+ * en cada `getToken()` — re-registrar repetidamente dispara revalidaciones del
22821
+ * SW en iOS PWA. Solo registramos como fallback si todavía no existe.
22822
+ */
22823
+ async resolveServiceWorkerRegistration() {
22824
+ const existing = await navigator.serviceWorker.getRegistration('/firebase-messaging-sw.js');
22825
+ if (existing) {
22826
+ return existing;
22827
+ }
22828
+ console.warn('[Messaging] SW not registered yet, registering as fallback');
22829
+ return navigator.serviceWorker.register('/firebase-messaging-sw.js');
22830
+ }
22831
+ /**
22832
+ * Persiste el token FCM en localStorage (o lo limpia si es null/empty).
22833
+ */
22834
+ persistToken(token) {
22835
+ if (!isPlatformBrowser(this.platformId))
22836
+ return;
22837
+ try {
22838
+ if (token) {
22839
+ localStorage.setItem(this.TOKEN_STORAGE_KEY, token);
22840
+ }
22841
+ else {
22842
+ localStorage.removeItem(this.TOKEN_STORAGE_KEY);
22843
+ }
22844
+ }
22845
+ catch (e) {
22846
+ console.warn('[Messaging] token persistence failed:', e);
22847
+ }
22848
+ }
22849
+ /**
22850
+ * Hidrata el estado del token desde localStorage en el arranque del servicio.
22851
+ */
22852
+ hydrateTokenFromStorage() {
22853
+ if (!isPlatformBrowser(this.platformId))
22854
+ return;
22855
+ try {
22856
+ const cached = localStorage.getItem(this.TOKEN_STORAGE_KEY);
22857
+ if (cached) {
22858
+ this.setTokenFromStorage(cached);
22859
+ }
22860
+ }
22861
+ catch (e) {
22862
+ console.warn('[Messaging] token hydration failed:', e);
22863
+ }
22864
+ }
22796
22865
  /**
22797
22866
  * Elimina el token FCM actual (unsubscribe de notificaciones).
22798
22867
  *
@@ -22813,6 +22882,8 @@ class MessagingService {
22813
22882
  ...this.stateSubject.value,
22814
22883
  token: null,
22815
22884
  });
22885
+ // Limpiar el token persistido — ya no estamos suscritos.
22886
+ this.persistToken(null);
22816
22887
  // Limpiar listener de mensajes
22817
22888
  if (this.unsubscribeOnMessage) {
22818
22889
  this.unsubscribeOnMessage();
@@ -37283,6 +37354,16 @@ class PreferencesService {
37283
37354
  this.language = this._language.asReadonly();
37284
37355
  this.notificationsMaster = this._notificationsMaster.asReadonly();
37285
37356
  this.synced = this._synced.asReadonly();
37357
+ /**
37358
+ * `true` mientras un `update()` está en vuelo (PUT al backend).
37359
+ *
37360
+ * El listener Firestore (`bindToUser`) es real-time y puede emitir un snapshot
37361
+ * *stale* — anterior al sync del backend — que pisaría el valor optimista de
37362
+ * `_notificationsMaster` y revertiría el toggle. Mientras este flag esté `true`
37363
+ * el listener ignora las emisiones; el `update()` ya alinea las señales con la
37364
+ * respuesta del backend al terminar.
37365
+ */
37366
+ this._updateInFlight = false;
37286
37367
  const destroyRef = inject(DestroyRef);
37287
37368
  destroyRef.onDestroy(() => this.unbind());
37288
37369
  // Auto-bind al user actual. effect() corre en injection context (constructor OK).
@@ -37335,6 +37416,9 @@ class PreferencesService {
37335
37416
  if (partial.notifications && typeof partial.notifications.master === 'boolean') {
37336
37417
  this._notificationsMaster.set(partial.notifications.master);
37337
37418
  }
37419
+ // Bloquear el listener Firestore mientras el PUT está en vuelo: un snapshot
37420
+ // stale no debe pisar el valor optimista recién aplicado.
37421
+ this._updateInFlight = true;
37338
37422
  try {
37339
37423
  const res = await firstValueFrom(this.http.put(url, partial));
37340
37424
  // El listener Firestore es la fuente final, pero alinear ya con la
@@ -37356,6 +37440,9 @@ class PreferencesService {
37356
37440
  this._notificationsMaster.set(prev.master);
37357
37441
  throw err;
37358
37442
  }
37443
+ finally {
37444
+ this._updateInFlight = false;
37445
+ }
37359
37446
  }
37360
37447
  setTheme(theme) {
37361
37448
  return this.update({ theme });
@@ -37382,6 +37469,12 @@ class PreferencesService {
37382
37469
  // pisan el theme/lang locales con valores arbitrarios.
37383
37470
  return;
37384
37471
  }
37472
+ // Ignorar emisiones del listener mientras hay un update() en vuelo: un
37473
+ // snapshot stale (anterior al sync del backend) revertiría el valor
37474
+ // optimista. El update() ya alinea las señales con la respuesta real.
37475
+ if (this._updateInFlight) {
37476
+ return;
37477
+ }
37385
37478
  if (doc.theme)
37386
37479
  this._theme.set(doc.theme);
37387
37480
  if (doc.language)