valtech-components 2.0.804 → 2.0.806

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.
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Firebase Service
3
+ *
4
+ * Servicio principal para la autenticación con Firebase usando Custom Tokens.
5
+ * Permite que usuarios autenticados con tu backend (Cognito, etc.) accedan
6
+ * a servicios de Firebase (Firestore, Storage, FCM) de manera segura.
7
+ */
8
+ import { Signal } from '@angular/core';
1
9
  import { Auth, UserCredential } from '@angular/fire/auth';
2
10
  import { Observable } from 'rxjs';
3
11
  import { FirebaseUser, MembershipInfo, OrganizationInfo, SessionState, ValtechFirebaseConfig } from './types';
@@ -46,7 +54,51 @@ export declare class FirebaseService {
46
54
  readonly user$: Observable<FirebaseUser | null>;
47
55
  /** Indica si el usuario está autenticado en Firebase */
48
56
  readonly isAuthenticated$: Observable<boolean>;
57
+ /**
58
+ * Signal interna que respalda `firebaseAuthReady`.
59
+ * `true` cuando hay un `firebase.User` activo y por tanto las reglas de
60
+ * Firestore evaluarán `request.auth != null` correctamente.
61
+ */
62
+ private readonly _firebaseAuthReady;
63
+ /**
64
+ * Indica si la sesión de **Firebase Auth** está establecida y lista para
65
+ * leer Firestore.
66
+ *
67
+ * IMPORTANTE: esto es distinto del JWT del backend. La sesión de Firebase
68
+ * Auth es una sesión separada que se establece vía `signInWithCustomToken`
69
+ * (ver `AuthService.signInWithFirebase`). En cold start de PWA, el JWT del
70
+ * backend puede estar listo varios cientos de ms antes de que Firebase Auth
71
+ * confirme su `User` — adjuntar un listener de Firestore en esa ventana
72
+ * produce `permission-denied`.
73
+ *
74
+ * Usar este signal (o `whenFirebaseAuthReady()`) como gate antes de
75
+ * suscribirse a cualquier query/listener de Firestore.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * if (this.firebase.firebaseAuthReady()) {
80
+ * // seguro leer Firestore
81
+ * }
82
+ * ```
83
+ */
84
+ readonly firebaseAuthReady: Signal<boolean>;
85
+ /**
86
+ * Emite `true` una sola vez en cuanto la sesión de Firebase Auth está lista,
87
+ * y completa. Si ya está lista, emite inmediatamente.
88
+ *
89
+ * Pensado como gate compartido para abrir streams Firestore — espera la
90
+ * ventana de hidratación de Firebase Auth sin necesidad de retries.
91
+ */
92
+ readonly firebaseAuthReady$: Observable<boolean>;
49
93
  constructor(auth: Auth, config: ValtechFirebaseConfig);
94
+ /**
95
+ * Resuelve en cuanto la sesión de Firebase Auth está lista para leer
96
+ * Firestore. Si ya está lista, resuelve inmediatamente.
97
+ *
98
+ * Útil para gatear la primera suscripción a un listener de Firestore y así
99
+ * cerrar la ventana de `permission-denied` en cold start.
100
+ */
101
+ whenFirebaseAuthReady(): Promise<boolean>;
50
102
  /**
51
103
  * Autentica al usuario con un Custom Token generado por el backend.
52
104
  *
@@ -64,7 +64,16 @@ export declare class NotificationsService {
64
64
  private currentUserId;
65
65
  private collectionReady$;
66
66
  private authService;
67
+ private firebaseService;
67
68
  constructor(injector: Injector, collectionFactory: FirestoreCollectionFactory);
69
+ /**
70
+ * Gate de Firebase Auth: emite la colección lista SOLO cuando la sesión de
71
+ * Firebase Auth está confirmada. Sin FirebaseService disponible, no gatea
72
+ * (degrada al comportamiento previo). Espera a que `firebaseAuthReady$`
73
+ * emita antes de propagar la colección — así el listener Firestore nunca se
74
+ * adjunta antes de que `request.auth` esté disponible.
75
+ */
76
+ private collectionWhenAuthReady$;
68
77
  /**
69
78
  * Configura auto-inicialización observando el estado de AuthService.
70
79
  * Se ejecuta en el contexto del injector para poder usar effect().
@@ -90,6 +99,11 @@ export declare class NotificationsService {
90
99
  * Obtiene notificaciones ordenadas por fecha descendente (real-time).
91
100
  * Se actualiza automáticamente cuando cambian los datos.
92
101
  *
102
+ * El listener Firestore NO se adjunta hasta que la sesión de Firebase Auth
103
+ * esté confirmada (`FirebaseService.firebaseAuthReady`). Esto cierra la
104
+ * ventana de `permission-denied` en cold start de PWA, donde el JWT del
105
+ * backend puede estar listo antes que la sesión de Firebase Auth.
106
+ *
93
107
  * @param limit - Máximo de notificaciones a cargar (default: 50)
94
108
  */
95
109
  getAll(limit?: number): Observable<NotificationDocument[]>;
@@ -109,6 +123,9 @@ export declare class NotificationsService {
109
123
  /**
110
124
  * Cuenta notificaciones no leídas usando server-side aggregation query.
111
125
  * No descarga documentos — eficiente para badges en UI.
126
+ *
127
+ * Gateado por `firebaseAuthReady` — la aggregation query no se ejecuta hasta
128
+ * que la sesión de Firebase Auth esté lista.
112
129
  */
113
130
  getUnreadCount(): Observable<number>;
114
131
  /**
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Refreshable Stream
3
+ *
4
+ * Helper `createRefreshableStream` para vistas con datos Firestore.
5
+ * Soporta modo one-shot y real-time, con gate de `firebaseAuthReady`.
6
+ */
7
+ export { createRefreshableStream, type RefreshableStream, type RefreshableStreamOptions, } from './refreshable-stream';
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Refreshable Stream
3
+ *
4
+ * Helper estándar para vistas que consumen datos de Firestore. Soporta DOS
5
+ * modos — el caller decide cuál vía la `factory`:
6
+ *
7
+ * - **one-shot** (recomendado por defecto): `() => from(svc.getAllOnce())`.
8
+ * Carga una vez y completa. Más barato — sin listener vivo.
9
+ * - **real-time**: `() => svc.getAll()`. Listener Firestore que auto-actualiza.
10
+ * Solo donde importa (inbox de notificaciones, badges).
11
+ *
12
+ * En ambos modos la primera suscripción se gatea por `firebaseAuthReady`
13
+ * (sesión de Firebase Auth lista) — así el listener/lectura nunca se adjunta
14
+ * antes de que `request.auth` esté disponible en las reglas de Firestore, lo
15
+ * que cierra la ventana de `permission-denied` en cold start de PWA.
16
+ *
17
+ * El `retry` con backoff corto es solo red de seguridad para reconexiones
18
+ * transitorias (no para cubrir cold start — eso lo cubre el gate de auth).
19
+ */
20
+ import { Injector, Signal } from '@angular/core';
21
+ import { Observable } from 'rxjs';
22
+ /** Opciones de configuración para `createRefreshableStream`. */
23
+ export interface RefreshableStreamOptions<T> {
24
+ /**
25
+ * Valor emitido cuando la factory falla tras agotar los reintentos.
26
+ * Default: `null`.
27
+ */
28
+ fallback?: T;
29
+ /**
30
+ * Número máximo de reintentos ante un error de la factory.
31
+ * Backoff corto — NO cubre cold start (eso lo hace el gate de auth), solo
32
+ * reconexiones transitorias. Default: 4.
33
+ */
34
+ retryCount?: number;
35
+ /**
36
+ * Delay base del backoff (ms). El delay efectivo crece exponencialmente
37
+ * acotado: 500ms → 1s → 2s ... Default: 500.
38
+ */
39
+ retryBaseDelayMs?: number;
40
+ /**
41
+ * Si `false`, NO espera a `firebaseAuthReady` antes de la primera
42
+ * suscripción. Útil para streams que no leen Firestore. Default: `true`.
43
+ */
44
+ gateOnFirebaseAuth?: boolean;
45
+ /**
46
+ * Timeout (ms) del gate de Firebase Auth. Si la sesión de Firebase Auth no
47
+ * se establece en este tiempo, el stream procede igual (la factory correrá
48
+ * y, si no hay permiso, terminará en estado `error` — NUNCA en skeleton
49
+ * infinito). Muy por encima de cualquier handshake real. Default: 20000.
50
+ */
51
+ authGateTimeoutMs?: number;
52
+ /**
53
+ * Injector explícito. Solo necesario si `createRefreshableStream` se llama
54
+ * fuera de un injection context (raro). Por defecto usa `inject(Injector)`.
55
+ */
56
+ injector?: Injector;
57
+ }
58
+ /**
59
+ * Resultado de `createRefreshableStream`.
60
+ */
61
+ export interface RefreshableStream<T> {
62
+ /** Datos actuales. `null` mientras la primera emisión no ha llegado. */
63
+ readonly data: Signal<T | null>;
64
+ /** `true` mientras se espera la primera emisión de la suscripción actual. */
65
+ readonly loading: Signal<boolean>;
66
+ /** `true` si la factory falló tras agotar los reintentos. */
67
+ readonly error: Signal<boolean>;
68
+ /**
69
+ * Re-suscribe la factory desde cero (nuevo listener / nueva lectura).
70
+ * Es lo que registra el `PageRefreshService` como handler de pull-to-refresh,
71
+ * y también el "reconectar manual" si un listener murió.
72
+ */
73
+ reload: () => void;
74
+ }
75
+ /**
76
+ * Crea un stream refrescable a partir de una factory de Observable.
77
+ *
78
+ * DEBE llamarse en un injection context (field initializer o constructor) —
79
+ * usa `toSignal`/`toObservable`/`inject` internamente.
80
+ *
81
+ * @param factory - Función que produce el Observable a consumir. El caller
82
+ * decide el modo: `() => from(svc.getAllOnce())` (one-shot) o
83
+ * `() => svc.getAll()` (real-time).
84
+ * @param options - Configuración opcional (fallback, retry, gate).
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * // one-shot (recomendado por defecto)
89
+ * private readonly stream = createRefreshableStream(
90
+ * () => from(this.svc.getAllOnce()),
91
+ * );
92
+ *
93
+ * // real-time (inbox)
94
+ * private readonly stream = createRefreshableStream(
95
+ * () => this.notifs.getAll(50),
96
+ * );
97
+ *
98
+ * readonly items = this.stream.data;
99
+ * readonly loading = this.stream.loading;
100
+ *
101
+ * ionViewWillEnter() {
102
+ * this.pageRefresh.register(() => this.stream.reload());
103
+ * }
104
+ * ```
105
+ */
106
+ export declare function createRefreshableStream<T>(factory: () => Observable<T>, options?: RefreshableStreamOptions<T>): RefreshableStream<T>;
package/lib/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Current version of valtech-components.
3
3
  * This is automatically updated during the publish process.
4
4
  */
5
- export declare const VERSION = "2.0.804";
5
+ export declare const VERSION = "2.0.806";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valtech-components",
3
- "version": "2.0.804",
3
+ "version": "2.0.806",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "valtech-firebase-config": "./src/lib/services/firebase/scripts/generate-sw-config.js"
package/public-api.d.ts CHANGED
@@ -263,6 +263,7 @@ export * from './lib/services/auth';
263
263
  export * from './lib/services/i18n';
264
264
  export * from './lib/services/preferences';
265
265
  export * from './lib/services/page-refresh/page-refresh.service';
266
+ export * from './lib/services/refreshable-stream';
266
267
  export * from './lib/services/app-config';
267
268
  export * from './lib/services/presets';
268
269
  export * from './lib/services/skeleton';