valtech-components 2.0.838 → 2.0.840
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/components/molecules/update-banner/types.mjs +4 -4
- package/esm2022/lib/components/molecules/update-banner/update-banner.component.mjs +5 -5
- package/esm2022/lib/components/organisms/change-password-modal/change-password-modal.component.mjs +19 -22
- package/esm2022/lib/components/organisms/mfa-modal/mfa-modal.component.mjs +5 -5
- package/esm2022/lib/config/company-footer.config.mjs +10 -3
- package/esm2022/lib/services/auth/auth.service.mjs +8 -3
- package/esm2022/lib/services/errors/index.mjs +9 -0
- package/esm2022/lib/services/errors/interpret-error.mjs +130 -0
- package/esm2022/lib/services/firebase/index.mjs +2 -2
- package/esm2022/lib/services/firebase/messaging.service.mjs +212 -3
- package/esm2022/lib/services/firebase/types.mjs +1 -1
- package/esm2022/lib/services/i18n/default-content.mjs +5 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +6 -1
- package/fesm2022/valtech-components.mjs +397 -36
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/update-banner/types.d.ts +3 -3
- package/lib/components/organisms/change-password-modal/change-password-modal.component.d.ts +0 -1
- package/lib/config/company-footer.config.d.ts +4 -0
- package/lib/services/errors/index.d.ts +9 -0
- package/lib/services/errors/interpret-error.d.ts +63 -0
- package/lib/services/firebase/index.d.ts +1 -1
- package/lib/services/firebase/messaging.service.d.ts +110 -1
- package/lib/services/firebase/types.d.ts +30 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
|
@@ -15,7 +15,7 @@ export declare const UPDATE_BANNER_I18N_NAMESPACE = "UpdateBanner";
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const UPDATE_BANNER_DEFAULT_CONTENT: {
|
|
17
17
|
readonly es: {
|
|
18
|
-
readonly availableTitle: "Hay una versión nueva disponible";
|
|
18
|
+
readonly availableTitle: "✨ Hay una versión nueva disponible";
|
|
19
19
|
readonly availableMessage: "Actualiza para obtener las últimas mejoras.";
|
|
20
20
|
readonly requiredTitle: "Debes actualizar para continuar";
|
|
21
21
|
readonly requiredMessage: "Esta versión ya no es compatible. Actualiza para seguir.";
|
|
@@ -23,7 +23,7 @@ export declare const UPDATE_BANNER_DEFAULT_CONTENT: {
|
|
|
23
23
|
readonly dismissAction: "Cerrar";
|
|
24
24
|
};
|
|
25
25
|
readonly en: {
|
|
26
|
-
readonly availableTitle: "A new version is available";
|
|
26
|
+
readonly availableTitle: "✨ A new version is available";
|
|
27
27
|
readonly availableMessage: "Update to get the latest improvements.";
|
|
28
28
|
readonly requiredTitle: "You must update to continue";
|
|
29
29
|
readonly requiredMessage: "This version is no longer supported. Please update.";
|
|
@@ -31,7 +31,7 @@ export declare const UPDATE_BANNER_DEFAULT_CONTENT: {
|
|
|
31
31
|
readonly dismissAction: "Close";
|
|
32
32
|
};
|
|
33
33
|
readonly pt: {
|
|
34
|
-
readonly availableTitle: "Há uma nova versão disponível";
|
|
34
|
+
readonly availableTitle: "✨ Há uma nova versão disponível";
|
|
35
35
|
readonly availableMessage: "Atualize para obter as últimas melhorias.";
|
|
36
36
|
readonly requiredTitle: "Você precisa atualizar para continuar";
|
|
37
37
|
readonly requiredMessage: "Esta versão não é mais compatível. Atualize para seguir.";
|
|
@@ -49,7 +49,6 @@ export declare class ChangePasswordModalComponent {
|
|
|
49
49
|
/** Modo actual — `loading` mientras se consulta `checkHasPassword()`. */
|
|
50
50
|
readonly mode: import("@angular/core").Signal<PasswordModalMode>;
|
|
51
51
|
private readonly _formState;
|
|
52
|
-
constructor();
|
|
53
52
|
/** Traduce una clave del namespace `_auth`. */
|
|
54
53
|
t(key: string): string;
|
|
55
54
|
readonly formProps: import("@angular/core").Signal<FormMetadata>;
|
|
@@ -64,6 +64,8 @@ export declare const VALTECH_FOOTER_I18N: {
|
|
|
64
64
|
aboutUs: string;
|
|
65
65
|
privacyPolicy: string;
|
|
66
66
|
termsConditions: string;
|
|
67
|
+
cookiesPolicy: string;
|
|
68
|
+
legalNotice: string;
|
|
67
69
|
contactSupport: string;
|
|
68
70
|
faq: string;
|
|
69
71
|
feedback: string;
|
|
@@ -75,6 +77,8 @@ export declare const VALTECH_FOOTER_I18N: {
|
|
|
75
77
|
aboutUs: string;
|
|
76
78
|
privacyPolicy: string;
|
|
77
79
|
termsConditions: string;
|
|
80
|
+
cookiesPolicy: string;
|
|
81
|
+
legalNotice: string;
|
|
78
82
|
contactSupport: string;
|
|
79
83
|
faq: string;
|
|
80
84
|
feedback: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error interpretation helpers.
|
|
3
|
+
*
|
|
4
|
+
* `interpretError` normaliza cualquier error (HttpErrorResponse crudo,
|
|
5
|
+
* AuthError aplanado, Error de JS, o un valor arbitrario) a un
|
|
6
|
+
* `InterpretedError` con forma estable. Función pura, sin Angular DI.
|
|
7
|
+
*/
|
|
8
|
+
export { interpretError } from './interpret-error';
|
|
9
|
+
export type { InterpretedError } from './interpret-error';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error interpretation helper for the Valtech factory.
|
|
3
|
+
*
|
|
4
|
+
* Todos los frontends del factory consumen la misma API (backend Go) y la misma
|
|
5
|
+
* librería. La lógica de interpretar errores debe vivir una sola vez, acá.
|
|
6
|
+
*
|
|
7
|
+
* El backend Go (`apperrors`) SIEMPRE devuelve errores como JSON:
|
|
8
|
+
* { "code": string, "message": string, "operationId": string }
|
|
9
|
+
* donde `message` ya viene en español y es user-friendly.
|
|
10
|
+
*
|
|
11
|
+
* En Angular un error HTTP llega como `HttpErrorResponse` (body en `.error`).
|
|
12
|
+
* Un fallo de red es un `HttpErrorResponse` con `status === 0`.
|
|
13
|
+
*
|
|
14
|
+
* Además `AuthService.handleAuthError` aplana el `HttpErrorResponse` a un
|
|
15
|
+
* `AuthError { code, message }` (code/message al nivel superior). Por eso este
|
|
16
|
+
* helper acepta AMBAS formas — el crudo y el aplanado.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Resultado normalizado de cualquier error. Garantiza una forma estable y
|
|
20
|
+
* predecible para que las webapps no tengan que hacer narrowing manual.
|
|
21
|
+
*/
|
|
22
|
+
export interface InterpretedError {
|
|
23
|
+
/** Código del backend, o el sentinel `'NETWORK'` / `'UNKNOWN'`. */
|
|
24
|
+
code: string;
|
|
25
|
+
/** Mensaje del backend (español, user-friendly) o un genérico en español. */
|
|
26
|
+
message: string;
|
|
27
|
+
/** `operationId` del backend, si vino. Útil para soporte / tracing. */
|
|
28
|
+
operationId?: string;
|
|
29
|
+
/** HTTP status, si aplica (`0` para fallos de red). */
|
|
30
|
+
status?: number;
|
|
31
|
+
/** `true` si fue un fallo de red (status 0 / sin respuesta). */
|
|
32
|
+
isNetwork: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Normaliza CUALQUIER error a un `InterpretedError`.
|
|
36
|
+
*
|
|
37
|
+
* Función pura — sin dependencias de Angular DI, testeable y usable desde
|
|
38
|
+
* cualquier lado (componentes, servicios, interceptores, scripts).
|
|
39
|
+
*
|
|
40
|
+
* Nunca lanza: siempre devuelve un `InterpretedError` válido.
|
|
41
|
+
*
|
|
42
|
+
* Casos cubiertos:
|
|
43
|
+
* - `HttpErrorResponse` con `status === 0` (o sin respuesta) → fallo de red.
|
|
44
|
+
* - `HttpErrorResponse` con body `{ code, message, operationId }` del backend.
|
|
45
|
+
* - `AuthError` aplanado `{ code, message }` (code/message top-level).
|
|
46
|
+
* - `Error` plano de JS → `code: 'UNKNOWN'`, `message: err.message`.
|
|
47
|
+
* - Cualquier otra cosa (string, null, undefined, objeto raro) → genérico.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* try {
|
|
52
|
+
* await firstValueFrom(this.http.get(url));
|
|
53
|
+
* } catch (err) {
|
|
54
|
+
* const e = interpretError(err);
|
|
55
|
+
* if (e.isNetwork) {
|
|
56
|
+
* this.toast.show({ message: e.message, color: 'dark' });
|
|
57
|
+
* } else {
|
|
58
|
+
* this.errorCode.set(e.code);
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function interpretError(err: unknown): InterpretedError;
|
|
@@ -36,7 +36,7 @@ export { CollectionOptions, FirestoreCollection, FirestoreCollectionFactory, Sub
|
|
|
36
36
|
export { QueryBuilder, query } from './utils/query-builder';
|
|
37
37
|
export { buildPath, extractPathParams, getCollectionPath, getDocumentId, isCollectionPath, isDocumentPath, isValidPath, joinPath, } from './utils/path-builder';
|
|
38
38
|
export { StorageService } from './storage.service';
|
|
39
|
-
export { MessagingService } from './messaging.service';
|
|
39
|
+
export { MessagingService, type EnablePushOptions, type RegisterDeviceFn, } from './messaging.service';
|
|
40
40
|
export { NotificationDocument, NotificationsService } from './notifications.service';
|
|
41
41
|
export { AnalyticsService } from './analytics.service';
|
|
42
42
|
export { AnalyticsRouterTracker } from './analytics-router-tracker';
|
|
@@ -7,8 +7,28 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Injector, NgZone } from '@angular/core';
|
|
9
9
|
import { Observable } from 'rxjs';
|
|
10
|
-
import { NotificationAction, NotificationClickEvent, NotificationPayload, NotificationPermission, ValtechFirebaseConfig } from './types';
|
|
10
|
+
import { EnablePushResult, NotificationAction, NotificationClickEvent, NotificationPayload, NotificationPermission, ValtechFirebaseConfig } from './types';
|
|
11
11
|
import * as i0 from "@angular/core";
|
|
12
|
+
/**
|
|
13
|
+
* Callback opcional de registro de device para `MessagingService.enable()`.
|
|
14
|
+
*
|
|
15
|
+
* El registro del device vive en `AuthService` (necesita el JWT + el endpoint
|
|
16
|
+
* del backend). `MessagingService` NO puede depender de `AuthService` sin
|
|
17
|
+
* crear un ciclo (AuthService → MessagingService). Por eso el caller le pasa
|
|
18
|
+
* el paso de registro como callback. Recibe el token FCM y devuelve si el
|
|
19
|
+
* device quedó registrado.
|
|
20
|
+
*/
|
|
21
|
+
export type RegisterDeviceFn = (token: string) => Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Opciones de `MessagingService.enable()`.
|
|
24
|
+
*/
|
|
25
|
+
export interface EnablePushOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Paso opcional de registro de device en el backend. Si se omite, `enable()`
|
|
28
|
+
* resuelve con `status: 'enabled'` apenas obtiene el token (sin registrar).
|
|
29
|
+
*/
|
|
30
|
+
registerDevice?: RegisterDeviceFn;
|
|
31
|
+
}
|
|
12
32
|
/**
|
|
13
33
|
* Estado interno del servicio de messaging
|
|
14
34
|
*/
|
|
@@ -69,6 +89,31 @@ export declare class MessagingService {
|
|
|
69
89
|
* Es un *optimistic hint* — la verdad la confirma el siguiente `getToken()`.
|
|
70
90
|
*/
|
|
71
91
|
private readonly TOKEN_STORAGE_KEY;
|
|
92
|
+
/**
|
|
93
|
+
* Timeout (ms) para `navigator.serviceWorker.ready` dentro de `getToken()`.
|
|
94
|
+
*
|
|
95
|
+
* En un cold load el SW puede no estar activado aún y `serviceWorker.ready`
|
|
96
|
+
* no resolver nunca. Pasado este tiempo, `getToken()` rechaza limpio en lugar
|
|
97
|
+
* de colgarse indefinidamente.
|
|
98
|
+
*/
|
|
99
|
+
private readonly SW_READY_TIMEOUT_MS;
|
|
100
|
+
/**
|
|
101
|
+
* Timeout (ms) del watchdog de `enable()` antes de auto-recargar la página.
|
|
102
|
+
*
|
|
103
|
+
* El flujo de activación a veces se cuelga ANTES de `getToken()` — caso
|
|
104
|
+
* típico: `Notification.requestPermission()` no muestra el popup del SO en un
|
|
105
|
+
* cold load. El timeout de `getToken()` (SW_READY_TIMEOUT_MS) no cubre eso;
|
|
106
|
+
* por eso `enable()` envuelve el flujo completo en este watchdog.
|
|
107
|
+
*
|
|
108
|
+
* Un flujo exitoso real llega a token+device en ~4s; 15s es holgado y no
|
|
109
|
+
* atrapa un éxito lento.
|
|
110
|
+
*/
|
|
111
|
+
private readonly ENABLE_WATCHDOG_MS;
|
|
112
|
+
/**
|
|
113
|
+
* Key de sessionStorage — garantiza un solo auto-reload por sesión. Si el
|
|
114
|
+
* flujo se cuelga una 2ª vez tras el reload, NO se recarga de nuevo (anti-loop).
|
|
115
|
+
*/
|
|
116
|
+
private readonly AUTORELOAD_FLAG;
|
|
72
117
|
constructor(injector: Injector, config: ValtechFirebaseConfig, platformId: Object, ngZone: NgZone);
|
|
73
118
|
/**
|
|
74
119
|
* Obtiene la instancia de Messaging via Firebase SDK directo (NO AngularFire DI).
|
|
@@ -122,6 +167,56 @@ export declare class MessagingService {
|
|
|
122
167
|
* ```
|
|
123
168
|
*/
|
|
124
169
|
requestPermission(): Promise<string | null>;
|
|
170
|
+
/**
|
|
171
|
+
* Flujo completo de activación de push, robusto, cross-app.
|
|
172
|
+
*
|
|
173
|
+
* Orquesta: pedir permiso → SW ready → obtener token FCM → (opcional)
|
|
174
|
+
* registrar el device en backend. Es el punto de orquestación único que cada
|
|
175
|
+
* app del factory consume — la lógica de robustez vive aquí, no en cada página.
|
|
176
|
+
*
|
|
177
|
+
* **Watchdog de auto-reload.** El flujo a veces se cuelga ANTES de `getToken()`
|
|
178
|
+
* (ej. `Notification.requestPermission()` que no muestra el popup en un cold
|
|
179
|
+
* load) — el timeout interno de `getToken()` no cubre ese caso. Por eso
|
|
180
|
+
* `enable()` envuelve el flujo entero en un watchdog: si no alcanza un estado
|
|
181
|
+
* terminal en `ENABLE_WATCHDOG_MS` (15s):
|
|
182
|
+
* - 1ª vez en la sesión → marca un flag en `sessionStorage` y hace
|
|
183
|
+
* `window.location.reload()` (un fresh load suele tener el SW activo).
|
|
184
|
+
* Resuelve con `{ status: 'timeout', reloaded: true }`.
|
|
185
|
+
* - ya se auto-recargó antes → NO recarga (anti-loop): limpia el flag y
|
|
186
|
+
* resuelve con `{ status: 'timeout', reloaded: false }` para que la app
|
|
187
|
+
* muestre un error.
|
|
188
|
+
*
|
|
189
|
+
* NO hace throw — siempre resuelve con un `EnablePushResult` descriptivo que
|
|
190
|
+
* la página consumidora inspecciona para decidir qué toast mostrar.
|
|
191
|
+
*
|
|
192
|
+
* @param options.registerDevice Callback opcional que registra el device en
|
|
193
|
+
* el backend (vive en `AuthService` — se pasa como callback para evitar el
|
|
194
|
+
* ciclo de DI AuthService ↔ MessagingService).
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* const result = await messaging.enable({
|
|
199
|
+
* registerDevice: (token) => auth.registerDevice(token).then(r => r.registered),
|
|
200
|
+
* });
|
|
201
|
+
* if (result.status === 'enabled') {
|
|
202
|
+
* // result.token disponible — push activo
|
|
203
|
+
* } else if (result.status === 'timeout' && !result.reloaded) {
|
|
204
|
+
* // mostrar error: el flujo se colgó y ya se consumió el auto-reload
|
|
205
|
+
* }
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
enable(options?: EnablePushOptions): Promise<EnablePushResult>;
|
|
209
|
+
/**
|
|
210
|
+
* Flujo real de activación, sin el watchdog (lo envuelve `enable()`).
|
|
211
|
+
* Resuelve siempre con un `EnablePushResult` — no hace throw.
|
|
212
|
+
*/
|
|
213
|
+
private runEnableFlow;
|
|
214
|
+
/** True si ya se auto-recargó una vez en esta sesión. */
|
|
215
|
+
private hasAutoReloaded;
|
|
216
|
+
/** Marca que se consumió el auto-reload de esta sesión. */
|
|
217
|
+
private markAutoReloaded;
|
|
218
|
+
/** Limpia el flag anti-loop — tras un éxito o un fallo definitivo. */
|
|
219
|
+
private clearAutoReloadFlag;
|
|
125
220
|
/**
|
|
126
221
|
* Obtiene el token FCM actual (sin solicitar permiso).
|
|
127
222
|
*
|
|
@@ -142,6 +237,20 @@ export declare class MessagingService {
|
|
|
142
237
|
* SW en iOS PWA. Solo registramos como fallback si todavía no existe.
|
|
143
238
|
*/
|
|
144
239
|
private resolveServiceWorkerRegistration;
|
|
240
|
+
/**
|
|
241
|
+
* Espera a que el Service Worker quede activo (`navigator.serviceWorker.ready`)
|
|
242
|
+
* pero con un timeout duro.
|
|
243
|
+
*
|
|
244
|
+
* `navigator.serviceWorker.ready` resuelve cuando hay un SW *activo*. En un
|
|
245
|
+
* cold load (primera visita, SW recién registrado, iOS PWA recién abierta) el
|
|
246
|
+
* SW puede quedar en estado `installing`/`waiting` y `ready` no resolver nunca
|
|
247
|
+
* → `getToken()` se cuelga. El `Promise.race` contra un `setTimeout` garantiza
|
|
248
|
+
* que esta espera termina: o gana `ready` (caso normal) o gana el timeout y
|
|
249
|
+
* lanzamos un error claro para que `getToken()` rechace en vez de colgarse.
|
|
250
|
+
*
|
|
251
|
+
* El timer se limpia en ambas ramas para no dejar timers colgando.
|
|
252
|
+
*/
|
|
253
|
+
private waitForServiceWorkerReady;
|
|
145
254
|
/**
|
|
146
255
|
* Persiste el token FCM en localStorage (o lo limpia si es null/empty).
|
|
147
256
|
*/
|
|
@@ -346,6 +346,36 @@ export interface NotificationAction {
|
|
|
346
346
|
/** Datos adicionales para la acción */
|
|
347
347
|
actionData?: Record<string, unknown>;
|
|
348
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Resultado de `MessagingService.enable()`.
|
|
351
|
+
*
|
|
352
|
+
* Tipo discriminado descriptivo (NO se hace throw): la página consumidora lo
|
|
353
|
+
* inspecciona para decidir qué toast/UX mostrar.
|
|
354
|
+
*
|
|
355
|
+
* `status`:
|
|
356
|
+
* - `enabled` — permiso otorgado + token FCM obtenido (`token` presente).
|
|
357
|
+
* - `denied` — el usuario denegó el permiso del navegador.
|
|
358
|
+
* - `unsupported` — el navegador no soporta FCM (incl. iOS sin standalone).
|
|
359
|
+
* - `error` — el flujo falló por otra causa (ver `reason`).
|
|
360
|
+
* - `timeout` — el flujo se colgó >15s. Si `reloaded` es true, la página
|
|
361
|
+
* está por recargarse (auto-reload, 1ª vez en la sesión); si
|
|
362
|
+
* es false, ya se consumió el auto-reload y la página debe
|
|
363
|
+
* mostrar un error sin recargar.
|
|
364
|
+
*/
|
|
365
|
+
export interface EnablePushResult {
|
|
366
|
+
/** Resultado del flujo de activación de push. */
|
|
367
|
+
status: 'enabled' | 'denied' | 'unsupported' | 'error' | 'timeout';
|
|
368
|
+
/** Token FCM — presente solo cuando `status === 'enabled'`. */
|
|
369
|
+
token?: string;
|
|
370
|
+
/** Detalle legible de la causa — presente en `error` / `timeout`. */
|
|
371
|
+
reason?: string;
|
|
372
|
+
/**
|
|
373
|
+
* Solo para `status === 'timeout'`: true si se disparó el auto-reload
|
|
374
|
+
* (la página está por recargarse). False si el auto-reload ya se consumió
|
|
375
|
+
* esta sesión y la app debe mostrar un error en lugar de recargar.
|
|
376
|
+
*/
|
|
377
|
+
reloaded?: boolean;
|
|
378
|
+
}
|
|
349
379
|
/**
|
|
350
380
|
* Evento de click en una notificación
|
|
351
381
|
*/
|
package/lib/version.d.ts
CHANGED
package/package.json
CHANGED
package/public-api.d.ts
CHANGED
|
@@ -255,6 +255,7 @@ export * from './lib/services/navigation';
|
|
|
255
255
|
export * from './lib/services/theme.service';
|
|
256
256
|
export * from './lib/services/toast.service';
|
|
257
257
|
export * from './lib/services/types';
|
|
258
|
+
export * from './lib/services/errors';
|
|
258
259
|
export * from './lib/services/confirmation-dialog/confirmation-dialog.service';
|
|
259
260
|
export * from './lib/services/confirmation-dialog/types';
|
|
260
261
|
export * from './lib/services/qr-generator/qr-generator.service';
|