@masterteam/delegations 0.0.56 → 0.0.57
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.
|
@@ -10,7 +10,7 @@ import { ModalService } from '@masterteam/components/modal';
|
|
|
10
10
|
import { ToastService } from '@masterteam/components/toast';
|
|
11
11
|
import { Icon } from '@masterteam/icons';
|
|
12
12
|
import { Popover } from 'primeng/popover';
|
|
13
|
-
import { EMPTY, switchMap, from, finalize, shareReplay, filter,
|
|
13
|
+
import { EMPTY, switchMap, catchError, from, finalize, shareReplay, filter, throwError } from 'rxjs';
|
|
14
14
|
import { HttpContextToken, HttpContext, HttpClient, HttpParams, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
|
15
15
|
import { handleApiRequest, REQUEST_CONTEXT, UserSearchFieldConfig, TextareaFieldConfig, DateFieldConfig, RadioButtonFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig } from '@masterteam/components';
|
|
16
16
|
import { Button } from '@masterteam/components/button';
|
|
@@ -53,13 +53,21 @@ class StartDelegationSession {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Re-mint the session the user was in before they reloaded the page
|
|
57
|
-
*
|
|
56
|
+
* Re-mint the session the user was in before they reloaded the page, from the
|
|
57
|
+
* persisted delegation id — never a token.
|
|
58
|
+
*
|
|
59
|
+
* Pass `delegation` when the row is already in hand; otherwise the state loads
|
|
60
|
+
* the candidates itself, so a host can await this action **before** the
|
|
61
|
+
* authenticated shell mounts and boot straight into delegated mode instead of
|
|
62
|
+
* loading everything twice.
|
|
63
|
+
*
|
|
64
|
+
* Completes quietly when there is nothing to resume or the delegation is no
|
|
65
|
+
* longer startable — the user simply stays themselves.
|
|
58
66
|
*/
|
|
59
67
|
class ResumeDelegationSession {
|
|
60
68
|
delegation;
|
|
61
69
|
static type = '[DelegationSession] Resume';
|
|
62
|
-
constructor(delegation) {
|
|
70
|
+
constructor(delegation = null) {
|
|
63
71
|
this.delegation = delegation;
|
|
64
72
|
}
|
|
65
73
|
}
|
|
@@ -178,12 +186,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
178
186
|
args: [{ providedIn: 'root' }]
|
|
179
187
|
}] });
|
|
180
188
|
|
|
181
|
-
var DelegationSessionActionKey;
|
|
182
|
-
(function (DelegationSessionActionKey) {
|
|
183
|
-
DelegationSessionActionKey["LoadCandidates"] = "loadCandidates";
|
|
184
|
-
DelegationSessionActionKey["StartSession"] = "startSession";
|
|
185
|
-
})(DelegationSessionActionKey || (DelegationSessionActionKey = {}));
|
|
186
|
-
|
|
187
189
|
const STORAGE_KEY = 'mt.delegation.session-resume.v1';
|
|
188
190
|
/**
|
|
189
191
|
* Lets a delegated session survive a page reload **without persisting the
|
|
@@ -271,6 +273,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
271
273
|
args: [{ providedIn: 'root' }]
|
|
272
274
|
}] });
|
|
273
275
|
|
|
276
|
+
var DelegationSessionActionKey;
|
|
277
|
+
(function (DelegationSessionActionKey) {
|
|
278
|
+
DelegationSessionActionKey["LoadCandidates"] = "loadCandidates";
|
|
279
|
+
DelegationSessionActionKey["StartSession"] = "startSession";
|
|
280
|
+
})(DelegationSessionActionKey || (DelegationSessionActionKey = {}));
|
|
281
|
+
|
|
274
282
|
/** Package-private: raw delegated credentials never enter NGXS or public models. */
|
|
275
283
|
class DelegationTokenVault {
|
|
276
284
|
value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
@@ -437,37 +445,60 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
437
445
|
});
|
|
438
446
|
}
|
|
439
447
|
/**
|
|
440
|
-
*
|
|
448
|
+
* Fallback resume for hosts that do not await {@link ResumeDelegationSession}
|
|
449
|
+
* before mounting their authenticated shell: pick the session back up as soon
|
|
450
|
+
* as the candidates arrive.
|
|
441
451
|
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* expired or been revoked must not retry on every candidate refresh.
|
|
452
|
+
* Prefer the awaited path — resuming after the shell has already loaded means
|
|
453
|
+
* every actor-scoped read runs twice, once as the real user and again under
|
|
454
|
+
* delegated authority.
|
|
446
455
|
*/
|
|
447
456
|
tryResume(candidates) {
|
|
448
|
-
if (this.resumeAttempted) {
|
|
457
|
+
if (this.resumeAttempted || this.resumeStore.pending() == null) {
|
|
449
458
|
return;
|
|
450
459
|
}
|
|
451
460
|
const delegationId = this.resumeStore.pending();
|
|
452
|
-
if (delegationId == null) {
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
461
|
const row = candidates.find((candidate) => candidate.delegationId === delegationId);
|
|
456
|
-
this.resumeAttempted = true;
|
|
457
462
|
if (!row) {
|
|
458
463
|
// No longer startable (expired, cancelled, scope revoked) — forget it.
|
|
464
|
+
this.resumeAttempted = true;
|
|
459
465
|
this.resumeStore.clear();
|
|
460
466
|
return;
|
|
461
467
|
}
|
|
462
|
-
queueMicrotask(() => this.store.dispatch(new ResumeDelegationSession(row)).subscribe(
|
|
463
|
-
error: () => this.resumeStore.clear(),
|
|
464
|
-
}));
|
|
468
|
+
queueMicrotask(() => this.store.dispatch(new ResumeDelegationSession(row)).subscribe());
|
|
465
469
|
}
|
|
466
470
|
resume(ctx, { delegation }) {
|
|
467
|
-
|
|
471
|
+
const delegationId = this.resumeStore.pending();
|
|
472
|
+
if (ctx.getState().active || delegationId == null) {
|
|
468
473
|
return EMPTY;
|
|
469
474
|
}
|
|
470
|
-
|
|
475
|
+
// Claim the attempt up front so the nested candidate load below does not
|
|
476
|
+
// also fire `tryResume` for the same delegation.
|
|
477
|
+
this.resumeAttempted = true;
|
|
478
|
+
const known = delegation?.delegationId === delegationId
|
|
479
|
+
? delegation
|
|
480
|
+
: ctx
|
|
481
|
+
.getState()
|
|
482
|
+
.candidates.find((candidate) => candidate.delegationId === delegationId);
|
|
483
|
+
const start$ = known
|
|
484
|
+
? this.startSession(ctx, known, null, 'ResumeSession')
|
|
485
|
+
: this.store.dispatch(new LoadDelegationCandidates()).pipe(switchMap(() => {
|
|
486
|
+
const row = ctx
|
|
487
|
+
.getState()
|
|
488
|
+
.candidates.find((candidate) => candidate.delegationId === delegationId);
|
|
489
|
+
if (!row) {
|
|
490
|
+
// Expired, cancelled, or scope revoked while the tab was closed.
|
|
491
|
+
this.resumeStore.clear();
|
|
492
|
+
return EMPTY;
|
|
493
|
+
}
|
|
494
|
+
return this.startSession(ctx, row, null, 'ResumeSession');
|
|
495
|
+
}));
|
|
496
|
+
// A failed resume is not the caller's problem: drop the marker and let the
|
|
497
|
+
// user carry on as themselves rather than blocking the boot on an error.
|
|
498
|
+
return start$.pipe(catchError(() => {
|
|
499
|
+
this.resumeStore.clear();
|
|
500
|
+
return EMPTY;
|
|
501
|
+
}));
|
|
471
502
|
}
|
|
472
503
|
start(ctx, { delegation }) {
|
|
473
504
|
const previous = ctx.getState().active;
|
|
@@ -611,6 +642,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
611
642
|
class DelegationSessionFacade {
|
|
612
643
|
store = inject(Store);
|
|
613
644
|
promptReceipts = inject(DelegationPromptReceiptService);
|
|
645
|
+
resumeStore = inject(DelegationSessionResumeStore);
|
|
614
646
|
endInFlight = null;
|
|
615
647
|
// ---------------------------------------------------------------------------
|
|
616
648
|
// Data slices
|
|
@@ -654,6 +686,23 @@ class DelegationSessionFacade {
|
|
|
654
686
|
claimPromptCandidates(candidates) {
|
|
655
687
|
return this.promptReceipts.claimUnseen(candidates);
|
|
656
688
|
}
|
|
689
|
+
/**
|
|
690
|
+
* True when a previous session is waiting to be re-minted after a reload.
|
|
691
|
+
* Synchronous (a `localStorage` read), so a route guard can check it without
|
|
692
|
+
* paying for a round-trip on the overwhelmingly common no-delegation boot.
|
|
693
|
+
*/
|
|
694
|
+
hasPendingResume() {
|
|
695
|
+
return this.resumeStore.pending() != null;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Re-mints the delegated session the user reloaded out of, and completes when
|
|
699
|
+
* delegated mode is in force (or when there is nothing to resume). Await this
|
|
700
|
+
* **before** mounting the authenticated shell so its reads run once, already
|
|
701
|
+
* delegated.
|
|
702
|
+
*/
|
|
703
|
+
resumeSession() {
|
|
704
|
+
return this.store.dispatch(new ResumeDelegationSession());
|
|
705
|
+
}
|
|
657
706
|
startSession(delegation) {
|
|
658
707
|
return this.store.dispatch(new StartDelegationSession(delegation));
|
|
659
708
|
}
|