oidc-spa 6.15.0 → 7.0.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 (43) hide show
  1. package/README.md +12 -13
  2. package/core/Oidc.d.ts +24 -12
  3. package/core/createOidc.d.ts +15 -30
  4. package/core/createOidc.js +137 -136
  5. package/core/createOidc.js.map +1 -1
  6. package/core/handleOidcCallback.js +11 -30
  7. package/core/handleOidcCallback.js.map +1 -1
  8. package/core/loginOrGoToAuthServer.d.ts +1 -2
  9. package/core/loginOrGoToAuthServer.js +239 -180
  10. package/core/loginOrGoToAuthServer.js.map +1 -1
  11. package/core/loginSilent.d.ts +1 -1
  12. package/core/loginSilent.js +4 -4
  13. package/core/loginSilent.js.map +1 -1
  14. package/core/logoutPropagationToOtherTabs.d.ts +1 -5
  15. package/core/logoutPropagationToOtherTabs.js +3 -10
  16. package/core/logoutPropagationToOtherTabs.js.map +1 -1
  17. package/core/oidcClientTsUserToTokens.d.ts +1 -2
  18. package/core/oidcClientTsUserToTokens.js +93 -58
  19. package/core/oidcClientTsUserToTokens.js.map +1 -1
  20. package/mock/oidc.d.ts +1 -1
  21. package/mock/oidc.js +29 -19
  22. package/mock/oidc.js.map +1 -1
  23. package/package.json +1 -5
  24. package/react/react.d.ts +1 -7
  25. package/react/react.js +8 -59
  26. package/react/react.js.map +1 -1
  27. package/src/core/Oidc.ts +27 -14
  28. package/src/core/createOidc.ts +124 -129
  29. package/src/core/handleOidcCallback.ts +12 -56
  30. package/src/core/loginOrGoToAuthServer.ts +26 -12
  31. package/src/core/loginSilent.ts +4 -4
  32. package/src/core/logoutPropagationToOtherTabs.ts +6 -24
  33. package/src/core/oidcClientTsUserToTokens.ts +129 -82
  34. package/src/mock/oidc.ts +16 -6
  35. package/src/react/react.tsx +11 -72
  36. package/src/tools/readExpirationTimeInJwt.ts +4 -5
  37. package/tools/readExpirationTimeInJwt.js +4 -4
  38. package/tools/readExpirationTimeInJwt.js.map +1 -1
  39. package/vendor/frontend/oidc-client-ts-and-jwt-decode.js +1 -1
  40. package/core/debug966975.d.ts +0 -7
  41. package/core/debug966975.js +0 -88
  42. package/core/debug966975.js.map +0 -1
  43. package/src/core/debug966975.ts +0 -85
@@ -8,7 +8,6 @@ import { assert, id } from "../vendor/frontend/tsafe";
8
8
  import type { AuthResponse } from "./AuthResponse";
9
9
  import { initialLocationHref } from "./initialLocationHref";
10
10
  import { captureFetch } from "./trustedFetch";
11
- import { debug966975 } from "./debug966975";
12
11
 
13
12
  captureFetch();
14
13
 
@@ -16,19 +15,8 @@ const globalContext = {
16
15
  previousCall: id<{ isHandled: boolean } | undefined>(undefined)
17
16
  };
18
17
 
19
- debug966975.log(
20
- `=================== Evaluating the handleOidcCallback file, isInIframe: ${
21
- window.self !== window.top ? "true" : "false"
22
- }, location.href: ${initialLocationHref}`
23
- );
24
-
25
18
  export function handleOidcCallback(): { isHandled: boolean } {
26
19
  if (globalContext.previousCall !== undefined) {
27
- debug966975.log(
28
- `handleOidcCallback() call, it has been called previously ${JSON.stringify(
29
- globalContext.previousCall
30
- )}`
31
- );
32
20
  return globalContext.previousCall;
33
21
  }
34
22
 
@@ -36,20 +24,16 @@ export function handleOidcCallback(): { isHandled: boolean } {
36
24
  }
37
25
 
38
26
  function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
39
- debug966975.log(`In handleOidcCallback_nonMemoized()`);
40
-
41
27
  const location_urlObj = new URL(initialLocationHref);
42
28
 
43
29
  const stateQueryParamValue = (() => {
44
30
  const stateQueryParamValue = location_urlObj.searchParams.get("state");
45
31
 
46
32
  if (stateQueryParamValue === null) {
47
- debug966975.log("No state in url");
48
33
  return undefined;
49
34
  }
50
35
 
51
36
  if (!getIsStatQueryParamValue({ maybeStateQueryParamValue: stateQueryParamValue })) {
52
- debug966975.log(`State query param value ${stateQueryParamValue} is malformed`);
53
37
  return undefined;
54
38
  }
55
39
 
@@ -58,9 +42,6 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
58
42
  location_urlObj.searchParams.get("response_type") !== null &&
59
43
  location_urlObj.searchParams.get("redirect_uri") !== null
60
44
  ) {
61
- debug966975.log(
62
- "NOTE: We are probably in a Keycloakify theme and oidc-spa was loaded by mistake."
63
- );
64
45
  // NOTE: We are probably in a Keycloakify theme and oidc-spa was loaded by mistake.
65
46
  return undefined;
66
47
  }
@@ -68,13 +49,9 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
68
49
  return stateQueryParamValue;
69
50
  })();
