@mentra/cloud-client 3.2.0-dev.137 → 3.2.0-dev.154
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/cloud-client",
|
|
3
|
-
"version": "3.2.0-dev.
|
|
3
|
+
"version": "3.2.0-dev.154",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"test": "bun test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@mentra/cloud-protocol": "3.2.0-dev.
|
|
15
|
+
"@mentra/cloud-protocol": "3.2.0-dev.154",
|
|
16
16
|
"tweetnacl": "^1.0.3"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
package/src/client.ts
CHANGED
|
@@ -14,28 +14,28 @@
|
|
|
14
14
|
*
|
|
15
15
|
* See docs/issues/004-cloud-client/design.md ("The top-level CloudClient").
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
18
|
-
import type {
|
|
19
|
-
import type {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import type {
|
|
23
|
-
import {
|
|
17
|
+
import {noopLogger} from "./logger"
|
|
18
|
+
import type {Logger} from "./logger"
|
|
19
|
+
import type {CloudClientConfig} from "./config"
|
|
20
|
+
import {createHttpClient} from "./http"
|
|
21
|
+
import {CloudClientError} from "./errors"
|
|
22
|
+
import type {ConnectionInit} from "@mentra/cloud-protocol"
|
|
23
|
+
import {systemTimers} from "./timers"
|
|
24
24
|
|
|
25
25
|
// The module implementations. Each is owned by another agent under ./modules/**;
|
|
26
26
|
// this file only constructs them, matching the constructor signatures fixed in
|
|
27
27
|
// design.md exactly.
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
28
|
+
import {Auth} from "./modules/auth/auth"
|
|
29
|
+
import {TokenStore} from "./modules/auth/token-store"
|
|
30
|
+
import {Runtime} from "./modules/runtime/runtime"
|
|
31
|
+
import {Connection} from "./modules/runtime/connection"
|
|
32
|
+
import {RuntimeEmitter} from "./modules/runtime/emitter"
|
|
33
|
+
import {Subscriptions} from "./modules/runtime/subscriptions"
|
|
34
|
+
import {Camera} from "./modules/runtime/camera"
|
|
35
|
+
import {Maps} from "./modules/runtime/maps"
|
|
36
|
+
import {Tts} from "./modules/runtime/tts"
|
|
37
|
+
import {UdpAudio} from "./modules/runtime/audio-udp"
|
|
38
|
+
import {Core} from "./modules/core/core"
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Default reconnect/backoff for the live socket when a host supplies none.
|
|
@@ -47,7 +47,7 @@ import { Core } from "./modules/core/core";
|
|
|
47
47
|
* lockstep after a shared blip. A host can override any of these through
|
|
48
48
|
* `config.reconnect`.
|
|
49
49
|
*/
|
|
50
|
-
const DEFAULT_RECONNECT = {
|
|
50
|
+
const DEFAULT_RECONNECT = {baseMs: 500, maxMs: 5_000, jitter: true}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* The default audio codec the client announces in the handshake.
|
|
@@ -57,8 +57,8 @@ const DEFAULT_RECONNECT = { baseMs: 500, maxMs: 5_000, jitter: true };
|
|
|
57
57
|
* now the handshake announces the device default so audio that starts immediately
|
|
58
58
|
* after connect is decoded correctly.
|
|
59
59
|
*/
|
|
60
|
-
const DEFAULT_AUDIO_CODEC = "pcm" as const
|
|
61
|
-
const DEFAULT_AUDIO_SAMPLE_RATE = 16_000
|
|
60
|
+
const DEFAULT_AUDIO_CODEC = "pcm" as const
|
|
61
|
+
const DEFAULT_AUDIO_SAMPLE_RATE = 16_000
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* The protocol semver this client build speaks, announced in `connection.init`.
|
|
@@ -66,7 +66,7 @@ const DEFAULT_AUDIO_SAMPLE_RATE = 16_000;
|
|
|
66
66
|
* Hardcoded to the 2.x line this package targets; bumped here when the client
|
|
67
67
|
* starts speaking a newer protocol build, so there is one place to change it.
|
|
68
68
|
*/
|
|
69
|
-
const PROTOCOL_VERSION = "2.0.0"
|
|
69
|
+
const PROTOCOL_VERSION = "2.0.0"
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Rewrite a base URL to route through a proxy host while preserving its path.
|
|
@@ -77,12 +77,12 @@ const PROTOCOL_VERSION = "2.0.0";
|
|
|
77
77
|
* core/runtime address that carries a path prefix is not lost when proxied.
|
|
78
78
|
*/
|
|
79
79
|
function rewriteThroughProxy(target: string, proxy: string): string {
|
|
80
|
-
const proxyUrl = new URL(proxy)
|
|
81
|
-
const targetUrl = new URL(target)
|
|
80
|
+
const proxyUrl = new URL(proxy)
|
|
81
|
+
const targetUrl = new URL(target)
|
|
82
82
|
// Keep the target's path/query, take the proxy's origin.
|
|
83
|
-
targetUrl.protocol = proxyUrl.protocol
|
|
84
|
-
targetUrl.host = proxyUrl.host
|
|
85
|
-
return targetUrl.toString()
|
|
83
|
+
targetUrl.protocol = proxyUrl.protocol
|
|
84
|
+
targetUrl.host = proxyUrl.host
|
|
85
|
+
return targetUrl.toString()
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
@@ -95,10 +95,10 @@ function rewriteThroughProxy(target: string, proxy: string): string {
|
|
|
95
95
|
* place that knows the runtime's HTTP shape also derives its socket URL.
|
|
96
96
|
*/
|
|
97
97
|
function toRuntimeWsUrl(httpBase: string): string {
|
|
98
|
-
const u = new URL(httpBase)
|
|
99
|
-
u.protocol = u.protocol === "https:" ? "wss:" : "ws:"
|
|
100
|
-
u.pathname = `${u.pathname.replace(/\/$/, "")}/ws/session
|
|
101
|
-
return u.toString()
|
|
98
|
+
const u = new URL(httpBase)
|
|
99
|
+
u.protocol = u.protocol === "https:" ? "wss:" : "ws:"
|
|
100
|
+
u.pathname = `${u.pathname.replace(/\/$/, "")}/ws/session`
|
|
101
|
+
return u.toString()
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
export class CloudClient {
|
|
@@ -107,27 +107,27 @@ export class CloudClient {
|
|
|
107
107
|
// of its public contract (per design.md), so a host gets the full, typed
|
|
108
108
|
// surface (`cloud.auth.getRuntimeToken()`, etc.) straight off these fields with
|
|
109
109
|
// no parallel interface to keep in sync.
|
|
110
|
-
readonly auth: Auth
|
|
111
|
-
readonly runtime: Runtime
|
|
112
|
-
readonly core?: Core
|
|
110
|
+
readonly auth: Auth
|
|
111
|
+
readonly runtime: Runtime
|
|
112
|
+
readonly core?: Core
|
|
113
113
|
|
|
114
114
|
constructor(config: CloudClientConfig) {
|
|
115
115
|
// One logger for the whole client, so a host routes every module's logs in
|
|
116
116
|
// one place. Default to the silent no-op so we never print uninvited.
|
|
117
|
-
const logger: Logger = config.logger ?? noopLogger
|
|
117
|
+
const logger: Logger = config.logger ?? noopLogger
|
|
118
118
|
|
|
119
119
|
// Reconnect/backoff lives here so the socket's timing is tuned in one spot.
|
|
120
|
-
const reconnect = config.reconnect ?? DEFAULT_RECONNECT
|
|
121
|
-
const timers = config.timers ?? systemTimers
|
|
120
|
+
const reconnect = config.reconnect ?? DEFAULT_RECONNECT
|
|
121
|
+
const timers = config.timers ?? systemTimers
|
|
122
122
|
|
|
123
123
|
// Resolve the two base addresses. With a proxy set, both route through it;
|
|
124
124
|
// without one, each module talks to its own service directly.
|
|
125
|
-
const {
|
|
126
|
-
const coreUrl = coreBase ? (proxy ? rewriteThroughProxy(coreBase, proxy) : coreBase) : undefined
|
|
127
|
-
const runtimeUrl = proxy ? rewriteThroughProxy(runtimeBase, proxy) : runtimeBase
|
|
125
|
+
const {core: coreBase, runtime: runtimeBase, proxy} = config.endpoints
|
|
126
|
+
const coreUrl = coreBase ? (proxy ? rewriteThroughProxy(coreBase, proxy) : coreBase) : undefined
|
|
127
|
+
const runtimeUrl = proxy ? rewriteThroughProxy(runtimeBase, proxy) : runtimeBase
|
|
128
128
|
|
|
129
129
|
if (config.auth.core && !coreUrl) {
|
|
130
|
-
throw new CloudClientError("auth.core requires endpoints.core")
|
|
130
|
+
throw new CloudClientError("auth.core requires endpoints.core")
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// Runtime auth is the mandatory half (Core is optional). Guard it before the
|
|
@@ -135,12 +135,12 @@ export class CloudClient {
|
|
|
135
135
|
// (`{ subjectToken, subjectTokenType }`, no `runtime`) gets a clear
|
|
136
136
|
// configuration error instead of an opaque `TypeError` from `"source" in undefined`.
|
|
137
137
|
if (!config.auth.runtime) {
|
|
138
|
-
throw new CloudClientError("auth.runtime is required (got a pre-split/flat auth config?)")
|
|
138
|
+
throw new CloudClientError("auth.runtime is required (got a pre-split/flat auth config?)")
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
const runtimeUsesCore = "source" in config.auth.runtime && config.auth.runtime.source === "core"
|
|
141
|
+
const runtimeUsesCore = "source" in config.auth.runtime && config.auth.runtime.source === "core"
|
|
142
142
|
if (runtimeUsesCore && (!coreUrl || !config.auth.core)) {
|
|
143
|
-
throw new CloudClientError("auth.runtime.source='core' requires endpoints.core and auth.core")
|
|
143
|
+
throw new CloudClientError("auth.runtime.source='core' requires endpoints.core and auth.core")
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// Build auth FIRST: runtime and core both source their Bearer from it, so it
|
|
@@ -151,9 +151,12 @@ export class CloudClient {
|
|
|
151
151
|
// before any access token exists. It is deliberately Core-only: runtime-only
|
|
152
152
|
// clients never get a fallback that points Core/Auth calls at Runtime.
|
|
153
153
|
const authHttp = coreUrl
|
|
154
|
-
? createHttpClient({
|
|
155
|
-
: undefined
|
|
156
|
-
const store = new TokenStore({
|
|
154
|
+
? createHttpClient({baseUrl: coreUrl, logger, fetch: config.transports.http, timers})
|
|
155
|
+
: undefined
|
|
156
|
+
const store = new TokenStore({
|
|
157
|
+
storage: config.transports.storage,
|
|
158
|
+
storageKey: config.authStorageKey,
|
|
159
|
+
})
|
|
157
160
|
const auth = new Auth({
|
|
158
161
|
http: authHttp,
|
|
159
162
|
store,
|
|
@@ -163,10 +166,11 @@ export class CloudClient {
|
|
|
163
166
|
// requests, but still use the host's injected HTTP transport.
|
|
164
167
|
baseUrl: coreUrl,
|
|
165
168
|
fetch: config.transports.http,
|
|
166
|
-
|
|
169
|
+
timers,
|
|
170
|
+
})
|
|
167
171
|
|
|
168
|
-
const getRuntimeToken = (): Promise<string> => auth.getRuntimeToken()
|
|
169
|
-
const getCoreToken = (): Promise<string> => auth.getCoreToken()
|
|
172
|
+
const getRuntimeToken = (): Promise<string> => auth.getRuntimeToken()
|
|
173
|
+
const getCoreToken = (): Promise<string> => auth.getCoreToken()
|
|
170
174
|
|
|
171
175
|
const coreHttp =
|
|
172
176
|
coreUrl && config.auth.core
|
|
@@ -177,17 +181,17 @@ export class CloudClient {
|
|
|
177
181
|
fetch: config.transports.http,
|
|
178
182
|
timers,
|
|
179
183
|
})
|
|
180
|
-
: null
|
|
184
|
+
: null
|
|
181
185
|
const runtimeHttp = createHttpClient({
|
|
182
186
|
baseUrl: runtimeUrl,
|
|
183
187
|
getToken: getRuntimeToken,
|
|
184
188
|
logger,
|
|
185
189
|
fetch: config.transports.http,
|
|
186
190
|
timers,
|
|
187
|
-
})
|
|
191
|
+
})
|
|
188
192
|
|
|
189
|
-
const emitter = new RuntimeEmitter()
|
|
190
|
-
const subscriptions = new Subscriptions({
|
|
193
|
+
const emitter = new RuntimeEmitter()
|
|
194
|
+
const subscriptions = new Subscriptions({http: runtimeHttp, timers})
|
|
191
195
|
|
|
192
196
|
// The handshake payload the connection sends on every (re)open. It is a
|
|
193
197
|
// factory (not a fixed value) so each reconnect re-reads the current defaults
|
|
@@ -213,10 +217,10 @@ export class CloudClient {
|
|
|
213
217
|
sampleRate: config.audio?.sampleRate ?? DEFAULT_AUDIO_SAMPLE_RATE,
|
|
214
218
|
// Only LC3 carries a frame size; the config type forces LC3 hosts to
|
|
215
219
|
// state theirs explicitly (decoder is sized from this — no safe guess).
|
|
216
|
-
...(config.audio?.codec === "lc3" ? {
|
|
220
|
+
...(config.audio?.codec === "lc3" ? {frameSizeBytes: config.audio.frameSizeBytes} : {}),
|
|
217
221
|
initialSubscriptions: subscriptions.currentSet(),
|
|
218
222
|
},
|
|
219
|
-
})
|
|
223
|
+
})
|
|
220
224
|
|
|
221
225
|
// Build the remaining runtime pieces, then the runtime that orchestrates them.
|
|
222
226
|
const connection = new Connection({
|
|
@@ -227,14 +231,14 @@ export class CloudClient {
|
|
|
227
231
|
reconnect,
|
|
228
232
|
timers,
|
|
229
233
|
onAuthRejected: async () => {
|
|
230
|
-
await auth.getRuntimeToken({
|
|
234
|
+
await auth.getRuntimeToken({forceRefresh: true})
|
|
231
235
|
},
|
|
232
236
|
logger,
|
|
233
|
-
})
|
|
234
|
-
const camera = new Camera({
|
|
235
|
-
const tts = new Tts({
|
|
236
|
-
const maps = new Maps({
|
|
237
|
-
const audio = new UdpAudio({
|
|
237
|
+
})
|
|
238
|
+
const camera = new Camera({http: runtimeHttp, timers})
|
|
239
|
+
const tts = new Tts({http: runtimeHttp})
|
|
240
|
+
const maps = new Maps({http: runtimeHttp})
|
|
241
|
+
const audio = new UdpAudio({udp: config.transports.udp})
|
|
238
242
|
|
|
239
243
|
const runtime = new Runtime({
|
|
240
244
|
connection,
|
|
@@ -249,14 +253,14 @@ export class CloudClient {
|
|
|
249
253
|
// On a fatal AUTH_EXPIRED at handshake, runtime forces auth to drop its
|
|
250
254
|
// cached access token and refresh; the connection then re-reads the fresh
|
|
251
255
|
// token via getRuntimeToken on the reopen.
|
|
252
|
-
forceRefreshToken: () => auth.getRuntimeToken({
|
|
253
|
-
})
|
|
256
|
+
forceRefreshToken: () => auth.getRuntimeToken({forceRefresh: true}),
|
|
257
|
+
})
|
|
254
258
|
|
|
255
259
|
// Core is last: stateless REST on the core service, Bearer from auth.
|
|
256
|
-
const core = coreHttp ? new Core({
|
|
260
|
+
const core = coreHttp ? new Core({http: coreHttp}) : undefined
|
|
257
261
|
|
|
258
|
-
this.auth = auth
|
|
259
|
-
this.runtime = runtime
|
|
260
|
-
this.core = core
|
|
262
|
+
this.auth = auth
|
|
263
|
+
this.runtime = runtime
|
|
264
|
+
this.core = core
|
|
261
265
|
}
|
|
262
266
|
}
|
package/src/config.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* See docs/issues/004-cloud-client/spec.md ("Construction") and design.md.
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
12
|
-
import type {
|
|
10
|
+
import type {Logger} from "./logger"
|
|
11
|
+
import type {CloudClientTransports} from "./transports"
|
|
12
|
+
import type {CloudClientTimers} from "./timers"
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* The full shape passed to the root `CloudClient`.
|
|
@@ -23,28 +23,28 @@ export interface CloudClientConfig {
|
|
|
23
23
|
// `core` is optional only for runtime-only deployments. If `auth.core` is set,
|
|
24
24
|
// or if `auth.runtime.source` is `"core"`, this must be present; Core/Auth
|
|
25
25
|
// calls are never routed to Runtime.
|
|
26
|
-
endpoints: {
|
|
27
|
-
auth: AuthConfig
|
|
28
|
-
transports: CloudClientTransports
|
|
26
|
+
endpoints: {core?: string; runtime: string; proxy?: string}
|
|
27
|
+
auth: AuthConfig
|
|
28
|
+
transports: CloudClientTransports
|
|
29
29
|
/**
|
|
30
30
|
* Scheduler for all delayed client work, including connection/audio
|
|
31
31
|
* liveness, retries, and request timeouts. React Native hosts must provide
|
|
32
32
|
* native background timers because ordinary JS timers pause when Android
|
|
33
33
|
* backgrounds the app.
|
|
34
34
|
*/
|
|
35
|
-
timers?: CloudClientTimers
|
|
36
|
-
logger?: Logger
|
|
35
|
+
timers?: CloudClientTimers
|
|
36
|
+
logger?: Logger
|
|
37
|
+
/** Override the persisted refresh-token key to isolate deployment sessions. */
|
|
38
|
+
authStorageKey?: string
|
|
37
39
|
// backoff tuning for the live socket; one place so a host can match its fleet
|
|
38
|
-
reconnect?: {
|
|
40
|
+
reconnect?: {baseMs: number; maxMs: number; jitter: boolean}
|
|
39
41
|
/**
|
|
40
42
|
* Audio format announced in `connection.init`. Defaults to PCM at 16 kHz when
|
|
41
43
|
* omitted. An LC3 host MUST pass the frame size its encoder emits — the
|
|
42
44
|
* runtime sizes its decoder from this field, and phone builds legitimately
|
|
43
45
|
* differ (20/40/60); there is no safe default, so the type requires it.
|
|
44
46
|
*/
|
|
45
|
-
audio?:
|
|
46
|
-
| { codec: "pcm"; sampleRate?: number }
|
|
47
|
-
| { codec: "lc3"; sampleRate?: number; frameSizeBytes: 20 | 40 | 60 };
|
|
47
|
+
audio?: {codec: "pcm"; sampleRate?: number} | {codec: "lc3"; sampleRate?: number; frameSizeBytes: 20 | 40 | 60}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -53,7 +53,7 @@ export interface CloudClientConfig {
|
|
|
53
53
|
* The cloud's `/exchange` endpoint needs to know how to verify the incoming
|
|
54
54
|
* token, so the type travels alongside the token itself.
|
|
55
55
|
*/
|
|
56
|
-
export type SubjectTokenType = "oem-jwt" | "mentra-core" | "supabase"
|
|
56
|
+
export type SubjectTokenType = "oidc" | "oem-jwt" | "mentra-core" | "supabase"
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* The three ways a host can give the client its credentials.
|
|
@@ -65,11 +65,11 @@ export type SubjectTokenType = "oem-jwt" | "mentra-core" | "supabase";
|
|
|
65
65
|
*/
|
|
66
66
|
export type CoreAuthConfig =
|
|
67
67
|
// exchanged once on first use
|
|
68
|
-
| {
|
|
68
|
+
| {subjectToken: string; subjectTokenType: SubjectTokenType}
|
|
69
69
|
// fetched on demand, for subject tokens that expire before exchange
|
|
70
|
-
| {
|
|
70
|
+
| {getSubjectToken: () => Promise<{token: string; type: SubjectTokenType}>}
|
|
71
71
|
// already exchanged, skip straight to refresh
|
|
72
|
-
| {
|
|
72
|
+
| {accessToken: string; refreshToken: string}
|
|
73
73
|
|
|
74
74
|
export type RuntimeAuthConfig =
|
|
75
75
|
| {
|
|
@@ -77,16 +77,16 @@ export type RuntimeAuthConfig =
|
|
|
77
77
|
* Ask Cloud Core/Auth to mint a short-lived `cloud-runtime` token. This is
|
|
78
78
|
* explicit hosted-Core mode, not an implicit Core-token fallback.
|
|
79
79
|
*/
|
|
80
|
-
source: "core"
|
|
80
|
+
source: "core"
|
|
81
81
|
}
|
|
82
82
|
| {
|
|
83
83
|
/** Host/OEM/local-dev supplied runtime-token provider. */
|
|
84
|
-
getToken(opts?: {
|
|
85
|
-
}
|
|
84
|
+
getToken(opts?: {forceRefresh?: boolean}): Promise<string>
|
|
85
|
+
}
|
|
86
86
|
|
|
87
87
|
export interface AuthConfig {
|
|
88
88
|
// Core-backed auth owns identity, Core token exchange/refresh, miniapp token
|
|
89
89
|
// minting, and miniapp auto-auth. Omit only for true runtime-only deployments.
|
|
90
|
-
core?: CoreAuthConfig
|
|
91
|
-
runtime: RuntimeAuthConfig
|
|
90
|
+
core?: CoreAuthConfig
|
|
91
|
+
runtime: RuntimeAuthConfig
|
|
92
92
|
}
|
package/src/errors.ts
CHANGED
|
@@ -40,6 +40,24 @@ export class HttpError extends CloudClientError {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Thrown by `cloud.auth.clearSession()` when local credentials were cleared but
|
|
45
|
+
* Core did not confirm revocation of the session (revoke failed after bounded
|
|
46
|
+
* retries, or the token needed to authorize it could not be refreshed). The
|
|
47
|
+
* client is logged out locally; the host must not treat the server-side session
|
|
48
|
+
* as dead and should surface or defer the failure. `cause` is the underlying error.
|
|
49
|
+
*/
|
|
50
|
+
export class SessionRevocationError extends CloudClientError {
|
|
51
|
+
readonly cause: unknown;
|
|
52
|
+
|
|
53
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = "SessionRevocationError";
|
|
56
|
+
this.cause = options?.cause;
|
|
57
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
/**
|
|
44
62
|
* Thrown when a token refresh fails and the host must send the user back through
|
|
45
63
|
* login. Separate from `HttpError` so a host can catch the "credentials are
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// The top-level object. Implemented in ./client by the client agent; re-exported
|
|
18
18
|
// here so the public import is `@mentra/cloud-client`, not a deep path.
|
|
19
19
|
export { CloudClient } from "./client";
|
|
20
|
+
export { DEFAULT_REFRESH_TOKEN_KEY } from "./modules/auth/token-store";
|
|
20
21
|
|
|
21
22
|
// Construction contract.
|
|
22
23
|
export type {
|
|
@@ -35,7 +36,12 @@ export type {
|
|
|
35
36
|
export type { CloudClientTimers } from "./timers";
|
|
36
37
|
|
|
37
38
|
// Local error types a host can branch on with `instanceof`.
|
|
38
|
-
export {
|
|
39
|
+
export {
|
|
40
|
+
CloudClientError,
|
|
41
|
+
HttpError,
|
|
42
|
+
AuthExpiredError,
|
|
43
|
+
SessionRevocationError,
|
|
44
|
+
} from "./errors";
|
|
39
45
|
|
|
40
46
|
// The logging hook a host can implement to route library logs.
|
|
41
47
|
export { noopLogger } from "./logger";
|