ngx-sp-auth 4.23.15 → 4.23.17

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.
@@ -821,14 +821,20 @@ class AuthStorageService {
821
821
  });
822
822
  }
823
823
  }
824
+ /**
825
+ * Verifica a sessão atual no servidor (`/verify`).
826
+ * Chamadas simultâneas compartilham a mesma requisição em andamento.
827
+ * @returns Status da sessão e, quando válida, a identidade (domínio/usuário) informada pelo servidor.
828
+ */
824
829
  checkAuthentication() {
825
830
  if (!this.__authenticationCheck$) {
826
- let errorMessage = '';
827
831
  this.__authenticationCheck$ = this.verifyToken()
828
- .pipe(map((response) => {
829
- errorMessage = response.ErrorMessage;
830
- return { error: response.ErrorMessage, isLoggedIn: !response.Error };
831
- }), catchError((err) => {
832
+ .pipe(map((response) => ({
833
+ error: response.ErrorMessage,
834
+ isLoggedIn: !response.Error,
835
+ dominio: response.Dominio,
836
+ usuario: response.Usuario
837
+ })), catchError((err) => {
832
838
  this.isLoggedInSub.next(false);
833
839
  return of({ error: err.error.ErrorMessage, isLoggedIn: false });
834
840
  }), finalize(() => this.__authenticationCheck$ = null), shareReplay({ bufferSize: 1, refCount: true }));
@@ -2353,6 +2359,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
2353
2359
  }], ctorParameters: () => [] });
2354
2360
 
2355
2361
  class LoginOSComponent {
2362
+ /**
2363
+ * Nome do lock (Web Locks API) usado para garantir que apenas um login integrado OS
2364
+ * seja processado por vez no navegador, mesmo com várias abas abertas simultaneamente.
2365
+ */
2366
+ static { this.LOGIN_OS_LOCK_NAME = 'ngx-sp-auth:login-os'; }
2356
2367
  // #endregion PUBLIC
2357
2368
  // #endregion ==========> PROPERTIES <==========
2358
2369
  // #region ==========> INITIALIZATION <==========
@@ -2379,65 +2390,105 @@ class LoginOSComponent {
2379
2390
  }
2380
2391
  });
2381
2392
  this.getParams();
2382
- this.logOn();
2393
+ this.runExclusive(() => this.logOn());
2383
2394
  }
2384
2395
  // #endregion ==========> INITIALIZATION <==========
2385
2396
  // #region ==========> API METHODS <==========
2386
2397
  // #region LOGIN
