oidc-spa 6.5.2 → 6.6.1

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 +366 -298
  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 +288 -251
  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
@@ -1,5 +1,5 @@
1
1
  import type { User as OidcClientTsUser } from "../vendor/frontend/oidc-client-ts-and-jwt-decode";
2
- import { assert } from "../vendor/frontend/tsafe";
2
+ import { assert, id } from "../vendor/frontend/tsafe";
3
3
  import { readExpirationTimeInJwt } from "../tools/readExpirationTimeInJwt";
4
4
  import { decodeJwt } from "../tools/decodeJwt";
5
5
  import type { Oidc } from "./Oidc";
@@ -9,7 +9,7 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
9
9
  decodedIdTokenSchema?: { parse: (data: unknown) => DecodedIdToken };
10
10
  __unsafe_useIdTokenAsAccessToken: boolean;
11
11
  decodedIdToken_previous: DecodedIdToken | undefined;
12
- log: ((message: string) => void) | undefined;
12
+ log: typeof console.log | undefined;
13
13
  }): Oidc.Tokens<DecodedIdToken> {
14
14
  const {
15
15
  oidcClientTsUser,
@@ -19,14 +19,16 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
19
19
  log
20
20
  } = params;
21
21
 
22
+ const isFirstInit = decodedIdToken_previous === undefined;
23
+
22
24
  const accessToken = oidcClientTsUser.access_token;
23
25
 
24
26
  const accessTokenExpirationTime = (() => {
25
- read_from_metadata: {
27
+ read_from_token_response: {
26
28
  const { expires_at } = oidcClientTsUser;
27
29
 
28
30
  if (expires_at === undefined) {
29
- break read_from_metadata;
31
+ break read_from_token_response;
30
32
  }
31
33
 
32
34
  return expires_at * 1000;
@@ -49,7 +51,7 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
49
51
 
50
52
  const refreshTokenExpirationTime = (() => {
51
53
  if (refreshToken === undefined) {
52
- return Number.POSITIVE_INFINITY;
54
+ return undefined;
53
55
  }
54
56
 
55
57
  read_from_jwt: {
@@ -62,23 +64,50 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
62
64
  return expirationTime;
63
65
  }
64
66
 
65
- log?.(
66
- [
67
- "Couldn't read the expiration time of the refresh token from the jwt",
68
- "It's ok. Some OIDC server like Microsoft Entra ID does not use JWT for the refresh token.",
69
- "Be aware that it prevent you from implementing the auto logout mechanism: https://docs.oidc-spa.dev/v/v6/auto-logout",
70
- "If you need auto logout you'll have to provide use the __unsafe_ssoSessionIdleSeconds param."
71
- ].join("\n")
72
- );
73
-
74
- return Number.POSITIVE_INFINITY;
67
+ return undefined;
75
68
  })();
76
69
 
77
70
  const idToken = oidcClientTsUser.id_token;
78
71
 
79
72
  assert(idToken !== undefined, "No id token provided by the oidc server");
80
73
 
81
- const tokens: Oidc.Tokens<DecodedIdToken> = {
74
+ 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
+ }
86
+
87
+ if (decodedIdTokenSchema !== undefined) {
88
+ decodedIdToken = decodedIdTokenSchema.parse(decodedIdToken);
89
+
90
+ if (isFirstInit) {
91
+ log?.(
92
+ [
93
+ "Decoded ID token after `decodedIdTokenSchema.parse()`\n",
94
+ JSON.stringify(decodedIdToken, null, 2)
95
+ ].join("")
96
+ );
97
+ }
98
+ }
99
+
100
+ if (
101
+ decodedIdToken_previous !== undefined &&
102
+ JSON.stringify(decodedIdToken) === JSON.stringify(decodedIdToken_previous)
103
+ ) {
104
+ return decodedIdToken_previous;
105
+ }
106
+
107
+ return decodedIdToken;
108
+ })();
109
+
110
+ const tokens_common: Oidc.Tokens.Common<DecodedIdToken> = {
82
111
  ...(__unsafe_useIdTokenAsAccessToken
83
112
  ? {
84
113
  accessToken: idToken,
@@ -94,27 +123,37 @@ export function oidcClientTsUserToTokens<DecodedIdToken extends Record<string, u
94
123
  })()
95
124
  }
96
125
  : { accessToken, accessTokenExpirationTime }),
97
- refreshToken: refreshToken ?? "",
98
- refreshTokenExpirationTime,
99
126
  idToken,
100
- decodedIdToken: (() => {
101
- let decodedIdToken = decodeJwt(idToken) as DecodedIdToken;
102
-
103
- if (decodedIdTokenSchema !== undefined) {
104
- decodedIdToken = decodedIdTokenSchema.parse(decodedIdToken);
105
- }
106
-
107
- if (
108
- decodedIdToken_previous !== undefined &&
109
- JSON.stringify(decodedIdToken) === JSON.stringify(decodedIdToken_previous)
110
- ) {
111
- return decodedIdToken_previous;
112
- }
113
-
114
- return decodedIdToken;
115
- })()
127
+ decodedIdToken
116
128
  };
117
129
 
130
+ const tokens: Oidc.Tokens<DecodedIdToken> =
131
+ refreshToken === undefined
132
+ ? id<Oidc.Tokens.WithoutRefreshToken<DecodedIdToken>>({
133
+ ...tokens_common,
134
+ hasRefreshToken: false
135
+ })
136
+ : id<Oidc.Tokens.WithRefreshToken<DecodedIdToken>>({
137
+ ...tokens_common,
138
+ hasRefreshToken: true,
139
+ refreshToken,
140
+ refreshTokenExpirationTime
141
+ });
142
+
143
+ if (
144
+ isFirstInit &&
145
+ tokens.hasRefreshToken &&
146
+ tokens.refreshTokenExpirationTime !== undefined &&
147
+ tokens.refreshTokenExpirationTime < tokens.accessTokenExpirationTime
148
+ ) {
149
+ console.warn(
150
+ [
151
+ "The OIDC refresh token shorter than the one of the access token.",
152
+ "This is very unusual and probably a misconfiguration."
153
+ ].join(" ")
154
+ );
155
+ }
156
+
118
157
  return tokens;
119
158
  }
