@pooflabs/core 0.0.41 → 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/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
- const client = await getAxiosAuthClient();
66
- const config = await getConfig();
67
- const appId = config.appId;
68
- const response = await client.post('/session/refresh', {
69
- refreshToken,
70
- appId
71
- });
72
- return response.data;
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
- __proto__: null,
211
- WebSessionManager: WebSessionManager
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
- const { value: table } = await connection.getAddressLookupTable(new PublicKey(lutKey));
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
- __proto__: null,
3150
- ServerSessionManager: ServerSessionManager
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 axios(requestConfig);
3629
+ const response = await apiClient(requestConfig);
3321
3630
  return { data: response.data, status: response.status, headers: response.headers };
3322
3631
  }
3323
3632
  try {
@@ -3728,6 +4037,90 @@ function cleanupExpiredCache() {
3728
4037
  });
3729
4038
  lastCacheCleanup = now;
3730
4039
  }
4040
+ async function getMany(paths, opts = {}) {
4041
+ if (paths.length === 0) {
4042
+ return [];
4043
+ }
4044
+ if (paths.length > 30) {
4045
+ throw new Error('Cannot fetch more than 30 documents at once');
4046
+ }
4047
+ const normalizedPaths = [];
4048
+ for (const path of paths) {
4049
+ let normalizedPath = path.startsWith("/") ? path.slice(1) : path;
4050
+ if (normalizedPath.endsWith("*") && normalizedPath.length > 1) {
4051
+ normalizedPath = normalizedPath.slice(0, -1);
4052
+ }
4053
+ if (!normalizedPath || normalizedPath.length === 0) {
4054
+ throw new Error(`Invalid path provided: ${path}`);
4055
+ }
4056
+ const pathIsDocument = normalizedPath.split("/").length % 2 === 0;
4057
+ if (!pathIsDocument) {
4058
+ throw new Error(`Path must point to a document (even number of segments): ${path}`);
4059
+ }
4060
+ normalizedPaths.push(normalizedPath);
4061
+ }
4062
+ const now = Date.now();
4063
+ const results = new Array(paths.length);
4064
+ const uncachedIndices = [];
4065
+ const uncachedPaths = [];
4066
+ for (let i = 0; i < normalizedPaths.length; i++) {
4067
+ const normalizedPath = normalizedPaths[i];
4068
+ const cacheKey = `${normalizedPath}:`;
4069
+ if (!opts.bypassCache && getCache[cacheKey] && now < getCache[cacheKey].expiresAt) {
4070
+ results[i] = { path: normalizedPath, data: getCache[cacheKey].data };
4071
+ }
4072
+ else {
4073
+ uncachedIndices.push(i);
4074
+ uncachedPaths.push(normalizedPath);
4075
+ }
4076
+ }
4077
+ if (uncachedPaths.length > 0) {
4078
+ try {
4079
+ const response = await makeApiRequest('POST', 'items/batch', { paths: uncachedPaths }, opts._overrides);
4080
+ const serverResults = response.data.results;
4081
+ const serverResultsMap = new Map();
4082
+ for (const result of serverResults) {
4083
+ serverResultsMap.set(result.path, result);
4084
+ }
4085
+ for (let i = 0; i < uncachedIndices.length; i++) {
4086
+ const originalIndex = uncachedIndices[i];
4087
+ const normalizedPath = uncachedPaths[i];
4088
+ const serverResult = serverResultsMap.get(normalizedPath);
4089
+ if (serverResult) {
4090
+ results[originalIndex] = serverResult;
4091
+ if (!serverResult.error && !opts.bypassCache) {
4092
+ const cacheKey = `${normalizedPath}:`;
4093
+ getCache[cacheKey] = {
4094
+ data: serverResult.data,
4095
+ expiresAt: now + GET_CACHE_TTL
4096
+ };
4097
+ }
4098
+ }
4099
+ else {
4100
+ results[originalIndex] = {
4101
+ path: normalizedPath,
4102
+ data: null,
4103
+ error: { code: 'NOT_FOUND', message: `No result returned for path ${normalizedPath}` }
4104
+ };
4105
+ }
4106
+ }
4107
+ if (now - lastCacheCleanup > 5000) {
4108
+ cleanupExpiredCache();
4109
+ lastCacheCleanup = now;
4110
+ }
4111
+ }
4112
+ catch (error) {
4113
+ for (const originalIndex of uncachedIndices) {
4114
+ results[originalIndex] = {
4115
+ path: normalizedPaths[originalIndex],
4116
+ data: null,
4117
+ error: { code: 'NOT_FOUND', message: error instanceof Error ? error.message : 'Unknown error' }
4118
+ };
4119
+ }
4120
+ }
4121
+ }
4122
+ return results;
4123
+ }
3731
4124
  async function runQuery(absolutePath, queryName, queryArgs, opts) {
3732
4125
  const result = await runQueryMany([{ absolutePath, queryName, queryArgs }], opts);
3733
4126
  return result[0];
@@ -4169,6 +4562,7 @@ function scheduleTokenRefresh(connection, isServer) {
4169
4562
  // This replaces the old single setTimeout approach which was unreliable for long
4170
4563
  // delays (browsers throttle/suspend timers in background tabs).
4171
4564
  connection.tokenRefreshTimer = setInterval(async () => {
4565
+ var _a, _b;
4172
4566
  try {
4173
4567
  const currentToken = await getIdToken(isServer);
4174
4568
  if (!currentToken)
@@ -4199,6 +4593,17 @@ function scheduleTokenRefresh(connection, isServer) {
4199
4593
  }
4200
4594
  }
4201
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
+ }
4202
4607
  console.warn('[WS v2] Proactive token refresh failed, will retry next interval:', refreshError);
4203
4608
  }
4204
4609
  }
@@ -4209,6 +4614,7 @@ function scheduleTokenRefresh(connection, isServer) {
4209
4614
  }, TOKEN_CHECK_INTERVAL);
