@pooflabs/core 0.0.28 → 0.0.29

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.js CHANGED
@@ -204,6 +204,11 @@ class WebSessionManager {
204
204
  }
205
205
  WebSessionManager.TAROBASE_SESSION_STORAGE_KEY = "tarobase_session_storage";
206
206
 
207
+ var webSessionManager = /*#__PURE__*/Object.freeze({
208
+ __proto__: null,
209
+ WebSessionManager: WebSessionManager
210
+ });
211
+
207
212
  function getDefaultExportFromCjs (x) {
208
213
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
209
214
  }
@@ -3101,11 +3106,19 @@ class ServerSessionManager {
3101
3106
  /* The single, shared instance */
3102
3107
  ServerSessionManager.instance = new ServerSessionManager();
3103
3108
 
3109
+ var serverSessionManager = /*#__PURE__*/Object.freeze({
3110
+ __proto__: null,
3111
+ ServerSessionManager: ServerSessionManager
3112
+ });
3113
+
3104
3114
  async function createBearerToken(isServer) {
3105
3115
  if (isServer) {
3106
3116
  const sessionMgr = ServerSessionManager.instance;
3107
3117
  const session = await sessionMgr.getSession();
3108
- return `Bearer ${session === null || session === void 0 ? void 0 : session.idToken}`;
3118
+ if (!(session === null || session === void 0 ? void 0 : session.idToken)) {
3119
+ return null;
3120
+ }
3121
+ return `Bearer ${session.idToken}`;
3109
3122
  }
3110
3123
  const idToken = WebSessionManager.getIdToken();
3111
3124
  if (!idToken) {
@@ -3138,13 +3151,72 @@ async function getRefreshToken(isServer) {
3138
3151
  }
3139
3152
  return WebSessionManager.getRefreshToken();
3140
3153
  }
3141
- function updateIdTokenAndAccessToken(idToken, accessToken) {
3142
- WebSessionManager.updateIdTokenAndAccessToken(idToken, accessToken);
3154
+ async function updateIdTokenAndAccessToken(idToken, accessToken, isServer = false) {
3155
+ if (isServer) {
3156
+ const sessionMgr = ServerSessionManager.instance;
3157
+ // Ensure we have a session to update; if missing, let lazy creation happen on next request.
3158
+ let session = null;
3159
+ try {
3160
+ session = await sessionMgr.getSession();
3161
+ }
3162
+ catch (_a) {
3163
+ return;
3164
+ }
3165
+ if (!session) {
3166
+ return;
3167
+ }
3168
+ sessionMgr.setSession(Object.assign(Object.assign({}, session), { idToken,
3169
+ accessToken }));
3170
+ return;
3171
+ }
3172
+ await WebSessionManager.updateIdTokenAndAccessToken(idToken, accessToken);
3143
3173
  }
3144
3174
 
3175
+ const refreshInFlight = new Map();
3176
+ async function refreshAuthSessionOnce(appId, isServer) {
3177
+ const key = `${isServer ? "server" : "web"}:${appId}`;
3178
+ const existing = refreshInFlight.get(key);
3179
+ if (existing) {
3180
+ return existing;
3181
+ }
3182
+ const refreshPromise = (async () => {
3183
+ var _a, _b;
3184
+ try {
3185
+ const refreshToken = await getRefreshToken(isServer);
3186
+ if (!refreshToken) {
3187
+ return false;
3188
+ }
3189
+ const refreshData = await refreshSession(refreshToken);
3190
+ if (!(refreshData === null || refreshData === void 0 ? void 0 : refreshData.idToken) || !(refreshData === null || refreshData === void 0 ? void 0 : refreshData.accessToken)) {
3191
+ return false;
3192
+ }
3193
+ await updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken, isServer);
3194
+ return true;
3195
+ }
3196
+ catch (error) {
3197
+ 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)) {
3198
+ const { WebSessionManager } = await Promise.resolve().then(function () { return webSessionManager; });
3199
+ WebSessionManager.clearSession();
3200
+ }
3201
+ return false;
3202
+ }
3203
+ finally {
3204
+ refreshInFlight.delete(key);
3205
+ }
3206
+ })();
3207
+ refreshInFlight.set(key, refreshPromise);
3208
+ return refreshPromise;
3209
+ }
3145
3210
  async function makeApiRequest(method, urlPath, data, _overrides) {
3146
3211
  var _a, _b, _c, _d, _e, _f;
3147
3212
  const config = await getConfig();
3213
+ let hasRetriedAfterServerSessionReset = false;
3214
+ const clearServerSession = async () => {
3215
+ if (!config.isServer)
3216
+ return;
3217
+ const { ServerSessionManager } = await Promise.resolve().then(function () { return serverSessionManager; });
3218
+ ServerSessionManager.instance.clearSession();
3219
+ };
3148
3220
  async function executeRequest() {
3149
3221
  const authHeader = await createAuthHeader(config.isServer);
3150
3222
  const headers = Object.assign({ "Content-Type": "application/json", "X-Public-App-Id": config.appId, "X-App-Id": config.appId }, authHeader);
@@ -3176,19 +3248,20 @@ async function makeApiRequest(method, urlPath, data, _overrides) {
3176
3248
  catch (error) {
3177
3249
  if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
3178
3250
  try {
3179
- const refreshToken = await getRefreshToken(config.isServer);
3180
- if (!refreshToken) {
3181
- throw new Error("No refresh token found");
3182
- }
3183
- const refreshData = await refreshSession(refreshToken);
3184
- if (refreshData &&
3185
- refreshData.idToken &&
3186
- refreshData.accessToken) {
3187
- updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken);
3251
+ const refreshed = await refreshAuthSessionOnce(config.appId, config.isServer);
3252
+ if (!refreshed) {
3253
+ throw new Error("Unable to refresh auth session");
3188
3254
  }
3189
3255
  return await executeRequest();
3190
3256
  }
3191
- catch (refreshError) {
3257
+ catch (_refreshError) {
3258
+ // Server-side fallback: clear cached session and retry once to force
3259
+ // createSession() with a fresh Cognito session.
3260
+ if (config.isServer && !hasRetriedAfterServerSessionReset) {
3261
+ hasRetriedAfterServerSessionReset = true;
3262
+ await clearServerSession();
3263
+ return await executeRequest();
3264
+ }
3192
3265
  throw error;
3193
3266
  }
3194
3267
  }
@@ -3720,14 +3793,26 @@ const responseCache = new Map();
3720
3793
  const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
3721
3794
  const TOKEN_REFRESH_BUFFER = 5 * 60 * 1000; // Refresh token 5 minutes before expiry
3722
3795
  // ============ WebSocket Config ============
3796
+ const BASE_MIN_RECONNECT_DELAY_MS = 1000;
3797
+ const MIN_RECONNECT_DELAY_JITTER_MS = 1000;
3798
+ const MAX_RECONNECT_DELAY_MS = 300000;
3799
+ const RECONNECT_DELAY_GROW_FACTOR = 1.8;
3800
+ const MIN_BROWSER_RECONNECT_INTERVAL_MS = 30000;
3723
3801
  const WS_CONFIG = {
3724
- maxRetries: 10,
3725
- minReconnectionDelay: 1000,
3726
- maxReconnectionDelay: 30000,
3727
- reconnectionDelayGrowFactor: 1.3,
3728
- connectionTimeout: 4000,
3802
+ // Keep retrying indefinitely so long outages recover without page refresh.
3803
+ maxRetries: Infinity,
3804
+ // Conservative starting delay with jitter spreads reconnects across clients.
3805
+ minReconnectionDelay: BASE_MIN_RECONNECT_DELAY_MS + Math.floor(Math.random() * MIN_RECONNECT_DELAY_JITTER_MS),
3806
+ // Stretch backoff to 5 minutes during prolonged outages to protect backend pools.
3807
+ maxReconnectionDelay: MAX_RECONNECT_DELAY_MS,
3808
+ // Moderate growth to avoid rapid retry bursts while still converging to max delay.
3809
+ reconnectionDelayGrowFactor: RECONNECT_DELAY_GROW_FACTOR,
3810
+ // Give handshakes more time before considering the attempt failed.
3811
+ connectionTimeout: 10000,
3729
3812
  };
3730
3813
  const WS_V2_PATH = '/ws/v2';
3814
+ let browserReconnectHooksAttached = false;
3815
+ let lastBrowserTriggeredReconnectAt = 0;
3731
3816
  // ============ Helper Functions ============
3732
3817
  function generateSubscriptionId() {
3733
3818
  return `sub_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
@@ -3797,7 +3882,7 @@ async function getFreshAuthToken(isServer) {
3797
3882
  }
3798
3883
  const refreshData = await refreshSession(refreshToken);
3799
3884
  if (refreshData && refreshData.idToken && refreshData.accessToken) {
3800
- updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken);
3885
+ await updateIdTokenAndAccessToken(refreshData.idToken, refreshData.accessToken, isServer);
3801
3886
  return refreshData.idToken;
3802
3887
  }
3803
3888
  }
@@ -3806,8 +3891,54 @@ async function getFreshAuthToken(isServer) {
3806
3891
  }
3807
3892
  return currentToken;
3808
3893
  }
3894
+ function hasDisconnectedActiveConnections() {
3895
+ for (const connection of connections.values()) {
3896
+ if (!connection.ws) {
3897
+ continue;
3898
+ }
3899
+ // Only nudge reconnection when socket is fully closed.
3900
+ // If ReconnectingWebSocket is already in CONNECTING/backoff state, don't interfere.
3901
+ if (connection.ws.readyState === WS_READY_STATE_CLOSED) {
3902
+ return true;
3903
+ }
3904
+ }
3905
+ return false;
3906
+ }
3907
+ function maybeReconnectFromBrowserSignal() {
3908
+ const now = Date.now();
3909
+ if (now - lastBrowserTriggeredReconnectAt < MIN_BROWSER_RECONNECT_INTERVAL_MS) {
3910
+ return;
3911
+ }
3912
+ if (!hasDisconnectedActiveConnections()) {
3913
+ return;
3914
+ }
3915
+ lastBrowserTriggeredReconnectAt = now;
3916
+ reconnectWithNewAuthV2().catch((error) => {
3917
+ console.error('[WS v2] Browser-triggered reconnect failed:', error);
3918
+ });
3919
+ }
3920
+ function attachBrowserReconnectHooksOnce() {
3921
+ if (browserReconnectHooksAttached) {
3922
+ return;
3923
+ }
3924
+ if (typeof window === 'undefined') {
3925
+ return;
3926
+ }
3927
+ browserReconnectHooksAttached = true;
3928
+ window.addEventListener('online', () => {
3929
+ maybeReconnectFromBrowserSignal();
3930
+ });
3931
+ if (typeof document !== 'undefined') {
3932
+ document.addEventListener('visibilitychange', () => {
3933
+ if (document.visibilityState === 'visible') {
3934
+ maybeReconnectFromBrowserSignal();
3935
+ }
3936
+ });
3937
+ }
3938
+ }
3809
3939
  // ============ Connection Management ============
3810
3940
  async function getOrCreateConnection(appId, isServer) {
3941
+ attachBrowserReconnectHooksOnce();
3811
3942
  let connection = connections.get(appId);
3812
3943
  if (connection && connection.ws) {
3813
3944
  return connection;
@@ -3900,13 +4031,15 @@ function handleServerMessage(connection, message) {
3900
4031
  case 'subscribed': {
3901
4032
  const subscription = connection.subscriptions.get(message.subscriptionId);
3902
4033
  if (subscription) {
3903
- // Update cache
3904
- const cacheKey = getCacheKey(subscription.path, subscription.prompt, subscription.shape);
3905
- responseCache.set(cacheKey, { data: message.data, timestamp: Date.now() });
3906
- // Store last data
3907
- subscription.lastData = message.data;
3908
- // Notify callbacks
3909
- notifyCallbacks(subscription, message.data);
4034
+ // A data update can race ahead of subscribed during reconnect.
4035
+ // If we already received data for this subscription, treat subscribed
4036
+ // as an ack only and avoid regressing to an older snapshot.
4037
+ if (subscription.lastData === undefined) {
4038
+ const cacheKey = getCacheKey(subscription.path, subscription.prompt, subscription.shape);
4039
+ responseCache.set(cacheKey, { data: message.data, timestamp: Date.now() });
4040
+ subscription.lastData = message.data;
4041
+ notifyCallbacks(subscription, message.data);
4042
+ }
3910
4043
  }
3911
4044
  // Resolve pending subscription promise
3912
4045
  const pending = connection.pendingSubscriptions.get(message.subscriptionId);
@@ -3972,6 +4105,7 @@ function notifyCallbacks(subscription, data) {
3972
4105
  }
3973
4106
  // WebSocket readyState constants
3974
4107
  const WS_READY_STATE_OPEN = 1;
4108
+ const WS_READY_STATE_CLOSED = 3;
3975
4109
  function sendSubscribe(connection, subscription) {
3976
4110
  if (!connection.ws || connection.ws.readyState !== WS_READY_STATE_OPEN) {
3977
4111
  return;