@mentra/cloud-client 3.2.0-dev.136 → 3.2.0-dev.153
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 +2 -2
- package/src/client.ts +72 -68
- package/src/config.ts +21 -21
- package/src/errors.ts +18 -0
- package/src/index.ts +7 -1
- package/src/modules/auth/auth.ts +308 -195
- package/src/modules/auth/token-store.ts +27 -25
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
*
|
|
18
18
|
* See docs/issues/004-cloud-client/design.md ("cloud.auth").
|
|
19
19
|
*/
|
|
20
|
-
import type {
|
|
21
|
-
import {
|
|
20
|
+
import type {KeyValueStore} from "../../transports"
|
|
21
|
+
import {decodeClaims} from "./jwt"
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Storage key for the persisted refresh token.
|
|
@@ -26,19 +26,20 @@ import { decodeClaims } from "./jwt";
|
|
|
26
26
|
* Namespaced so it cannot collide with anything else a host keeps in the same
|
|
27
27
|
* secure store.
|
|
28
28
|
*/
|
|
29
|
-
const
|
|
29
|
+
export const DEFAULT_REFRESH_TOKEN_KEY = "mentra.cloud-client.refreshToken"
|
|
30
30
|
|
|
31
31
|
/** The access token plus its Unix-seconds expiry, held in memory only. */
|
|
32
32
|
interface AccessTokenState {
|
|
33
|
-
accessToken: string
|
|
34
|
-
exp: number
|
|
33
|
+
accessToken: string
|
|
34
|
+
exp: number
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export class TokenStore {
|
|
38
|
-
private readonly storage: KeyValueStore
|
|
38
|
+
private readonly storage: KeyValueStore
|
|
39
|
+
private readonly storageKey: string
|
|
39
40
|
|
|
40
41
|
/** The current access token, or null before the first exchange/refresh. */
|
|
41
|
-
private access: AccessTokenState | null = null
|
|
42
|
+
private access: AccessTokenState | null = null
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* In-flight operations keyed by a caller-chosen string.
|
|
@@ -46,10 +47,11 @@ export class TokenStore {
|
|
|
46
47
|
* Holding the promise (not just a boolean) lets every concurrent caller await
|
|
47
48
|
* the same result, which is the whole point of single-flight here.
|
|
48
49
|
*/
|
|
49
|
-
private readonly inFlight = new Map<string, Promise<unknown>>()
|
|
50
|
+
private readonly inFlight = new Map<string, Promise<unknown>>()
|
|
50
51
|
|
|
51
|
-
constructor(deps: {
|
|
52
|
-
this.storage = deps.storage
|
|
52
|
+
constructor(deps: {storage: KeyValueStore; storageKey?: string}) {
|
|
53
|
+
this.storage = deps.storage
|
|
54
|
+
this.storageKey = deps.storageKey ?? DEFAULT_REFRESH_TOKEN_KEY
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/**
|
|
@@ -59,7 +61,7 @@ export class TokenStore {
|
|
|
59
61
|
* caller (auth) decides whether it is still fresh using `exp`.
|
|
60
62
|
*/
|
|
61
63
|
current(): AccessTokenState | null {
|
|
62
|
-
return this.access
|
|
64
|
+
return this.access
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
/**
|
|
@@ -70,12 +72,12 @@ export class TokenStore {
|
|
|
70
72
|
* relaunch. We read `exp` here (rather than in the caller) to keep all the
|
|
71
73
|
* token-state bookkeeping in one place.
|
|
72
74
|
*/
|
|
73
|
-
async save(tokens: {
|
|
75
|
+
async save(tokens: {accessToken: string; refreshToken: string}): Promise<void> {
|
|
74
76
|
this.access = {
|
|
75
77
|
accessToken: tokens.accessToken,
|
|
76
78
|
exp: decodeClaims(tokens.accessToken).exp,
|
|
77
|
-
}
|
|
78
|
-
await this.storage.set(
|
|
79
|
+
}
|
|
80
|
+
await this.storage.set(this.storageKey, tokens.refreshToken)
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
/**
|
|
@@ -85,7 +87,7 @@ export class TokenStore {
|
|
|
85
87
|
* process or restored out of band is always the one used.
|
|
86
88
|
*/
|
|
87
89
|
refreshToken(): Promise<string | null> {
|
|
88
|
-
return this.storage.get(
|
|
90
|
+
return this.storage.get(this.storageKey)
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
/**
|
|
@@ -97,7 +99,7 @@ export class TokenStore {
|
|
|
97
99
|
* refresh token, rather than handing back the rejected token again.
|
|
98
100
|
*/
|
|
99
101
|
invalidateAccess(): void {
|
|
100
|
-
this.access = null
|
|
102
|
+
this.access = null
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
/**
|
|
@@ -107,8 +109,8 @@ export class TokenStore {
|
|
|
107
109
|
* presenting a known-bad token to the cloud.
|
|
108
110
|
*/
|
|
109
111
|
async clear(): Promise<void> {
|
|
110
|
-
this.access = null
|
|
111
|
-
await this.storage.delete(
|
|
112
|
+
this.access = null
|
|
113
|
+
await this.storage.delete(this.storageKey)
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
/**
|
|
@@ -121,18 +123,18 @@ export class TokenStore {
|
|
|
121
123
|
* refresh failed, all callers waiting on it should see that failure.
|
|
122
124
|
*/
|
|
123
125
|
singleFlight<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
124
|
-
const existing = this.inFlight.get(key)
|
|
125
|
-
if (existing) return existing as Promise<T
|
|
126
|
+
const existing = this.inFlight.get(key)
|
|
127
|
+
if (existing) return existing as Promise<T>
|
|
126
128
|
|
|
127
129
|
const promise = (async () => {
|
|
128
130
|
try {
|
|
129
|
-
return await fn()
|
|
131
|
+
return await fn()
|
|
130
132
|
} finally {
|
|
131
|
-
this.inFlight.delete(key)
|
|
133
|
+
this.inFlight.delete(key)
|
|
132
134
|
}
|
|
133
|
-
})()
|
|
135
|
+
})()
|
|
134
136
|
|
|
135
|
-
this.inFlight.set(key, promise)
|
|
136
|
-
return promise
|
|
137
|
+
this.inFlight.set(key, promise)
|
|
138
|
+
return promise
|
|
137
139
|
}
|
|
138
140
|
}
|