@acorex-platform/framework-client 0.0.38 → 0.0.48

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.
Files changed (111) hide show
  1. package/fesm2022/acorex-platform-framework-client-auth.mjs +212 -44
  2. package/fesm2022/acorex-platform-framework-client-auth.mjs.map +1 -1
  3. package/fesm2022/{acorex-platform-framework-client-common-common-settings.provider-CzBr0cRi.mjs → acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs} +31 -1
  4. package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-ChU2tvyt.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-framework-client-common.mjs +1172 -193
  6. package/fesm2022/acorex-platform-framework-client-common.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-framework-client-core.mjs +233 -132
  8. package/fesm2022/acorex-platform-framework-client-core.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-framework-client-layout-builder.mjs +295 -59
  10. package/fesm2022/acorex-platform-framework-client-layout-builder.mjs.map +1 -1
  11. package/fesm2022/{acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-Bp2cNCPO.mjs → acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-BqPaHKPM.mjs} +10 -6
  12. package/fesm2022/acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-BqPaHKPM.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-framework-client-layout-components-property-viewer-inline-code-editor.component-CcpUTEVQ.mjs +66 -0
  14. package/fesm2022/acorex-platform-framework-client-layout-components-property-viewer-inline-code-editor.component-CcpUTEVQ.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-framework-client-layout-components-resource-appointment-board.component-DCkpgsFH.mjs +195 -0
  16. package/fesm2022/acorex-platform-framework-client-layout-components-resource-appointment-board.component-DCkpgsFH.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-framework-client-layout-components.mjs +4015 -1014
  18. package/fesm2022/acorex-platform-framework-client-layout-components.mjs.map +1 -1
  19. package/fesm2022/acorex-platform-framework-client-layout-designer.mjs +1179 -304
  20. package/fesm2022/acorex-platform-framework-client-layout-designer.mjs.map +1 -1
  21. package/fesm2022/{acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-CvaOWbJx.mjs → acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-UkQsf84C.mjs} +1701 -300
  22. package/fesm2022/acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-UkQsf84C.mjs.map +1 -0
  23. package/fesm2022/{acorex-platform-framework-client-layout-entity-attachments-page.component-Ds63Rt2n.mjs → acorex-platform-framework-client-layout-entity-attachments-page.component-DmmiDLZw.mjs} +2 -2
  24. package/fesm2022/{acorex-platform-framework-client-layout-entity-attachments-page.component-Ds63Rt2n.mjs.map → acorex-platform-framework-client-layout-entity-attachments-page.component-DmmiDLZw.mjs.map} +1 -1
  25. package/fesm2022/{acorex-platform-framework-client-layout-entity-bulk-entity.command-C0pohwdo.mjs → acorex-platform-framework-client-layout-entity-bulk-entity.command-BOFTb66c.mjs} +2 -2
  26. package/fesm2022/{acorex-platform-framework-client-layout-entity-bulk-entity.command-C0pohwdo.mjs.map → acorex-platform-framework-client-layout-entity-bulk-entity.command-BOFTb66c.mjs.map} +1 -1
  27. package/fesm2022/{acorex-platform-framework-client-layout-entity-gallery-page.component-o2HuZrDN.mjs → acorex-platform-framework-client-layout-entity-gallery-page.component-Bg-cMOlQ.mjs} +2 -2
  28. package/fesm2022/{acorex-platform-framework-client-layout-entity-gallery-page.component-o2HuZrDN.mjs.map → acorex-platform-framework-client-layout-entity-gallery-page.component-Bg-cMOlQ.mjs.map} +1 -1
  29. package/fesm2022/acorex-platform-framework-client-layout-entity.mjs +2 -2
  30. package/fesm2022/acorex-platform-framework-client-layout-views.mjs +47 -8
  31. package/fesm2022/acorex-platform-framework-client-layout-views.mjs.map +1 -1
  32. package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs +3854 -3292
  33. package/fesm2022/acorex-platform-framework-client-layout-widget-core.mjs.map +1 -1
  34. package/fesm2022/acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-DJNSBNVe.mjs +33319 -0
  35. package/fesm2022/acorex-platform-framework-client-layout-widgets-acorex-platform-framework-client-layout-widgets-DJNSBNVe.mjs.map +1 -0
  36. package/fesm2022/{acorex-platform-framework-client-layout-widgets-button-widget-designer.component-BDOdcoyN.mjs → acorex-platform-framework-client-layout-widgets-button-widget-designer.component-BkrPu1wO.mjs} +8 -5
  37. package/fesm2022/acorex-platform-framework-client-layout-widgets-button-widget-designer.component-BkrPu1wO.mjs.map +1 -0
  38. package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-value.util-Cb7R1qLO.mjs +45 -0
  39. package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-value.util-Cb7R1qLO.mjs.map +1 -0
  40. package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-widget-column.component-BL621E1r.mjs +68 -0
  41. package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-widget-column.component-BL621E1r.mjs.map +1 -0
  42. package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-widget-edit.component-CVsPKhNl.mjs +88 -0
  43. package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-widget-edit.component-CVsPKhNl.mjs.map +1 -0
  44. package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-widget-view.component-BEq4O4al.mjs +71 -0
  45. package/fesm2022/acorex-platform-framework-client-layout-widgets-code-editor-widget-view.component-BEq4O4al.mjs.map +1 -0
  46. package/fesm2022/acorex-platform-framework-client-layout-widgets-condition-builder-edit-popup.component-WpiEHusZ.mjs +81 -0
  47. package/fesm2022/acorex-platform-framework-client-layout-widgets-condition-builder-edit-popup.component-WpiEHusZ.mjs.map +1 -0
  48. package/fesm2022/{acorex-platform-framework-client-layout-widgets-page-widget-designer.component-WzIiwnGC.mjs → acorex-platform-framework-client-layout-widgets-page-widget-designer.component-Cadl6guA.mjs} +41 -23
  49. package/fesm2022/acorex-platform-framework-client-layout-widgets-page-widget-designer.component-Cadl6guA.mjs.map +1 -0
  50. package/fesm2022/{acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-4mX8aYzA.mjs → acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-CKtGsWPI.mjs} +3 -3
  51. package/fesm2022/{acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-4mX8aYzA.mjs.map → acorex-platform-framework-client-layout-widgets-repeater-widget-column.component-CKtGsWPI.mjs.map} +1 -1
  52. package/fesm2022/acorex-platform-framework-client-layout-widgets-scheduler-picker-widget-column.component-Clb27Loe.mjs +71 -0
  53. package/fesm2022/acorex-platform-framework-client-layout-widgets-scheduler-picker-widget-column.component-Clb27Loe.mjs.map +1 -0
  54. package/fesm2022/acorex-platform-framework-client-layout-widgets-scheduler-picker-widget-edit.component-l7q8A-D3.mjs +111 -0
  55. package/fesm2022/acorex-platform-framework-client-layout-widgets-scheduler-picker-widget-edit.component-l7q8A-D3.mjs.map +1 -0
  56. package/fesm2022/acorex-platform-framework-client-layout-widgets-scheduler-picker-widget-view.component-Cc1AH09b.mjs +77 -0
  57. package/fesm2022/acorex-platform-framework-client-layout-widgets-scheduler-picker-widget-view.component-Cc1AH09b.mjs.map +1 -0
  58. package/fesm2022/{acorex-platform-framework-client-layout-widgets-tabular-data-edit-popup.component-TWxthl21.mjs → acorex-platform-framework-client-layout-widgets-tabular-data-edit-popup.component-BCLxS3rS.mjs} +3 -3
  59. package/fesm2022/{acorex-platform-framework-client-layout-widgets-tabular-data-edit-popup.component-TWxthl21.mjs.map → acorex-platform-framework-client-layout-widgets-tabular-data-edit-popup.component-BCLxS3rS.mjs.map} +1 -1
  60. package/fesm2022/{acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-D6HFPmGC.mjs → acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-DYxgwR2E.mjs} +21 -8
  61. package/fesm2022/acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-DYxgwR2E.mjs.map +1 -0
  62. package/fesm2022/acorex-platform-framework-client-layout-widgets-trigger-expression-editor-popup.component-C2PGoC7r.mjs +124 -0
  63. package/fesm2022/acorex-platform-framework-client-layout-widgets-trigger-expression-editor-popup.component-C2PGoC7r.mjs.map +1 -0
  64. package/fesm2022/acorex-platform-framework-client-layout-widgets-triggers-widget-editor.component-DDDoPoQa.mjs +345 -0
  65. package/fesm2022/acorex-platform-framework-client-layout-widgets-triggers-widget-editor.component-DDDoPoQa.mjs.map +1 -0
  66. package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs +1 -31985
  67. package/fesm2022/acorex-platform-framework-client-layout-widgets.mjs.map +1 -1
  68. package/fesm2022/acorex-platform-framework-client-realtime.mjs +2 -1
  69. package/fesm2022/acorex-platform-framework-client-realtime.mjs.map +1 -1
  70. package/fesm2022/acorex-platform-framework-client-runtime.mjs +2 -2
  71. package/fesm2022/acorex-platform-framework-client-runtime.mjs.map +1 -1
  72. package/fesm2022/{acorex-platform-framework-client-themes-default-entity-master-create-view.component-CT7WWWG0.mjs → acorex-platform-framework-client-themes-default-entity-master-create-view.component-DlS4v_zb.mjs} +3 -3
  73. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-DlS4v_zb.mjs.map +1 -0
  74. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-DBmdBKBL.mjs +122 -0
  75. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-DBmdBKBL.mjs.map +1 -0
  76. package/fesm2022/{acorex-platform-framework-client-themes-default-entity-master-single-view.component-DBUwSzvg.mjs → acorex-platform-framework-client-themes-default-entity-master-single-view.component-CkpRT1a9.mjs} +3 -3
  77. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-single-view.component-CkpRT1a9.mjs.map +1 -0
  78. package/fesm2022/acorex-platform-framework-client-themes-default.mjs +424 -103
  79. package/fesm2022/acorex-platform-framework-client-themes-default.mjs.map +1 -1
  80. package/fesm2022/{acorex-platform-framework-client-themes-shared-settings.provider-CpsAIsxj.mjs → acorex-platform-framework-client-themes-shared-settings.provider-DIkjSTU7.mjs} +102 -1
  81. package/fesm2022/acorex-platform-framework-client-themes-shared-settings.provider-DIkjSTU7.mjs.map +1 -0
  82. package/fesm2022/acorex-platform-framework-client-themes-shared.mjs +174 -22
  83. package/fesm2022/acorex-platform-framework-client-themes-shared.mjs.map +1 -1
  84. package/package.json +2 -2
  85. package/realtime/README.md +1 -1
  86. package/types/acorex-platform-framework-client-auth.d.ts +18 -1
  87. package/types/acorex-platform-framework-client-common.d.ts +243 -9
  88. package/types/acorex-platform-framework-client-core.d.ts +152 -119
  89. package/types/acorex-platform-framework-client-layout-builder.d.ts +29 -0
  90. package/types/acorex-platform-framework-client-layout-components.d.ts +717 -147
  91. package/types/acorex-platform-framework-client-layout-designer.d.ts +192 -14
  92. package/types/acorex-platform-framework-client-layout-entity.d.ts +167 -92
  93. package/types/acorex-platform-framework-client-layout-views.d.ts +23 -1
  94. package/types/acorex-platform-framework-client-layout-widget-core.d.ts +148 -40
  95. package/types/acorex-platform-framework-client-layout-widgets.d.ts +261 -119
  96. package/types/acorex-platform-framework-client-realtime.d.ts +16 -3
  97. package/types/acorex-platform-framework-client-themes-default.d.ts +62 -11
  98. package/types/acorex-platform-framework-client-themes-shared.d.ts +25 -2
  99. package/fesm2022/acorex-platform-framework-client-common-common-settings.provider-CzBr0cRi.mjs.map +0 -1
  100. package/fesm2022/acorex-platform-framework-client-layout-components-binding-expression-editor-popup.component-Bp2cNCPO.mjs.map +0 -1
  101. package/fesm2022/acorex-platform-framework-client-layout-components-file-provider-picker.popup.component-BtLEUWAA.mjs +0 -77
  102. package/fesm2022/acorex-platform-framework-client-layout-components-file-provider-picker.popup.component-BtLEUWAA.mjs.map +0 -1
  103. package/fesm2022/acorex-platform-framework-client-layout-entity-acorex-platform-framework-client-layout-entity-CvaOWbJx.mjs.map +0 -1
  104. package/fesm2022/acorex-platform-framework-client-layout-widgets-button-widget-designer.component-BDOdcoyN.mjs.map +0 -1
  105. package/fesm2022/acorex-platform-framework-client-layout-widgets-page-widget-designer.component-WzIiwnGC.mjs.map +0 -1
  106. package/fesm2022/acorex-platform-framework-client-layout-widgets-text-block-widget-designer.component-D6HFPmGC.mjs.map +0 -1
  107. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-create-view.component-CT7WWWG0.mjs.map +0 -1
  108. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-DienTcPE.mjs +0 -122
  109. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-modify-view.component-DienTcPE.mjs.map +0 -1
  110. package/fesm2022/acorex-platform-framework-client-themes-default-entity-master-single-view.component-DBUwSzvg.mjs.map +0 -1
  111. package/fesm2022/acorex-platform-framework-client-themes-shared-settings.provider-CpsAIsxj.mjs.map +0 -1
