valtech-components 4.0.172 → 4.0.174
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/update-banner.component.mjs +18 -6
- package/esm2022/lib/services/auth/auth.service.mjs +28 -4
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +46 -10
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/update-banner/update-banner.component.d.ts +7 -0
- package/lib/services/auth/auth.service.d.ts +1 -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 = '4.0.
|
|
6
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
5
|
+
export const VERSION = '4.0.174';
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsU0FBUyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDdXJyZW50IHZlcnNpb24gb2YgdmFsdGVjaC1jb21wb25lbnRzLlxuICogVGhpcyBpcyBhdXRvbWF0aWNhbGx5IHVwZGF0ZWQgZHVyaW5nIHRoZSBwdWJsaXNoIHByb2Nlc3MuXG4gKi9cbmV4cG9ydCBjb25zdCBWRVJTSU9OID0gJzQuMC4xNzQnO1xuIl19
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, SecurityContext, NgZone, ViewChild, ElementRef, ChangeDetectorRef, output, ContentChild, model, ViewEncapsulation, untracked, viewChild, Injector, isSignal } from '@angular/core';
|
|
3
|
-
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, forkJoin, race, timer, shareReplay } from 'rxjs';
|
|
4
|
-
import { catchError, switchMap, finalize, filter, take, timeout, map as map$1, tap, distinctUntilChanged as distinctUntilChanged$1, retry, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
3
|
+
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, forkJoin, race, timer, shareReplay as shareReplay$1 } from 'rxjs';
|
|
4
|
+
import { catchError, switchMap, finalize, filter, take, timeout, map as map$1, tap, shareReplay, distinctUntilChanged as distinctUntilChanged$1, retry, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
5
5
|
import * as i1$3 from '@angular/common/http';
|
|
6
6
|
import { provideHttpClient, withInterceptors, HttpClient, HttpErrorResponse } from '@angular/common/http';
|
|
7
7
|
import * as i1$1 from '@angular/fire/firestore';
|
|
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
|
|
|
56
56
|
* Current version of valtech-components.
|
|
57
57
|
* This is automatically updated during the publish process.
|
|
58
58
|
*/
|
|
59
|
-
const VERSION = '4.0.
|
|
59
|
+
const VERSION = '4.0.174';
|
|
60
60
|
|
|
61
61
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
62
62
|
let isRefreshing = false;
|
|
@@ -8349,6 +8349,13 @@ class AuthService {
|
|
|
8349
8349
|
this.refreshTimerId = null;
|
|
8350
8350
|
this.syncSubscription = null;
|
|
8351
8351
|
this.firestoreRBACUnsubscribe = null;
|
|
8352
|
+
// Single-flight refresh: un solo POST /refresh en vuelo a la vez. Todos los
|
|
8353
|
+
// callers (bootstrap, reestablecer Firebase, interceptor 401, timer proactivo)
|
|
8354
|
+
// comparten el MISMO observable. Sin esto, refreshes concurrentes chocan contra
|
|
8355
|
+
// la rotación de refresh token del backend: el primero rota el token y el
|
|
8356
|
+
// segundo llega con el token ya invalidado → 401 → logout espurio (el usuario
|
|
8357
|
+
// cae a la landing al actualizar). Ver project_pwa-login-freeze.
|
|
8358
|
+
this.inFlightRefresh$ = null;
|
|
8352
8359
|
// =============================================
|
|
8353
8360
|
// ESTADO PÚBLICO (Signals readonly)
|
|
8354
8361
|
// =============================================
|
|
@@ -8759,13 +8766,20 @@ class AuthService {
|
|
|
8759
8766
|
* que el cliente debe guardar para el próximo refresh.
|
|
8760
8767
|
*/
|
|
8761
8768
|
refreshAccessToken() {
|
|
8769
|
+
// Single-flight: si ya hay un /refresh en vuelo, devolvemos el MISMO
|
|
8770
|
+
// observable (multicast vía shareReplay). Evita la carrera contra la rotación
|
|
8771
|
+
// de refresh token del backend, que producía 401 espurios → logout al
|
|
8772
|
+
// actualizar. Ver inFlightRefresh$ y project_pwa-login-freeze.
|
|
8773
|
+
if (this.inFlightRefresh$) {
|
|
8774
|
+
return this.inFlightRefresh$;
|
|
8775
|
+
}
|
|
8762
8776
|
// H-05 same-site-aware: en same-site (prod) el refresh va SOLO por la cookie
|
|
8763
8777
|
// HttpOnly (body vacío), no se persiste en localStorage → un XSS no lo roba.
|
|
8764
8778
|
// En cross-site (dev) la cookie de terceros se bloquea (Safari ITP / Chrome
|
|
8765
8779
|
// 3rd-party phase-out) → se manda en el body + se persiste (fallback).
|
|
8766
8780
|
// withCredentials adjunta la cookie cuando el browser la permite.
|
|
8767
8781
|
const body = this.storageService.cookieOnly ? {} : { refreshToken: this.state().refreshToken };
|
|
8768
|
-
|
|
8782
|
+
this.inFlightRefresh$ = this.http
|
|
8769
8783
|
.post(`${this.baseUrl}/refresh`, body, { withCredentials: true })
|
|
8770
8784
|
.pipe(
|
|
8771
8785
|
// Nunca dejar el refresh colgado: si la red/Lambda no responde a tiempo,
|
|
@@ -8795,7 +8809,17 @@ class AuthService {
|
|
|
8795
8809
|
// initialize() only clears state (no redirect). Auto-navigating to
|
|
8796
8810
|
// /login from this method redirects even users on public routes.
|
|
8797
8811
|
return throwError(() => error);
|
|
8798
|
-
})
|
|
8812
|
+
}),
|
|
8813
|
+
// Liberar el slot al terminar (éxito, error o timeout) para que el
|
|
8814
|
+
// próximo refresh real arranque uno nuevo.
|
|
8815
|
+
finalize(() => {
|
|
8816
|
+
this.inFlightRefresh$ = null;
|
|
8817
|
+
}),
|
|
8818
|
+
// Multicast: los side-effects del tap corren UNA vez y todos los callers
|
|
8819
|
+
// concurrentes reciben la misma respuesta/error. refCount:false → que un
|
|
8820
|
+
// caller se desuscribe temprano NO cancela la rotación de token en vuelo.
|
|
8821
|
+
shareReplay({ bufferSize: 1, refCount: false }));
|
|
8822
|
+
return this.inFlightRefresh$;
|
|
8799
8823
|
}
|
|
8800
8824
|
/**
|
|
8801
8825
|
* Maneja autenticación exitosa desde fuentes externas (OAuth, etc).
|
|
@@ -28348,6 +28372,13 @@ class UpdateBannerComponent {
|
|
|
28348
28372
|
dismissAction: this.tr('dismissAction'),
|
|
28349
28373
|
};
|
|
28350
28374
|
});
|
|
28375
|
+
/**
|
|
28376
|
+
* Feedback visual mientras se aplica el update. El botón pasa a estado
|
|
28377
|
+
* WORKING (spinner) y se bloquean nuevos taps. `applyUpdate()` siempre
|
|
28378
|
+
* recarga la página al terminar (try/finally), así que NO reseteamos este
|
|
28379
|
+
* flag: queda en true hasta que el reload reemplaza la app.
|
|
28380
|
+
*/
|
|
28381
|
+
this.applying = signal(false);
|
|
28351
28382
|
if (!this.i18n.hasNamespace(UPDATE_BANNER_I18N_NAMESPACE)) {
|
|
28352
28383
|
this.i18n.registerContent(UPDATE_BANNER_I18N_NAMESPACE, {
|
|
28353
28384
|
es: UPDATE_BANNER_DEFAULT_CONTENT.es,
|
|
@@ -28357,6 +28388,11 @@ class UpdateBannerComponent {
|
|
|
28357
28388
|
}
|
|
28358
28389
|
/** Aplica la actualización (activa el SW si existe + recarga). */
|
|
28359
28390
|
onUpdate() {
|
|
28391
|
+
// Anti multi-tap: el botón WORKING no queda `disabled` a nivel DOM, así que
|
|
28392
|
+
// sin este guard un usuario impaciente dispararía applyUpdate() N veces.
|
|
28393
|
+
if (this.applying())
|
|
28394
|
+
return;
|
|
28395
|
+
this.applying.set(true);
|
|
28360
28396
|
void this.version.applyUpdate();
|
|
28361
28397
|
}
|
|
28362
28398
|
/** Descarta el banner opcional. No-op en modo obligatorio. */
|
|
@@ -28426,7 +28462,7 @@ class UpdateBannerComponent {
|
|
|
28426
28462
|
color: 'primary',
|
|
28427
28463
|
fill: 'solid',
|
|
28428
28464
|
size: 'small',
|
|
28429
|
-
state: 'ENABLED',
|
|
28465
|
+
state: applying() ? 'WORKING' : 'ENABLED',
|
|
28430
28466
|
type: 'button',
|
|
28431
28467
|
}"
|
|
28432
28468
|
(onClick)="onUpdate()"
|
|
@@ -28438,7 +28474,7 @@ class UpdateBannerComponent {
|
|
|
28438
28474
|
color: 'dark',
|
|
28439
28475
|
fill: 'clear',
|
|
28440
28476
|
size: 'small',
|
|
28441
|
-
state: 'ENABLED',
|
|
28477
|
+
state: applying() ? 'DISABLED' : 'ENABLED',
|
|
28442
28478
|
type: 'button',
|
|
28443
28479
|
}"
|
|
28444
28480
|
(onClick)="onDismiss()"
|
|
@@ -28501,7 +28537,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
28501
28537
|
color: 'primary',
|
|
28502
28538
|
fill: 'solid',
|
|
28503
28539
|
size: 'small',
|
|
28504
|
-
state: 'ENABLED',
|
|
28540
|
+
state: applying() ? 'WORKING' : 'ENABLED',
|
|
28505
28541
|
type: 'button',
|
|
28506
28542
|
}"
|
|
28507
28543
|
(onClick)="onUpdate()"
|
|
@@ -28513,7 +28549,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
28513
28549
|
color: 'dark',
|
|
28514
28550
|
fill: 'clear',
|
|
28515
28551
|
size: 'small',
|
|
28516
|
-
state: 'ENABLED',
|
|
28552
|
+
state: applying() ? 'DISABLED' : 'ENABLED',
|
|
28517
28553
|
type: 'button',
|
|
28518
28554
|
}"
|
|
28519
28555
|
(onClick)="onDismiss()"
|
|
@@ -61908,7 +61944,7 @@ class LegalContentService {
|
|
|
61908
61944
|
const stream = fallback && fallback !== locale
|
|
61909
61945
|
? primary.pipe(catchError$1(() => this.loadOne(slug, fallback, options.basePath)))
|
|
61910
61946
|
: primary;
|
|
61911
|
-
const shared = stream.pipe(shareReplay({ bufferSize: 1, refCount: false }));
|
|
61947
|
+
const shared = stream.pipe(shareReplay$1({ bufferSize: 1, refCount: false }));
|
|
61912
61948
|
this.cache.set(key, shared);
|
|
61913
61949
|
return shared;
|
|
61914
61950
|
}
|