2387
- logOn() {
2398
+ /**
2399
+ * Processa o login integrado OS → V6.
2400
+ *
2401
+ * O fluxo é totalmente sequencial para evitar que respostas concorrentes sobrescrevam/expirem os cookies de autenticação:
2402
+ * 1. Aguarda a verificação da sessão atual (`/verify`). Se já houver uma verificação em andamento
2403
+ * (ex.: disparada pelo construtor do `AuthStorageService`), ela é reaproveitada.
2404
+ * Assim, um eventual 401 (que expira os cookies) acontece ANTES do novo login gravar os cookies.
2405
+ * 2. Se a sessão do servidor já pertence ao mesmo domínio/usuário do payload, reaproveita a sessão (sem criar outra).
2406
+ * 3. Caso contrário, realiza o login do zero.
2407
+ *
2408
+ * Deve ser executado via `runExclusive()` para que apenas uma aba processe o login por vez.
2409
+ */
2410
+ async logOn() {
2388
2411
  this.loginStatus = "loading";
2389
- let status = "none";
2390
- const currDominio = this._storageService.dominio ?? undefined;
2391
- const currUsuario = this._storageService.user ?? undefined;
2392
- this._authService.getAuthentication(this._loginOSModel.dominio).subscribe({
2393
- next: () => {
2394
- if (currDominio === this._loginOSModel.dominio && currUsuario === this._loginOSModel.usuario) {
2395
- // VALIDAR ÚLTIMO ESTAB LOGADO
2396
- this._loginOSUtils.getEstabelecimentoSession('').subscribe({
2397
- next: res => {
2398
- // Atualiza os dados do estabelecimento na sessão
2399
- this._storageService.infraEstabId = res.InfraEstabId;
2400
- this._storageService.infraEstabNome = res.InfraEstabNome;
2401
- this._storageService.infraEmpresaId = res.InfraEmpresaId;
2402
- this._storageService.infraEmpresaNome = res.InfraEmpresaNome;
2403
- // Após, redireciona para a URL de destino
2404
- status = "keep";
2405
- this.redirect();
2406
- return;
2407
- },
2408
- error: error => {
2409
- this.loginStatus = "error";
2410
- this._authUtilService.showHTTPErrorOS(error);
2411
- }
2412
- });
2412
+ const hadPreviousUser = !!this._storageService.dominio && !!this._storageService.user;
2413
+ try {
2414
+ // (1) Serializa com qualquer /verify em andamento
2415
+ const check = await firstValueFrom(this._storageService.checkAuthentication());
2416
+ // (2) Sessão atual válida e do mesmo usuário do payload → reaproveita
2417
+ if (check.isLoggedIn && this.isSameUserSession(check.dominio, check.usuario)) {
2418
+ const kept = await this.keepCurrentSession();
2419
+ if (kept) {
2420
+ this.loginStatus = "updated";
2421
+ this.startTimer();
2422
+ return;
2413
2423
  }
2414
- status = (!currDominio || !currUsuario) ? "none" : "updated";
2415
- this._authService.loginOS(this._parmsLogin, this._loginOSModel.serialV6).subscribe({
2416
- next: () => {
2417
- if (status === "none") {
2418
- this.startTimer();
2419
- this.loginStatus = "success";
2420
- }
2421
- else {
2422
- this.startTimer();
2423
- this.loginStatus = "updated";
2424
- }
2425
- },
2426
- error: (error) => {
2427
- this.loginStatus = "error";
2428
- this._authUtilService.showHTTPErrorOS(error);
2429
- },
2430
- });
2431
- },
2432
- error: (error) => {
2433
- this.loginStatus = "error";
2434
- this._authUtilService.showHTTPErrorOS(error);
2435
2424
  }
2436
- });
2425
+ // (3) Login do zero
2426
+ await firstValueFrom(this._authService.getAuthentication(this._loginOSModel.dominio));
2427
+ await firstValueFrom(this._authService.loginOS(this._parmsLogin, this._loginOSModel.serialV6));
2428
+ this.loginStatus = hadPreviousUser ? "updated" : "success";
2429
+ this.startTimer();
2430
+ }
2431
+ catch (error) {
2432
+ this.loginStatus = "error";
2433
+ this._authUtilService.showHTTPErrorOS(error);
2434
+ }
2435
+ }
2436
+ /**
2437
+ * Reaproveita a sessão atual, atualizando os dados do estabelecimento logado.
2438
+ * Só reaproveita se os dados locais (localStorage) também forem do mesmo usuário, pois as telas de destino dependem deles.
2439
+ * @returns `true` se a sessão foi reaproveitada; `false` se deve seguir com um login novo.
2440
+ */
2441
+ async keepCurrentSession() {
2442
+ const localMatches = this.equalsIgnoreCase(this._storageService.dominio, this._loginOSModel.dominio)
2443
+ && this.equalsIgnoreCase(this._storageService.user, this._loginOSModel.usuario)
2444
+ && !!this._storageService.infraUsuarioId;
2445
+ if (!localMatches)
2446
+ return false;
2447
+ try {
2448
+ // Atualiza o último estabelecimento logado do usuário
2449
+ const res = await firstValueFrom(this._loginOSUtils.getEstabelecimentoSession(''));
2450
+ this._storageService.infraEstabId = res.InfraEstabId;
2451
+ this._storageService.infraEstabNome = res.InfraEstabNome;
2452
+ this._storageService.infraEmpresaId = res.InfraEmpresaId;
2453
+ this._storageService.infraEmpresaNome = res.InfraEmpresaNome;
2454
+ return true;
2455
+ }
2456
+ catch (error) {
2457
+ // Não foi possível confirmar os dados da sessão atual: segue com login novo
2458
+ console.warn('login-os: não foi possível reaproveitar a sessão atual, realizando novo login.', error);
2459
+ return false;
2460
+ }
2437
2461
  }
2438
2462
  // #endregion LOGIN
2439
2463
  // #endregion ==========> API METHODS <==========
2440
2464
  // #region ==========> UTILS <==========
2465
+ /**
2466
+ * Executa a função de forma exclusiva no navegador (entre abas da mesma origem) usando a Web Locks API.
2467
+ * Se a API não estiver disponível, executa diretamente.
2468
+ */
2469
+ runExclusive(fn) {
2470
+ const locks = navigator.locks;
2471
+ if (locks?.request) {
2472
+ locks.request(LoginOSComponent.LOGIN_OS_LOCK_NAME, () => fn())
2473
+ .catch(error => {
2474
+ console.error('login-os: falha ao obter lock, executando sem exclusividade.', error);
2475
+ fn();
2476
+ });
2477
+ }
2478
+ else {
2479
+ fn();
2480
+ }
2481
+ }
2482
+ /** Verifica se a sessão informada pelo servidor pertence ao mesmo domínio/usuário do payload. */
2483
+ isSameUserSession(dominio, usuario) {
2484
+ return this.equalsIgnoreCase(dominio, this._loginOSModel.dominio)
2485
+ && this.equalsIgnoreCase(usuario, this._loginOSModel.usuario);
2486
+ }
2487
+ equalsIgnoreCase(a, b) {
2488
+ if (!a || !b)
2489
+ return false;
2490
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
2491
+ }
2441
2492
  redirect() {
2442
2493
  let hostname = window.location.host == "localhost" ? "v6siscandes.sispro.com.br" : window.location.host;
2443
2494
  let baseURL = `https://${hostname}/SisproErpCloud`;