@@ -2,6 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { InjectionToken, Injector, Injectable, signal, inject, Input, Directive, Optional, Inject, NgModule, input, output, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { map, BehaviorSubject, shareReplay, defaultIfEmpty, switchMap, filter, distinctUntilChanged, of, from, combineLatest, first } from 'rxjs';
4
4
  import { AXPDebugService, AXPBroadcastEventService, AXP_SESSION_SERVICE, AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, provideAppStartUpTask, AXP_APP_STARTUP_PRIORITY } from '@acorex-platform/framework-client/core';
5
+ import { Router } from '@angular/router';
5
6
  import { isEmpty } from 'lodash-es';
6
7
  import { AXPCommandService, AXPQueryService } from '@acorex-platform/framework-client/runtime';
7
8
  import { AUTH_LOGOUT_DEFAULT, AUTH_REFRESH_DEFAULT, AUTH_UPDATE_SESSION_DEFAULT, AUTH_LIST_OWN_DEVICE_SESSIONS_DEFAULT, AUTH_TERMINATE_OWN_DEVICE_SESSION_DEFAULT, AUTH_LIST_PROVIDERS_DEFAULT } from '@acorex-platform/framework-shared/runtime';
@@ -131,6 +132,10 @@ function traceAuthLifecycle(message, data) {
131
132
  function traceAuthLifecycleError(message, data) {
132
133
  AXPDebugService.instance?.error(AUTH_SESSION_TRACE_NS, AUTH_SESSION_TRACE_TITLE.Lifecycle, message, data);
133
134
  }
135
+ /** Always emits (Debug Mode not required) — use for logout / empty-menu diagnosis. */
136
+ function warnAuthLifecycle(message, data) {
137
+ AXPDebugService.instance?.warn(AUTH_SESSION_TRACE_NS, AUTH_SESSION_TRACE_TITLE.Lifecycle, message, data);
138
+ }
134
139
  //#endregion
135
140
  //#region ----- Restore -----
136
141
  function traceAuthRestore(message, data) {
@@ -141,11 +146,17 @@ function traceAuthRestore(message, data) {
141
146
  function traceAuthWorkspace(message, data) {
142
147
  AXPDebugService.instance?.trace(AUTH_SESSION_TRACE_NS, AUTH_SESSION_TRACE_TITLE.Workspace, message, data);
143
148
  }
149
+ function warnAuthWorkspace(message, data) {
150
+ AXPDebugService.instance?.warn(AUTH_SESSION_TRACE_NS, AUTH_SESSION_TRACE_TITLE.Workspace, message, data);
151
+ }
144
152
  //#endregion
145
153
  //#region ----- Access policy -----
146
154
  function traceAuthAccessPolicy(message, data) {
147
155
  AXPDebugService.instance?.trace(AUTH_SESSION_TRACE_NS, AUTH_SESSION_TRACE_TITLE.AccessPolicy, message, data);
148
156
  }
157
+ function warnAuthAccessPolicy(message, data) {
158
+ AXPDebugService.instance?.warn(AUTH_SESSION_TRACE_NS, AUTH_SESSION_TRACE_TITLE.AccessPolicy, message, data);
159
+ }
149
160
  //#endregion
150
161
  //#region ----- Catalog -----
151
162
  function traceAuthCatalog(message, data) {
@@ -353,6 +364,87 @@ function isOAuthRedirectInFlight() {
353
364
  }
354
365
  //#endregion
355
366
 
367
+ //#region ---- Auth refresh lock (cross-tab) ----
368
+ /**
369
+ * Serializes cookie refresh across browser tabs so two restores cannot consume the
370
+ * same refresh `jti` and trigger server reuse detection (full `sid` revoke).
371
+ *
372
+ * 1. Web Locks API (Chrome / Edge / Firefox) — exclusive across tabs of this origin.
373
+ * 2. localStorage lock — fallback when Web Locks is missing (two JS heaps otherwise race).
374
+ * 3. In-tab promise chain — last resort (does not coordinate tabs).
375
+ */
376
+ const AUTH_REFRESH_LOCK_NAME = 'axp.auth.refresh';
377
+ const AUTH_REFRESH_LS_LOCK_KEY = 'axp.auth.refresh.ls-lock';
378
+ const AUTH_REFRESH_LS_TTL_MS = 20_000;
379
+ const AUTH_REFRESH_LS_WAIT_MS = 40;
380
+ let memoryTail = Promise.resolve();
381
+ async function withAuthRefreshLock(run) {
382
+ const locks = typeof navigator !== 'undefined' ? navigator.locks : undefined;
383
+ if (typeof locks?.request === 'function') {
384
+ return locks.request(AUTH_REFRESH_LOCK_NAME, { mode: 'exclusive' }, async () => run());
385
+ }
386
+ if (typeof localStorage !== 'undefined') {
387
+ return withLocalStorageRefreshLock(run);
388
+ }
389
+ return withMemoryRefreshLock(run);
390
+ }
391
+ async function withLocalStorageRefreshLock(run) {
392
+ const owner = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
393
+ const deadline = Date.now() + AUTH_REFRESH_LS_TTL_MS;
394
+ while (Date.now() < deadline) {
395
+ const now = Date.now();
396
+ const held = readLocalStorageLock();
397
+ if (held && held.until > now && held.owner !== owner) {
398
+ await delay(AUTH_REFRESH_LS_WAIT_MS);
399
+ continue;
400
+ }
401
+ writeLocalStorageLock(owner, now + AUTH_REFRESH_LS_TTL_MS);
402
+ const confirmed = readLocalStorageLock();
403
+ if (confirmed?.owner !== owner) {
404
+ await delay(AUTH_REFRESH_LS_WAIT_MS);
405
+ continue;
406
+ }
407
+ try {
408
+ return await run();
409
+ }
410
+ finally {
411
+ const current = readLocalStorageLock();
412
+ if (current?.owner === owner) {
413
+ localStorage.removeItem(AUTH_REFRESH_LS_LOCK_KEY);
414
+ }
415
+ }
416
+ }
417
+ return withMemoryRefreshLock(run);
418
+ }
419
+ function readLocalStorageLock() {
420
+ try {
421
+ const raw = localStorage.getItem(AUTH_REFRESH_LS_LOCK_KEY);
422
+ if (!raw) {
423
+ return null;
424
+ }
425
+ const parsed = JSON.parse(raw);
426
+ if (typeof parsed.owner !== 'string' || typeof parsed.until !== 'number') {
427
+ return null;
428
+ }
429
+ return { owner: parsed.owner, until: parsed.until };
430
+ }
431
+ catch {
432
+ return null;
433
+ }
434
+ }
435
+ function writeLocalStorageLock(owner, until) {
436
+ localStorage.setItem(AUTH_REFRESH_LS_LOCK_KEY, JSON.stringify({ owner, until }));
437
+ }
438
+ function delay(ms) {
439
+ return new Promise((resolve) => setTimeout(resolve, ms));
440
+ }
441
+ function withMemoryRefreshLock(run) {
442
+ const runExclusive = memoryTail.then(() => run(), () => run());
443
+ memoryTail = runExclusive.then(() => undefined, () => undefined);
444
+ return runExclusive;
445
+ }
446
+ //#endregion
447
+
356
448
  //#region ---- Max active device sessions (client) ----
357
449
  /** Stable API / ForbiddenException code from AuthService. */
358
450
  const MAX_ACTIVE_DEVICE_SESSIONS_CODE = 'MAX_ACTIVE_DEVICE_SESSIONS';
@@ -447,6 +539,7 @@ class AXPSessionService {
447
539
  /** In-memory catalog caches — shared by guards and chooser UI (one List* per bind). */
448
540
  this.tenantsListCache = null;
449
541
  this.applicationsListCache = null;
542
+ this.signoutInFlight = null;
450
543
  this.status = new BehaviorSubject(AXPSessionStatus.Unauthenticated);
451
544
  this.status$ = this.status.asObservable().pipe(shareReplay(1));
452
545
  // Add loading state to prevent premature redirects
@@ -581,23 +674,15 @@ class AXPSessionService {
581
674
  applicationId: sessionData.application?.id,
582
675
  strategy: strategyName,
583
676
  }));
584
- const refreshResult = await strategy.refreshToken(this.getContext());
677
+ // Cross-tab exclusive refresh — avoids concurrent jti reuse / sid revoke.
678
+ const refreshResult = await this.refreshCookieSessionExclusive(strategyName, strategy);
585
679
  if (!refreshResult?.succeed || !refreshResult.data?.user) {
586
680
  this.clearSession();
587
681
  this.status.next(AXPSessionStatus.Unauthorized);
588
682
  traceAuthRestore('Refresh failed — cleared');
589
683
  return;
590
684
  }
591
- // Preserve workspace binding from local metadata when refresh omits it.
592
- const merged = {
593
- succeed: true,
594
- data: {
595
- ...refreshResult.data,
596
- tenant: refreshResult.data.tenant ?? sessionData.tenant ?? null,
597
- application: refreshResult.data.application ?? sessionData.application ?? null,
598
- },
599
- };
600
- await this.applySignInResult(strategyName, merged);
685
+ await this.applySignInResult(strategyName, refreshResult);
601
686
  traceAuthRestore('Restore complete', () => ({
602
687
  ms: Date.now() - startedAt,
603
688
  status: this.status.value,
@@ -745,7 +830,9 @@ class AXPSessionService {
745
830
  if (!strategy) {
746
831
  throw new Error(`Authentication strategy '${strategyName}' is not supported`);
747
832
  }
748
- const result = await strategy.updateToken(params);
833
+ // Same lock as cookie refresh — UpdateSession rotates refresh `jti`.
834
+ // A restore in another tab must not consume the previous cookie concurrently.
835
+ const result = await withAuthRefreshLock(() => strategy.updateToken(params));
749
836
  if (result?.succeed) {
750
837
  if (params?.['code']) {
751
838
  this.clearSession();
@@ -784,6 +871,11 @@ class AXPSessionService {
784
871
  traceAuthLifecycleError('UpdateSession failed', () => ({
785
872
  error: error instanceof Error ? error.message : String(error),
786
873
  }));
874
+ warnAuthWorkspace('UpdateSession failed — clearing session (logout UX)', () => ({
875
+ error: error instanceof Error ? error.message : String(error),
876
+ tenantId: params?.['tenant']?.id ?? params?.['tenantId'] ?? null,
877
+ applicationId: params?.['application']?.id ?? params?.['applicationId'] ?? null,
878
+ }));
787
879
  // Failed workspace bind must not leave a half-authenticated cookie session
788
880
  // (that causes splash restore loops after refresh).
789
881
  // Exception: max device sessions — keep identity session so the user can
@@ -802,28 +894,51 @@ class AXPSessionService {
802
894
  }
803
895
  }
804
896
  async signout() {
805
- traceAuthLifecycle('Sign-out started', () => ({ userId: this.user?.id }));
806
- this.isLoading.next(true);
897
+ if (this.signoutInFlight) {
898
+ return this.signoutInFlight;
899
+ }
900
+ this.signoutInFlight = this.performSignout().finally(() => {
901
+ this.signoutInFlight = null;
902
+ });
903
+ return this.signoutInFlight;
904
+ }
905
+ /**
906
+ * Clears the client session first, then redirects, then revokes the server session.
907
+ * Waiting on the logout HTTP call (or flipping isLoading) used to skip the login
908
+ * redirect and let in-flight 401s start a competing navigation back into the app.
909
+ */
910
+ async performSignout() {
911
+ const userId = this.user?.id;
912
+ const sessionData = this.getSessionData();
913
+ const strategy = sessionData?.strategy
914
+ ? this.authStrategyRegistry.get(sessionData.strategy)
915
+ : null;
916
+ traceAuthLifecycle('Sign-out started', () => ({ userId }));
917
+ this.clearSession();
918
+ this.eventService.publish(AXPSessionStatus.SignedOut, { id: userId });
919
+ this.status.next(AXPSessionStatus.SignedOut);
920
+ this.navigateToLoginAfterSignout();
921
+ if (strategy) {
922
+ try {
923
+ await strategy.signout();
924
+ }
925
+ catch {
926
+ // Ignore logout network errors; client session is already cleared.
927
+ }
928
+ }
929
+ traceAuthLifecycle('Sign-out complete', () => ({ userId }));
930
+ }
931
+ navigateToLoginAfterSignout() {
807
932
  try {
808
- const sessionData = this.getSessionData();
809
- if (sessionData?.strategy) {
810
- const strategy = this.authStrategyRegistry.get(sessionData?.strategy);
811
- if (strategy) {
812
- try {
813
- await strategy.signout();
814
- }
815
- catch (error) { }
816
- }
933
+ const router = this.injector.get(Router);
934
+ const path = (router.url ?? '').split('?')[0];
935
+ if (path.startsWith('/auth')) {
936
+ return;
817
937
  }
818
- const userId = this.user?.id;
819
- this.clearSession();
820
- this.eventService.publish(AXPSessionStatus.SignedOut, { id: userId });
821
- this.isLoading.next(false);
822
- this.status.next(AXPSessionStatus.SignedOut);
823
- traceAuthLifecycle('Sign-out complete', () => ({ userId }));
938
+ void router.navigateByUrl('/auth/login', { replaceUrl: true });
824
939
  }
825
- finally {
826
- this.isLoading.next(false);
940
+ catch {
941
+ // Router is unavailable in some test / early-boot hosts.
827
942
  }
828
943
  }
829
944
  async refreshToken() {
@@ -831,34 +946,74 @@ class AXPSessionService {
831
946
  if (!sessionData?.strategy || !sessionData.cookieSession) {
832
947
  throw new Error('No cookie session available');
833
948
  }
834
- const strategy = this.authStrategyRegistry.get(sessionData.strategy);
949
+ const strategyName = sessionData.strategy;
950
+ const strategy = this.authStrategyRegistry.get(strategyName);
835
951
  if (!strategy) {
836
- throw new Error(`Authentication strategy '${sessionData.strategy}' is not found`);
952
+ throw new Error(`Authentication strategy '${strategyName}' is not found`);
837
953
  }
838
954
  try {
839
- const result = await strategy.refreshToken(this.getContext());
955
+ const result = await this.refreshCookieSessionExclusive(strategyName, strategy);
840
956
  if (!result.succeed || !result.data?.user) {
957
+ warnAuthLifecycle('Token refresh failed — clearing session', () => ({
958
+ strategy: strategyName,
959
+ }));
841
960
  this.clearSession();
842
961
  this.status.next(AXPSessionStatus.Expired);
843
962
  throw new Error('Token refresh failed');
844
963
  }
845
- this.setSession({
846
- strategy: sessionData.strategy,
847
- user: result.data.user,
848
- tenant: result.data.tenant ?? sessionData.tenant ?? null,
849
- application: result.data.application ?? sessionData.application ?? null,
850
- expiresIn: result.data.expiresIn,
851
- cookieSession: true,
852
- locked: sessionData.locked === true,
853
- });
854
964
  return true;
855
965
  }
856
966
  catch (error) {
967
+ warnAuthLifecycle('Token refresh error — clearing session', () => ({
968
+ error: error instanceof Error ? error.message : String(error),
969
+ }));
857
970
  this.clearSession();
858
971
  this.status.next(AXPSessionStatus.Expired);
859
972
  throw error;
860
973
  }
861
974
  }
975
+ /**
976
+ * One refresh at a time across tabs (Web Locks). Re-reads and writes session
977
+ * metadata inside the lock so a peer tab never replays the same refresh `jti`.
978
+ */
979
+ async refreshCookieSessionExclusive(strategyName, strategy) {
980
+ return withAuthRefreshLock(async () => {
981
+ const latest = this.getSessionData();
982
+ if (!latest?.user || !latest.cookieSession) {
983
+ return { succeed: false };
984
+ }
985
+ const activeStrategyName = latest.strategy?.trim() || strategyName;
986
+ const activeStrategy = activeStrategyName === strategyName
987
+ ? strategy
988
+ : this.authStrategyRegistry.get(activeStrategyName);
989
+ if (!activeStrategy) {
990
+ return { succeed: false };
991
+ }
992
+ const refreshResult = await activeStrategy.refreshToken(this.getContext());
993
+ if (!refreshResult?.succeed || !refreshResult.data?.user) {
994
+ return { succeed: false };
995
+ }
996
+ const merged = {
997
+ succeed: true,
998
+ data: {
999
+ ...refreshResult.data,
1000
+ tenant: refreshResult.data.tenant ?? latest.tenant ?? null,
1001
+ application: refreshResult.data.application ?? latest.application ?? null,
1002
+ },
1003
+ };
1004
+ // Persist before releasing the lock so the next tab reads updated metadata.
1005
+ this.setSession({
1006
+ strategy: activeStrategyName,
1007
+ user: merged.data.user,
1008
+ tenant: merged.data.tenant ?? null,
1009
+ application: merged.data.application ?? null,
1010
+ expiresIn: merged.data.expiresIn,
1011
+ cookieSession: true,
1012
+ locked: latest.locked === true,
1013
+ });
1014
+ return merged;
1015
+ });
1016
+ }
862
1017
  /**
863
1018
  * Auth:GetAccessPolicy is only meaningful with tenant + application bound.
864
1019
  * Skipping incomplete contexts cuts 2 redundant calls on the common login path.
@@ -885,6 +1040,14 @@ class AXPSessionService {
885
1040
  tenantId: this.tenant?.id,
886
1041
  applicationId: this.application?.id,
887
1042
  }));
1043
+ if (reason === 'updateToken' && this.permissions.length === 0) {
1044
+ warnAuthAccessPolicy('GetAccessPolicy returned empty permissions after workspace bind', () => ({
1045
+ reason,
1046
+ tenantId: this.tenant?.id ?? null,
1047
+ applicationId: this.application?.id ?? null,
1048
+ featureCount: this.features.length,
1049
+ }));
1050
+ }
888
1051
  }
889
1052
  async loadPermissions() {
890
1053
  try {
@@ -895,6 +1058,11 @@ class AXPSessionService {
895
1058
  traceAuthLifecycleError('Load permissions failed', () => ({
896
1059
  error: error instanceof Error ? error.message : String(error),
897
1060
  }));
1061
+ warnAuthLifecycle('Load permissions failed — menus may be empty', () => ({
1062
+ error: error instanceof Error ? error.message : String(error),
1063
+ tenantId: this.tenant?.id ?? null,
1064
+ applicationId: this.application?.id ?? null,
1065
+ }));
898
1066
  this.permissionsSubject.next([]);
899
1067
  }
900
1068
  }
@@ -2327,5 +2495,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2327
2495
  * Generated bundle index. Do not edit.
2328
2496
  */
2329
2497
 
2330
- export { AUTH_SESSION_TRACE_NS, AUTH_SESSION_TRACE_TITLE, AXPAuthGuard, AXPAuthModule, AXPAuthStrategy, AXPAuthStrategyRegistryService, AXPFeatureDirective, AXPFeatureEvaluatorScopeProvider, AXPFeatureGuard, AXPLoginChallengeComponentBase, AXPLoginChallengeProvider, AXPPermissionDefinitionBuilder, AXPPermissionDefinitionGroupBuilder, AXPPermissionDefinitionProviderContext, AXPPermissionDefinitionService, AXPPermissionDirective, AXPPermissionEvaluatorScopeProvider, AXPPermissionGuard, AXPPlatformAuthRuntimeService, AXPSessionContext, AXPSessionService, AXPSessionStatus, AXPSignInMethod, AXPSignInMethodRegistry, AXPUnauthenticatedError, AXPUnauthorizedError, AXPUserProfileIdentityService, AXP_ACCESS_POLICY_CACHE_PORT, AXP_APPLICATION_LOADER, AXP_DEFAULT_PLATFORM_OAUTH_CLIENT_ID, AXP_DEFAULT_PLATFORM_OAUTH_SCOPE, AXP_FEATURE_CHECKER, AXP_FEATURE_LOADER, AXP_LOGIN_CHALLENGE_PROVIDER, AXP_OAUTH_PKCE_VERIFIER_KEY, AXP_PERMISSION_CHECKER, AXP_PERMISSION_DEFINITION_PROVIDER, AXP_PERMISSION_LOADER, AXP_PERMISSION_MODULES_RESOLVER, AXP_PLATFORM_OAUTH_CSRF_COOKIE, AXP_TENANT_LOADER, AXP_USER_AVATAR_CACHE_PORT, JwtUtil, MAX_ACTIVE_DEVICE_SESSIONS_CODE, PkceUtil, TimeUtil, asAuthListProvidersResponse, clearOAuthPendingStrategy, clearSessionLimitPendingTenant, commandMessageTextForError, exchangePlatformOAuthAuthorizationCode, fetchPlatformOAuthDiscoveryDocument, findIdentityProvider, getOAuthPendingStrategy, hasLegacyOAuthPkceVerifier, hasOAuthPendingFlow, initPlatformOAuthAuthorization, isMaxActiveDeviceSessionsError, isOAuthRedirectInFlight, platformOAuthSessionUpdateParams, readPlatformOAuthCsrfToken, readSessionLimitPendingTenant, resolvePlatformOAuthAuthorizeEndpoint, resolvePlatformOAuthClientConfig, resolvePlatformOAuthIssuer, resolvePlatformOAuthRedirectUri, resolvePlatformOAuthTokenEndpoint, setOAuthPendingStrategy, shouldPlatformOAuthBindSession, storeSessionLimitPendingTenant, toPlatformOAuthSignInResult, traceAuthAccessPolicy, traceAuthCatalog, traceAuthGuard, traceAuthLifecycle, traceAuthLifecycleError, traceAuthRestore, traceAuthWorkspace };
2498
+ export { AUTH_SESSION_TRACE_NS, AUTH_SESSION_TRACE_TITLE, AXPAuthGuard, AXPAuthModule, AXPAuthStrategy, AXPAuthStrategyRegistryService, AXPFeatureDirective, AXPFeatureEvaluatorScopeProvider, AXPFeatureGuard, AXPLoginChallengeComponentBase, AXPLoginChallengeProvider, AXPPermissionDefinitionBuilder, AXPPermissionDefinitionGroupBuilder, AXPPermissionDefinitionProviderContext, AXPPermissionDefinitionService, AXPPermissionDirective, AXPPermissionEvaluatorScopeProvider, AXPPermissionGuard, AXPPlatformAuthRuntimeService, AXPSessionContext, AXPSessionService, AXPSessionStatus, AXPSignInMethod, AXPSignInMethodRegistry, AXPUnauthenticatedError, AXPUnauthorizedError, AXPUserProfileIdentityService, AXP_ACCESS_POLICY_CACHE_PORT, AXP_APPLICATION_LOADER, AXP_DEFAULT_PLATFORM_OAUTH_CLIENT_ID, AXP_DEFAULT_PLATFORM_OAUTH_SCOPE, AXP_FEATURE_CHECKER, AXP_FEATURE_LOADER, AXP_LOGIN_CHALLENGE_PROVIDER, AXP_OAUTH_PKCE_VERIFIER_KEY, AXP_PERMISSION_CHECKER, AXP_PERMISSION_DEFINITION_PROVIDER, AXP_PERMISSION_LOADER, AXP_PERMISSION_MODULES_RESOLVER, AXP_PLATFORM_OAUTH_CSRF_COOKIE, AXP_TENANT_LOADER, AXP_USER_AVATAR_CACHE_PORT, JwtUtil, MAX_ACTIVE_DEVICE_SESSIONS_CODE, PkceUtil, TimeUtil, asAuthListProvidersResponse, clearOAuthPendingStrategy, clearSessionLimitPendingTenant, commandMessageTextForError, exchangePlatformOAuthAuthorizationCode, fetchPlatformOAuthDiscoveryDocument, findIdentityProvider, getOAuthPendingStrategy, hasLegacyOAuthPkceVerifier, hasOAuthPendingFlow, initPlatformOAuthAuthorization, isMaxActiveDeviceSessionsError, isOAuthRedirectInFlight, platformOAuthSessionUpdateParams, readPlatformOAuthCsrfToken, readSessionLimitPendingTenant, resolvePlatformOAuthAuthorizeEndpoint, resolvePlatformOAuthClientConfig, resolvePlatformOAuthIssuer, resolvePlatformOAuthRedirectUri, resolvePlatformOAuthTokenEndpoint, setOAuthPendingStrategy, shouldPlatformOAuthBindSession, storeSessionLimitPendingTenant, toPlatformOAuthSignInResult, traceAuthAccessPolicy, traceAuthCatalog, traceAuthGuard, traceAuthLifecycle, traceAuthLifecycleError, traceAuthRestore, traceAuthWorkspace, warnAuthAccessPolicy, warnAuthLifecycle, warnAuthWorkspace };
2331
2499
  //# sourceMappingURL=acorex-platform-framework-client-auth.mjs.map