70
51
 
71
- debug966975.log(`state query param value ${stateQueryParamValue ?? "undefined"}`);
72
-
73
52
  if (stateQueryParamValue === undefined) {
74
53
  const backForwardTracker = readBackForwardTracker();
75
54
 
76
- debug966975.log(`backForwardTracker: ${JSON.stringify(backForwardTracker)}`);
77
-
78
55
  if (backForwardTracker !== undefined) {
79
56
  writeBackForwardTracker({
80
57
  backForwardTracker: {
@@ -84,8 +61,6 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
84
61
  });
85
62
  }
86
63
 
87
- debug966975.log("returning isHandled false");
88
-
89
64
  return { isHandled: false };
90
65
  }
91
66
 
@@ -98,8 +73,6 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
98
73
 
99
74
  const stateData = getStateData({ stateQueryParamValue });
100
75
 
101
- debug966975.log(`stateData: ${JSON.stringify(stateData)}`);
102
-
103
76
  if (
104
77
  stateData === undefined ||
105
78
  (stateData.context === "redirect" && stateData.hasBeenProcessedByCallback)
@@ -123,8 +96,6 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
123
96
  }
124
97
  })();
125
98
 
126
- debug966975.log(`historyMethod: ${historyMethod}`);
127
-
128
99
  writeBackForwardTracker({
129
100
  backForwardTracker: {
130
101
  previousHistoryMethod: historyMethod,
@@ -132,15 +103,20 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
132
103
  }
133
104
  });
134
105
 
135
- reloadOnBfCacheNavigation();
136
-
137
106
  setTimeout(() => {
138
- debug966975.log(`(callback 0) Calling window.history.${historyMethod}()`);
107
+ reloadOnBfCacheNavigation();
139
108
 
140
109
  window.history[historyMethod]();
141
- }, 0);
142
110
 
143
- debug966975.log(`returning isHandled: ${isHandled ? "true" : "false"}`);
111
+ // NOTE: This is a "better than nothing" approach.
112
+ // Under some circumstances it's possible to get stuck on this url
113
+ // if there is no "next" page in the history for example, navigating
114
+ // forward is a NoOp. So in that case it's better to navigate to the home.
115
+ setTimeout(() => {
116
+ const { protocol, host, pathname, hash } = window.location;
117
+ window.location.href = `${protocol}//${host}${pathname}${hash}`;
118
+ }, 350);
119
+ }, 0);
144
120
 
145
121
  return { isHandled };
146
122
  }
@@ -153,12 +129,9 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
153
129
 
154
130
  assert(authResponse.state !== "", "063965");
155
131
 
156
- debug966975.log(`authResponse: ${JSON.stringify(authResponse)}`);
157
-
158
132
  switch (stateData.context) {
159
133
  case "iframe":
160
134
  setTimeout(() => {
161
- debug966975.log(`(callback 0) posting message to parent`);
162
135
  parent.postMessage(authResponse, location.origin);
163
136
  }, 0);
164
137
  break;
@@ -178,15 +151,11 @@ function handleOidcCallback_nonMemoized(): { isHandled: boolean } {
178
151
  return stateData.redirectUrl;
179
152
  })();
180
153
 
181
- debug966975.log(`(callback 0) location.href = "${href}";`);
182
-
183
154
  location.href = href;
184
155
  }, 0);
185
156
  break;
186
157
  }
187
158
 
