@multiplatform.one/keycloak 6.3.0 → 6.4.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/package.json +9 -9
- package/src/frappeToken.ts +30 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/keycloak",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "keycloak client for multiplatform.one ecosystem",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@better-auth/expo": "^1.6.9",
|
|
55
55
|
"better-auth": "^1.6.9",
|
|
56
56
|
"jwt-decode": "^4.0.0",
|
|
57
|
-
"@multiplatform.one/
|
|
58
|
-
"@multiplatform.one/
|
|
57
|
+
"@multiplatform.one/store": "6.4.0",
|
|
58
|
+
"@multiplatform.one/keycloak-js": "6.4.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@tamagui/build": "2.0.0-rc.41",
|
|
@@ -70,15 +70,15 @@
|
|
|
70
70
|
"react": "19.2.5",
|
|
71
71
|
"typescript": "~5.9.3",
|
|
72
72
|
"vitest": "^4.1.5",
|
|
73
|
-
"@multiplatform.one/
|
|
74
|
-
"@multiplatform.one/
|
|
75
|
-
"@multiplatform.one/
|
|
76
|
-
"@multiplatform.one/logger": "6.
|
|
73
|
+
"@multiplatform.one/config": "6.4.0",
|
|
74
|
+
"@multiplatform.one/platform": "6.4.0",
|
|
75
|
+
"@multiplatform.one/test-utils": "6.4.0",
|
|
76
|
+
"@multiplatform.one/logger": "6.4.0"
|
|
77
77
|
},
|
|
78
78
|
"peerDependencies": {
|
|
79
79
|
"react": "^19.1.0",
|
|
80
|
-
"@multiplatform.one/platform": "^6.
|
|
81
|
-
"@multiplatform.one/logger": "^6.
|
|
80
|
+
"@multiplatform.one/platform": "^6.4.0",
|
|
81
|
+
"@multiplatform.one/logger": "^6.4.0"
|
|
82
82
|
},
|
|
83
83
|
"optionalDependencies": {
|
|
84
84
|
"expo-linking": "^55.0.15",
|
package/src/frappeToken.ts
CHANGED
|
@@ -14,12 +14,35 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
let cached: { token: string; expiresAt: number } | undefined;
|
|
17
|
+
/** In-flight fetch, shared so parallel callers don't stampede the endpoint. */
|
|
18
|
+
let pending: Promise<string | undefined> | undefined;
|
|
19
|
+
/** After a signed-out answer, skip re-asking until this time (ms epoch). */
|
|
20
|
+
let signedOutUntil = 0;
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
const SIGNED_OUT_COOLDOWN_MS = 15_000;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns a valid Keycloak access token, or undefined when signed out.
|
|
26
|
+
*
|
|
27
|
+
* Anonymous visitors get a single 401 per cooldown window instead of one per
|
|
28
|
+
* concurrent data request: parallel callers share the in-flight fetch, and a
|
|
29
|
+
* signed-out answer short-circuits further attempts for a few seconds. A real
|
|
30
|
+
* sign-in arrives via the OAuth redirect (full navigation), which resets this
|
|
31
|
+
* module state anyway.
|
|
32
|
+
*/
|
|
19
33
|
export async function getKeycloakBearerToken(): Promise<string | undefined> {
|
|
20
34
|
if (typeof window === "undefined") return undefined;
|
|
21
35
|
// 60s slack so we never hand out a token that expires mid-request.
|
|
22
36
|
if (cached && cached.expiresAt - 60_000 > Date.now()) return cached.token;
|
|
37
|
+
if (Date.now() < signedOutUntil) return undefined;
|
|
38
|
+
if (pending) return pending;
|
|
39
|
+
pending = fetchToken().finally(() => {
|
|
40
|
+
pending = undefined;
|
|
41
|
+
});
|
|
42
|
+
return pending;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function fetchToken(): Promise<string | undefined> {
|
|
23
46
|
try {
|
|
24
47
|
const res = await fetch(`${window.location.origin}/api/auth/get-access-token`, {
|
|
25
48
|
method: "POST",
|
|
@@ -29,6 +52,7 @@ export async function getKeycloakBearerToken(): Promise<string | undefined> {
|
|
|
29
52
|
});
|
|
30
53
|
if (!res.ok) {
|
|
31
54
|
cached = undefined;
|
|
55
|
+
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
32
56
|
return undefined;
|
|
33
57
|
}
|
|
34
58
|
const data = (await res.json().catch(() => null)) as {
|
|
@@ -37,6 +61,7 @@ export async function getKeycloakBearerToken(): Promise<string | undefined> {
|
|
|
37
61
|
} | null;
|
|
38
62
|
if (!data?.accessToken) {
|
|
39
63
|
cached = undefined;
|
|
64
|
+
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
40
65
|
return undefined;
|
|
41
66
|
}
|
|
42
67
|
cached = {
|
|
@@ -49,11 +74,14 @@ export async function getKeycloakBearerToken(): Promise<string | undefined> {
|
|
|
49
74
|
} catch {
|
|
50
75
|
// Signed out / server unreachable -> guest requests.
|
|
51
76
|
cached = undefined;
|
|
77
|
+
signedOutUntil = Date.now() + SIGNED_OUT_COOLDOWN_MS;
|
|
52
78
|
return undefined;
|
|
53
79
|
}
|
|
54
80
|
}
|
|
55
81
|
|
|
56
|
-
/** Drop the cached token (sign-out). */
|
|
82
|
+
/** Drop the cached token (sign-out / fresh sign-in). */
|
|
57
83
|
export function clearKeycloakBearerToken() {
|
|
58
84
|
cached = undefined;
|
|
85
|
+
pending = undefined;
|
|
86
|
+
signedOutUntil = 0;
|
|
59
87
|
}
|