oidc-spa 6.5.2 → 6.6.0

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 (81) hide show
  1. package/mock/oidc.js +6 -5
  2. package/mock/oidc.js.map +1 -1
  3. package/oidc/AuthResponse.d.ts +6 -0
  4. package/oidc/AuthResponse.js +59 -0
  5. package/oidc/AuthResponse.js.map +1 -0
  6. package/oidc/Oidc.d.ts +19 -8
  7. package/oidc/createOidc.d.ts +4 -4
  8. package/oidc/createOidc.js +359 -295
  9. package/oidc/createOidc.js.map +1 -1
  10. package/oidc/evtIsUserActive.d.ts +15 -0
  11. package/oidc/{isUserActive.js → evtIsUserActive.js} +29 -12
  12. package/oidc/evtIsUserActive.js.map +1 -0
  13. package/oidc/handleOidcCallback.d.ts +8 -1
  14. package/oidc/handleOidcCallback.js +68 -13
  15. package/oidc/handleOidcCallback.js.map +1 -1
  16. package/oidc/loginOrGoToAuthServer.d.ts +5 -4
  17. package/oidc/loginOrGoToAuthServer.js +190 -227
  18. package/oidc/loginOrGoToAuthServer.js.map +1 -1
  19. package/oidc/loginPropagationToOtherTabs.d.ts +17 -0
  20. package/oidc/loginPropagationToOtherTabs.js +41 -0
  21. package/oidc/loginPropagationToOtherTabs.js.map +1 -0
  22. package/oidc/loginSilent.d.ts +1 -5
  23. package/oidc/loginSilent.js +3 -51
  24. package/oidc/loginSilent.js.map +1 -1
  25. package/oidc/logoutPropagationToOtherTabs.js +1 -1
  26. package/oidc/logoutPropagationToOtherTabs.js.map +1 -1
  27. package/oidc/oidcClientTsUserToTokens.d.ts +1 -1
  28. package/oidc/oidcClientTsUserToTokens.js +45 -23
  29. package/oidc/oidcClientTsUserToTokens.js.map +1 -1
  30. package/oidc/ongoingLoginOrRefreshProcesses.d.ts +16 -0
  31. package/oidc/ongoingLoginOrRefreshProcesses.js +102 -0
  32. package/oidc/ongoingLoginOrRefreshProcesses.js.map +1 -0
  33. package/oidc/persistedAuthState.d.ts +16 -3
  34. package/oidc/persistedAuthState.js +35 -4
  35. package/oidc/persistedAuthState.js.map +1 -1
  36. package/package.json +36 -21
  37. package/react/react.js +8 -14
  38. package/react/react.js.map +1 -1
  39. package/src/mock/oidc.ts +14 -3
  40. package/src/oidc/AuthResponse.ts +26 -0
  41. package/src/oidc/Oidc.ts +19 -4
  42. package/src/oidc/createOidc.ts +233 -206
  43. package/src/oidc/{isUserActive.ts → evtIsUserActive.ts} +36 -10
  44. package/src/oidc/handleOidcCallback.ts +73 -12
  45. package/src/oidc/loginOrGoToAuthServer.ts +94 -87
  46. package/src/oidc/loginPropagationToOtherTabs.ts +63 -0
  47. package/src/oidc/loginSilent.ts +2 -20
  48. package/src/oidc/logoutPropagationToOtherTabs.ts +2 -2
  49. package/src/oidc/oidcClientTsUserToTokens.ts +74 -35
  50. package/src/oidc/ongoingLoginOrRefreshProcesses.ts +60 -0
  51. package/src/oidc/persistedAuthState.ts +66 -8
  52. package/src/react/react.tsx +8 -16
  53. package/src/tools/{ephemeralSessionStorage.ts → EphemeralSessionStorage.ts} +59 -27
  54. package/src/tools/Evt.ts +56 -0
  55. package/src/tools/StatefulEvt.ts +38 -0
  56. package/src/tools/subscribeToUserInteraction.ts +0 -1
  57. package/src/tools/workerTimers.ts +10 -12
  58. package/tools/EphemeralSessionStorage.d.ts +12 -0
  59. package/tools/{ephemeralSessionStorage.js → EphemeralSessionStorage.js} +29 -16
  60. package/tools/EphemeralSessionStorage.js.map +1 -0
  61. package/tools/Evt.d.ts +11 -0
  62. package/tools/{AwaitableEventEmitter.js → Evt.js} +24 -8
  63. package/tools/Evt.js.map +1 -0
  64. package/tools/StatefulEvt.d.ts +12 -0
  65. package/tools/StatefulEvt.js +24 -0
  66. package/tools/StatefulEvt.js.map +1 -0
  67. package/tools/subscribeToUserInteraction.js +2 -3
  68. package/tools/subscribeToUserInteraction.js.map +1 -1
  69. package/tools/workerTimers.js +11 -13
  70. package/tools/workerTimers.js.map +1 -1
  71. package/oidc/isUserActive.d.ts +0 -13
  72. package/oidc/isUserActive.js.map +0 -1
  73. package/src/tools/AwaitableEventEmitter.ts +0 -33
  74. package/src/tools/StatefulObservable.ts +0 -52
  75. package/tools/AwaitableEventEmitter.d.ts +0 -5
  76. package/tools/AwaitableEventEmitter.js.map +0 -1
  77. package/tools/StatefulObservable.d.ts +0 -12
  78. package/tools/StatefulObservable.js +0 -33
  79. package/tools/StatefulObservable.js.map +0 -1
  80. package/tools/ephemeralSessionStorage.d.ts +0 -3
  81. package/tools/ephemeralSessionStorage.js.map +0 -1
