valtech-components 2.0.798 → 2.0.800
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/esm2022/lib/services/firebase/messaging.service.mjs +77 -6
- package/esm2022/lib/services/preferences/preferences.service.mjs +28 -2
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +104 -7
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/services/firebase/messaging.service.d.ts +26 -0
- package/lib/services/preferences/preferences.service.d.ts +10 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
55
|
+
const VERSION = '2.0.800';
|
|
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
|
-
//
|
|
22774
|
-
//
|
|
22775
|
-
|
|
22776
|
-
|
|
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
|
-
|
|
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,8 +37416,15 @@ 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
|
+
// Enviar appId explícito — el backend lo usa para escribir el mirror
|
|
37424
|
+
// Firestore en apps/{appId}/... correcto, sin depender del header Origin
|
|
37425
|
+
// (que en PWA iOS puede no resolver al AppID esperado).
|
|
37426
|
+
const body = { ...partial, appId: this.config.appId };
|
|
37427
|
+
const res = await firstValueFrom(this.http.put(url, body));
|
|
37340
37428
|
// El listener Firestore es la fuente final, pero alinear ya con la
|
|
37341
37429
|
// respuesta del backend acelera UX (especialmente si el listener
|
|
37342
37430
|
// tarda en propagar).
|
|
@@ -37356,6 +37444,9 @@ class PreferencesService {
|
|
|
37356
37444
|
this._notificationsMaster.set(prev.master);
|
|
37357
37445
|
throw err;
|
|
37358
37446
|
}
|
|
37447
|
+
finally {
|
|
37448
|
+
this._updateInFlight = false;
|
|
37449
|
+
}
|
|
37359
37450
|
}
|
|
37360
37451
|
setTheme(theme) {
|
|
37361
37452
|
return this.update({ theme });
|
|
@@ -37382,6 +37473,12 @@ class PreferencesService {
|
|
|
37382
37473
|
// pisan el theme/lang locales con valores arbitrarios.
|
|
37383
37474
|
return;
|
|
37384
37475
|
}
|
|
37476
|
+
// Ignorar emisiones del listener mientras hay un update() en vuelo: un
|
|
37477
|
+
// snapshot stale (anterior al sync del backend) revertiría el valor
|
|
37478
|
+
// optimista. El update() ya alinea las señales con la respuesta real.
|
|
37479
|
+
if (this._updateInFlight) {
|
|
37480
|
+
return;
|
|
37481
|
+
}
|
|
37385
37482
|
if (doc.theme)
|
|
37386
37483
|
this._theme.set(doc.theme);
|
|
37387
37484
|
if (doc.language)
|