4210
4615
  }
4211
4616
  async function getFreshAuthToken(isServer) {
4617
+ var _a, _b;
4212
4618
  const currentToken = await getIdToken(isServer);
4213
4619
  if (!currentToken) {
4214
4620
  return null;
@@ -4231,6 +4637,17 @@ async function getFreshAuthToken(isServer) {
4231
4637
  }
4232
4638
  catch (error) {
4233
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
+ }
4234
4651
  }
4235
4652
  // Return null instead of the expired token to prevent infinite 401 reconnect storms.
4236
4653
  // The server accepts unauthenticated connections; auth-required subscriptions will
@@ -4351,7 +4768,16 @@ async function getOrCreateConnection(appId, isServer) {
4351
4768
  ws.addEventListener('open', () => {
4352
4769
  connection.isConnecting = false;
4353
4770
  connection.isConnected = true;
4354
- connection.consecutiveAuthFailures = 0;
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.
4355
4781
  // Schedule periodic token freshness checks
4356
4782
  scheduleTokenRefresh(connection, isServer);
4357
4783
  // Re-subscribe to all existing subscriptions after reconnect
@@ -4968,5 +5394,5 @@ class ReactNativeSessionManager {
4968
5394
  }
4969
5395
  ReactNativeSessionManager.TAROBASE_SESSION_STORAGE_KEY = "tarobase_session_storage";
4970
5396
 
4971
- export { InsufficientBalanceError, ReactNativeSessionManager, ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithPrivy, createSessionWithSignature, genAuthNonce, genSolanaMessage, get, getCachedData, getConfig, getFiles, getIdToken, init, reconnectWithNewAuth, refreshSession, runExpression, runExpressionMany, runQuery, runQueryMany, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe };
5397
+ export { InsufficientBalanceError, ReactNativeSessionManager, ServerSessionManager, WebSessionManager, aggregate, buildSetDocumentsTransaction, clearCache, closeAllSubscriptions, convertRemainingAccounts, count, createSessionWithPrivy, createSessionWithSignature, genAuthNonce, genSolanaMessage, get, getCachedData, getConfig, getFiles, getIdToken, getMany, init, reconnectWithNewAuth, refreshSession, runExpression, runExpressionMany, runQuery, runQueryMany, set, setFile, setMany, signAndSubmitTransaction, signMessage, signSessionCreateMessage, signTransaction, subscribe };
4972
5398
  //# sourceMappingURL=index.mjs.map