valtech-components 2.0.837 → 2.0.839
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/cookie-banner/cookie-banner.component.mjs +5 -5
- package/esm2022/lib/config/company-footer.config.mjs +10 -3
- 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/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +232 -16
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/update-banner/types.d.ts +3 -3
- package/lib/config/company-footer.config.d.ts +4 -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/esm2022/lib/version.mjs
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Current version of valtech-components.
|
|
3
3
|
* This is automatically updated during the publish process.
|
|
4
4
|
*/
|
|
5
|
-
export const VERSION = '2.0.
|
|
6
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
5
|
+
export const VERSION = '2.0.839';
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsU0FBUyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDdXJyZW50IHZlcnNpb24gb2YgdmFsdGVjaC1jb21wb25lbnRzLlxuICogVGhpcyBpcyBhdXRvbWF0aWNhbGx5IHVwZGF0ZWQgZHVyaW5nIHRoZSBwdWJsaXNoIHByb2Nlc3MuXG4gKi9cbmV4cG9ydCBjb25zdCBWRVJTSU9OID0gJzIuMC44MzknO1xuIl19
|
|
@@ -53,7 +53,7 @@ import 'prismjs/components/prism-json';
|
|
|
53
53
|
* Current version of valtech-components.
|
|
54
54
|
* This is automatically updated during the publish process.
|
|
55
55
|
*/
|
|
56
|
-
const VERSION = '2.0.
|
|
56
|
+
const VERSION = '2.0.839';
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Servicio para gestionar presets de componentes.
|
|
@@ -20056,7 +20056,7 @@ const UPDATE_BANNER_I18N_NAMESPACE = 'UpdateBanner';
|
|
|
20056
20056
|
*/
|
|
20057
20057
|
const UPDATE_BANNER_DEFAULT_CONTENT = {
|
|
20058
20058
|
es: {
|
|
20059
|
-
availableTitle: 'Hay una versión nueva disponible',
|
|
20059
|
+
availableTitle: '✨ Hay una versión nueva disponible',
|
|
20060
20060
|
availableMessage: 'Actualiza para obtener las últimas mejoras.',
|
|
20061
20061
|
requiredTitle: 'Debes actualizar para continuar',
|
|
20062
20062
|
requiredMessage: 'Esta versión ya no es compatible. Actualiza para seguir.',
|
|
@@ -20064,7 +20064,7 @@ const UPDATE_BANNER_DEFAULT_CONTENT = {
|
|
|
20064
20064
|
dismissAction: 'Cerrar',
|
|
20065
20065
|
},
|
|
20066
20066
|
en: {
|
|
20067
|
-
availableTitle: 'A new version is available',
|
|
20067
|
+
availableTitle: '✨ A new version is available',
|
|
20068
20068
|
availableMessage: 'Update to get the latest improvements.',
|
|
20069
20069
|
requiredTitle: 'You must update to continue',
|
|
20070
20070
|
requiredMessage: 'This version is no longer supported. Please update.',
|
|
@@ -20072,7 +20072,7 @@ const UPDATE_BANNER_DEFAULT_CONTENT = {
|
|
|
20072
20072
|
dismissAction: 'Close',
|
|
20073
20073
|
},
|
|
20074
20074
|
pt: {
|
|
20075
|
-
availableTitle: 'Há uma nova versão disponível',
|
|
20075
|
+
availableTitle: '✨ Há uma nova versão disponível',
|
|
20076
20076
|
availableMessage: 'Atualize para obter as últimas melhorias.',
|
|
20077
20077
|
requiredTitle: 'Você precisa atualizar para continuar',
|
|
20078
20078
|
requiredMessage: 'Esta versão não é mais compatível. Atualize para seguir.',
|
|
@@ -20224,7 +20224,7 @@ class UpdateBannerComponent {
|
|
|
20224
20224
|
<val-button
|
|
20225
20225
|
[props]="{
|
|
20226
20226
|
text: t().dismissAction,
|
|
20227
|
-
color: '
|
|
20227
|
+
color: 'dark',
|
|
20228
20228
|
fill: 'clear',
|
|
20229
20229
|
size: 'small',
|
|
20230
20230
|
state: 'ENABLED',
|
|
@@ -20237,7 +20237,7 @@ class UpdateBannerComponent {
|
|
|
20237
20237
|
</div>
|
|
20238
20238
|
</div>
|
|
20239
20239
|
}
|
|
20240
|
-
`, isInline: true, styles: ["
|
|
20240
|
+
`, isInline: true, styles: ["@charset \"UTF-8\";:host{display:contents}.val-update-banner{position:fixed;top:0;left:0;right:0;z-index:1100;display:flex;justify-content:center;padding:calc(12px + env(safe-area-inset-top,0px)) 16px 12px;pointer-events:none}.val-update-banner__backdrop{position:fixed;inset:0;z-index:-1;background:#0000008c;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);pointer-events:auto}.val-update-banner__panel{pointer-events:auto;display:flex;flex-direction:row;align-items:center;gap:12px;width:100%;max-width:640px;padding:12px 16px;background:var(--ion-background-color, #fff);border:1px solid var(--val-border-color, rgba(0, 0, 0, .08));border-radius:var(--val-border-radius, 12px);box-shadow:0 4px 24px #0000001f;animation:val-update-banner-in .25s ease-out}.val-update-banner--required{align-items:center;height:100%}.val-update-banner--required .val-update-banner__panel{flex-direction:column;text-align:center;max-width:420px}.val-update-banner__icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:12px;background:color-mix(in srgb,var(--ion-color-primary) 14%,transparent)}.val-update-banner--required .val-update-banner__icon{background:color-mix(in srgb,var(--ion-color-warning) 16%,transparent)}.val-update-banner__body{flex:1 1 auto;display:flex;flex-direction:column;gap:2px;min-width:0}.val-update-banner__actions{flex:0 0 auto;display:flex;flex-direction:row;align-items:center;gap:4px}.val-update-banner--required .val-update-banner__actions{margin-top:8px}@keyframes val-update-banner-in{0%{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:translateY(0)}}@media (max-width: 540px){.val-update-banner__panel{flex-direction:column;text-align:center}.val-update-banner__actions{width:100%;justify-content:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: IconComponent, selector: "val-icon", inputs: ["props"] }] }); }
|
|
20241
20241
|
}
|
|
20242
20242
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: UpdateBannerComponent, decorators: [{
|
|
20243
20243
|
type: Component,
|
|
@@ -20299,7 +20299,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
20299
20299
|
<val-button
|
|
20300
20300
|
[props]="{
|
|
20301
20301
|
text: t().dismissAction,
|
|
20302
|
-
color: '
|
|
20302
|
+
color: 'dark',
|
|
20303
20303
|
fill: 'clear',
|
|
20304
20304
|
size: 'small',
|
|
20305
20305
|
state: 'ENABLED',
|
|
@@ -20312,7 +20312,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
20312
20312
|
</div>
|
|
20313
20313
|
</div>
|
|
20314
20314
|
}
|
|
20315
|
-
`, styles: ["
|
|
20315
|
+
`, styles: ["@charset \"UTF-8\";:host{display:contents}.val-update-banner{position:fixed;top:0;left:0;right:0;z-index:1100;display:flex;justify-content:center;padding:calc(12px + env(safe-area-inset-top,0px)) 16px 12px;pointer-events:none}.val-update-banner__backdrop{position:fixed;inset:0;z-index:-1;background:#0000008c;backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);pointer-events:auto}.val-update-banner__panel{pointer-events:auto;display:flex;flex-direction:row;align-items:center;gap:12px;width:100%;max-width:640px;padding:12px 16px;background:var(--ion-background-color, #fff);border:1px solid var(--val-border-color, rgba(0, 0, 0, .08));border-radius:var(--val-border-radius, 12px);box-shadow:0 4px 24px #0000001f;animation:val-update-banner-in .25s ease-out}.val-update-banner--required{align-items:center;height:100%}.val-update-banner--required .val-update-banner__panel{flex-direction:column;text-align:center;max-width:420px}.val-update-banner__icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:12px;background:color-mix(in srgb,var(--ion-color-primary) 14%,transparent)}.val-update-banner--required .val-update-banner__icon{background:color-mix(in srgb,var(--ion-color-warning) 16%,transparent)}.val-update-banner__body{flex:1 1 auto;display:flex;flex-direction:column;gap:2px;min-width:0}.val-update-banner__actions{flex:0 0 auto;display:flex;flex-direction:row;align-items:center;gap:4px}.val-update-banner--required .val-update-banner__actions{margin-top:8px}@keyframes val-update-banner-in{0%{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:translateY(0)}}@media (max-width: 540px){.val-update-banner__panel{flex-direction:column;text-align:center}.val-update-banner__actions{width:100%;justify-content:center}}\n"] }]
|
|
20316
20316
|
}] });
|
|
20317
20317
|
|
|
20318
20318
|
/**
|
|
@@ -23242,6 +23242,31 @@ class MessagingService {
|
|
|
23242
23242
|
* Es un *optimistic hint* — la verdad la confirma el siguiente `getToken()`.
|
|
23243
23243
|
*/
|
|
23244
23244
|
this.TOKEN_STORAGE_KEY = 'valtech_fcm_token';
|
|
23245
|
+
/**
|
|
23246
|
+
* Timeout (ms) para `navigator.serviceWorker.ready` dentro de `getToken()`.
|
|
23247
|
+
*
|
|
23248
|
+
* En un cold load el SW puede no estar activado aún y `serviceWorker.ready`
|
|
23249
|
+
* no resolver nunca. Pasado este tiempo, `getToken()` rechaza limpio en lugar
|
|
23250
|
+
* de colgarse indefinidamente.
|
|
23251
|
+
*/
|
|
23252
|
+
this.SW_READY_TIMEOUT_MS = 10_000;
|
|
23253
|
+
/**
|
|
23254
|
+
* Timeout (ms) del watchdog de `enable()` antes de auto-recargar la página.
|
|
23255
|
+
*
|
|
23256
|
+
* El flujo de activación a veces se cuelga ANTES de `getToken()` — caso
|
|
23257
|
+
* típico: `Notification.requestPermission()` no muestra el popup del SO en un
|
|
23258
|
+
* cold load. El timeout de `getToken()` (SW_READY_TIMEOUT_MS) no cubre eso;
|
|
23259
|
+
* por eso `enable()` envuelve el flujo completo en este watchdog.
|
|
23260
|
+
*
|
|
23261
|
+
* Un flujo exitoso real llega a token+device en ~4s; 15s es holgado y no
|
|
23262
|
+
* atrapa un éxito lento.
|
|
23263
|
+
*/
|
|
23264
|
+
this.ENABLE_WATCHDOG_MS = 15_000;
|
|
23265
|
+
/**
|
|
23266
|
+
* Key de sessionStorage — garantiza un solo auto-reload por sesión. Si el
|
|
23267
|
+
* flujo se cuelga una 2ª vez tras el reload, NO se recarga de nuevo (anti-loop).
|
|
23268
|
+
*/
|
|
23269
|
+
this.AUTORELOAD_FLAG = 'notif-enable-autoreload';
|
|
23245
23270
|
this.debugPersistence = this.config?.debugMessagePersistence ?? false;
|
|
23246
23271
|
this.initializeMessaging();
|
|
23247
23272
|
}
|
|
@@ -23395,6 +23420,158 @@ class MessagingService {
|
|
|
23395
23420
|
return null;
|
|
23396
23421
|
}
|
|
23397
23422
|
}
|
|
23423
|
+
/**
|
|
23424
|
+
* Flujo completo de activación de push, robusto, cross-app.
|
|
23425
|
+
*
|
|
23426
|
+
* Orquesta: pedir permiso → SW ready → obtener token FCM → (opcional)
|
|
23427
|
+
* registrar el device en backend. Es el punto de orquestación único que cada
|
|
23428
|
+
* app del factory consume — la lógica de robustez vive aquí, no en cada página.
|
|
23429
|
+
*
|
|
23430
|
+
* **Watchdog de auto-reload.** El flujo a veces se cuelga ANTES de `getToken()`
|
|
23431
|
+
* (ej. `Notification.requestPermission()` que no muestra el popup en un cold
|
|
23432
|
+
* load) — el timeout interno de `getToken()` no cubre ese caso. Por eso
|
|
23433
|
+
* `enable()` envuelve el flujo entero en un watchdog: si no alcanza un estado
|
|
23434
|
+
* terminal en `ENABLE_WATCHDOG_MS` (15s):
|
|
23435
|
+
* - 1ª vez en la sesión → marca un flag en `sessionStorage` y hace
|
|
23436
|
+
* `window.location.reload()` (un fresh load suele tener el SW activo).
|
|
23437
|
+
* Resuelve con `{ status: 'timeout', reloaded: true }`.
|
|
23438
|
+
* - ya se auto-recargó antes → NO recarga (anti-loop): limpia el flag y
|
|
23439
|
+
* resuelve con `{ status: 'timeout', reloaded: false }` para que la app
|
|
23440
|
+
* muestre un error.
|
|
23441
|
+
*
|
|
23442
|
+
* NO hace throw — siempre resuelve con un `EnablePushResult` descriptivo que
|
|
23443
|
+
* la página consumidora inspecciona para decidir qué toast mostrar.
|
|
23444
|
+
*
|
|
23445
|
+
* @param options.registerDevice Callback opcional que registra el device en
|
|
23446
|
+
* el backend (vive en `AuthService` — se pasa como callback para evitar el
|
|
23447
|
+
* ciclo de DI AuthService ↔ MessagingService).
|
|
23448
|
+
*
|
|
23449
|
+
* @example
|
|
23450
|
+
* ```typescript
|
|
23451
|
+
* const result = await messaging.enable({
|
|
23452
|
+
* registerDevice: (token) => auth.registerDevice(token).then(r => r.registered),
|
|
23453
|
+
* });
|
|
23454
|
+
* if (result.status === 'enabled') {
|
|
23455
|
+
* // result.token disponible — push activo
|
|
23456
|
+
* } else if (result.status === 'timeout' && !result.reloaded) {
|
|
23457
|
+
* // mostrar error: el flujo se colgó y ya se consumió el auto-reload
|
|
23458
|
+
* }
|
|
23459
|
+
* ```
|
|
23460
|
+
*/
|
|
23461
|
+
async enable(options = {}) {
|
|
23462
|
+
console.log('[Messaging] enable() start');
|
|
23463
|
+
let watchdog;
|
|
23464
|
+
// El watchdog corre en paralelo al flujo real. Lo que gane el race define
|
|
23465
|
+
// el resultado. El flujo real, si gana, limpia el watchdog en el `finally`.
|
|
23466
|
+
const watchdogPromise = new Promise(resolve => {
|
|
23467
|
+
if (typeof window === 'undefined')
|
|
23468
|
+
return; // SSR — sin watchdog.
|
|
23469
|
+
watchdog = setTimeout(() => {
|
|
23470
|
+
watchdog = undefined;
|
|
23471
|
+
console.warn(`[Messaging] enable() colgado >${this.ENABLE_WATCHDOG_MS}ms — watchdog`);
|
|
23472
|
+
if (this.hasAutoReloaded()) {
|
|
23473
|
+
// Anti-loop: ya recargamos una vez esta sesión y volvió a colgar.
|
|
23474
|
+
console.warn('[Messaging] auto-reload ya consumido — no se recarga de nuevo');
|
|
23475
|
+
this.clearAutoReloadFlag();
|
|
23476
|
+
resolve({
|
|
23477
|
+
status: 'timeout',
|
|
23478
|
+
reloaded: false,
|
|
23479
|
+
reason: `Flujo de activación colgado >${this.ENABLE_WATCHDOG_MS / 1000}s`,
|
|
23480
|
+
});
|
|
23481
|
+
return;
|
|
23482
|
+
}
|
|
23483
|
+
// 1ª vez: marcar y recargar. Un fresh load suele tener el SW activo.
|
|
23484
|
+
console.warn('[Messaging] auto-recargando la página (1/1 por sesión)');
|
|
23485
|
+
this.markAutoReloaded();
|
|
23486
|
+
resolve({
|
|
23487
|
+
status: 'timeout',
|
|
23488
|
+
reloaded: true,
|
|
23489
|
+
reason: 'Auto-recargando para reintentar la activación',
|
|
23490
|
+
});
|
|
23491
|
+
window.location.reload();
|
|
23492
|
+
}, this.ENABLE_WATCHDOG_MS);
|
|
23493
|
+
});
|
|
23494
|
+
const flow = this.runEnableFlow(options).finally(() => {
|
|
23495
|
+
if (watchdog !== undefined) {
|
|
23496
|
+
clearTimeout(watchdog);
|
|
23497
|
+
watchdog = undefined;
|
|
23498
|
+
}
|
|
23499
|
+
});
|
|
23500
|
+
return Promise.race([flow, watchdogPromise]);
|
|
23501
|
+
}
|
|
23502
|
+
/**
|
|
23503
|
+
* Flujo real de activación, sin el watchdog (lo envuelve `enable()`).
|
|
23504
|
+
* Resuelve siempre con un `EnablePushResult` — no hace throw.
|
|
23505
|
+
*/
|
|
23506
|
+
async runEnableFlow(options) {
|
|
23507
|
+
if (!(await this.isSupported())) {
|
|
23508
|
+
console.warn('[Messaging] enable: FCM no soportado');
|
|
23509
|
+
return { status: 'unsupported', reason: 'FCM no soportado en este navegador' };
|
|
23510
|
+
}
|
|
23511
|
+
try {
|
|
23512
|
+
// permission → SW ready → getToken (requestPermission encadena las 3).
|
|
23513
|
+
const token = await this.requestPermission();
|
|
23514
|
+
if (!token) {
|
|
23515
|
+
// requestPermission devuelve null tanto por permiso denegado como por
|
|
23516
|
+
// fallo al obtener el token. Distinguimos por el estado del permiso.
|
|
23517
|
+
const permission = this.getPermissionState();
|
|
23518
|
+
if (permission === 'denied' || permission === 'default') {
|
|
23519
|
+
return { status: 'denied', reason: 'Permiso de notificaciones denegado' };
|
|
23520
|
+
}
|
|
23521
|
+
return { status: 'error', reason: 'No se pudo obtener el token FCM' };
|
|
23522
|
+
}
|
|
23523
|
+
// Registro de device en backend (opcional — el caller pasa el callback).
|
|
23524
|
+
if (options.registerDevice) {
|
|
23525
|
+
const registered = await options.registerDevice(token);
|
|
23526
|
+
if (!registered) {
|
|
23527
|
+
return { status: 'error', token, reason: 'No se pudo registrar el dispositivo' };
|
|
23528
|
+
}
|
|
23529
|
+
}
|
|
23530
|
+
// Éxito: estado terminal. Limpiar el flag anti-loop para futuros intentos.
|
|
23531
|
+
this.clearAutoReloadFlag();
|
|
23532
|
+
console.log('[Messaging] enable() success');
|
|
23533
|
+
return { status: 'enabled', token };
|
|
23534
|
+
}
|
|
23535
|
+
catch (error) {
|
|
23536
|
+
const reason = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
23537
|
+
console.error('[Messaging] enable() error:', reason, error);
|
|
23538
|
+
return { status: 'error', reason };
|
|
23539
|
+
}
|
|
23540
|
+
}
|
|
23541
|
+
/** True si ya se auto-recargó una vez en esta sesión. */
|
|
23542
|
+
hasAutoReloaded() {
|
|
23543
|
+
if (!isPlatformBrowser(this.platformId))
|
|
23544
|
+
return false;
|
|
23545
|
+
try {
|
|
23546
|
+
return sessionStorage.getItem(this.AUTORELOAD_FLAG) === '1';
|
|
23547
|
+
}
|
|
23548
|
+
catch {
|
|
23549
|
+
// sessionStorage puede no estar disponible (modo privado estricto, SSR).
|
|
23550
|
+
return false;
|
|
23551
|
+
}
|
|
23552
|
+
}
|
|
23553
|
+
/** Marca que se consumió el auto-reload de esta sesión. */
|
|
23554
|
+
markAutoReloaded() {
|
|
23555
|
+
if (!isPlatformBrowser(this.platformId))
|
|
23556
|
+
return;
|
|
23557
|
+
try {
|
|
23558
|
+
sessionStorage.setItem(this.AUTORELOAD_FLAG, '1');
|
|
23559
|
+
}
|
|
23560
|
+
catch {
|
|
23561
|
+
/* sessionStorage no disponible — best-effort. */
|
|
23562
|
+
}
|
|
23563
|
+
}
|
|
23564
|
+
/** Limpia el flag anti-loop — tras un éxito o un fallo definitivo. */
|
|
23565
|
+
clearAutoReloadFlag() {
|
|
23566
|
+
if (!isPlatformBrowser(this.platformId))
|
|
23567
|
+
return;
|
|
23568
|
+
try {
|
|
23569
|
+
sessionStorage.removeItem(this.AUTORELOAD_FLAG);
|
|
23570
|
+
}
|
|
23571
|
+
catch {
|
|
23572
|
+
/* sessionStorage no disponible — nada que limpiar. */
|
|
23573
|
+
}
|
|
23574
|
+
}
|
|
23398
23575
|
/**
|
|
23399
23576
|
* Obtiene el token FCM actual (sin solicitar permiso).
|
|
23400
23577
|
*
|
|
@@ -23423,8 +23600,11 @@ class MessagingService {
|
|
|
23423
23600
|
// revalidaciones del SW en iOS PWA. Solo registramos si no existe aún.
|
|
23424
23601
|
const registration = await this.resolveServiceWorkerRegistration();
|
|
23425
23602
|
console.log('[Messaging] SW resolved, waiting ready...');
|
|
23426
|
-
// Esperar a que el SW esté activo
|
|
23427
|
-
|
|
23603
|
+
// Esperar a que el SW esté activo, con timeout: `navigator.serviceWorker.ready`
|
|
23604
|
+
// puede no resolver NUNCA en un cold load (SW aún no activado) → sin el
|
|
23605
|
+
// timeout `getToken()` se cuelga indefinidamente. Con el race, si el SW no
|
|
23606
|
+
// queda listo en SW_READY_TIMEOUT_MS, esta promesa rechaza limpio.
|
|
23607
|
+
await this.waitForServiceWorkerReady();
|
|
23428
23608
|
console.log('[Messaging] SW ready, calling Firebase getToken()...');
|
|
23429
23609
|
const token = await getToken(messaging, {
|
|
23430
23610
|
vapidKey,
|
|
@@ -23463,6 +23643,35 @@ class MessagingService {
|
|
|
23463
23643
|
console.warn('[Messaging] SW not registered yet, registering as fallback');
|
|
23464
23644
|
return navigator.serviceWorker.register('/firebase-messaging-sw.js');
|
|
23465
23645
|
}
|
|
23646
|
+
/**
|
|
23647
|
+
* Espera a que el Service Worker quede activo (`navigator.serviceWorker.ready`)
|
|
23648
|
+
* pero con un timeout duro.
|
|
23649
|
+
*
|
|
23650
|
+
* `navigator.serviceWorker.ready` resuelve cuando hay un SW *activo*. En un
|
|
23651
|
+
* cold load (primera visita, SW recién registrado, iOS PWA recién abierta) el
|
|
23652
|
+
* SW puede quedar en estado `installing`/`waiting` y `ready` no resolver nunca
|
|
23653
|
+
* → `getToken()` se cuelga. El `Promise.race` contra un `setTimeout` garantiza
|
|
23654
|
+
* que esta espera termina: o gana `ready` (caso normal) o gana el timeout y
|
|
23655
|
+
* lanzamos un error claro para que `getToken()` rechace en vez de colgarse.
|
|
23656
|
+
*
|
|
23657
|
+
* El timer se limpia en ambas ramas para no dejar timers colgando.
|
|
23658
|
+
*/
|
|
23659
|
+
waitForServiceWorkerReady() {
|
|
23660
|
+
let timer;
|
|
23661
|
+
const ready = navigator.serviceWorker.ready.then(() => {
|
|
23662
|
+
if (timer !== undefined)
|
|
23663
|
+
clearTimeout(timer);
|
|
23664
|
+
});
|
|
23665
|
+
const timeout = new Promise((_, reject) => {
|
|
23666
|
+
timer = setTimeout(() => {
|
|
23667
|
+
reject(new Error(`[Messaging] service worker no quedó listo en ${this.SW_READY_TIMEOUT_MS / 1000}s`));
|
|
23668
|
+
}, this.SW_READY_TIMEOUT_MS);
|
|
23669
|
+
});
|
|
23670
|
+
return Promise.race([ready, timeout]).finally(() => {
|
|
23671
|
+
if (timer !== undefined)
|
|
23672
|
+
clearTimeout(timer);
|
|
23673
|
+
});
|
|
23674
|
+
}
|
|
23466
23675
|
/**
|
|
23467
23676
|
* Persiste el token FCM en localStorage (o lo limpia si es null/empty).
|
|
23468
23677
|
*/
|
|
@@ -29410,7 +29619,7 @@ class CookieBannerComponent {
|
|
|
29410
29619
|
</div>
|
|
29411
29620
|
|
|
29412
29621
|
<div class="val-cookie-banner__actions">
|
|
29413
|
-
<ion-button fill="clear" size="small" [color]="props.rejectColor || '
|
|
29622
|
+
<ion-button fill="clear" size="small" [color]="props.rejectColor || 'dark'" (click)="reject.emit()">
|
|
29414
29623
|
{{ props.rejectText }}
|
|
29415
29624
|
</ion-button>
|
|
29416
29625
|
|
|
@@ -29444,7 +29653,7 @@ class CookieBannerComponent {
|
|
|
29444
29653
|
</div>
|
|
29445
29654
|
</div>
|
|
29446
29655
|
}
|
|
29447
|
-
`, isInline: true, styles: [":host{display:contents}.val-cookie-banner{position:fixed;left:0;right:0;z-index:1000;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-color:var(--val-border-color, rgba(0, 0, 0, .08));border-style:solid;border-width:0;box-shadow:0 -2px 16px #00000014;animation:val-cookie-banner-in .25s ease-out}.val-cookie-banner--bottom{bottom:0;border-top-width:1px}.val-cookie-banner--top{top:0;border-bottom-width:1px;padding:calc(12px + env(safe-area-inset-top,0px)) 16px 12px;box-shadow:0 2px 16px #00000014}.val-cookie-banner--translucent{background
|
|
29656
|
+
`, isInline: true, styles: [":host{display:contents}.val-cookie-banner{position:fixed;left:0;right:0;z-index:1000;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-color:var(--val-border-color, rgba(0, 0, 0, .08));border-style:solid;border-width:0;box-shadow:0 -2px 16px #00000014;animation:val-cookie-banner-in .25s ease-out}.val-cookie-banner--bottom{bottom:0;border-top-width:1px}.val-cookie-banner--top{top:0;border-bottom-width:1px;padding:calc(12px + env(safe-area-inset-top,0px)) 16px 12px;box-shadow:0 2px 16px #00000014}.val-cookie-banner--translucent{background:color-mix(in srgb,var(--ion-background-color, #fff) 85%,transparent);backdrop-filter:saturate(180%) blur(16px);-webkit-backdrop-filter:saturate(180%) blur(16px)}.val-cookie-banner__inner{margin:0 auto;display:flex;flex-direction:row;align-items:center;gap:16px;position:relative}.val-cookie-banner__dismiss{position:absolute;top:-8px;right:-4px;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:0;border-radius:50%;cursor:pointer;color:var(--ion-color-medium);transition:color .15s ease,background .15s ease}.val-cookie-banner__dismiss:hover{color:var(--ion-color-dark);background:var(--ion-color-light-shade, rgba(0, 0, 0, .04))}.val-cookie-banner__dismiss ion-icon{font-size:18px}.val-cookie-banner__copy{flex:1 1 auto;min-width:0}.val-cookie-banner__title{margin:0 0 4px;font-size:14px;font-weight:600;color:var(--ion-color-dark)}.val-cookie-banner__message{margin:0;font-size:13px;line-height:1.45;color:var(--ion-color-dark)}.val-cookie-banner__policy{margin-left:4px;color:var(--ion-color-primary);text-decoration:underline;text-underline-offset:2px}.val-cookie-banner__actions{display:inline-flex;flex-direction:row;align-items:center;gap:8px;flex-shrink:0}@media (max-width: 768px){.val-cookie-banner__inner{flex-direction:column;align-items:stretch;gap:12px}.val-cookie-banner__actions{flex-wrap:wrap;justify-content:flex-end}}@media (max-width: 480px){.val-cookie-banner__actions{flex-direction:column;align-items:stretch}.val-cookie-banner__actions ion-button{width:100%}}@keyframes val-cookie-banner-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.val-cookie-banner--top{animation-name:val-cookie-banner-in-top}@keyframes val-cookie-banner-in-top{0%{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:translateY(0)}}@media (prefers-reduced-motion: reduce){.val-cookie-banner{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
29448
29657
|
}
|
|
29449
29658
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CookieBannerComponent, decorators: [{
|
|
29450
29659
|
type: Component,
|
|
@@ -29486,7 +29695,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
29486
29695
|
</div>
|
|
29487
29696
|
|
|
29488
29697
|
<div class="val-cookie-banner__actions">
|
|
29489
|
-
<ion-button fill="clear" size="small" [color]="props.rejectColor || '
|
|
29698
|
+
<ion-button fill="clear" size="small" [color]="props.rejectColor || 'dark'" (click)="reject.emit()">
|
|
29490
29699
|
{{ props.rejectText }}
|
|
29491
29700
|
</ion-button>
|
|
29492
29701
|
|
|
@@ -29520,7 +29729,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
29520
29729
|
</div>
|
|
29521
29730
|
</div>
|
|
29522
29731
|
}
|
|
29523
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:contents}.val-cookie-banner{position:fixed;left:0;right:0;z-index:1000;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-color:var(--val-border-color, rgba(0, 0, 0, .08));border-style:solid;border-width:0;box-shadow:0 -2px 16px #00000014;animation:val-cookie-banner-in .25s ease-out}.val-cookie-banner--bottom{bottom:0;border-top-width:1px}.val-cookie-banner--top{top:0;border-bottom-width:1px;padding:calc(12px + env(safe-area-inset-top,0px)) 16px 12px;box-shadow:0 2px 16px #00000014}.val-cookie-banner--translucent{background
|
|
29732
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:contents}.val-cookie-banner{position:fixed;left:0;right:0;z-index:1000;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-color:var(--val-border-color, rgba(0, 0, 0, .08));border-style:solid;border-width:0;box-shadow:0 -2px 16px #00000014;animation:val-cookie-banner-in .25s ease-out}.val-cookie-banner--bottom{bottom:0;border-top-width:1px}.val-cookie-banner--top{top:0;border-bottom-width:1px;padding:calc(12px + env(safe-area-inset-top,0px)) 16px 12px;box-shadow:0 2px 16px #00000014}.val-cookie-banner--translucent{background:color-mix(in srgb,var(--ion-background-color, #fff) 85%,transparent);backdrop-filter:saturate(180%) blur(16px);-webkit-backdrop-filter:saturate(180%) blur(16px)}.val-cookie-banner__inner{margin:0 auto;display:flex;flex-direction:row;align-items:center;gap:16px;position:relative}.val-cookie-banner__dismiss{position:absolute;top:-8px;right:-4px;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:0;border-radius:50%;cursor:pointer;color:var(--ion-color-medium);transition:color .15s ease,background .15s ease}.val-cookie-banner__dismiss:hover{color:var(--ion-color-dark);background:var(--ion-color-light-shade, rgba(0, 0, 0, .04))}.val-cookie-banner__dismiss ion-icon{font-size:18px}.val-cookie-banner__copy{flex:1 1 auto;min-width:0}.val-cookie-banner__title{margin:0 0 4px;font-size:14px;font-weight:600;color:var(--ion-color-dark)}.val-cookie-banner__message{margin:0;font-size:13px;line-height:1.45;color:var(--ion-color-dark)}.val-cookie-banner__policy{margin-left:4px;color:var(--ion-color-primary);text-decoration:underline;text-underline-offset:2px}.val-cookie-banner__actions{display:inline-flex;flex-direction:row;align-items:center;gap:8px;flex-shrink:0}@media (max-width: 768px){.val-cookie-banner__inner{flex-direction:column;align-items:stretch;gap:12px}.val-cookie-banner__actions{flex-wrap:wrap;justify-content:flex-end}}@media (max-width: 480px){.val-cookie-banner__actions{flex-direction:column;align-items:stretch}.val-cookie-banner__actions ion-button{width:100%}}@keyframes val-cookie-banner-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.val-cookie-banner--top{animation-name:val-cookie-banner-in-top}@keyframes val-cookie-banner-in-top{0%{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:translateY(0)}}@media (prefers-reduced-motion: reduce){.val-cookie-banner{animation:none}}\n"] }]
|
|
29524
29733
|
}], ctorParameters: () => [], propDecorators: { props: [{
|
|
29525
29734
|
type: Input
|
|
29526
29735
|
}], accept: [{
|
|
@@ -47070,10 +47279,13 @@ const VALTECH_FOOTER_LOGO = {
|
|
|
47070
47279
|
* Company links organized by section
|
|
47071
47280
|
*/
|
|
47072
47281
|
const VALTECH_COMPANY_LINKS = {
|
|
47282
|
+
// Links legales con `external: true` → abren en tab nueva (target=_blank).
|
|
47073
47283
|
legal: [
|
|
47074
47284
|
{ key: 'aboutUs', url: '/legal/about', kind: 'site', external: false },
|
|
47075
|
-
{ key: 'privacyPolicy', url: '/legal/privacy', kind: 'legal', external:
|
|
47076
|
-
{ key: 'termsConditions', url: '/legal/terms', kind: 'legal', external:
|
|
47285
|
+
{ key: 'privacyPolicy', url: '/legal/privacy', kind: 'legal', external: true },
|
|
47286
|
+
{ key: 'termsConditions', url: '/legal/terms', kind: 'legal', external: true },
|
|
47287
|
+
{ key: 'cookiesPolicy', url: '/legal/cookies', kind: 'legal', external: true },
|
|
47288
|
+
{ key: 'legalNotice', url: '/legal/legal-notice', kind: 'legal', external: true },
|
|
47077
47289
|
],
|
|
47078
47290
|
support: [
|
|
47079
47291
|
{ key: 'contactSupport', url: '/contact', kind: 'support', external: false },
|
|
@@ -47096,6 +47308,8 @@ const VALTECH_FOOTER_I18N = {
|
|
|
47096
47308
|
aboutUs: 'Nosotros',
|
|
47097
47309
|
privacyPolicy: 'Política de privacidad',
|
|
47098
47310
|
termsConditions: 'Términos y condiciones',
|
|
47311
|
+
cookiesPolicy: 'Política de cookies',
|
|
47312
|
+
legalNotice: 'Aviso legal',
|
|
47099
47313
|
// Support links
|
|
47100
47314
|
contactSupport: 'Contactar a soporte',
|
|
47101
47315
|
faq: 'Preguntas frecuentes',
|
|
@@ -47111,6 +47325,8 @@ const VALTECH_FOOTER_I18N = {
|
|
|
47111
47325
|
aboutUs: 'About Us',
|
|
47112
47326
|
privacyPolicy: 'Privacy Policy',
|
|
47113
47327
|
termsConditions: 'Terms & Conditions',
|
|
47328
|
+
cookiesPolicy: 'Cookie Policy',
|
|
47329
|
+
legalNotice: 'Legal Notice',
|
|
47114
47330
|
// Support links
|
|
47115
47331
|
contactSupport: 'Contact Support',
|
|
47116
47332
|
faq: 'FAQ',
|