188
- debug966975.log(`Returning isHandled: ${isHandled ? "true" : "false"}`);
189
-
190
159
  return { isHandled };
191
160
  }
192
161
 
@@ -220,28 +189,18 @@ export function retrieveRedirectAuthResponseAndStateData(params: {
220
189
  }): { authResponse: AuthResponse; stateData: StateData.Redirect } | undefined {
221
190
  const { configId } = params;
222
191
 
223
- debug966975.log(`>>> In retrieveRedirectAuthResponseAndStateData(${JSON.stringify({ configId })})`);
224
-
225
192
  const authResponses = readRedirectAuthResponses();
226
193
 
227
- debug966975.log(`authResponses: ${JSON.stringify(authResponses)}`);
228
-
229
194
  let authResponseAndStateData:
230
195
  | { authResponse: AuthResponse; stateData: StateData.Redirect }
231
196
  | undefined = undefined;
232
197
 
233
198
  for (const authResponse of [...authResponses]) {
234
- debug966975.log(`authResponse: ${JSON.stringify(authResponse)}`);
235
-
236
199
  const stateData = getStateData({ stateQueryParamValue: authResponse.state });
237
200
 
238
- debug966975.log(`stateDate: ${JSON.stringify(stateData)}`);
239
-
240
- try {
241
- assert(stateData !== undefined, "966975");
242
- } catch {
201
+ if (stateData === undefined) {
202
+ // NOTE: We do not understand how this can happen but it can.
243
203
  authResponses.splice(authResponses.indexOf(authResponse), 1);
244
- debug966975.report();
245
204
  continue;
246
205
  }
247
206
 
@@ -257,12 +216,9 @@ export function retrieveRedirectAuthResponseAndStateData(params: {
257
216
  }
258
217
 
259
218
  if (authResponseAndStateData !== undefined) {
260
- debug966975.log(`writeRedirectAuthResponses(${JSON.stringify({ authResponses })})`);
261
219
  writeRedirectAuthResponses({ authResponses });
262
220
  }
263
221
 
264
- debug966975.log(`Returning ${JSON.stringify({ authResponseAndStateData })} <<<<<<<<<`);
265
-
266
222
  return authResponseAndStateData;
267
223
  }
268
224
 
@@ -51,8 +51,7 @@ export function getPrSafelyRestoredFromBfCacheAfterLoginBackNavigation() {
51
51
  export function createLoginOrGoToAuthServer(params: {
52
52
  configId: string;
53
53
  oidcClientTsUserManager: OidcClientTsUserManager;
54
- transformUrlBeforeRedirect: ((url: string) => string) | undefined;
55
- transformUrlBeforeRedirect_next:
54
+ transformUrlBeforeRedirect:
56
55
  | ((params: { authorizationUrl: string; isSilent: boolean }) => string)
57
56
  | undefined;
58
57
 
@@ -71,7 +70,6 @@ export function createLoginOrGoToAuthServer(params: {
71
70
  oidcClientTsUserManager,
72
71
 
73
72
  transformUrlBeforeRedirect,
74
- transformUrlBeforeRedirect_next,
75
73
  getExtraQueryParams,
76
74
 
77
75
  getExtraTokenParams,
@@ -85,11 +83,11 @@ export function createLoginOrGoToAuthServer(params: {
85
83
 
86
84
  let lastPublicUrl: string | undefined = undefined;
87
85
 
88
- function loginOrGoToAuthServer(params: Params): Promise<never> {
86
+ async function loginOrGoToAuthServer(params: Params): Promise<never> {
89
87
  const {
90
88
  redirectUrl: redirectUrl_params,
91
89
  extraQueryParams_local,
92
- transformUrlBeforeRedirect_local: transformUrl,
90
+ transformUrlBeforeRedirect_local,
93
91
  ...rest
94
92
  } = params;
95
93
 
@@ -107,6 +105,23 @@ export function createLoginOrGoToAuthServer(params: {
107
105
 
108
106
  globalContext.evtHasLoginBeenCalled.current = true;
109
107
 
108
+ if (document.visibilityState !== "visible") {
109
+ rest.interaction === "ensure no interaction";
110
+
111
+ const dVisible = new Deferred<void>();
112
+
113
+ const onVisible = () => {
114
+ if (document.visibilityState !== "visible") {
115
+ return;
116
+ }
117
+ document.removeEventListener("visibilitychange", onVisible);
118
+ dVisible.resolve();
119
+ };
120
+ document.addEventListener("visibilitychange", onVisible);
121
+
122
+ await dVisible.pr;
123
+ }
124
+
110
125
  bf_cache_handling: {
111
126
  if (rest.doForceReloadOnBfCache) {
112
127
  window.removeEventListener("pageshow", () => {
@@ -181,20 +196,19 @@ export function createLoginOrGoToAuthServer(params: {
181
196
  (
182
197
  [
183
198
  [
184
- undefined,
185
- transformUrlBeforeRedirect_next === undefined
199
+ getExtraQueryParams,
200
+ transformUrlBeforeRedirect === undefined
186
201
  ? undefined
187
202
  : (url: string) =>
188
- transformUrlBeforeRedirect_next({
203
+ transformUrlBeforeRedirect({
189
204
  isSilent,
190
205
  authorizationUrl: url
191
206
  })
192
207
  ],
193
- [getExtraQueryParams, transformUrlBeforeRedirect],
194
- [extraQueryParams_local, transformUrl]
208
+ [extraQueryParams_local, transformUrlBeforeRedirect_local]
195
209
  ] as const
196
- ).forEach(([extraQueryParamsMaybeGetter, transformUrlBeforeRedirect], i) => {
197
- const url_before = i !== 2 ? undefined : url;
210
+ ).forEach(([extraQueryParamsMaybeGetter, transformUrlBeforeRedirect], i, arr) => {
211
+ const url_before = i !== arr.length - 1 ? undefined : url;
198
212
 
199
213
  add_extra_query_params: {
200
214
  if (extraQueryParamsMaybeGetter === undefined) {
@@ -27,7 +27,7 @@ export async function loginSilent(params: {
27
27
  stateQueryParamValue_instance: string;
28
28
  configId: string;
29
29
 
30
- transformUrlBeforeRedirect_next:
30
+ transformUrlBeforeRedirect:
31
31
  | ((params: { authorizationUrl: string; isSilent: true }) => string)
32
32
  | undefined;
33
33
 
@@ -42,7 +42,7 @@ export async function loginSilent(params: {
42
42
  oidcClientTsUserManager,
43
43
  stateQueryParamValue_instance,
44
44
  configId,
45
- transformUrlBeforeRedirect_next,
45
+ transformUrlBeforeRedirect,
46
46
  getExtraQueryParams,
47
47
  getExtraTokenParams,
48
48
  autoLogin
@@ -126,10 +126,10 @@ export async function loginSilent(params: {
126
126
  }
127
127
 
128
128
  apply_transform_url: {
129
- if (transformUrlBeforeRedirect_next === undefined) {
129
+ if (transformUrlBeforeRedirect === undefined) {
130
130
  break apply_transform_url;
131
131
  }
132
- url = transformUrlBeforeRedirect_next({ authorizationUrl: url, isSilent: true });
132
+ url = transformUrlBeforeRedirect({ authorizationUrl: url, isSilent: true });
133
133
  }
134
134
 
135
135
  return url;
@@ -7,7 +7,6 @@ const globalContext = {
7
7
 
8
8
  type Message = {
9
9
  appInstanceId: string;
10
- redirectUrl_initiator: string;
11
10
  configId: string;
12
11
  };
13
12
 
@@ -16,15 +15,10 @@ function getChannelName(params: { sessionIdOrConfigId: string }) {
16
15
  return `oidc-spa:logout-propagation:${sessionIdOrConfigId}`;
17
16
  }
18
17
 
19
- export function notifyOtherTabsOfLogout(params: {
20
- redirectUrl: string;
21
- configId: string;
22
- sessionId: string | undefined;
23
- }) {
24
- const { redirectUrl, configId, sessionId } = params;
18
+ export function notifyOtherTabsOfLogout(params: { configId: string; sessionId: string | undefined }) {
19
+ const { configId, sessionId } = params;
25
20
 
26
21
  const message: Message = {
27
- redirectUrl_initiator: redirectUrl,
28
22
  configId,
29
23
  appInstanceId: globalContext.appInstanceId
30
24
  };
@@ -34,14 +28,10 @@ export function notifyOtherTabsOfLogout(params: {
34
28
  );
35
29
  }
36
30
 
37
- export function getPrOtherTabLogout(params: {
38
- sessionId: string | undefined;
39
- configId: string;
40
- homeUrl: string;
41
- }) {
42
- const { sessionId, configId, homeUrl } = params;
31
+ export function getPrOtherTabLogout(params: { sessionId: string | undefined; configId: string }) {
32
+ const { sessionId, configId } = params;
43
33
 
44
- const dOtherTabLogout = new Deferred<{ redirectUrl: string }>();
34
+ const dOtherTabLogout = new Deferred<void>();
45
35
 
46
36
  const channel = new BroadcastChannel(getChannelName({ sessionIdOrConfigId: sessionId ?? configId }));
47
37
 
@@ -54,15 +44,7 @@ export function getPrOtherTabLogout(params: {
54
44
 
55
45
  channel.close();
56
46
 
57
- const redirectUrl = (() => {
58
- if (configId === message.configId) {
59
- return message.redirectUrl_initiator;
60
- }
61
-
62
- return homeUrl;
63
- })();
64
-
65
- dOtherTabLogout.resolve({ redirectUrl });
47
+ dOtherTabLogout.resolve();
66
48
  };
67
49
 
68
50
  const prOtherTabLogout = dOtherTabLogout.pr;
@@ -6,7 +6,9 @@ import type { Oidc } from "./Oidc";
6
6
 
7
7
  export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, unknown>>(params: {
8
8
  oidcClientTsUser: OidcClientTsUser;
9
- decodedIdTokenSchema?: { parse: (data: unknown) => DecodedIdToken };
9
+ decodedIdTokenSchema?: {
10
+ parse: (decodedIdToken_original: Oidc.Tokens.DecodedIdToken_base) => DecodedIdToken;
11
+ };
10
12
  __unsafe_useIdTokenAsAccessToken: boolean;
11
13
  decodedIdToken_previous: DecodedIdToken | undefined;
12
14
  log: typeof console.log | undefined;
@@ -23,69 +25,29 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
23
25
 
24
26
  const accessToken = oidcClientTsUser.access_token;
25
27
 
26
- const accessTokenExpirationTime = (() => {
27
- read_from_token_response: {
28
- const { expires_at } = oidcClientTsUser;
29
-
30
- if (expires_at === undefined) {
31
- break read_from_token_response;
32
- }
33
-
34
- return expires_at * 1000;
35
- }
36
-
37
- read_from_jwt: {
38
- const expirationTime = readExpirationTimeInJwt(accessToken);
39
-
40
- if (expirationTime === undefined) {
41
- break read_from_jwt;
42
- }
43
-
44
- return expirationTime;
45
- }
46
-
47
- assert(false, "Failed to get access token expiration time");
48
- })();
49
-
50
28
  const refreshToken = oidcClientTsUser.refresh_token;
51
29
 
52
- const refreshTokenExpirationTime = (() => {
53
- if (refreshToken === undefined) {
54
- return undefined;
55
- }
56
-
57
- read_from_jwt: {
58
- const expirationTime = readExpirationTimeInJwt(refreshToken);
59
-
60
- if (expirationTime === undefined) {
61
- break read_from_jwt;
62
- }
63
-
64
- return expirationTime;
65
- }
66
-
67
- return undefined;
68
- })();
69
-
70
30
  const idToken = oidcClientTsUser.id_token;
71
31
 
72
32
  assert(idToken !== undefined, "No id token provided by the oidc server");
73
33
 
34
+ const decodedIdToken_original = decodeJwt<Oidc.Tokens.DecodedIdToken_base>(idToken);
35
+
36
+ if (isFirstInit) {
37
+ log?.(
38
+ [
39
+ `Decoded ID token`,
40
+ decodedIdTokenSchema === undefined ? "" : " before `decodedIdTokenSchema.parse()`\n",
41
+ JSON.stringify(decodedIdToken_original, null, 2)
42
+ ].join("")
43
+ );
44
+ }
45
+
74
46
  const decodedIdToken = (() => {
75
- let decodedIdToken = decodeJwt(idToken) as DecodedIdToken;
76
-
77
- if (isFirstInit) {
78
- log?.(
79
- [
80
- `Decoded ID token`,
81
- decodedIdTokenSchema === undefined ? "" : " before `decodedIdTokenSchema.parse()`\n",
82
- JSON.stringify(decodedIdToken, null, 2)
83
- ].join("")
84
- );
85
- }
47
+ let decodedIdToken: DecodedIdToken;
86
48
 
87
49
  if (decodedIdTokenSchema !== undefined) {
88
- decodedIdToken = decodedIdTokenSchema.parse(decodedIdToken);
50
+ decodedIdToken = decodedIdTokenSchema.parse(decodedIdToken_original);
89
51
 
90
52
  if (isFirstInit) {
91
53
  log?.(
@@ -95,18 +57,50 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
95
57
  ].join("")
96
58
  );
97
59
  }
60
+ } else {
61
+ // @ts-expect-error
62
+ decodedIdToken = decodedIdToken_original;
98
63
  }
99
64
 
100
65
  if (
101
66
  decodedIdToken_previous !== undefined &&
102
67
  JSON.stringify(decodedIdToken) === JSON.stringify(decodedIdToken_previous)
103
68
  ) {
69
+ // NOTE: For stable ref, prevent re-render for component that would memoize
104
70
  return decodedIdToken_previous;
105
71
  }
106
72
 
107
73
  return decodedIdToken;
108
74
  })();
109
75
 
76
+ const issuedAtTime = (() => {
77
+ // NOTE: The id_token is always a JWT as per the protocol.
78
+ // We don't use Date.now() due to network latency.
79
+ const id_token_iat = (() => {
80
+ let iat: number | undefined;
81
+
82
+ try {
83
+ const iat_claimValue = decodedIdToken_original.iat;
84
+ assert(iat_claimValue === undefined || typeof iat_claimValue === "number");
85
+ iat = iat_claimValue;
86
+ } catch {
87
+ iat = undefined;
88
+ }
89
+
90
+ if (iat === undefined) {
91
+ return undefined;
92
+ }
93
+
94
+ return iat;
95
+ })();
96
+
97
+ if (id_token_iat === undefined) {
98
+ return Date.now();
99
+ }
100
+
101
+ return id_token_iat * 1000;
102
+ })();
103
+
110
104
  const tokens_common: Oidc.Tokens.Common<DecodedIdToken> = {
111
105
  ...(__unsafe_useIdTokenAsAccessToken
112
106
  ? {
@@ -122,9 +116,50 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
122
116
  return expirationTime;
123
117
  })()
124
118
  }
125
- : { accessToken, accessTokenExpirationTime }),
119
+ : {
120
+ accessToken,
121
+ accessTokenExpirationTime: (() => {
122
+ read_from_jwt: {
123
+ const expirationTime = readExpirationTimeInJwt(accessToken);
124
+
125
+ if (expirationTime === undefined) {
126
+ break read_from_jwt;
127
+ }
128
+
129
+ return expirationTime;
130
+ }
131
+
132
+ read_from_token_response_expires_at: {
133
+ const { expires_at } = oidcClientTsUser.__oidc_spa_tokenResponse;
134
+
135
+ if (expires_at === undefined) {
136
+ break read_from_token_response_expires_at;
137
+ }
138
+
139
+ assert(typeof expires_at === "number", "2033392");
140
+
141
+ return expires_at * 1000;
142
+ }
143
+
144
+ read_from_token_response_expires_in: {
145
+ const { expires_in } = oidcClientTsUser.__oidc_spa_tokenResponse;
146
+
147
+ if (expires_in === undefined) {
148
+ break read_from_token_response_expires_in;
149
+ }
150
+
151
+ assert(typeof expires_in === "number", "203333425");
152
+
153
+ return issuedAtTime + expires_in * 1_000;
154
+ }
155
+
156
+ assert(false, "Failed to get access token expiration time");
157
+ })()
158
+ }),
126
159
  idToken,
127
- decodedIdToken
160
+ decodedIdToken,
161
+ decodedIdToken_original,
162
+ issuedAtTime
128
163
  };
129
164
 
130
165
  const tokens: Oidc.Tokens<DecodedIdToken> =
@@ -137,7 +172,43 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
137
172
  ...tokens_common,
138
173
  hasRefreshToken: true,
139
174
  refreshToken,
140
- refreshTokenExpirationTime
175
+ refreshTokenExpirationTime: (() => {
176
+ read_from_token_response_expires_at: {
177
+ const { refresh_expires_at } = oidcClientTsUser.__oidc_spa_tokenResponse;
178
+
179
+ if (refresh_expires_at === undefined) {
180
+ break read_from_token_response_expires_at;
181
+ }
182
+
183
+ assert(typeof refresh_expires_at === "number", "2033392");
184
+
185
+ return refresh_expires_at * 1000;
186
+ }
187
+
188
+ read_from_token_response_expires_in: {
189
+ const { refresh_expires_in } = oidcClientTsUser.__oidc_spa_tokenResponse;
190
+
191
+ if (refresh_expires_in === undefined) {
192
+ break read_from_token_response_expires_in;
193
+ }
194
+
195
+ assert(typeof refresh_expires_in === "number", "2033425330");
196
+
197
+ return issuedAtTime + refresh_expires_in * 1000;
198
+ }
199
+
200
+ read_from_jwt: {
201
+ const expirationTime = readExpirationTimeInJwt(refreshToken);
202
+
203
+ if (expirationTime === undefined) {
204
+ break read_from_jwt;
205
+ }
206
+
207
+ return expirationTime;
208
+ }
209
+
210
+ return undefined;
211
+ })()
141
212
  });
142
213
 
143
214
  if (
@@ -156,27 +227,3 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
156
227
 
157
228
  return tokens;
158
229
  }
159
-
160
- export function getMsBeforeExpiration(tokens: Oidc.Tokens): number {
161
- // NOTE: In general the access token is supposed to have a shorter
162
- // lifespan than the refresh token but we don't want to make any
163
- // assumption here.
164
- const tokenExpirationTime = Math.min(
165
- tokens.accessTokenExpirationTime,
166
- tokens.refreshTokenExpirationTime ?? Number.POSITIVE_INFINITY
167
- );
168
-
169
- const msBeforeExpiration = Math.min(
170
- tokenExpirationTime - Date.now(),
171
- // NOTE: We want to make sure we do not overflow the setTimeout
172
- // that must be a 32 bit unsigned integer.
173
- // This can happen if the tokenExpirationTime is more than 24.8 days in the future.
174
- Math.pow(2, 31) - 1
175
- );
176
-
177
- if (msBeforeExpiration < 0) {
178
- return 0;
179
- }
180
-
181
- return msBeforeExpiration;
182
- }
package/src/mock/oidc.ts CHANGED
@@ -29,7 +29,7 @@ export type ParamsOfCreateMockOidc<
29
29
  const URL_SEARCH_PARAM_NAME = "isUserLoggedIn";
30
30
 
31
31
  export async function createMockOidc<
32
- DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
32
+ DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_base,
33
33
  AutoLogin extends boolean = false
34
34
  >(
35
35
  params: ParamsOfCreateMockOidc<DecodedIdToken, AutoLogin>
@@ -130,7 +130,7 @@ export async function createMockOidc<
130
130
  ...common,
131
131
  isUserLoggedIn: true,
132
132
  renewTokens: async () => {},
133
- getTokens: (() => {
133
+ ...(() => {
134
134
  const tokens_common: Oidc.Tokens.Common<DecodedIdToken> = {
135
135
  accessToken: mockedTokens.accessToken ?? "mocked-access-token",
136
136
  accessTokenExpirationTime: mockedTokens.accessTokenExpirationTime ?? Infinity,
@@ -142,7 +142,16 @@ export async function createMockOidc<
142
142
  "You haven't provided a mocked decodedIdToken",
143
143
  "See https://docs.oidc-spa.dev/v/v6/mock"
144
144
  ].join("\n")
145
- })
145
+ }),
146
+ decodedIdToken_original:
147
+ mockedTokens.decodedIdToken_original ??
148
+ createObjectThatThrowsIfAccessed<Oidc.Tokens.DecodedIdToken_base>({
149
+ debugMessage: [
150
+ "You haven't provided a mocked decodedIdToken_original",
151
+ "See https://docs.oidc-spa.dev/v/v6/mock"
152
+ ].join("\n")
153
+ }),
154
+ issuedAtTime: Date.now()
146
155
  };
147
156
 
148
157
  const tokens: Oidc.Tokens<DecodedIdToken> =
@@ -158,10 +167,11 @@ export async function createMockOidc<
158
167
  hasRefreshToken: false
159
168
  });
160
169
 
161
- return () => tokens;
170
+ return {
171
+ getTokens: () => Promise.resolve(tokens),
172
+ getDecodedIdToken: () => tokens_common.decodedIdToken
173
+ };
162
174
  })(),
163
- getTokens_next: () => Promise.resolve(oidc.getTokens()),
164
- getDecodedIdToken: () => oidc.getTokens().decodedIdToken,
165
175
  subscribeToTokensChange: () => ({
166
176
  unsubscribe: () => {}
167
177
  }),