120
159
 
@@ -124,7 +163,7 @@ export function getMsBeforeExpiration(tokens: Oidc.Tokens): number {
124
163
  // assumption here.
125
164
  const tokenExpirationTime = Math.min(
126
165
  tokens.accessTokenExpirationTime,
127
- tokens.refreshTokenExpirationTime
166
+ tokens.refreshTokenExpirationTime ?? Number.POSITIVE_INFINITY
128
167
  );
129
168
 
130
169
  const msBeforeExpiration = Math.min(
@@ -0,0 +1,60 @@
1
+ import { Deferred } from "../tools/Deferred";
2
+ import { assert } from "../vendor/frontend/tsafe";
3
+
4
+ const GLOBAL_CONTEXT_KEY = "__oidc-spa.ongoingLoginOrRefreshProcesses.globalContext";
5
+
6
+ declare global {
7
+ interface Window {
8
+ [GLOBAL_CONTEXT_KEY]: {
9
+ prDone_arr: Promise<void>[];
10
+ prUnlock: Promise<void>;
11
+ };
12
+ }
13
+ }
14
+
15
+ window[GLOBAL_CONTEXT_KEY] ??= {
16
+ prDone_arr: [],
17
+ prUnlock: Promise.resolve()
18
+ };
19
+
20
+ const globalContext = window[GLOBAL_CONTEXT_KEY];
21
+
22
+ export async function startLoginOrRefreshProcess(): Promise<{
23
+ completeLoginOrRefreshProcess: () => void;
24
+ }> {
25
+ await globalContext.prUnlock;
26
+
27
+ const dDone = new Deferred<void>();
28
+
29
+ const { prDone_arr } = globalContext;
30
+
31
+ prDone_arr.push(dDone.pr);
32
+
33
+ function completeLoginOrRefreshProcess() {
34
+ const index = prDone_arr.indexOf(dDone.pr);
35
+
36
+ assert(index !== -1);
37
+
38
+ prDone_arr.splice(index, 1);
39
+
40
+ dDone.resolve();
41
+ }
42
+
43
+ return { completeLoginOrRefreshProcess };
44
+ }
45
+
46
+ export async function waitForAllOtherOngoingLoginOrRefreshProcessesToComplete(params: {
47
+ prUnlock: Promise<void>;
48
+ }): Promise<void> {
49
+ const { prUnlock } = params;
50
+
51
+ const prUnlock_current = globalContext.prUnlock;
52
+
53
+ globalContext.prUnlock = (async () => {
54
+ await prUnlock_current;
55
+
56
+ await prUnlock;
57
+ })();
58
+
59
+ await Promise.all(globalContext.prDone_arr);
60
+ }
@@ -1,4 +1,4 @@
1
- import { assert } from "../vendor/frontend/tsafe";
1
+ import { typeGuard, id } from "../vendor/frontend/tsafe";
2
2
 
3
3
  function getKey(params: { configId: string }) {
4
4
  const { configId } = params;
@@ -6,9 +6,29 @@ function getKey(params: { configId: string }) {
6
6
  return `oidc-spa:auth-state:${configId}`;
7
7
  }
8
8
 
9
- type PersistedAuthState = "logged in" | "explicitly logged out";
9
+ type PersistedAuthState = PersistedAuthState.LoggedIn | PersistedAuthState.ExplicitlyLoggedOut;
10
+ namespace PersistedAuthState {
11
+ type Common = {
12
+ __brand: "PersistedAuthState-v1";
13
+ };
10
14
 
11
- export function persistAuthState(params: { configId: string; state: PersistedAuthState | undefined }) {
15
+ export type LoggedIn = Common & {
16
+ stateDescription: "logged in";
17
+ untilTime: number | undefined;
18
+ };
19
+
20
+ export type ExplicitlyLoggedOut = Common & {
21
+ stateDescription: "explicitly logged out";
22
+ };
23
+ }
24
+
25
+ export function persistAuthState(params: {
26
+ configId: string;
27
+ state:
28
+ | Omit<PersistedAuthState.ExplicitlyLoggedOut, "__brand">
29
+ | Omit<PersistedAuthState.LoggedIn, "__brand">
30
+ | undefined;
31
+ }) {
12
32
  const { configId, state } = params;
13
33
 
14
34
  const key = getKey({ configId });
@@ -18,19 +38,57 @@ export function persistAuthState(params: { configId: string; state: PersistedAut
18
38
  return;
19
39
  }
20
40
 
21
- localStorage.setItem(key, state);
41
+ localStorage.setItem(
42
+ key,
43
+ JSON.stringify(
44
+ id<PersistedAuthState>({
45
+ __brand: "PersistedAuthState-v1",
46
+ ...state
47
+ })
48
+ )
49
+ );
22
50
  }
23
51
 
24
- export function getPersistedAuthState(params: { configId: string }): PersistedAuthState | undefined {
52
+ export function getPersistedAuthState(params: {
53
+ configId: string;
54
+ }): PersistedAuthState["stateDescription"] | undefined {
25
55
  const { configId } = params;
26
56
 
27
- const value = localStorage.getItem(getKey({ configId }));
57
+ const key = getKey({ configId });
58
+
59
+ const value = localStorage.getItem(key);
28
60
 
29
61
  if (value === null) {
30
62
  return undefined;
31
63
  }
32
64
 
33
- assert(value === "logged in" || value === "explicitly logged out");
65
+ let state: unknown;
66
+
67
+ try {
68
+ state = JSON.parse(value);
69
+ } catch {
70
+ localStorage.removeItem(key);
71
+ return undefined;
72
+ }
73
+
74
+ if (
75
+ !typeGuard<PersistedAuthState>(
76
+ state,
77
+ state instanceof Object &&
78
+ "__brand" in state &&
79
+ state.__brand === id<PersistedAuthState["__brand"]>("PersistedAuthState-v1")
80
+ )
81
+ ) {
82
+ localStorage.removeItem(key);
83
+ return undefined;
84
+ }
85
+
86
+ if (state.stateDescription === "logged in") {
87
+ if (state.untilTime !== undefined && state.untilTime <= Date.now()) {
88
+ localStorage.removeItem(key);
89
+ return undefined;
90
+ }
91
+ }
34
92
 
35
- return value;
93
+ return state.stateDescription;
36
94
  }
@@ -235,7 +235,7 @@ export function createOidcReactApi_dependencyInjection<
235
235
  return unsubscribe;
236
236
  }, [oidc]);
237
237
 
238
- const refTokensState = useRef<{
238
+ const tokensState_ref = useRef<{
239
239
  isConsumerReadingTokens: boolean;
240
240
  tokens: Oidc.Tokens<DecodedIdToken> | undefined;
241
241
  }>({
@@ -243,23 +243,17 @@ export function createOidcReactApi_dependencyInjection<
243
243
  tokens: undefined
244
244
  });
245
245
 
246
- const tokensPropertyDescriptorGetter = () => {
247
- const tokenState = refTokensState.current;
248
- tokenState.isConsumerReadingTokens = true;
249
- return tokenState.tokens;
250
- };
251
-
252
246
  useEffect(() => {
253
247
  if (!oidc.isUserLoggedIn) {
254
248
  return;
255
249
  }
256
250
 
257
251
  const updateTokens = (tokens: Oidc.Tokens<DecodedIdToken>) => {
258
- if (tokens === refTokensState.current.tokens) {
252
+ if (tokens === tokensState_ref.current.tokens) {
259
253
  return;
260
254
  }
261
255
 
262
- const tokenState = refTokensState.current;
256
+ const tokenState = tokensState_ref.current;
263
257
 
264
258
  tokenState.tokens = tokens;
265
259
 
@@ -305,7 +299,11 @@ export function createOidcReactApi_dependencyInjection<
305
299
  isUserLoggedIn: true,
306
300
  oidcTokens: oidc.getTokens(),
307
301
  decodedIdToken: oidc.getDecodedIdToken(),
308
- tokens: null as any,
302
+ get tokens() {
303
+ const tokensState = tokensState_ref.current;
304
+ tokensState.isConsumerReadingTokens = true;
305
+ return tokensState.tokens;
306
+ },
309
307
  logout: oidc.logout,
310
308
  renewTokens: oidc.renewTokens,
311
309
  subscribeToAutoLogoutCountdown: oidc.subscribeToAutoLogoutCountdown,
@@ -314,12 +312,6 @@ export function createOidcReactApi_dependencyInjection<
314
312
  backFromAuthServer: oidc.backFromAuthServer
315
313
  };
316
314
 
317
- Object.defineProperty(oidcReact, "tokens", {
318
- get: tokensPropertyDescriptorGetter,
319
- enumerable: true,
320
- configurable: true
321
- });
322
-
323
315
  return oidcReact;
324
316
  }
325
317
 
@@ -1,15 +1,11 @@
1
1
  import { assert, typeGuard, id } from "../vendor/frontend/tsafe";
2
2
 
3
3
  type SessionStorageItem_Parsed = {
4
- __brand: typeof SessionStorageItem_Parsed.brand;
4
+ __brand: "SessionStorageItem_Parsed-v1";
5
5
  value: string;
6
6
  expiresAtTime: number;
7
7
  };
8
8
 
9
- namespace SessionStorageItem_Parsed {
10
- export const brand = "SessionStorageItem_Parsed";
11
- }
12
-
13
9
  function parseSessionStorageItem(
14
10
  sessionStorageItemValue: string
15
11
  ): SessionStorageItem_Parsed | undefined {
@@ -26,7 +22,7 @@ function parseSessionStorageItem(
26
22
  json,
27
23
  json instanceof Object &&
28
24
  "__brand" in json &&
29
- json.__brand === SessionStorageItem_Parsed.brand
25
+ json.__brand === id<SessionStorageItem_Parsed["__brand"]>("SessionStorageItem_Parsed-v1")
30
26
  )
31
27
  ) {
32
28
  return undefined;
@@ -72,7 +68,7 @@ function createStoreInSessionStorageAndScheduleRemovalInMemoryItem(params: {
72
68
  sessionStorageKey,
73
69
  JSON.stringify(
74
70
  id<SessionStorageItem_Parsed>({
75
- __brand: "SessionStorageItem_Parsed",
71
+ __brand: "SessionStorageItem_Parsed-v1",
76
72
  value,
77
73
  expiresAtTime: Date.now() + remainingTtlMs
78
74
  })
@@ -82,7 +78,22 @@ function createStoreInSessionStorageAndScheduleRemovalInMemoryItem(params: {
82
78
  return inMemoryItem;
83
79
  }
84
80
 
85
- export function createEphemeralSessionStorage(params: { sessionStorageTtlMs: number }): Storage {
81
+ export type EphemeralSessionStorage = {
82
+ // `Storage` methods, we don't use the type directly because it has [name: string]: any;
83
+ readonly length: number;
84
+ clear(): void;
85
+ getItem(key: string): string | null;
86
+ key(index: number): string | null;
87
+ removeItem(key: string): void;
88
+ setItem(key: string, value: string): void;
89
+
90
+ // Custom method
91
+ persistCurrentStateAndSubsequentChanges: () => void;
92
+ };
93
+
94
+ export function createEphemeralSessionStorage(params: {
95
+ sessionStorageTtlMs: number;
96
+ }): EphemeralSessionStorage {
86
97
  const { sessionStorageTtlMs } = params;
87
98
 
88
99
  const inMemoryItems: InMemoryItem[] = [];
@@ -107,25 +118,40 @@ export function createEphemeralSessionStorage(params: { sessionStorageTtlMs: num
107
118
 
108
119
  const remainingTtlMs = sessionStorageItem_parsed.expiresAtTime - Date.now();
109
120
 
121
+ sessionStorage.removeItem(sessionStorageKey);
122
+
110
123
  if (remainingTtlMs <= 0) {
111
- sessionStorage.removeItem(sessionStorageKey);
112
124
  continue;
113
125
  }
114
126
 
115
- inMemoryItems.push(
116
- createStoreInSessionStorageAndScheduleRemovalInMemoryItem({
117
- key: sessionStorageKey.slice(SESSION_STORAGE_PREFIX.length),
118
- value: sessionStorageItem_parsed.value,
119
- remainingTtlMs
120
- })
121
- );
127
+ inMemoryItems.push({
128
+ key: sessionStorageKey.slice(SESSION_STORAGE_PREFIX.length),
129
+ value: sessionStorageItem_parsed.value,
130
+ removeFromSessionStorage: undefined
131
+ });
122
132
  }
123
133
 
124
- const storage = {
134
+ let isPersistenceEnabled = false;
135
+
136
+ const storage: EphemeralSessionStorage = {
137
+ persistCurrentStateAndSubsequentChanges: () => {
138
+ isPersistenceEnabled = true;
139
+
140
+ for (let i = 0; i < storage.length; i++) {
141
+ const key = storage.key(i);
142
+ assert(key !== null);
143
+
144
+ const value = storage.getItem(key);
145
+
146
+ assert(value !== null);
147
+
148
+ storage.setItem(key, value);
149
+ }
150
+ },
125
151
  get length() {
126
152
  return inMemoryItems.length;
127
153
  },
128
- key(index: number) {
154
+ key: index => {
129
155
  const inMemoryItem = inMemoryItems[index];
130
156
 
131
157
  if (inMemoryItem === undefined) {
@@ -134,7 +160,7 @@ export function createEphemeralSessionStorage(params: { sessionStorageTtlMs: num
134
160
 
135
161
  return inMemoryItem.key;
136
162
  },
137
- removeItem(key: string) {
163
+ removeItem: key => {
138
164
  const inMemoryItem = inMemoryItems.find(item => item.key === key);
139
165
 
140
166
  if (inMemoryItem === undefined) {
@@ -147,21 +173,21 @@ export function createEphemeralSessionStorage(params: { sessionStorageTtlMs: num
147
173
 
148
174
  inMemoryItems.splice(index, 1);
149
175
  },
150
- clear() {
176
+ clear: () => {
151
177
  for (let i = 0; i < storage.length; i++) {
152
178
  const key = storage.key(i);
153
179
  assert(key !== null);
154
180
  storage.removeItem(key);
155
181
  }
156
182
  },
157
- getItem(key: string) {
183
+ getItem: key => {
158
184
  const inMemoryItem = inMemoryItems.find(item => item.key === key);
159
185
  if (inMemoryItem === undefined) {
160
186
  return null;
161
187
  }
162
188
  return inMemoryItem.value;
163
189
  },
164
- setItem(key: string, value: string) {
190
+ setItem: (key, value) => {
165
191
  let existingInMemoryItemIndex: number | undefined = undefined;
166
192
 
167
193
  {
@@ -173,11 +199,17 @@ export function createEphemeralSessionStorage(params: { sessionStorageTtlMs: num
173
199
  }
174
200
  }
175
201
 
176
- const inMemoryItem_new = createStoreInSessionStorageAndScheduleRemovalInMemoryItem({
177
- key,
178
- value,
179
- remainingTtlMs: sessionStorageTtlMs
180
- });
202
+ const inMemoryItem_new = isPersistenceEnabled
203
+ ? createStoreInSessionStorageAndScheduleRemovalInMemoryItem({
204
+ key,
205
+ value,
206
+ remainingTtlMs: sessionStorageTtlMs
207
+ })
208
+ : id<InMemoryItem>({
209
+ key,
210
+ value,
211
+ removeFromSessionStorage: undefined
212
+ });
181
213
 
182
214
  if (existingInMemoryItemIndex !== undefined) {
183
215
  inMemoryItems[existingInMemoryItemIndex] = inMemoryItem_new;
@@ -0,0 +1,56 @@
1
+ import { Deferred } from "./Deferred";
2
+ import { assert, is } from "../vendor/frontend/tsafe";
3
+
4
+ export type NonPostableEvt<T> = {
5
+ waitFor: () => Promise<T>;
6
+ subscribe: (next: (data: T) => void) => { unsubscribe: () => void };
7
+ postCount: number;
8
+ };
9
+
10
+ export type Evt<T> = NonPostableEvt<T> & {
11
+ post: (data: T) => void;
12
+ };
13
+
14
+ export function createEvt<T>(): Evt<T> {
15
+ const eventTarget = new EventTarget();
16
+ const KEY = "event";
17
+
18
+ let postCount = 0;
19
+
20
+ const evt: Evt<T> = {
21
+ subscribe: next => {
22
+ const listener = (e: Event) => {
23
+ assert(is<CustomEvent<T>>(e));
24
+
25
+ next(e.detail);
26
+ };
27
+
28
+ eventTarget.addEventListener(KEY, listener);
29
+
30
+ return {
31
+ unsubscribe: () => {
32
+ eventTarget.removeEventListener(KEY, listener);
33
+ }
34
+ };
35
+ },
36
+ waitFor: () => {
37
+ const d = new Deferred<T>();
38
+
39
+ const { unsubscribe } = evt.subscribe(data => {
40
+ unsubscribe();
41
+ d.resolve(data);
42
+ });
43
+
44
+ return d.pr;
45
+ },
46
+ post: (data: T) => {
47
+ postCount++;
48
+ eventTarget.dispatchEvent(new CustomEvent(KEY, { detail: data }));
49
+ },
50
+ get postCount() {
51
+ return postCount;
52
+ }
53
+ };
54
+
55
+ return evt;
56
+ }
@@ -0,0 +1,38 @@
1
+ export type StatefulEvt<T> = {
2
+ current: T;
3
+ subscribe: (next: (data: T) => void) => Subscription;
4
+ };
5
+
6
+ export type StatefulReadonlyEvt<T> = {
7
+ readonly current: T;
8
+ subscribe: (next: (data: T) => void) => Subscription;
9
+ };
10
+
11
+ export type Subscription = {
12
+ unsubscribe: () => void;
13
+ };
14
+
15
+ export function createStatefulEvt<T>(getInitialValue: () => T): StatefulEvt<T> {
16
+ let nextFunctions: ((data: T) => void)[] = [];
17
+
18
+ let wrappedState: [T] | undefined = undefined;
19
+
20
+ return {
21
+ get current() {
22
+ if (wrappedState === undefined) {
23
+ wrappedState = [getInitialValue()];
24
+ }
25
+ return wrappedState[0];
26
+ },
27
+ set current(data: T) {
28
+ wrappedState = [data];
29
+
30
+ nextFunctions.forEach(next => next(data));
31
+ },
32
+ subscribe: (next: (data: T) => void) => {
33
+ nextFunctions.push(next);
34
+
35
+ return { unsubscribe: () => nextFunctions.splice(nextFunctions.indexOf(next), 1) };
36
+ }
37
+ };
38
+ }
@@ -1,5 +1,4 @@
1
1
  import { getPrUserInteraction } from "./getPrUserInteraction";
2
- import { clearTimeout, setTimeout } from "../tools/workerTimers";
3
2
 
4
3
  export function subscribeToUserInteraction(params: { throttleMs: number; callback: () => void }) {
5
4
  const { throttleMs } = params;