valtech-components 4.0.254 → 4.0.256

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.
@@ -12,7 +12,7 @@ import { provideAnalytics, getAnalytics, Analytics, logEvent, setUserId, setUser
12
12
  import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
13
13
  import * as i1 from '@angular/fire/auth';
14
14
  import { provideAuth, getAuth, connectAuthEmulator, authState, signInWithCustomToken, signOut } from '@angular/fire/auth';
15
- import { provideMessaging, getMessaging, getToken, deleteToken, onMessage } from '@angular/fire/messaging';
15
+ import { provideMessaging, getMessaging } from '@angular/fire/messaging';
16
16
  import * as i1$2 from '@angular/fire/storage';
17
17
  import { provideStorage, getStorage, connectStorageEmulator, ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject, listAll } from '@angular/fire/storage';
18
18
  import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
@@ -20,7 +20,7 @@ import * as i1$4 from '@angular/router';
20
20
  import { Router, NavigationEnd, RouterLink, ActivatedRoute, RouterOutlet, RouterLinkActive, RouterModule } from '@angular/router';
21
21
  import * as i2 from '@ionic/angular';
22
22
  import { IonicModule, ToastController, NavController } from '@ionic/angular';
23
- import { isSupported, getMessaging as getMessaging$1 } from 'firebase/messaging';
23
+ import { isSupported, getMessaging as getMessaging$1, getToken, deleteToken, onMessage } from 'firebase/messaging';
24
24
  import { getApps, getApp, initializeApp as initializeApp$1 } from 'firebase/app';
25
25
  import * as i2$1 from '@ionic/angular/standalone';
26
26
  import { AlertController, IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal, IonHeader, IonToolbar, IonContent, IonButtons, IonTitle, IonProgressBar, IonSkeletonText, IonFab, IonFabButton, IonFabList, IonLabel, IonCardContent, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCheckbox, IonRadio, IonRadioGroup, IonTextarea, IonDatetime, IonSelect, IonSelectOption, IonPopover, IonList, IonItem, IonInput, IonChip, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonRippleEffect, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonInfiniteScroll, IonInfiniteScrollContent, IonNote, IonDatetimeButton, ToastController as ToastController$1, IonCol, IonRow, IonRefresher, IonRefresherContent, IonMenuButton, IonFooter, IonListHeader, IonGrid, MenuController, IonMenu, IonMenuToggle, IonSplitPane } from '@ionic/angular/standalone';
@@ -57,7 +57,7 @@ import fixWebmDuration from 'fix-webm-duration';
57
57
  * Current version of valtech-components.
58
58
  * This is automatically updated during the publish process.
59
59
  */
60
- const VERSION = '4.0.254';
60
+ const VERSION = '4.0.256';
61
61
 
62
62
  // Control de estado de refresco (singleton a nivel de módulo)
63
63
  let isRefreshing = false;
@@ -719,9 +719,12 @@ function provideValtechFirebase(config) {
719
719
  // Messaging (FCM) - solo si está explícitamente habilitado y estamos en browser.
720
720
  // Requiere Service Worker + Notifications API → skip total en SSR/Node.
721
721
  if (config.enableMessaging && isBrowser) {
722
- // Pre-registrar SW custom antes de que Firebase lo intente
722
+ // Pre-registrar SW custom antes de que Firebase lo intente. Usamos un
723
+ // scope dedicado para no competir con Angular `ngsw-worker.js` en `/`.
723
724
  if ('serviceWorker' in navigator) {
724
- navigator.serviceWorker.register('/firebase-messaging-sw.js').catch(console.error);
725
+ navigator.serviceWorker
726
+ .register('/firebase-messaging-sw.js', { scope: '/firebase-cloud-messaging-push-scope' })
727
+ .catch(console.error);
725
728
  }
726
729
  providers.push(provideMessaging(() => getMessaging()));
727
730
  }
@@ -5399,6 +5402,8 @@ class MessagingService {
5399
5402
  * Es un *optimistic hint* — la verdad la confirma el siguiente `getToken()`.
5400
5403
  */
5401
5404
  this.TOKEN_STORAGE_KEY = 'valtech_fcm_token';
5405
+ this.FCM_SW_URL = '/firebase-messaging-sw.js';
5406
+ this.FCM_SW_SCOPE = '/firebase-cloud-messaging-push-scope';
5402
5407
  /**
5403
5408
  * Timeout (ms) para `navigator.serviceWorker.ready` dentro de `getToken()`.
5404
5409
  *
@@ -5407,6 +5412,14 @@ class MessagingService {
5407
5412
  * de colgarse indefinidamente.
5408
5413
  */
5409
5414
  this.SW_READY_TIMEOUT_MS = 10_000;
5415
+ /**
5416
+ * Timeout duro para la llamada de Firebase `getToken()`.
5417
+ *
5418
+ * En Chromium puede quedar pendiente sin resolver si la registración FCM no
5419
+ * responde (VAPID/proyecto/API/red). Sin este timeout, el watchdog externo
5420
+ * fuerza un reload que no corrige la causa y deja un diagnóstico pobre.
5421
+ */
5422
+ this.FCM_GET_TOKEN_TIMEOUT_MS = 15_000;
5410
5423
  /**
5411
5424
  * Timeout (ms) del watchdog de `enable()` antes de auto-recargar la página.
5412
5425
  *
@@ -5415,10 +5428,10 @@ class MessagingService {
5415
5428
  * cold load. El timeout de `getToken()` (SW_READY_TIMEOUT_MS) no cubre eso;
5416
5429
  * por eso `enable()` envuelve el flujo completo en este watchdog.
5417
5430
  *
5418
- * Un flujo exitoso real llega a token+device en ~4s; 15s es holgado y no
5431
+ * Un flujo exitoso real llega a token+device en ~4s; 25s es holgado y no
5419
5432
  * atrapa un éxito lento.
5420
5433
  */
5421
- this.ENABLE_WATCHDOG_MS = 15_000;
5434
+ this.ENABLE_WATCHDOG_MS = 25_000;
5422
5435
  /**
5423
5436
  * Key de sessionStorage — garantiza un solo auto-reload por sesión. Si el
5424
5437
  * flujo se cuelga una 2ª vez tras el reload, NO se recarga de nuevo (anti-loop).
@@ -5444,8 +5457,8 @@ class MessagingService {
5444
5457
  try {
5445
5458
  const app = getApps().length > 0 ? getApp() : initializeApp$1(this.config.firebase);
5446
5459
  // Cast a Messaging (token de AngularFire) — el tipo subyacente del SDK
5447
- // es el mismo. Las funciones getToken/deleteToken/onMessage de
5448
- // @angular/fire/messaging son re-exports del SDK y aceptan esta instance.
5460
+ // es el mismo. Las funciones getToken/deleteToken/onMessage se llaman
5461
+ // desde `firebase/messaging` directo para evitar wrappers de Zone.
5449
5462
  return getMessaging$1(app);
5450
5463
  }
5451
5464
  catch (err) {
@@ -5763,10 +5776,10 @@ class MessagingService {
5763
5776
  // queda listo en SW_READY_TIMEOUT_MS, esta promesa rechaza limpio.
5764
5777
  await this.waitForServiceWorkerReady();
5765
5778
  console.log('[Messaging] SW ready, calling Firebase getToken()...');
5766
- const token = await getToken(messaging, {
5779
+ const token = await this.withTimeout(getToken(messaging, {
5767
5780
  vapidKey,
5768
5781
  serviceWorkerRegistration: registration,
5769
- });
5782
+ }), this.FCM_GET_TOKEN_TIMEOUT_MS, `[Messaging] Firebase getToken no respondió en ${this.FCM_GET_TOKEN_TIMEOUT_MS / 1000}s`);
5770
5783
  console.log('[Messaging] Firebase getToken returned:', token ? `${token.slice(0, 16)}…` : 'null/empty');
5771
5784
  this.stateSubject.next({
5772
5785
  ...this.stateSubject.value,
@@ -5787,18 +5800,28 @@ class MessagingService {
5787
5800
  /**
5788
5801
  * Resuelve el `ServiceWorkerRegistration` del SW de FCM.
5789
5802
  *
5790
- * El SW `/firebase-messaging-sw.js` ya se registra una vez en el bootstrap de
5791
- * la app (`config.ts`). Reutilizamos ese registro en lugar de re-registrarlo
5803
+ * El SW de FCM ya se registra una vez en el bootstrap de la app (`config.ts`)
5804
+ * con un scope dedicado. Reutilizamos ese registro en lugar de re-registrarlo
5792
5805
  * en cada `getToken()` — re-registrar repetidamente dispara revalidaciones del
5793
5806
  * SW en iOS PWA. Solo registramos como fallback si todavía no existe.
5794
5807
  */
5795
5808
  async resolveServiceWorkerRegistration() {
5796
- const existing = await navigator.serviceWorker.getRegistration('/firebase-messaging-sw.js');
5809
+ const existing = await this.findFcmServiceWorkerRegistration();
5797
5810
  if (existing) {
5811
+ console.log('[Messaging] SW registration resolved:', existing.scope, existing.active?.scriptURL || existing.installing?.scriptURL || existing.waiting?.scriptURL);
5798
5812
  return existing;
5799
5813
  }
5800
5814
  console.warn('[Messaging] SW not registered yet, registering as fallback');
5801
- return navigator.serviceWorker.register('/firebase-messaging-sw.js');
5815
+ const registration = await navigator.serviceWorker.register(this.FCM_SW_URL, {
5816
+ scope: this.FCM_SW_SCOPE,
5817
+ });
5818
+ console.log('[Messaging] SW registration created:', registration.scope, registration.active?.scriptURL || registration.installing?.scriptURL || registration.waiting?.scriptURL);
5819
+ return registration;
5820
+ }
5821
+ async findFcmServiceWorkerRegistration() {
5822
+ const expectedScope = new URL(this.FCM_SW_SCOPE, window.location.origin).href;
5823
+ const registrations = await navigator.serviceWorker.getRegistrations();
5824
+ return registrations.find(registration => registration.scope === expectedScope);
5802
5825
  }
5803
5826
  /**
5804
5827
  * Espera a que el Service Worker quede activo (`navigator.serviceWorker.ready`)
@@ -5814,17 +5837,35 @@ class MessagingService {
5814
5837
  * El timer se limpia en ambas ramas para no dejar timers colgando.
5815
5838
  */
5816
5839
  waitForServiceWorkerReady() {
5840
+ return this.withTimeout(this.findFcmServiceWorkerRegistration().then(registration => {
5841
+ if (!registration) {
5842
+ throw new Error('[Messaging] service worker FCM no registrado');
5843
+ }
5844
+ if (registration.active) {
5845
+ return undefined;
5846
+ }
5847
+ return new Promise((resolve, reject) => {
5848
+ const worker = registration.installing || registration.waiting;
5849
+ if (!worker) {
5850
+ reject(new Error('[Messaging] service worker FCM sin worker activo'));
5851
+ return;
5852
+ }
5853
+ worker.addEventListener('statechange', () => {
5854
+ if (worker.state === 'activated')
5855
+ resolve();
5856
+ if (worker.state === 'redundant') {
5857
+ reject(new Error('[Messaging] service worker FCM quedó redundante'));
5858
+ }
5859
+ });
5860
+ });
5861
+ }), this.SW_READY_TIMEOUT_MS, `[Messaging] service worker no quedó listo en ${this.SW_READY_TIMEOUT_MS / 1000}s`);
5862
+ }
5863
+ withTimeout(promise, ms, message) {
5817
5864
  let timer;
5818
- const ready = navigator.serviceWorker.ready.then(() => {
5819
- if (timer !== undefined)
5820
- clearTimeout(timer);
5821
- });
5822
5865
  const timeout = new Promise((_, reject) => {
5823
- timer = setTimeout(() => {
5824
- reject(new Error(`[Messaging] service worker no quedó listo en ${this.SW_READY_TIMEOUT_MS / 1000}s`));
5825
- }, this.SW_READY_TIMEOUT_MS);
5866
+ timer = setTimeout(() => reject(new Error(message)), ms);
5826
5867
  });
5827
- return Promise.race([ready, timeout]).finally(() => {
5868
+ return Promise.race([promise, timeout]).finally(() => {
5828
5869
  if (timer !== undefined)
5829
5870
  clearTimeout(timer);
5830
5871
  });
@@ -9724,6 +9765,7 @@ class AuthService {
9724
9765
  const response = await firstValueFrom(this.http.post(`${this.config?.apiUrl}/v2/users/me/devices`, {
9725
9766
  token,
9726
9767
  platform,
9768
+ appId: this.config?.appId,
9727
9769
  browser,
9728
9770
  os,
9729
9771
  }));
@@ -9767,6 +9809,7 @@ class AuthService {
9767
9809
  await firstValueFrom(this.http.post(`${this.config?.apiUrl}/v2/users/me/devices`, {
9768
9810
  token,
9769
9811
  platform,
9812
+ appId: this.config?.appId,
9770
9813
  browser,
9771
9814
  os,
9772
9815
  }));
@@ -47001,12 +47044,8 @@ class PreferencesViewComponent {
47001
47044
  this.fontSizeTitle = computed(() => this.t('fontSizeTitle'));
47002
47045
  this.fontSizeHint = computed(() => this.t('fontSizeHint'));
47003
47046
  this.langProps = computed(() => ({
47004
- currentLanguage: this.prefs.language(),
47005
47047
  availableLanguages: this.resolvedConfig().supportedLanguages,
47006
- showLabel: false,
47007
- disabled: this.saving(),
47008
- expand: 'block',
47009
- fill: 'outline',
47048
+ showFlags: false,
47010
47049
  }));
47011
47050
  this.themePickerProps = computed(() => ({
47012
47051
  selectedValue: this.prefs.theme(),
@@ -47137,7 +47176,7 @@ class PreferencesViewComponent {
47137
47176
  content: langHint(),
47138
47177
  }"
47139
47178
  />
47140
- <val-language-selector [props]="langProps()" (languageChange)="onLanguageChange($event)" />
47179
+ <val-language-selector-v2 [props]="langProps()" (languageChange)="onLanguageChange($event)" />
47141
47180
  </div>
47142
47181
  </section>
47143
47182
  }
@@ -47166,14 +47205,14 @@ class PreferencesViewComponent {
47166
47205
  </section>
47167
47206
  }
47168
47207
  </div>
47169
- `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.section-body{display:flex;flex-direction:column;gap:10px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PopoverSelectorComponent, selector: "val-popover-selector", inputs: ["props"], outputs: ["selectionChange"] }, { kind: "component", type: LanguageSelectorComponent, selector: "val-language-selector", inputs: ["props"], outputs: ["languageChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }] }); }
47208
+ `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.section-body{display:flex;flex-direction:column;gap:10px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PopoverSelectorComponent, selector: "val-popover-selector", inputs: ["props"], outputs: ["selectionChange"] }, { kind: "component", type: LanguageSelectorV2Component, selector: "val-language-selector-v2", inputs: ["props"], outputs: ["languageChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }] }); }
47170
47209
  }
47171
47210
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PreferencesViewComponent, decorators: [{
47172
47211
  type: Component,
47173
47212
  args: [{ selector: 'val-preferences-view', standalone: true, imports: [
47174
47213
  CommonModule,
47175
47214
  PopoverSelectorComponent,
47176
- LanguageSelectorComponent,
47215
+ LanguageSelectorV2Component,
47177
47216
  DisplayComponent,
47178
47217
  TitleComponent,
47179
47218
  TextComponent,
@@ -47234,7 +47273,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
47234
47273
  content: langHint(),
47235
47274
  }"
47236
47275
  />
47237
- <val-language-selector [props]="langProps()" (languageChange)="onLanguageChange($event)" />
47276
+ <val-language-selector-v2 [props]="langProps()" (languageChange)="onLanguageChange($event)" />
47238
47277
  </div>
47239
47278
  </section>
47240
47279
  }