@@ -4,13 +4,12 @@ import {
4
4
  type User as OidcClientTsUser,
5
5
  InMemoryWebStorage
6
6
  } from "../vendor/frontend/oidc-client-ts-and-jwt-decode";
7
- import { id, assert, is, type Equals, typeGuard } from "../vendor/frontend/tsafe";
7
+ import { id, assert, is, type Equals } from "../vendor/frontend/tsafe";
8
8
  import { setTimeout, clearTimeout } from "../tools/workerTimers";
9
9
  import { Deferred } from "../tools/Deferred";
10
10
  import { decodeJwt } from "../tools/decodeJwt";
11
- import { create$isUserActive } from "./isUserActive";
11
+ import { createEvtIsUserActive } from "./evtIsUserActive";
12
12
  import { createStartCountdown } from "../tools/startCountdown";
13
- import type { StatefulObservable } from "../tools/StatefulObservable";
14
13
  import { toHumanReadableDuration } from "../tools/toHumanReadableDuration";
15
14
  import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
16
15
  import {
@@ -19,23 +18,29 @@ import {
19
18
  createIframeTimeoutInitializationError,
20
19
  createWellKnownOidcConfigurationEndpointUnreachableInitializationError
21
20
  } from "./OidcInitializationError";
22
- import {
23
- getStateData,
24
- type StateData,
25
- generateStateQueryParamValue,
26
- STATE_STORE_KEY_PREFIX
27
- } from "./StateData";
21
+ import { type StateData, generateStateQueryParamValue, STATE_STORE_KEY_PREFIX } from "./StateData";
28
22
  import { notifyOtherTabsOfLogout, getPrOtherTabLogout } from "./logoutPropagationToOtherTabs";
23
+ import { notifyOtherTabsOfLogin, getPrOtherTabLogin } from "./loginPropagationToOtherTabs";
29
24
  import { getConfigId } from "./configId";
30
25
  import { oidcClientTsUserToTokens, getMsBeforeExpiration } from "./oidcClientTsUserToTokens";
31
- import { loginSilent, authResponseToUrl } from "./loginSilent";
32
- import { handleOidcCallback, AUTH_RESPONSE_KEY } from "./handleOidcCallback";
26
+ import { loginSilent } from "./loginSilent";
27
+ import { authResponseToUrl } from "./AuthResponse";
28
+ import { handleOidcCallback, retrieveRedirectAuthResponseAndStateData } from "./handleOidcCallback";
33
29
  import { getPersistedAuthState, persistAuthState } from "./persistedAuthState";
34
30
  import type { Oidc } from "./Oidc";
35
- import { type AwaitableEventEmitter, createAwaitableEventEmitter } from "../tools/AwaitableEventEmitter";
31
+ import { createEvt, type Evt } from "../tools/Evt";
36
32
  import { getHaveSharedParentDomain } from "../tools/haveSharedParentDomain";
37
- import { createLoginOrGoToAuthServer } from "./loginOrGoToAuthServer";
38
- import { createEphemeralSessionStorage } from "../tools/ephemeralSessionStorage";
33
+ import {
34
+ createLoginOrGoToAuthServer,
35
+ getPrSafelyRestoredFromBfCacheAfterLoginBackNavigation
36
+ } from "./loginOrGoToAuthServer";
37
+ import { createEphemeralSessionStorage } from "../tools/EphemeralSessionStorage";
38
+ import {
39
+ startLoginOrRefreshProcess,
40
+ waitForAllOtherOngoingLoginOrRefreshProcessesToComplete
41
+ } from "./ongoingLoginOrRefreshProcesses";
42
+
43
+ handleOidcCallback();
39
44
 
40
45
  // NOTE: Replaced at build time
41
46
  const VERSION = "{{OIDC_SPA_VERSION}}";
@@ -120,26 +125,22 @@ export type ParamsOfCreateOidc<
120
125
  __unsafe_useIdTokenAsAccessToken?: boolean;
121
126
  };
