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.
- package/mock/oidc.js +6 -5
- package/mock/oidc.js.map +1 -1
- package/oidc/AuthResponse.d.ts +6 -0
- package/oidc/AuthResponse.js +59 -0
- package/oidc/AuthResponse.js.map +1 -0
- package/oidc/Oidc.d.ts +19 -8
- package/oidc/createOidc.d.ts +4 -4
- package/oidc/createOidc.js +359 -295
- package/oidc/createOidc.js.map +1 -1
- package/oidc/evtIsUserActive.d.ts +15 -0
- package/oidc/{isUserActive.js → evtIsUserActive.js} +29 -12
- package/oidc/evtIsUserActive.js.map +1 -0
- package/oidc/handleOidcCallback.d.ts +8 -1
- package/oidc/handleOidcCallback.js +68 -13
- package/oidc/handleOidcCallback.js.map +1 -1
- package/oidc/loginOrGoToAuthServer.d.ts +5 -4
- package/oidc/loginOrGoToAuthServer.js +190 -227
- package/oidc/loginOrGoToAuthServer.js.map +1 -1
- package/oidc/loginPropagationToOtherTabs.d.ts +17 -0
- package/oidc/loginPropagationToOtherTabs.js +41 -0
- package/oidc/loginPropagationToOtherTabs.js.map +1 -0
- package/oidc/loginSilent.d.ts +1 -5
- package/oidc/loginSilent.js +3 -51
- package/oidc/loginSilent.js.map +1 -1
- package/oidc/logoutPropagationToOtherTabs.js +1 -1
- package/oidc/logoutPropagationToOtherTabs.js.map +1 -1
- package/oidc/oidcClientTsUserToTokens.d.ts +1 -1
- package/oidc/oidcClientTsUserToTokens.js +45 -23
- package/oidc/oidcClientTsUserToTokens.js.map +1 -1
- package/oidc/ongoingLoginOrRefreshProcesses.d.ts +16 -0
- package/oidc/ongoingLoginOrRefreshProcesses.js +102 -0
- package/oidc/ongoingLoginOrRefreshProcesses.js.map +1 -0
- package/oidc/persistedAuthState.d.ts +16 -3
- package/oidc/persistedAuthState.js +35 -4
- package/oidc/persistedAuthState.js.map +1 -1
- package/package.json +36 -21
- package/react/react.js +8 -14
- package/react/react.js.map +1 -1
- package/src/mock/oidc.ts +14 -3
- package/src/oidc/AuthResponse.ts +26 -0
- package/src/oidc/Oidc.ts +19 -4
- package/src/oidc/createOidc.ts +233 -206
- package/src/oidc/{isUserActive.ts → evtIsUserActive.ts} +36 -10
- package/src/oidc/handleOidcCallback.ts +73 -12
- package/src/oidc/loginOrGoToAuthServer.ts +94 -87
- package/src/oidc/loginPropagationToOtherTabs.ts +63 -0
- package/src/oidc/loginSilent.ts +2 -20
- package/src/oidc/logoutPropagationToOtherTabs.ts +2 -2
- package/src/oidc/oidcClientTsUserToTokens.ts +74 -35
- package/src/oidc/ongoingLoginOrRefreshProcesses.ts +60 -0
- package/src/oidc/persistedAuthState.ts +66 -8
- package/src/react/react.tsx +8 -16
- package/src/tools/{ephemeralSessionStorage.ts → EphemeralSessionStorage.ts} +59 -27
- package/src/tools/Evt.ts +56 -0
- package/src/tools/StatefulEvt.ts +38 -0
- package/src/tools/subscribeToUserInteraction.ts +0 -1
- package/src/tools/workerTimers.ts +10 -12
- package/tools/EphemeralSessionStorage.d.ts +12 -0
- package/tools/{ephemeralSessionStorage.js → EphemeralSessionStorage.js} +29 -16
- package/tools/EphemeralSessionStorage.js.map +1 -0
- package/tools/Evt.d.ts +11 -0
- package/tools/{AwaitableEventEmitter.js → Evt.js} +24 -8
- package/tools/Evt.js.map +1 -0
- package/tools/StatefulEvt.d.ts +12 -0
- package/tools/StatefulEvt.js +24 -0
- package/tools/StatefulEvt.js.map +1 -0
- package/tools/subscribeToUserInteraction.js +2 -3
- package/tools/subscribeToUserInteraction.js.map +1 -1
- package/tools/workerTimers.js +11 -13
- package/tools/workerTimers.js.map +1 -1
- package/oidc/isUserActive.d.ts +0 -13
- package/oidc/isUserActive.js.map +0 -1
- package/src/tools/AwaitableEventEmitter.ts +0 -33
- package/src/tools/StatefulObservable.ts +0 -52
- package/tools/AwaitableEventEmitter.d.ts +0 -5
- package/tools/AwaitableEventEmitter.js.map +0 -1
- package/tools/StatefulObservable.d.ts +0 -12
- package/tools/StatefulObservable.js +0 -33
- package/tools/StatefulObservable.js.map +0 -1
- package/tools/ephemeralSessionStorage.d.ts +0 -3
- 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:
|
|
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
|
-
|
|
27
|
+
read_from_token_response: {
|
|
26
28
|
const { expires_at } = oidcClientTsUser;
|
|
27
29
|
|
|
28
30
|
if (expires_at === undefined) {
|
|
29
|
-
break
|
|
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
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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 =
|
|
9
|
+
type PersistedAuthState = PersistedAuthState.LoggedIn | PersistedAuthState.ExplicitlyLoggedOut;
|
|
10
|
+
namespace PersistedAuthState {
|
|
11
|
+
type Common = {
|
|
12
|
+
__brand: "PersistedAuthState-v1";
|
|
13
|
+
};
|
|
10
14
|
|
|
11
|
-
export
|
|
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(
|
|
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: {
|
|
52
|
+
export function getPersistedAuthState(params: {
|
|
53
|
+
configId: string;
|
|
54
|
+
}): PersistedAuthState["stateDescription"] | undefined {
|
|
25
55
|
const { configId } = params;
|
|
26
56
|
|
|
27
|
-
const
|
|
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
|
-
|
|
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
|
|
93
|
+
return state.stateDescription;
|
|
36
94
|
}
|
package/src/react/react.tsx
CHANGED
|
@@ -235,7 +235,7 @@ export function createOidcReactApi_dependencyInjection<
|
|
|
235
235
|
return unsubscribe;
|
|
236
236
|
}, [oidc]);
|
|
237
237
|
|
|
238
|
-
const
|
|
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 ===
|
|
252
|
+
if (tokens === tokensState_ref.current.tokens) {
|
|
259
253
|
return;
|
|
260
254
|
}
|
|
261
255
|
|
|
262
|
-
const tokenState =
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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;
|
package/src/tools/Evt.ts
ADDED
|
@@ -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
|
+
}
|