@pooflabs/core 0.0.42 → 0.0.43
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/dist/client/operations.d.ts +1 -0
- package/dist/index.js +375 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +361 -19
- package/dist/index.mjs.map +1 -1
- package/dist/utils/api.d.ts +1 -0
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,261 @@ import { Program } from '@coral-xyz/anchor';
|
|
|
6
6
|
import BN from 'bn.js';
|
|
7
7
|
import ReconnectingWebSocket from 'reconnecting-websocket';
|
|
8
8
|
|
|
9
|
+
function getDefaultExportFromCjs (x) {
|
|
10
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
var isRetryAllowed$1;
|
|
14
|
+
var hasRequiredIsRetryAllowed;
|
|
15
|
+
|
|
16
|
+
function requireIsRetryAllowed () {
|
|
17
|
+
if (hasRequiredIsRetryAllowed) return isRetryAllowed$1;
|
|
18
|
+
hasRequiredIsRetryAllowed = 1;
|
|
19
|
+
|
|
20
|
+
const denyList = new Set([
|
|
21
|
+
'ENOTFOUND',
|
|
22
|
+
'ENETUNREACH',
|
|
23
|
+
|
|
24
|
+
// SSL errors from https://github.com/nodejs/node/blob/fc8e3e2cdc521978351de257030db0076d79e0ab/src/crypto/crypto_common.cc#L301-L328
|
|
25
|
+
'UNABLE_TO_GET_ISSUER_CERT',
|
|
26
|
+
'UNABLE_TO_GET_CRL',
|
|
27
|
+
'UNABLE_TO_DECRYPT_CERT_SIGNATURE',
|
|
28
|
+
'UNABLE_TO_DECRYPT_CRL_SIGNATURE',
|
|
29
|
+
'UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY',
|
|
30
|
+
'CERT_SIGNATURE_FAILURE',
|
|
31
|
+
'CRL_SIGNATURE_FAILURE',
|
|
32
|
+
'CERT_NOT_YET_VALID',
|
|
33
|
+
'CERT_HAS_EXPIRED',
|
|
34
|
+
'CRL_NOT_YET_VALID',
|
|
35
|
+
'CRL_HAS_EXPIRED',
|
|
36
|
+
'ERROR_IN_CERT_NOT_BEFORE_FIELD',
|
|
37
|
+
'ERROR_IN_CERT_NOT_AFTER_FIELD',
|
|
38
|
+
'ERROR_IN_CRL_LAST_UPDATE_FIELD',
|
|
39
|
+
'ERROR_IN_CRL_NEXT_UPDATE_FIELD',
|
|
40
|
+
'OUT_OF_MEM',
|
|
41
|
+
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
42
|
+
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
43
|
+
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
|
|
44
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
|
45
|
+
'CERT_CHAIN_TOO_LONG',
|
|
46
|
+
'CERT_REVOKED',
|
|
47
|
+
'INVALID_CA',
|
|
48
|
+
'PATH_LENGTH_EXCEEDED',
|
|
49
|
+
'INVALID_PURPOSE',
|
|
50
|
+
'CERT_UNTRUSTED',
|
|
51
|
+
'CERT_REJECTED',
|
|
52
|
+
'HOSTNAME_MISMATCH'
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
// TODO: Use `error?.code` when targeting Node.js 14
|
|
56
|
+
isRetryAllowed$1 = error => !denyList.has(error && error.code);
|
|
57
|
+
return isRetryAllowed$1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
var isRetryAllowedExports = requireIsRetryAllowed();
|
|
61
|
+
var isRetryAllowed = /*@__PURE__*/getDefaultExportFromCjs(isRetryAllowedExports);
|
|
62
|
+
|
|
63
|
+
const namespace = 'axios-retry';
|
|
64
|
+
function isNetworkError(error) {
|
|
65
|
+
const CODE_EXCLUDE_LIST = ['ERR_CANCELED', 'ECONNABORTED'];
|
|
66
|
+
if (error.response) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (!error.code) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// Prevents retrying timed out & cancelled requests
|
|
73
|
+
if (CODE_EXCLUDE_LIST.includes(error.code)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// Prevents retrying unsafe errors
|
|
77
|
+
return isRetryAllowed(error);
|
|
78
|
+
}
|
|
79
|
+
const SAFE_HTTP_METHODS = ['get', 'head', 'options'];
|
|
80
|
+
const IDEMPOTENT_HTTP_METHODS = SAFE_HTTP_METHODS.concat(['put', 'delete']);
|
|
81
|
+
function isRetryableError(error) {
|
|
82
|
+
return (error.code !== 'ECONNABORTED' &&
|
|
83
|
+
(!error.response ||
|
|
84
|
+
error.response.status === 429 ||
|
|
85
|
+
(error.response.status >= 500 && error.response.status <= 599)));
|
|
86
|
+
}
|
|
87
|
+
function isSafeRequestError(error) {
|
|
88
|
+
if (!error.config?.method) {
|
|
89
|
+
// Cannot determine if the request can be retried
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return isRetryableError(error) && SAFE_HTTP_METHODS.indexOf(error.config.method) !== -1;
|
|
93
|
+
}
|
|
94
|
+
function isIdempotentRequestError(error) {
|
|
95
|
+
if (!error.config?.method) {
|
|
96
|
+
// Cannot determine if the request can be retried
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return isRetryableError(error) && IDEMPOTENT_HTTP_METHODS.indexOf(error.config.method) !== -1;
|
|
100
|
+
}
|
|
101
|
+
function isNetworkOrIdempotentRequestError(error) {
|
|
102
|
+
return isNetworkError(error) || isIdempotentRequestError(error);
|
|
103
|
+
}
|
|
104
|
+
function retryAfter(error = undefined) {
|
|
105
|
+
const retryAfterHeader = error?.response?.headers['retry-after'];
|
|
106
|
+
if (!retryAfterHeader) {
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
// if the retry after header is a number, convert it to milliseconds
|
|
110
|
+
let retryAfterMs = (Number(retryAfterHeader) || 0) * 1000;
|
|
111
|
+
// If the retry after header is a date, get the number of milliseconds until that date
|
|
112
|
+
if (retryAfterMs === 0) {
|
|
113
|
+
retryAfterMs = (new Date(retryAfterHeader).valueOf() || 0) - Date.now();
|
|
114
|
+
}
|
|
115
|
+
return Math.max(0, retryAfterMs);
|
|
116
|
+
}
|
|
117
|
+
function noDelay(_retryNumber = 0, error = undefined) {
|
|
118
|
+
return Math.max(0, retryAfter(error));
|
|
119
|
+
}
|
|
120
|
+
function exponentialDelay(retryNumber = 0, error = undefined, delayFactor = 100) {
|
|
121
|
+
const calculatedDelay = 2 ** retryNumber * delayFactor;
|
|
122
|
+
const delay = Math.max(calculatedDelay, retryAfter(error));
|
|
123
|
+
const randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
|
|
124
|
+
return delay + randomSum;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Linear delay
|
|
128
|
+
* @param {number | undefined} delayFactor - delay factor in milliseconds (default: 100)
|
|
129
|
+
* @returns {function} (retryNumber: number, error: AxiosError | undefined) => number
|
|
130
|
+
*/
|
|
131
|
+
function linearDelay(delayFactor = 100) {
|
|
132
|
+
return (retryNumber = 0, error = undefined) => {
|
|
133
|
+
const delay = retryNumber * delayFactor;
|
|
134
|
+
return Math.max(delay, retryAfter(error));
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const DEFAULT_OPTIONS = {
|
|
138
|
+
retries: 3,
|
|
139
|
+
retryCondition: isNetworkOrIdempotentRequestError,
|
|
140
|
+
retryDelay: noDelay,
|
|
141
|
+
shouldResetTimeout: false,
|
|
142
|
+
onRetry: () => { },
|
|
143
|
+
onMaxRetryTimesExceeded: () => { },
|
|
144
|
+
validateResponse: null
|
|
145
|
+
};
|
|
146
|
+
function getRequestOptions(config, defaultOptions) {
|
|
147
|
+
return { ...DEFAULT_OPTIONS, ...defaultOptions, ...config[namespace] };
|
|
148
|
+
}
|
|
149
|
+
function setCurrentState(config, defaultOptions, resetLastRequestTime = false) {
|
|
150
|
+
const currentState = getRequestOptions(config, defaultOptions || {});
|
|
151
|
+
currentState.retryCount = currentState.retryCount || 0;
|
|
152
|
+
if (!currentState.lastRequestTime || resetLastRequestTime) {
|
|
153
|
+
currentState.lastRequestTime = Date.now();
|
|
154
|
+
}
|
|
155
|
+
config[namespace] = currentState;
|
|
156
|
+
return currentState;
|
|
157
|
+
}
|
|
158
|
+
function fixConfig(axiosInstance, config) {
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
if (axiosInstance.defaults.agent === config.agent) {
|
|
161
|
+
// @ts-ignore
|
|
162
|
+
delete config.agent;
|
|
163
|
+
}
|
|
164
|
+
if (axiosInstance.defaults.httpAgent === config.httpAgent) {
|
|
165
|
+
delete config.httpAgent;
|
|
166
|
+
}
|
|
167
|
+
if (axiosInstance.defaults.httpsAgent === config.httpsAgent) {
|
|
168
|
+
delete config.httpsAgent;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function shouldRetry(currentState, error) {
|
|
172
|
+
const { retries, retryCondition } = currentState;
|
|
173
|
+
const shouldRetryOrPromise = (currentState.retryCount || 0) < retries && retryCondition(error);
|
|
174
|
+
// This could be a promise
|
|
175
|
+
if (typeof shouldRetryOrPromise === 'object') {
|
|
176
|
+
try {
|
|
177
|
+
const shouldRetryPromiseResult = await shouldRetryOrPromise;
|
|
178
|
+
// keep return true unless shouldRetryPromiseResult return false for compatibility
|
|
179
|
+
return shouldRetryPromiseResult !== false;
|
|
180
|
+
}
|
|
181
|
+
catch (_err) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return shouldRetryOrPromise;
|
|
186
|
+
}
|
|
187
|
+
async function handleRetry(axiosInstance, currentState, error, config) {
|
|
188
|
+
currentState.retryCount += 1;
|
|
189
|
+
const { retryDelay, shouldResetTimeout, onRetry } = currentState;
|
|
190
|
+
const delay = retryDelay(currentState.retryCount, error);
|
|
191
|
+
// Axios fails merging this configuration to the default configuration because it has an issue
|
|
192
|
+
// with circular structures: https://github.com/mzabriskie/axios/issues/370
|
|
193
|
+
fixConfig(axiosInstance, config);
|
|
194
|
+
if (!shouldResetTimeout && config.timeout && currentState.lastRequestTime) {
|
|
195
|
+
const lastRequestDuration = Date.now() - currentState.lastRequestTime;
|
|
196
|
+
const timeout = config.timeout - lastRequestDuration - delay;
|
|
197
|
+
if (timeout <= 0) {
|
|
198
|
+
return Promise.reject(error);
|
|
199
|
+
}
|
|
200
|
+
config.timeout = timeout;
|
|
201
|
+
}
|
|
202
|
+
config.transformRequest = [(data) => data];
|
|
203
|
+
await onRetry(currentState.retryCount, error, config);
|
|
204
|
+
if (config.signal?.aborted) {
|
|
205
|
+
return Promise.resolve(axiosInstance(config));
|
|
206
|
+
}
|
|
207
|
+
return new Promise((resolve) => {
|
|
208
|
+
const abortListener = () => {
|
|
209
|
+
clearTimeout(timeout);
|
|
210
|
+
resolve(axiosInstance(config));
|
|
211
|
+
};
|
|
212
|
+
const timeout = setTimeout(() => {
|
|
213
|
+
resolve(axiosInstance(config));
|
|
214
|
+
if (config.signal?.removeEventListener) {
|
|
215
|
+
config.signal.removeEventListener('abort', abortListener);
|
|
216
|
+
}
|
|
217
|
+
}, delay);
|
|
218
|
+
if (config.signal?.addEventListener) {
|
|
219
|
+
config.signal.addEventListener('abort', abortListener, { once: true });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async function handleMaxRetryTimesExceeded(currentState, error) {
|
|
224
|
+
if (currentState.retryCount >= currentState.retries)
|
|
225
|
+
await currentState.onMaxRetryTimesExceeded(error, currentState.retryCount);
|
|
226
|
+
}
|
|
227
|
+
const axiosRetry = (axiosInstance, defaultOptions) => {
|
|
228
|
+
const requestInterceptorId = axiosInstance.interceptors.request.use((config) => {
|
|
229
|
+
setCurrentState(config, defaultOptions, true);
|
|
230
|
+
if (config[namespace]?.validateResponse) {
|
|
231
|
+
// by setting this, all HTTP responses will be go through the error interceptor first
|
|
232
|
+
config.validateStatus = () => false;
|
|
233
|
+
}
|
|
234
|
+
return config;
|
|
235
|
+
});
|
|
236
|
+
const responseInterceptorId = axiosInstance.interceptors.response.use(null, async (error) => {
|
|
237
|
+
const { config } = error;
|
|
238
|
+
// If we have no information to retry the request
|
|
239
|
+
if (!config) {
|
|
240
|
+
return Promise.reject(error);
|
|
241
|
+
}
|
|
242
|
+
const currentState = setCurrentState(config, defaultOptions);
|
|
243
|
+
if (error.response && currentState.validateResponse?.(error.response)) {
|
|
244
|
+
// no issue with response
|
|
245
|
+
return error.response;
|
|
246
|
+
}
|
|
247
|
+
if (await shouldRetry(currentState, error)) {
|
|
248
|
+
return handleRetry(axiosInstance, currentState, error, config);
|
|
249
|
+
}
|
|
250
|
+
await handleMaxRetryTimesExceeded(currentState, error);
|
|
251
|
+
return Promise.reject(error);
|
|
252
|
+
});
|
|
253
|
+
return { requestInterceptorId, responseInterceptorId };
|
|
254
|
+
};
|
|
255
|
+
// Compatibility with CommonJS
|
|
256
|
+
axiosRetry.isNetworkError = isNetworkError;
|
|
257
|
+
axiosRetry.isSafeRequestError = isSafeRequestError;
|
|
258
|
+
axiosRetry.isIdempotentRequestError = isIdempotentRequestError;
|
|
259
|
+
axiosRetry.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError;
|
|
260
|
+
axiosRetry.exponentialDelay = exponentialDelay;
|
|
261
|
+
axiosRetry.linearDelay = linearDelay;
|
|
262
|
+
axiosRetry.isRetryableError = isRetryableError;
|
|
263
|
+
|
|
9
264
|
let axiosClient;
|
|
10
265
|
async function getAxiosAuthClient() {
|
|
11
266
|
if (!axiosClient) {
|
|
@@ -15,6 +270,7 @@ async function getAxiosAuthClient() {
|
|
|
15
270
|
headers: {
|
|
16
271
|
'Content-Type': 'application/json',
|
|
17
272
|
},
|
|
273
|
+
timeout: 30000, // 30s timeout, matching makeApiRequest
|
|
18
274
|
});
|
|
19
275
|
}
|
|
20
276
|
return axiosClient;
|
|
@@ -61,15 +317,41 @@ async function createSessionWithPrivy(authToken, address, privyIdToken) {
|
|
|
61
317
|
});
|
|
62
318
|
return response.data;
|
|
63
319
|
}
|
|
320
|
+
// Deduplicate concurrent refreshSession calls to prevent multiple code paths
|
|
321
|
+
// (WebSocket reconnection, periodic refresh, API 401 retry) from all hitting
|
|
322
|
+
// /session/refresh simultaneously.
|
|
323
|
+
// Note: module-level state is shared across requests in SSR/Node — acceptable
|
|
324
|
+
// since there is only one active session per server instance (same pattern as
|
|
325
|
+
// refreshInFlight in api.ts).
|
|
326
|
+
let refreshInFlight$1 = null;
|
|
327
|
+
let refreshInFlightToken = null;
|
|
64
328
|
async function refreshSession(refreshToken) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
329
|
+
if (refreshInFlight$1 && refreshInFlightToken === refreshToken) {
|
|
330
|
+
return refreshInFlight$1;
|
|
331
|
+
}
|
|
332
|
+
refreshInFlightToken = refreshToken;
|
|
333
|
+
let currentFlight;
|
|
334
|
+
currentFlight = refreshInFlight$1 = (async () => {
|
|
335
|
+
try {
|
|
336
|
+
const client = await getAxiosAuthClient();
|
|
337
|
+
const config = await getConfig();
|
|
338
|
+
const appId = config.appId;
|
|
339
|
+
const response = await client.post('/session/refresh', {
|
|
340
|
+
refreshToken,
|
|
341
|
+
appId
|
|
342
|
+
});
|
|
343
|
+
return response.data;
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
// Only clear if we're still the active in-flight request.
|
|
347
|
+
// A second call with a different token may have replaced us.
|
|
348
|
+
if (refreshInFlight$1 === currentFlight) {
|
|
349
|
+
refreshInFlight$1 = null;
|
|
350
|
+
refreshInFlightToken = null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
})();
|
|
354
|
+
return refreshInFlight$1;
|
|
73
355
|
}
|
|
74
356
|
async function signSessionCreateMessage(_signMessageFunction) {
|
|
75
357
|
}
|
|
@@ -207,14 +489,10 @@ class WebSessionManager {
|
|
|
207
489
|
WebSessionManager.TAROBASE_SESSION_STORAGE_KEY = "tarobase_session_storage";
|
|
208
490
|
|
|
209
491
|
var webSessionManager = /*#__PURE__*/Object.freeze({
|
|
210
|
-
|
|
211
|
-
|
|
492
|
+
__proto__: null,
|
|
493
|
+
WebSessionManager: WebSessionManager
|
|
212
494
|
});
|
|
213
495
|
|
|
214
|
-
function getDefaultExportFromCjs (x) {
|
|
215
|
-
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
496
|
var buffer = {};
|
|
219
497
|
|
|
220
498
|
var base64Js = {};
|
|
@@ -3011,7 +3289,19 @@ async function buildSetDocumentsTransaction(connection, idl, anchorProvider, pay
|
|
|
3011
3289
|
}
|
|
3012
3290
|
}
|
|
3013
3291
|
else if (lutKey != null) {
|
|
3014
|
-
|
|
3292
|
+
// The LUT may have just been created server-side and the client's RPC node
|
|
3293
|
+
// may not have indexed it yet (load-balancer split). Retry with exponential
|
|
3294
|
+
// back-off; first retry at 100 ms almost always resolves it.
|
|
3295
|
+
let table = null;
|
|
3296
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
3297
|
+
const { value } = await connection.getAddressLookupTable(new PublicKey(lutKey));
|
|
3298
|
+
if (value) {
|
|
3299
|
+
table = value;
|
|
3300
|
+
break;
|
|
3301
|
+
}
|
|
3302
|
+
if (attempt < 4)
|
|
3303
|
+
await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt)));
|
|
3304
|
+
}
|
|
3015
3305
|
if (!table)
|
|
3016
3306
|
throw new Error('LUT not found after creation/extend');
|
|
3017
3307
|
lookupTables.push(table);
|
|
@@ -3146,8 +3436,8 @@ class ServerSessionManager {
|
|
|
3146
3436
|
ServerSessionManager.instance = new ServerSessionManager();
|
|
3147
3437
|
|
|
3148
3438
|
var serverSessionManager = /*#__PURE__*/Object.freeze({
|
|
3149
|
-
|
|
3150
|
-
|
|
3439
|
+
__proto__: null,
|
|
3440
|
+
ServerSessionManager: ServerSessionManager
|
|
3151
3441
|
});
|
|
3152
3442
|
|
|
3153
3443
|
/**
|
|
@@ -3246,6 +3536,23 @@ async function updateIdTokenAndAccessToken(idToken, accessToken, isServer = fals
|
|
|
3246
3536
|
await WebSessionManager.updateIdTokenAndAccessToken(idToken, accessToken);
|
|
3247
3537
|
}
|
|
3248
3538
|
|
|
3539
|
+
const apiClient = axios.create();
|
|
3540
|
+
axiosRetry(apiClient, {
|
|
3541
|
+
retries: 2,
|
|
3542
|
+
retryCondition: (error) => {
|
|
3543
|
+
var _a, _b;
|
|
3544
|
+
// Only retry GET requests on network errors (ECONNRESET, ETIMEDOUT, etc.)
|
|
3545
|
+
// Writes (POST/PUT) are not retried — the server may have processed
|
|
3546
|
+
// the request before the connection was reset.
|
|
3547
|
+
return axiosRetry.isNetworkError(error) && ((_b = (_a = error.config) === null || _a === void 0 ? void 0 : _a.method) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 'get';
|
|
3548
|
+
},
|
|
3549
|
+
retryDelay: axiosRetry.exponentialDelay,
|
|
3550
|
+
shouldResetTimeout: true,
|
|
3551
|
+
onRetry: (retryCount, error, requestConfig) => {
|
|
3552
|
+
var _a;
|
|
3553
|
+
console.warn(`[tarobase-sdk] retry ${retryCount} for ${(_a = requestConfig.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()} ${requestConfig.url} — ${error.code || error.message}`);
|
|
3554
|
+
},
|
|
3555
|
+
});
|
|
3249
3556
|
const refreshInFlight = new Map();
|
|
3250
3557
|
async function refreshAuthSessionOnce(appId, isServer) {
|
|
3251
3558
|
const key = `${isServer ? "server" : "web"}:${appId}`;
|
|
@@ -3292,6 +3599,7 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
3292
3599
|
ServerSessionManager.instance.clearSession();
|
|
3293
3600
|
};
|
|
3294
3601
|
async function executeRequest() {
|
|
3602
|
+
var _a;
|
|
3295
3603
|
// When _getAuthHeaders is provided (wallet client), use it as the sole auth source.
|
|
3296
3604
|
// Otherwise use the global createAuthHeader (default path).
|
|
3297
3605
|
const authHeader = (_overrides === null || _overrides === void 0 ? void 0 : _overrides._getAuthHeaders)
|
|
@@ -3313,11 +3621,12 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
|
|
|
3313
3621
|
method,
|
|
3314
3622
|
url: `${config.apiUrl}${urlPath.startsWith("/") ? urlPath : `/${urlPath}`}`,
|
|
3315
3623
|
headers,
|
|
3624
|
+
timeout: (_a = _overrides === null || _overrides === void 0 ? void 0 : _overrides.timeout) !== null && _a !== void 0 ? _a : 30000,
|
|
3316
3625
|
};
|
|
3317
3626
|
if (method !== "GET" && method !== "get") {
|
|
3318
3627
|
requestConfig.data = data ? JSON.stringify(data) : {};
|
|
3319
3628
|
}
|
|
3320
|
-
const response = await
|
|
3629
|
+
const response = await apiClient(requestConfig);
|
|
3321
3630
|
return { data: response.data, status: response.status, headers: response.headers };
|
|
3322
3631
|
}
|
|
3323
3632
|
try {
|
|
@@ -4253,6 +4562,7 @@ function scheduleTokenRefresh(connection, isServer) {
|
|
|
4253
4562
|
// This replaces the old single setTimeout approach which was unreliable for long
|
|
4254
4563
|
// delays (browsers throttle/suspend timers in background tabs).
|
|
4255
4564
|
connection.tokenRefreshTimer = setInterval(async () => {
|
|
4565
|
+
var _a, _b;
|
|
4256
4566
|
try {
|
|
4257
4567
|
const currentToken = await getIdToken(isServer);
|
|
4258
4568
|
if (!currentToken)
|
|
@@ -4283,6 +4593,17 @@ function scheduleTokenRefresh(connection, isServer) {
|
|
|
4283
4593
|
}
|
|
4284
4594
|
}
|
|
4285
4595
|
catch (refreshError) {
|
|
4596
|
+
// If the refresh token itself is invalid (401/403), stop retrying —
|
|
4597
|
+
// the token won't magically become valid next interval, and continuing
|
|
4598
|
+
// to hammer /session/refresh causes 429 rate-limit storms.
|
|
4599
|
+
if (((_a = refreshError === null || refreshError === void 0 ? void 0 : refreshError.response) === null || _a === void 0 ? void 0 : _a.status) === 401 || ((_b = refreshError === null || refreshError === void 0 ? void 0 : refreshError.response) === null || _b === void 0 ? void 0 : _b.status) === 403) {
|
|
4600
|
+
console.warn('[WS v2] Refresh token rejected (401/403), stopping periodic refresh');
|
|
4601
|
+
if (connection.tokenRefreshTimer) {
|
|
4602
|
+
clearInterval(connection.tokenRefreshTimer);
|
|
4603
|
+
connection.tokenRefreshTimer = null;
|
|
4604
|
+
}
|
|
4605
|
+
return;
|
|
4606
|
+
}
|
|
4286
4607
|
console.warn('[WS v2] Proactive token refresh failed, will retry next interval:', refreshError);
|
|
4287
4608
|
}
|
|
4288
4609
|
}
|
|
@@ -4293,6 +4614,7 @@ function scheduleTokenRefresh(connection, isServer) {
|
|
|
4293
4614
|
}, TOKEN_CHECK_INTERVAL);
|
|
4294
4615
|
}
|
|
4295
4616
|
async function getFreshAuthToken(isServer) {
|
|
4617
|
+
var _a, _b;
|
|
4296
4618
|
const currentToken = await getIdToken(isServer);
|
|
4297
4619
|
if (!currentToken) {
|
|
4298
4620
|
return null;
|
|
@@ -4315,6 +4637,17 @@ async function getFreshAuthToken(isServer) {
|
|
|
4315
4637
|
}
|
|
4316
4638
|
catch (error) {
|
|
4317
4639
|
console.error('[WS v2] Error refreshing token:', error);
|
|
4640
|
+
// If the refresh token is permanently invalid (401/403), clear the stale
|
|
4641
|
+
// session from storage so future attempts don't keep retrying with it.
|
|
4642
|
+
if (!isServer && (((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === 401 || ((_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status) === 403)) {
|
|
4643
|
+
try {
|
|
4644
|
+
const { WebSessionManager } = await Promise.resolve().then(function () { return webSessionManager; });
|
|
4645
|
+
WebSessionManager.clearSession();
|
|
4646
|
+
}
|
|
4647
|
+
catch (clearError) {
|
|
4648
|
+
console.warn('[WS v2] Failed to clear stale session:', clearError);
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4318
4651
|
}
|
|
4319
4652
|
// Return null instead of the expired token to prevent infinite 401 reconnect storms.
|
|
4320
4653
|
// The server accepts unauthenticated connections; auth-required subscriptions will
|
|
@@ -4435,7 +4768,16 @@ async function getOrCreateConnection(appId, isServer) {
|
|
|
4435
4768
|
ws.addEventListener('open', () => {
|
|
4436
4769
|
connection.isConnecting = false;
|
|
4437
4770
|
connection.isConnected = true;
|
|
4438
|
-
|
|
4771
|
+
// NOTE: Do NOT reset consecutiveAuthFailures here. It is reset when a
|
|
4772
|
+
// fresh auth token is actually obtained (in urlProvider, line ~389) or on
|
|
4773
|
+
// an explicit auth change (reconnectWithNewAuthV2, line ~854). Resetting
|
|
4774
|
+
// on every 'open' event created an infinite loop: auth fails 5x → connect
|
|
4775
|
+
// without auth → open resets counter → disconnect → auth fails 5x again →
|
|
4776
|
+
// repeat forever, hammering /session/refresh and causing 429s.
|
|
4777
|
+
//
|
|
4778
|
+
// An elevated counter is safe for anonymous/guest sessions: when there's no
|
|
4779
|
+
// token at all (getIdToken returns null), the counter is never checked —
|
|
4780
|
+
// urlProvider skips straight to unauthenticated connection.
|
|
4439
4781
|
// Schedule periodic token freshness checks
|
|
4440
4782
|
scheduleTokenRefresh(connection, isServer);
|
|
4441
4783
|
// Re-subscribe to all existing subscriptions after reconnect
|