122
127
 
123
- handleOidcCallback();
124
-
125
128
  const GLOBAL_CONTEXT_KEY = "__oidc-spa.createOidc.globalContext";
126
129
 
127
130
  declare global {
128
131
  interface Window {
129
132
  [GLOBAL_CONTEXT_KEY]: {
130
133
  prOidcByConfigId: Map<string, Promise<Oidc<any>>>;
131
- evtAuthResponseHandled: AwaitableEventEmitter<void>;
132
- $isUserActive: StatefulObservable<boolean> | undefined;
133
134
  hasLogoutBeenCalled: boolean;
135
+ evtRequestToPersistTokens: Evt<{ configIdOfInstancePostingTheRequest: string }>;
134
136
  };
135
137
  }
136
138
  }
137
139
 
138
140
  window[GLOBAL_CONTEXT_KEY] ??= {
139
141
  prOidcByConfigId: new Map(),
140
- evtAuthResponseHandled: createAwaitableEventEmitter<void>(),
141
- $isUserActive: undefined,
142
- hasLogoutBeenCalled: false
142
+ hasLogoutBeenCalled: false,
143
+ evtRequestToPersistTokens: createEvt()
143
144
  };
144
145
 
145
146
  const globalContext = window[GLOBAL_CONTEXT_KEY];
@@ -321,7 +322,7 @@ export async function createOidc_nonMemoized<
321
322
  }
322
323
  }
323
324
 
324
- const isUserStorePersistent = !areThirdPartyCookiesAllowed;
325
+ let isUserStoreInMemoryOnly: boolean;
325
326
 
326
327
  const oidcClientTsUserManager = new OidcClientTsUserManager({
327
328
  stateQueryParamValue: stateQueryParamValue_instance,
@@ -334,31 +335,51 @@ export async function createOidc_nonMemoized<
334
335
  scope: Array.from(new Set(["openid", ...scopes])).join(" "),
335
336
  automaticSilentRenew: false,
336
337
  userStore: new WebStorageStateStore({
337
- store: areThirdPartyCookiesAllowed
338
- ? new InMemoryWebStorage()
339
- : createEphemeralSessionStorage({
340
- sessionStorageTtlMs: 3 * 60_1000
341
- })
338
+ store: (() => {
339
+ if (areThirdPartyCookiesAllowed) {
340
+ isUserStoreInMemoryOnly = true;
341
+ return new InMemoryWebStorage();
342
+ }
343
+
344
+ isUserStoreInMemoryOnly = false;
345
+
346
+ const storage = createEphemeralSessionStorage({
347
+ sessionStorageTtlMs: 3 * 60_000
348
+ });
349
+
350
+ const { evtRequestToPersistTokens } = globalContext;
351
+
352
+ evtRequestToPersistTokens.subscribe(({ configIdOfInstancePostingTheRequest }) => {
353
+ if (configIdOfInstancePostingTheRequest === configId) {
354
+ return;
355
+ }
356
+
357
+ storage.persistCurrentStateAndSubsequentChanges();
358
+ });
359
+
360
+ return storage;
361
+ })()
342
362
  }),
343
363
  stateStore: new WebStorageStateStore({ store: localStorage, prefix: STATE_STORE_KEY_PREFIX }),
344
364
  client_secret: __unsafe_clientSecret
345
365
  });
346
366
 
