@cubee_ee/sdk 0.2.4 → 0.2.5
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.
|
@@ -4,6 +4,16 @@ export interface CubeBackendClientParams {
|
|
|
4
4
|
apiEndpoint: string;
|
|
5
5
|
apiKey?: string;
|
|
6
6
|
defaultHeaders?: Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Called when tokens are refreshed automatically after a 401.
|
|
9
|
+
* The frontend should persist the new tokens (e.g. to localStorage).
|
|
10
|
+
*/
|
|
11
|
+
onTokenRefreshed?: (tokens: AuthTokens) => void;
|
|
12
|
+
/**
|
|
13
|
+
* Called when both access and refresh tokens are expired/invalid.
|
|
14
|
+
* The frontend should trigger a full re-authentication (SIWS sign-in).
|
|
15
|
+
*/
|
|
16
|
+
onAuthExpired?: () => void;
|
|
7
17
|
}
|
|
8
18
|
export type StatsKind = "tvl" | "volume" | "swap_count" | "avg_swap" | "median_swap" | "fees_lp" | "fees_protocol" | "users_total" | "dau" | "mau" | "deposits" | "removals";
|
|
9
19
|
export type StatsWindow = "1d" | "7d" | "30d" | "all";
|
|
@@ -103,8 +113,9 @@ export interface NonceResponse {
|
|
|
103
113
|
nonce: string;
|
|
104
114
|
message: string;
|
|
105
115
|
}
|
|
106
|
-
export interface
|
|
116
|
+
export interface AuthTokens {
|
|
107
117
|
accessToken: string;
|
|
118
|
+
refreshToken: string;
|
|
108
119
|
wallet: string;
|
|
109
120
|
expiresIn: string;
|
|
110
121
|
}
|
|
@@ -112,10 +123,18 @@ export interface AuthVerifyResponse {
|
|
|
112
123
|
* REST wrapper around the Cube backend. Every method is a SdkResult; no
|
|
113
124
|
* exceptions escape. If a request fails, the result carries a
|
|
114
125
|
* human-readable error plus the original cause.
|
|
126
|
+
*
|
|
127
|
+
* Auto-refresh: when a request gets 401, the client automatically tries
|
|
128
|
+
* to refresh tokens via POST /api/auth/refresh. If successful, the
|
|
129
|
+
* original request is retried once with the new access token.
|
|
115
130
|
*/
|
|
116
131
|
export declare class CubeBackendClient {
|
|
117
132
|
private readonly endpoint;
|
|
118
133
|
private readonly headers;
|
|
134
|
+
private refreshToken;
|
|
135
|
+
private refreshInFlight;
|
|
136
|
+
private readonly onTokenRefreshed?;
|
|
137
|
+
private readonly onAuthExpired?;
|
|
119
138
|
constructor(params: CubeBackendClientParams);
|
|
120
139
|
listPools(): Promise<SdkResult<PoolSummary[]>>;
|
|
121
140
|
getPool(addr: string): Promise<SdkResult<PoolSummary>>;
|
|
@@ -147,16 +166,38 @@ export declare class CubeBackendClient {
|
|
|
147
166
|
getStats(kind: StatsKind, window?: StatsWindow, poolAddr?: string, unit?: "usd" | "token"): Promise<SdkResult<StatsSeries>>;
|
|
148
167
|
/** Request a SIWS nonce + pre-built message for the given wallet. */
|
|
149
168
|
getNonce(wallet: string): Promise<SdkResult<NonceResponse>>;
|
|
150
|
-
/** Submit signed SIWS message to receive
|
|
151
|
-
verifySignature(message: string, signature: string): Promise<SdkResult<
|
|
152
|
-
/**
|
|
169
|
+
/** Submit signed SIWS message to receive access + refresh tokens. */
|
|
170
|
+
verifySignature(message: string, signature: string): Promise<SdkResult<AuthTokens>>;
|
|
171
|
+
/**
|
|
172
|
+
* Set both tokens. Call this after verifySignature() and on app init
|
|
173
|
+
* (restoring tokens from storage).
|
|
174
|
+
*/
|
|
175
|
+
setTokens(accessToken: string, refreshToken: string): void;
|
|
176
|
+
/** Clear both tokens (logout). */
|
|
177
|
+
clearTokens(): void;
|
|
178
|
+
/** @deprecated Use setTokens() instead. */
|
|
153
179
|
setAccessToken(token: string): void;
|
|
154
|
-
/**
|
|
180
|
+
/** @deprecated Use clearTokens() instead. */
|
|
155
181
|
clearAccessToken(): void;
|
|
156
182
|
/** Generic GET with retry. Callers that need it for other endpoints. */
|
|
157
183
|
get<T>(path: string): Promise<SdkResult<T>>;
|
|
158
184
|
post<T>(path: string, body: unknown): Promise<SdkResult<T>>;
|
|
159
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Core request method with auto-refresh on 401.
|
|
187
|
+
* If a request gets 401 and we have a refresh token:
|
|
188
|
+
* 1. Call POST /api/auth/refresh (deduplicated if concurrent)
|
|
189
|
+
* 2. On success: update tokens, notify via callback, retry original request
|
|
190
|
+
* 3. On failure: notify via onAuthExpired callback, return original error
|
|
191
|
+
*/
|
|
192
|
+
private requestWithRefresh;
|
|
193
|
+
private rawRequest;
|
|
194
|
+
/**
|
|
195
|
+
* Attempt to refresh tokens. Returns true if successful.
|
|
196
|
+
* Deduplicates concurrent refresh attempts.
|
|
197
|
+
*/
|
|
198
|
+
private tryRefresh;
|
|
199
|
+
private doRefresh;
|
|
200
|
+
private is401;
|
|
160
201
|
/**
|
|
161
202
|
* Fetch a response envelope of the form `{ data: T, ... }` and unwrap
|
|
162
203
|
* the `.data` field. The existing Cube backend wraps most endpoints
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CubeBackendClient.d.ts","sourceRoot":"","sources":["../../src/clients/CubeBackendClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAW,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"CubeBackendClient.d.ts","sourceRoot":"","sources":["../../src/clients/CubeBackendClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,15 +7,23 @@ const retry_1 = require("../utils/retry");
|
|
|
7
7
|
* REST wrapper around the Cube backend. Every method is a SdkResult; no
|
|
8
8
|
* exceptions escape. If a request fails, the result carries a
|
|
9
9
|
* human-readable error plus the original cause.
|
|
10
|
+
*
|
|
11
|
+
* Auto-refresh: when a request gets 401, the client automatically tries
|
|
12
|
+
* to refresh tokens via POST /api/auth/refresh. If successful, the
|
|
13
|
+
* original request is retried once with the new access token.
|
|
10
14
|
*/
|
|
11
15
|
class CubeBackendClient {
|
|
12
16
|
constructor(params) {
|
|
17
|
+
this.refreshToken = null;
|
|
18
|
+
this.refreshInFlight = null;
|
|
13
19
|
this.endpoint = params.apiEndpoint.replace(/\/$/, "");
|
|
14
20
|
this.headers = {
|
|
15
21
|
"Content-Type": "application/json",
|
|
16
22
|
...(params.apiKey ? { Authorization: `Bearer ${params.apiKey}` } : {}),
|
|
17
23
|
...(params.defaultHeaders ?? {}),
|
|
18
24
|
};
|
|
25
|
+
this.onTokenRefreshed = params.onTokenRefreshed;
|
|
26
|
+
this.onAuthExpired = params.onAuthExpired;
|
|
19
27
|
}
|
|
20
28
|
listPools() {
|
|
21
29
|
return this.get("/api/pools");
|
|
@@ -106,29 +114,63 @@ class CubeBackendClient {
|
|
|
106
114
|
getNonce(wallet) {
|
|
107
115
|
return this.get(`/api/auth/nonce?wallet=${encodeURIComponent(wallet)}`);
|
|
108
116
|
}
|
|
109
|
-
/** Submit signed SIWS message to receive
|
|
117
|
+
/** Submit signed SIWS message to receive access + refresh tokens. */
|
|
110
118
|
verifySignature(message, signature) {
|
|
111
119
|
return this.post("/api/auth/verify", {
|
|
112
120
|
message,
|
|
113
121
|
signature,
|
|
114
122
|
});
|
|
115
123
|
}
|
|
116
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* Set both tokens. Call this after verifySignature() and on app init
|
|
126
|
+
* (restoring tokens from storage).
|
|
127
|
+
*/
|
|
128
|
+
setTokens(accessToken, refreshToken) {
|
|
129
|
+
this.headers["Authorization"] = `Bearer ${accessToken}`;
|
|
130
|
+
this.refreshToken = refreshToken;
|
|
131
|
+
}
|
|
132
|
+
/** Clear both tokens (logout). */
|
|
133
|
+
clearTokens() {
|
|
134
|
+
delete this.headers["Authorization"];
|
|
135
|
+
this.refreshToken = null;
|
|
136
|
+
}
|
|
137
|
+
/** @deprecated Use setTokens() instead. */
|
|
117
138
|
setAccessToken(token) {
|
|
118
139
|
this.headers["Authorization"] = `Bearer ${token}`;
|
|
119
140
|
}
|
|
120
|
-
/**
|
|
141
|
+
/** @deprecated Use clearTokens() instead. */
|
|
121
142
|
clearAccessToken() {
|
|
122
143
|
delete this.headers["Authorization"];
|
|
144
|
+
this.refreshToken = null;
|
|
123
145
|
}
|
|
124
146
|
/** Generic GET with retry. Callers that need it for other endpoints. */
|
|
125
147
|
get(path) {
|
|
126
|
-
return this.
|
|
148
|
+
return this.requestWithRefresh("GET", path);
|
|
127
149
|
}
|
|
128
150
|
post(path, body) {
|
|
129
|
-
return this.
|
|
151
|
+
return this.requestWithRefresh("POST", path, body);
|
|
130
152
|
}
|
|
131
|
-
|
|
153
|
+
// ── Private: HTTP layer with auto-refresh ──
|
|
154
|
+
/**
|
|
155
|
+
* Core request method with auto-refresh on 401.
|
|
156
|
+
* If a request gets 401 and we have a refresh token:
|
|
157
|
+
* 1. Call POST /api/auth/refresh (deduplicated if concurrent)
|
|
158
|
+
* 2. On success: update tokens, notify via callback, retry original request
|
|
159
|
+
* 3. On failure: notify via onAuthExpired callback, return original error
|
|
160
|
+
*/
|
|
161
|
+
async requestWithRefresh(method, path, body) {
|
|
162
|
+
const result = await this.rawRequest(method, path, body);
|
|
163
|
+
// Don't auto-refresh for auth endpoints themselves
|
|
164
|
+
const isAuthPath = path.startsWith("/api/auth/");
|
|
165
|
+
if (!isAuthPath && !result.ok && this.is401(result) && this.refreshToken) {
|
|
166
|
+
const refreshed = await this.tryRefresh();
|
|
167
|
+
if (refreshed) {
|
|
168
|
+
return this.rawRequest(method, path, body);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
async rawRequest(method, path, body) {
|
|
132
174
|
const url = `${this.endpoint}${path}`;
|
|
133
175
|
const fetchOpts = {
|
|
134
176
|
method,
|
|
@@ -138,7 +180,9 @@ class CubeBackendClient {
|
|
|
138
180
|
const raw = await (0, retry_1.safeCall)(async () => {
|
|
139
181
|
const res = await fetch(url, fetchOpts);
|
|
140
182
|
if (!res.ok) {
|
|
141
|
-
|
|
183
|
+
const error = new Error(`${method} ${path} → HTTP ${res.status} ${res.statusText}`);
|
|
184
|
+
error.status = res.status;
|
|
185
|
+
throw error;
|
|
142
186
|
}
|
|
143
187
|
return (await res.json());
|
|
144
188
|
});
|
|
@@ -146,6 +190,42 @@ class CubeBackendClient {
|
|
|
146
190
|
return raw;
|
|
147
191
|
return (0, result_1.ok)(raw.data);
|
|
148
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Attempt to refresh tokens. Returns true if successful.
|
|
195
|
+
* Deduplicates concurrent refresh attempts.
|
|
196
|
+
*/
|
|
197
|
+
async tryRefresh() {
|
|
198
|
+
// Deduplicate: if a refresh is already in flight, wait for it
|
|
199
|
+
if (this.refreshInFlight) {
|
|
200
|
+
return this.refreshInFlight;
|
|
201
|
+
}
|
|
202
|
+
this.refreshInFlight = this.doRefresh();
|
|
203
|
+
try {
|
|
204
|
+
return await this.refreshInFlight;
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
this.refreshInFlight = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async doRefresh() {
|
|
211
|
+
const res = await this.rawRequest("POST", "/api/auth/refresh", {
|
|
212
|
+
refreshToken: this.refreshToken,
|
|
213
|
+
});
|
|
214
|
+
if (res.ok) {
|
|
215
|
+
this.setTokens(res.data.accessToken, res.data.refreshToken);
|
|
216
|
+
this.onTokenRefreshed?.(res.data);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
// Refresh failed — both tokens are dead
|
|
220
|
+
this.clearTokens();
|
|
221
|
+
this.onAuthExpired?.();
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
is401(result) {
|
|
225
|
+
if (result.ok)
|
|
226
|
+
return false;
|
|
227
|
+
return result.error.humanMessage.includes("HTTP 401");
|
|
228
|
+
}
|
|
149
229
|
/**
|
|
150
230
|
* Fetch a response envelope of the form `{ data: T, ... }` and unwrap
|
|
151
231
|
* the `.data` field. The existing Cube backend wraps most endpoints
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CubeBackendClient.js","sourceRoot":"","sources":["../../src/clients/CubeBackendClient.ts"],"names":[],"mappings":";;;AAAA,4CAAqD;AAErD,0CAA0C;
|
|
1
|
+
{"version":3,"file":"CubeBackendClient.js","sourceRoot":"","sources":["../../src/clients/CubeBackendClient.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -6,6 +6,16 @@ export interface CubeBackendClientParams {
|
|
|
6
6
|
apiEndpoint: string;
|
|
7
7
|
apiKey?: string;
|
|
8
8
|
defaultHeaders?: Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Called when tokens are refreshed automatically after a 401.
|
|
11
|
+
* The frontend should persist the new tokens (e.g. to localStorage).
|
|
12
|
+
*/
|
|
13
|
+
onTokenRefreshed?: (tokens: AuthTokens) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Called when both access and refresh tokens are expired/invalid.
|
|
16
|
+
* The frontend should trigger a full re-authentication (SIWS sign-in).
|
|
17
|
+
*/
|
|
18
|
+
onAuthExpired?: () => void;
|
|
9
19
|
}
|
|
10
20
|
|
|
11
21
|
export type StatsKind =
|
|
@@ -138,8 +148,9 @@ export interface NonceResponse {
|
|
|
138
148
|
message: string;
|
|
139
149
|
}
|
|
140
150
|
|
|
141
|
-
export interface
|
|
151
|
+
export interface AuthTokens {
|
|
142
152
|
accessToken: string;
|
|
153
|
+
refreshToken: string;
|
|
143
154
|
wallet: string;
|
|
144
155
|
expiresIn: string;
|
|
145
156
|
}
|
|
@@ -148,10 +159,18 @@ export interface AuthVerifyResponse {
|
|
|
148
159
|
* REST wrapper around the Cube backend. Every method is a SdkResult; no
|
|
149
160
|
* exceptions escape. If a request fails, the result carries a
|
|
150
161
|
* human-readable error plus the original cause.
|
|
162
|
+
*
|
|
163
|
+
* Auto-refresh: when a request gets 401, the client automatically tries
|
|
164
|
+
* to refresh tokens via POST /api/auth/refresh. If successful, the
|
|
165
|
+
* original request is retried once with the new access token.
|
|
151
166
|
*/
|
|
152
167
|
export class CubeBackendClient {
|
|
153
168
|
private readonly endpoint: string;
|
|
154
169
|
private readonly headers: Record<string, string>;
|
|
170
|
+
private refreshToken: string | null = null;
|
|
171
|
+
private refreshInFlight: Promise<boolean> | null = null;
|
|
172
|
+
private readonly onTokenRefreshed?: (tokens: AuthTokens) => void;
|
|
173
|
+
private readonly onAuthExpired?: () => void;
|
|
155
174
|
|
|
156
175
|
constructor(params: CubeBackendClientParams) {
|
|
157
176
|
this.endpoint = params.apiEndpoint.replace(/\/$/, "");
|
|
@@ -160,6 +179,8 @@ export class CubeBackendClient {
|
|
|
160
179
|
...(params.apiKey ? { Authorization: `Bearer ${params.apiKey}` } : {}),
|
|
161
180
|
...(params.defaultHeaders ?? {}),
|
|
162
181
|
};
|
|
182
|
+
this.onTokenRefreshed = params.onTokenRefreshed;
|
|
183
|
+
this.onAuthExpired = params.onAuthExpired;
|
|
163
184
|
}
|
|
164
185
|
|
|
165
186
|
listPools(): Promise<SdkResult<PoolSummary[]>> {
|
|
@@ -302,37 +323,81 @@ export class CubeBackendClient {
|
|
|
302
323
|
);
|
|
303
324
|
}
|
|
304
325
|
|
|
305
|
-
/** Submit signed SIWS message to receive
|
|
326
|
+
/** Submit signed SIWS message to receive access + refresh tokens. */
|
|
306
327
|
verifySignature(
|
|
307
328
|
message: string,
|
|
308
329
|
signature: string,
|
|
309
|
-
): Promise<SdkResult<
|
|
310
|
-
return this.post<
|
|
330
|
+
): Promise<SdkResult<AuthTokens>> {
|
|
331
|
+
return this.post<AuthTokens>("/api/auth/verify", {
|
|
311
332
|
message,
|
|
312
333
|
signature,
|
|
313
334
|
});
|
|
314
335
|
}
|
|
315
336
|
|
|
316
|
-
/**
|
|
337
|
+
/**
|
|
338
|
+
* Set both tokens. Call this after verifySignature() and on app init
|
|
339
|
+
* (restoring tokens from storage).
|
|
340
|
+
*/
|
|
341
|
+
setTokens(accessToken: string, refreshToken: string): void {
|
|
342
|
+
this.headers["Authorization"] = `Bearer ${accessToken}`;
|
|
343
|
+
this.refreshToken = refreshToken;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Clear both tokens (logout). */
|
|
347
|
+
clearTokens(): void {
|
|
348
|
+
delete this.headers["Authorization"];
|
|
349
|
+
this.refreshToken = null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** @deprecated Use setTokens() instead. */
|
|
317
353
|
setAccessToken(token: string): void {
|
|
318
354
|
this.headers["Authorization"] = `Bearer ${token}`;
|
|
319
355
|
}
|
|
320
356
|
|
|
321
|
-
/**
|
|
357
|
+
/** @deprecated Use clearTokens() instead. */
|
|
322
358
|
clearAccessToken(): void {
|
|
323
359
|
delete this.headers["Authorization"];
|
|
360
|
+
this.refreshToken = null;
|
|
324
361
|
}
|
|
325
362
|
|
|
326
363
|
/** Generic GET with retry. Callers that need it for other endpoints. */
|
|
327
364
|
get<T>(path: string): Promise<SdkResult<T>> {
|
|
328
|
-
return this.
|
|
365
|
+
return this.requestWithRefresh<T>("GET", path);
|
|
329
366
|
}
|
|
330
367
|
|
|
331
368
|
post<T>(path: string, body: unknown): Promise<SdkResult<T>> {
|
|
332
|
-
return this.
|
|
369
|
+
return this.requestWithRefresh<T>("POST", path, body);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ── Private: HTTP layer with auto-refresh ──
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Core request method with auto-refresh on 401.
|
|
376
|
+
* If a request gets 401 and we have a refresh token:
|
|
377
|
+
* 1. Call POST /api/auth/refresh (deduplicated if concurrent)
|
|
378
|
+
* 2. On success: update tokens, notify via callback, retry original request
|
|
379
|
+
* 3. On failure: notify via onAuthExpired callback, return original error
|
|
380
|
+
*/
|
|
381
|
+
private async requestWithRefresh<T>(
|
|
382
|
+
method: "GET" | "POST",
|
|
383
|
+
path: string,
|
|
384
|
+
body?: unknown,
|
|
385
|
+
): Promise<SdkResult<T>> {
|
|
386
|
+
const result = await this.rawRequest<T>(method, path, body);
|
|
387
|
+
|
|
388
|
+
// Don't auto-refresh for auth endpoints themselves
|
|
389
|
+
const isAuthPath = path.startsWith("/api/auth/");
|
|
390
|
+
if (!isAuthPath && !result.ok && this.is401(result) && this.refreshToken) {
|
|
391
|
+
const refreshed = await this.tryRefresh();
|
|
392
|
+
if (refreshed) {
|
|
393
|
+
return this.rawRequest<T>(method, path, body);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return result;
|
|
333
398
|
}
|
|
334
399
|
|
|
335
|
-
private async
|
|
400
|
+
private async rawRequest<T>(
|
|
336
401
|
method: "GET" | "POST",
|
|
337
402
|
path: string,
|
|
338
403
|
body?: unknown
|
|
@@ -346,7 +411,11 @@ export class CubeBackendClient {
|
|
|
346
411
|
const raw = await safeCall(async () => {
|
|
347
412
|
const res = await fetch(url, fetchOpts);
|
|
348
413
|
if (!res.ok) {
|
|
349
|
-
|
|
414
|
+
const error = new Error(
|
|
415
|
+
`${method} ${path} → HTTP ${res.status} ${res.statusText}`,
|
|
416
|
+
);
|
|
417
|
+
(error as any).status = res.status;
|
|
418
|
+
throw error;
|
|
350
419
|
}
|
|
351
420
|
return (await res.json()) as T;
|
|
352
421
|
});
|
|
@@ -354,6 +423,46 @@ export class CubeBackendClient {
|
|
|
354
423
|
return ok(raw.data);
|
|
355
424
|
}
|
|
356
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Attempt to refresh tokens. Returns true if successful.
|
|
428
|
+
* Deduplicates concurrent refresh attempts.
|
|
429
|
+
*/
|
|
430
|
+
private async tryRefresh(): Promise<boolean> {
|
|
431
|
+
// Deduplicate: if a refresh is already in flight, wait for it
|
|
432
|
+
if (this.refreshInFlight) {
|
|
433
|
+
return this.refreshInFlight;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
this.refreshInFlight = this.doRefresh();
|
|
437
|
+
try {
|
|
438
|
+
return await this.refreshInFlight;
|
|
439
|
+
} finally {
|
|
440
|
+
this.refreshInFlight = null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private async doRefresh(): Promise<boolean> {
|
|
445
|
+
const res = await this.rawRequest<AuthTokens>("POST", "/api/auth/refresh", {
|
|
446
|
+
refreshToken: this.refreshToken,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
if (res.ok) {
|
|
450
|
+
this.setTokens(res.data.accessToken, res.data.refreshToken);
|
|
451
|
+
this.onTokenRefreshed?.(res.data);
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Refresh failed — both tokens are dead
|
|
456
|
+
this.clearTokens();
|
|
457
|
+
this.onAuthExpired?.();
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private is401(result: SdkResult<unknown>): boolean {
|
|
462
|
+
if (result.ok) return false;
|
|
463
|
+
return result.error.humanMessage.includes("HTTP 401");
|
|
464
|
+
}
|
|
465
|
+
|
|
357
466
|
/**
|
|
358
467
|
* Fetch a response envelope of the form `{ data: T, ... }` and unwrap
|
|
359
468
|
* the `.data` field. The existing Cube backend wraps most endpoints
|