347
- const {
348
- loginOrGoToAuthServer,
349
- toCallBeforeReturningOidcLoggedIn,
350
- toCallBeforeReturningOidcNotLoggedIn
351
- } = createLoginOrGoToAuthServer({
367
+ const evtIsUserLoggedIn = createEvt<boolean>();
368
+
369
+ const { loginOrGoToAuthServer } = createLoginOrGoToAuthServer({
352
370
  configId,
353
371
  oidcClientTsUserManager,
354
372
  getExtraQueryParams,
355
373
  transformUrlBeforeRedirect,
356
374
  homeAndCallbackUrl,
375
+ evtIsUserLoggedIn,
357
376
  log
358
377
  });
359
378
 
360
379
  const BROWSER_SESSION_NOT_FIRST_INIT_KEY = `oidc-spa.browser-session-not-first-init:${configId}`;
361
380
 
381
+ const { completeLoginOrRefreshProcess } = await startLoginOrRefreshProcess();
382
+
362
383
  const resultOfLoginProcess = await (async (): Promise<
363
384
  | undefined // User is currently not logged in
364
385
  | Error // Initialization error
@@ -368,58 +389,13 @@ export async function createOidc_nonMemoized<
368
389
  }
369
390
  > => {
370
391
  handle_redirect_auth_response: {
371
- const authResponse = (() => {
372
- const value = sessionStorage.getItem(AUTH_RESPONSE_KEY);
373
-
374
- if (value === null) {
375
- return undefined;
376
- }
377
-
378
- let authResponse: unknown;
379
-
380
- try {
381
- authResponse = JSON.parse(value);
382
-
383
- assert(
384
- typeGuard<{ state: string; [key: string]: string }>(
385
- authResponse,
386
- authResponse instanceof Object &&
387
- Object.values(authResponse).every(value => typeof value === "string")
388
- ),
389
- "Valid json but not expected shape"
390
- );
391
- } catch (error) {
392
- console.error(`Failed to parse auth response from callback URL ${String(error)}`);
393
- return undefined;
394
- }
392
+ const authResponseAndStateData = retrieveRedirectAuthResponseAndStateData({ configId });
395
393
 
396
- return authResponse;
397
- })();
398
-
399
- if (authResponse === undefined) {
394
+ if (authResponseAndStateData === undefined) {
400
395
  break handle_redirect_auth_response;
401
396
  }
402
397
 
403
- const stateData = getStateData({ stateQueryParamValue: authResponse["state"] });
404
-
405
- assert(stateData !== undefined);
406
- assert(stateData.context === "redirect");
407
-
408
- const { evtAuthResponseHandled } = globalContext;
409
-
410
- if (stateData.configId !== configId) {
411
- // NOTE: Best attempt at letting the other client handle the request synchronously
412
- // but we won't wait for it because the initialization of the other client might
413
- // be contingent on the initialization of this client.
414
- const prHandled = evtAuthResponseHandled.waitFor();
415
- await Promise.resolve();
416
- if (sessionStorage.getItem(AUTH_RESPONSE_KEY) === null) {
417
- await prHandled;
418
- }
419
- break handle_redirect_auth_response;
420
- }
421
-
422
- sessionStorage.removeItem(AUTH_RESPONSE_KEY);
398
+ const { authResponse, stateData } = authResponseAndStateData;
423
399
 
424
400
  switch (stateData.action) {
425
401
  case "login":
@@ -431,11 +407,9 @@ export async function createOidc_nonMemoized<
431
407
  let oidcClientTsUser: OidcClientTsUser | undefined = undefined;
432
408
 
433
409
  try {
434
- oidcClientTsUser = await oidcClientTsUserManager
435
- .signinRedirectCallback(authResponseUrl)
436
- .finally(() => {
437
- evtAuthResponseHandled.post();
438
- });
410
+ oidcClientTsUser = await oidcClientTsUserManager.signinRedirectCallback(
411
+ authResponseUrl
412
+ );
439
413
  } catch (error) {
440
414
  assert(error instanceof Error);
441
415
 
@@ -447,11 +421,11 @@ export async function createOidc_nonMemoized<
447
421
  }
448
422
 
449
423
  {
450
- const error: string | undefined = authResponse["error"];
424
+ const authResponse_error = authResponse.error;
451
425
 
452
- if (error !== undefined) {
426
+ if (authResponse_error !== undefined) {
453
427
  log?.(
454
- `The auth server responded with: ${error}, trying to restore from the http only cookie`
428
+ `The auth server responded with: ${authResponse_error}, trying to restore from the http only cookie`
455
429
  );
456
430
  break handle_redirect_auth_response;
457
431
  }
@@ -462,18 +436,31 @@ export async function createOidc_nonMemoized<
462
436
 
463
437
  sessionStorage.removeItem(BROWSER_SESSION_NOT_FIRST_INIT_KEY);
464
438
 
439
+ notifyOtherTabsOfLogin({ configId });
440
+
465
441
  return {
466
442
  oidcClientTsUser,
467
443
  backFromAuthServer: {
468
444
  extraQueryParams: stateData.extraQueryParams,
469
445
  result: Object.fromEntries(
470
- Object.entries(authResponse).filter(
471
- ([name]) =>
472
- name !== "state" &&
473
- name !== "session_state" &&
474
- name !== "iss" &&
475
- name !== "code"
476
- )
446
+ Object.entries(authResponse)
447
+ .map(([name, value]) => {
448
+ if (
449
+ name === "state" ||
450
+ name === "session_state" ||
451
+ name === "iss" ||
452
+ name === "code"
453
+ ) {
454
+ return undefined;
455
+ }
456
+
457
+ if (value === undefined) {
458
+ return undefined;
459
+ }
460
+
461
+ return [name, value];
462
+ })
463
+ .filter(entry => entry !== undefined)
477
464
  )
478
465
  }
479
466
  };
@@ -489,8 +476,6 @@ export async function createOidc_nonMemoized<
489
476
  await oidcClientTsUserManager.signoutRedirectCallback(authResponseUrl);
490
477
  } catch {}
491
478
 
492
- evtAuthResponseHandled.post();
493
-
494
479
  notifyOtherTabsOfLogout({
495
480
  configId,
496
481
  redirectUrl: stateData.redirectUrl,
@@ -505,7 +490,7 @@ export async function createOidc_nonMemoized<
505
490
  }
506
491
 
507
492
  restore_from_session_storage: {
508
- if (!isUserStorePersistent) {
493
+ if (isUserStoreInMemoryOnly) {
509
494
  break restore_from_session_storage;
510
495
  }
511
496
 
@@ -575,7 +560,7 @@ export async function createOidc_nonMemoized<
575
560
 
576
561
  log?.("Silent signin auth response", authResponse);
577
562
 
578
- const authResponse_error: string | undefined = authResponse["error"];
563
+ const authResponse_error = authResponse.error;
579
564
  let oidcClientTsUser: OidcClientTsUser | undefined = undefined;
580
565
 
581
566
  try {
@@ -608,6 +593,18 @@ export async function createOidc_nonMemoized<
608
593
  ) {
609
594
  persistAuthState({ configId, state: undefined });
610
595
 
596
+ completeLoginOrRefreshProcess();
597
+
598
+ await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
599
+ prUnlock: new Promise<never>(() => {})
600
+ });
601
+
602
+ if (persistedAuthState === "logged in") {
603
+ globalContext.evtRequestToPersistTokens.post({
604
+ configIdOfInstancePostingTheRequest: configId
605
+ });
606
+ }
607
+
611
608
  await loginOrGoToAuthServer({
612
609
  action: "login",
613
610
  doForceReloadOnBfCache: true,
@@ -617,8 +614,7 @@ export async function createOidc_nonMemoized<
617
614
  transformUrlBeforeRedirect_local: undefined,
618
615
  doForceInteraction: false
619
616
  });
620
-
621
- // NOTE: Never here
617
+ assert(false);
622
618
  }
623
619
 
624
620
  log?.(
@@ -643,71 +639,15 @@ export async function createOidc_nonMemoized<
643
639
 
644
640
  // NOTE: The user is not logged in.
645
641
  return undefined;
646
- })().then(result => {
647
- if (result === undefined) {
648
- return undefined;
649
- }
650
-
651
- if (result instanceof Error) {
652
- return result;
653
- }
654
-
655
- const { oidcClientTsUser, backFromAuthServer } = result;
656
-
657
- log_real_decoded_id_token: {
658
- if (log === undefined) {
659
- break log_real_decoded_id_token;
660
- }
661
- const idToken = oidcClientTsUser.id_token;
662
-
663
- if (idToken === undefined) {
664
- break log_real_decoded_id_token;
665
- }
666
-
667
- const decodedIdToken = decodeJwt(idToken);
668
-
669
- log(
670
- [
671
- `Decoded ID token`,
672
- decodedIdTokenSchema === undefined ? "" : " before `decodedIdTokenSchema.parse()`\n",
673
- JSON.stringify(decodedIdToken, null, 2)
674
- ].join("")
675
- );
676
-
677
- if (decodedIdTokenSchema === undefined) {
678
- break log_real_decoded_id_token;
679
- }
680
-
681
- log(
682
- [
683
- "Decoded ID token after `decodedIdTokenSchema.parse()`\n",
684
- JSON.stringify(decodedIdTokenSchema.parse(decodedIdToken), null, 2)
685
- ].join("")
686
- );
687
- }
642
+ })();
688
643
 
689
- const tokens = oidcClientTsUserToTokens({
690
- oidcClientTsUser,
691
- decodedIdTokenSchema,
692
- __unsafe_useIdTokenAsAccessToken,
693
- decodedIdToken_previous: undefined,
694
- log
695
- });
644
+ completeLoginOrRefreshProcess();
696
645
 
697
- if (tokens.refreshTokenExpirationTime < tokens.accessTokenExpirationTime) {
698
- console.warn(
699
- [
700
- "The OIDC refresh token shorter than the one of the access token.",
701
- "This is very unusual and probably a misconfiguration.",
702
- `Check your oidc server configuration for ${clientId} ${issuerUri}`
703
- ].join(" ")
704
- );
705
- }
706
-
707
- return { tokens, backFromAuthServer };
646
+ await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
647
+ prUnlock: Promise.resolve()
708
648
  });
709
649
 
710
- const common: Oidc.Common = {
650
+ const oidc_common: Oidc.Common = {
711
651
  params: {
712
652
  issuerUri,
713
653
  clientId
@@ -719,6 +659,12 @@ export async function createOidc_nonMemoized<
719
659
  break not_loggedIn_case;
720
660
  }
721
661
 
662
+ evtIsUserLoggedIn.post(false);
663
+
664
+ if (getPersistedAuthState({ configId }) !== "explicitly logged out") {
665
+ persistAuthState({ configId, state: undefined });
666
+ }
667
+
722
668
  const oidc_notLoggedIn: Oidc.NotLoggedIn = (() => {
723
669
  if (resultOfLoginProcess instanceof Error) {
724
670
  log?.("User not logged in and there was an initialization error");
@@ -747,7 +693,7 @@ export async function createOidc_nonMemoized<
747
693
  );
748
694
 
749
695
  return id<Oidc.NotLoggedIn>({
750
- ...common,
696
+ ...oidc_common,
751
697
  isUserLoggedIn: false,
752
698
  login: async () => {
753
699
  alert("Authentication is currently unavailable. Please try again later.");
@@ -761,15 +707,19 @@ export async function createOidc_nonMemoized<
761
707
  log?.("User not logged in");
762
708
 
763
709
  return id<Oidc.NotLoggedIn>({
764
- ...common,
710
+ ...oidc_common,
765
711
  isUserLoggedIn: false,
766
- login: ({
712
+ login: async ({
767
713
  doesCurrentHrefRequiresAuth,
768
714
  extraQueryParams,
769
715
  redirectUrl,
770
716
  transformUrlBeforeRedirect
771
- }) =>
772
- loginOrGoToAuthServer({
717
+ }) => {
718
+ await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
719
+ prUnlock: getPrSafelyRestoredFromBfCacheAfterLoginBackNavigation()
720
+ });
721
+
722
+ return loginOrGoToAuthServer({
773
723
  action: "login",
774
724
  doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack:
775
725
  doesCurrentHrefRequiresAuth,
@@ -780,7 +730,8 @@ export async function createOidc_nonMemoized<
780
730
  transformUrlBeforeRedirect_local: transformUrlBeforeRedirect,
781
731
  doForceInteraction:
782
732
  getPersistedAuthState({ configId }) === "explicitly logged out"
783
- }),
733
+ });
734
+ },
784
735
  initializationError: undefined
785
736
  });
786
737
  }
@@ -788,11 +739,21 @@ export async function createOidc_nonMemoized<
788
739
  assert<Equals<typeof resultOfLoginProcess, never>>(false);
789
740
  })();
790
741
 
791
- if (getPersistedAuthState({ configId }) !== "explicitly logged out") {
792
- persistAuthState({ configId, state: undefined });
793
- }
742
+ {
743
+ const { prOtherTabLogin } = getPrOtherTabLogin({
744
+ configId
745
+ });
794
746
 
795
- toCallBeforeReturningOidcNotLoggedIn();
747
+ prOtherTabLogin.then(async () => {
748
+ log?.(`Other tab has logged in, reloading this tab`);
749
+
750
+ await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
751
+ prUnlock: new Promise<never>(() => {})
752
+ });
753
+
754
+ window.location.reload();
755
+ });
756
+ }
796
757
 
797
758
  // @ts-expect-error: We know what we're doing
798
759
  return oidc_notLoggedIn;
@@ -800,7 +761,31 @@ export async function createOidc_nonMemoized<
800
761
 
801
762
  log?.("User is logged in");
802
763
 
803
- let currentTokens = resultOfLoginProcess.tokens;
764
+ evtIsUserLoggedIn.post(true);
765
+
766
+ let currentTokens = oidcClientTsUserToTokens({
767
+ oidcClientTsUser: resultOfLoginProcess.oidcClientTsUser,
768
+ decodedIdTokenSchema,
769
+ __unsafe_useIdTokenAsAccessToken,
770
+ decodedIdToken_previous: undefined,
771
+ log
772
+ });
773
+
774
+ {
775
+ if (getPersistedAuthState({ configId }) !== undefined) {
776
+ persistAuthState({ configId, state: undefined });
777
+ }
778
+
779
+ if (!areThirdPartyCookiesAllowed) {
780
+ persistAuthState({
781
+ configId,
782
+ state: {
783
+ stateDescription: "logged in",
784
+ untilTime: currentTokens.refreshTokenExpirationTime
785
+ }
786
+ });
787
+ }
788
+ }
804
789
 
805
790
  const autoLogoutCountdownTickCallbacks = new Set<
806
791
  (params: { secondsLeft: number | undefined }) => void
@@ -809,7 +794,7 @@ export async function createOidc_nonMemoized<
809
794
  const onTokenChanges = new Set<(tokens: Oidc.Tokens<DecodedIdToken>) => void>();
810
795
 
811
796
  const oidc_loggedIn = id<Oidc.LoggedIn<DecodedIdToken>>({
812
- ...common,
797
+ ...oidc_common,
813
798
  isUserLoggedIn: true,
814
799
  getTokens: () => currentTokens,
815
800
  getTokens_next: async () => {
@@ -828,12 +813,6 @@ export async function createOidc_nonMemoized<
828
813
 
829
814
  globalContext.hasLogoutBeenCalled = true;
830
815
 
831
- document.addEventListener("visibilitychange", () => {
832
- if (document.visibilityState === "visible") {
833
- location.reload();
834
- }
835
- });
836
-
837
816
  const postLogoutRedirectUrl: string = (() => {
838
817
  switch (params.redirectTo) {
839
818
  case "current page":
@@ -848,6 +827,14 @@ export async function createOidc_nonMemoized<
848
827
  }
849
828
  })();
850
829
 
830
+ await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
831
+ prUnlock: new Promise<never>(() => {})
832
+ });
833
+
834
+ window.addEventListener("pageshow", () => {
835
+ location.reload();
836
+ });
837
+
851
838
  try {
852
839
  await oidcClientTsUserManager.signoutRedirect({
853
840
  state: id<StateData>({
@@ -866,7 +853,7 @@ export async function createOidc_nonMemoized<
866
853
  if (error.message === "No end session endpoint") {
867
854
  log?.("No end session endpoint, managing logging state locally");
868
855
 
869
- persistAuthState({ configId, state: "explicitly logged out" });
856
+ persistAuthState({ configId, state: { stateDescription: "explicitly logged out" } });
870
857
 
871
858
  try {
872
859
  await oidcClientTsUserManager.removeUser();
@@ -874,6 +861,12 @@ export async function createOidc_nonMemoized<
874
861
  // NOTE: Not sure if it can throw
875
862
  }
876
863
 
864
+ notifyOtherTabsOfLogout({
865
+ configId,
866
+ redirectUrl: postLogoutRedirectUrl,
867
+ sessionId
868
+ });
869
+
877
870
  window.location.href = postLogoutRedirectUrl;
878
871
  } else {
879
872
  throw error;
@@ -888,6 +881,8 @@ export async function createOidc_nonMemoized<
888
881
 
889
882
  log?.("Renewing tokens");
890
883
 
884
+ const { completeLoginOrRefreshProcess } = await startLoginOrRefreshProcess();
885
+
891
886
  const result_loginSilent = await loginSilent({
892
887
  oidcClientTsUserManager,
893
888
  stateQueryParamValue_instance,
@@ -896,6 +891,7 @@ export async function createOidc_nonMemoized<
896
891
  });
897
892
 
898
893
  if (result_loginSilent.outcome === "failure") {
894
+ completeLoginOrRefreshProcess();
899
895
  throw new Error(result_loginSilent.cause);
900
896
  }
901
897
 
@@ -914,7 +910,7 @@ export async function createOidc_nonMemoized<
914
910
 
915
911
  log?.("Tokens refresh using iframe", authResponse);
916
912
 
917
- const authResponse_error: string | undefined = authResponse["error"];
913
+ const authResponse_error = authResponse.error;
918
914
 
919
915
  let oidcClientTsUser_scope: OidcClientTsUser | undefined = undefined;
920
916
 
@@ -927,6 +923,7 @@ export async function createOidc_nonMemoized<
927
923
  assert(error instanceof Error);
928
924
 
929
925
  if (authResponse_error === undefined) {
926
+ completeLoginOrRefreshProcess();
930
927
  throw error;
931
928
  }
932
929
 
@@ -936,6 +933,16 @@ export async function createOidc_nonMemoized<
936
933
  if (oidcClientTsUser_scope === undefined) {
937
934
  persistAuthState({ configId, state: undefined });
938
935
 
936
+ completeLoginOrRefreshProcess();
937
+
938
+ await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
939
+ prUnlock: new Promise<never>(() => {})
940
+ });
941
+
942
+ globalContext.evtRequestToPersistTokens.post({
943
+ configIdOfInstancePostingTheRequest: configId
944
+ });
945
+
939
946
  await loginOrGoToAuthServer({
940
947
  action: "login",
941
948
  redirectUrl: window.location.href,
@@ -964,7 +971,19 @@ export async function createOidc_nonMemoized<
964
971
  log
965
972
  });
966
973
 
974
+ if (getPersistedAuthState({ configId }) !== undefined) {
975
+ persistAuthState({
976
+ configId,
977
+ state: {
978
+ stateDescription: "logged in",
979
+ untilTime: currentTokens.refreshTokenExpirationTime
980
+ }
981
+ });
982
+ }
983
+
967
984
  Array.from(onTokenChanges).forEach(onTokenChange => onTokenChange(currentTokens));
985
+
986
+ completeLoginOrRefreshProcess();
968
987
  }
969
988
 
970
989
  let ongoingCall:
@@ -1079,14 +1098,27 @@ export async function createOidc_nonMemoized<
1079
1098
  sessionId
1080
1099
  });
1081
1100
 
1082
- prOtherTabLogout.then(({ redirectUrl }) => {
1101
+ prOtherTabLogout.then(async ({ redirectUrl }) => {
1083
1102
  log?.(`Other tab has logged out, redirecting to ${redirectUrl}`);
1103
+
1104
+ await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
1105
+ prUnlock: new Promise<never>(() => {})
1106
+ });
1107
+
1108
+ window.addEventListener("pageshow", () => {
1109
+ location.reload();
1110
+ });
1111
+
1084
1112
  window.location.href = redirectUrl;
1085
1113
  });
1086
1114
  }
1087
1115
 
1088
1116
  (function scheduleRenew() {
1089
- const login_dueToExpiration = () => {
1117
+ const login_dueToExpiration = async () => {
1118
+ await waitForAllOtherOngoingLoginOrRefreshProcessesToComplete({
1119
+ prUnlock: new Promise<never>(() => {})
1120
+ });
1121
+
1090
1122
  persistAuthState({ configId, state: undefined });
1091
1123
 
1092
1124
  return loginOrGoToAuthServer({
@@ -1149,9 +1181,16 @@ export async function createOidc_nonMemoized<
1149
1181
  })();
1150
1182
 
1151
1183
  auto_logout: {
1152
- if (currentTokens.refreshToken === "" && __unsafe_ssoSessionIdleSeconds === undefined) {
1184
+ if (
1185
+ (!currentTokens.hasRefreshToken || currentTokens.refreshTokenExpirationTime === undefined) &&
1186
+ __unsafe_ssoSessionIdleSeconds === undefined
1187
+ ) {
1153
1188
  log?.(
1154
- "No refresh token, and ____unsafe_ssoSessionIdleSeconds was not set, auto logout non applicable"
1189
+ `${
1190
+ currentTokens.hasRefreshToken
1191
+ ? "The refresh token is opaque, we can't read it's expiration time"
1192
+ : "No refresh token"
1193
+ }, and __unsafe_ssoSessionIdleSeconds was not set, can't implement auto logout mechanism`
1155
1194
  );
1156
1195
  break auto_logout;
1157
1196
  }
@@ -1161,7 +1200,9 @@ export async function createOidc_nonMemoized<
1161
1200
  const getCountdownEndTime = () =>
1162
1201
  __unsafe_ssoSessionIdleSeconds !== undefined
1163
1202
  ? Date.now() + __unsafe_ssoSessionIdleSeconds * 1000
1164
- : currentTokens.refreshTokenExpirationTime;
1203
+ : (assert(currentTokens.hasRefreshToken),
1204
+ assert(currentTokens.refreshTokenExpirationTime !== undefined),
1205
+ currentTokens.refreshTokenExpirationTime);
1165
1206
 
1166
1207
  const durationBeforeAutoLogout = toHumanReadableDuration(
1167
1208
  getCountdownEndTime() - Date.now()
@@ -1193,14 +1234,12 @@ export async function createOidc_nonMemoized<
1193
1234
 
1194
1235
  let stopCountdown: (() => void) | undefined = undefined;
1195
1236
 
1196
- if (globalContext.$isUserActive === undefined) {
1197
- globalContext.$isUserActive = create$isUserActive({
1198
- configId,
1199
- sessionId
1200
- });
1201
- }
1237
+ const evtIsUserActive = createEvtIsUserActive({
1238
+ configId,
1239
+ sessionId
1240
+ });
1202
1241
 
1203
- globalContext.$isUserActive.subscribe(isUserActive => {
1242
+ evtIsUserActive.subscribe(isUserActive => {
1204
1243
  if (isUserActive) {
1205
1244
  if (stopCountdown !== undefined) {
1206
1245
  stopCountdown();
@@ -1213,17 +1252,5 @@ export async function createOidc_nonMemoized<
1213
1252
  });
1214
1253
  }
1215
1254
 
1216
- {
1217
- if (getPersistedAuthState({ configId }) !== undefined) {
1218
- persistAuthState({ configId, state: undefined });
1219
- }
1220
-
1221
- if (!areThirdPartyCookiesAllowed) {
1222
- persistAuthState({ configId, state: "logged in" });
1223
- }
1224
- }
1225
-
1226
- toCallBeforeReturningOidcLoggedIn();
1227
-
1228
1255
  return oidc_loggedIn;
1229
1256
  }