@23blocks/sdk 13.4.0 → 13.5.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 13.5.0 (2026-03-14)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **@23blocks/sdk:** add token lifecycle management with auto-refresh and 401 retry ([5426358](https://github.com/23blocks-OS/frontend-sdk/commit/5426358))
6
+
7
+ ### 📖 Documentation
8
+
9
+ - add token lifecycle to llms.txt ([f42d730](https://github.com/23blocks-OS/frontend-sdk/commit/f42d730))
10
+
11
+ ### ❤️ Thank You
12
+
13
+ - Claude Opus 4.6
14
+ - Juan Pelaez
15
+
1
16
  ## 13.4.0 (2026-03-09)
2
17
 
3
18
  ### 🚀 Features
package/dist/index.esm.js CHANGED
@@ -55,6 +55,7 @@ export { blockOnboarding as onboarding };
55
55
  import { createUniversityBlock } from '@23blocks/block-university';
56
56
  import * as blockUniversity from '@23blocks/block-university';
57
57
  export { blockUniversity as university };
58
+ import { BlockErrorException } from '@23blocks/contracts';
58
59
  export * from '@23blocks/contracts';
59
60
  export * from '@23blocks/jsonapi-codec';
60
61
  import * as blockRag from '@23blocks/block-rag';
@@ -204,6 +205,252 @@ export { blockRag as rag };
204
205
  };
205
206
  }
206
207
 
208
+ // ─────────────────────────────────────────────────────────────────────────────
209
+ // JWT Decode Utility
210
+ // ─────────────────────────────────────────────────────────────────────────────
211
+ /**
212
+ * Decode the `exp` claim from a JWT without external dependencies.
213
+ * Returns the expiry as a Unix timestamp (seconds), or null if unavailable.
214
+ */ function decodeJwtExp(token) {
215
+ try {
216
+ const parts = token.split('.');
217
+ if (parts.length !== 3) return null;
218
+ // Base64url → Base64
219
+ let payload = parts[1];
220
+ payload = payload.replace(/-/g, '+').replace(/_/g, '/');
221
+ // Pad to multiple of 4
222
+ const pad = payload.length % 4;
223
+ if (pad) {
224
+ payload += '='.repeat(4 - pad);
225
+ }
226
+ // Decode — works in browser (atob) and Node 16+ (Buffer)
227
+ let decoded;
228
+ if (typeof atob === 'function') {
229
+ decoded = atob(payload);
230
+ } else if (typeof Buffer !== 'undefined') {
231
+ decoded = Buffer.from(payload, 'base64').toString('utf-8');
232
+ } else {
233
+ return null;
234
+ }
235
+ const parsed = JSON.parse(decoded);
236
+ if (typeof parsed.exp === 'number') {
237
+ return parsed.exp;
238
+ }
239
+ return null;
240
+ } catch (e) {
241
+ return null;
242
+ }
243
+ }
244
+ // ─────────────────────────────────────────────────────────────────────────────
245
+ // Lifecycle Manager Factory
246
+ // ─────────────────────────────────────────────────────────────────────────────
247
+ /**
248
+ * Create a token lifecycle manager that automatically refreshes tokens,
249
+ * handles tab visibility, and notifies listeners of auth state changes.
250
+ *
251
+ * @param tokenManager - Token storage (read/write tokens)
252
+ * @param refreshFn - Function to call the backend refresh endpoint
253
+ * @param config - Lifecycle configuration
254
+ */ function createTokenLifecycleManager(tokenManager, refreshFn, config = {}) {
255
+ const { refreshBufferSeconds = 120, enableVisibilityRefresh = true, enableProactiveRefresh = true } = config;
256
+ const listeners = new Set();
257
+ let refreshTimer = null;
258
+ let refreshPromise = null;
259
+ let visibilityHandler = null;
260
+ let destroyed = false;
261
+ let running = false;
262
+ function notify(event) {
263
+ listeners.forEach((listener)=>{
264
+ try {
265
+ listener(event);
266
+ } catch (e) {
267
+ // Listener errors should not break the lifecycle
268
+ }
269
+ });
270
+ }
271
+ function clearTimer() {
272
+ if (refreshTimer !== null) {
273
+ clearTimeout(refreshTimer);
274
+ refreshTimer = null;
275
+ }
276
+ }
277
+ function scheduleRefresh() {
278
+ if (!enableProactiveRefresh || destroyed || !running) return;
279
+ clearTimer();
280
+ const accessToken = tokenManager.getAccessToken();
281
+ if (!accessToken) return;
282
+ const exp = decodeJwtExp(accessToken);
283
+ if (!exp) return;
284
+ const nowSeconds = Math.floor(Date.now() / 1000);
285
+ const secondsUntilExpiry = exp - nowSeconds;
286
+ const refreshInSeconds = secondsUntilExpiry - refreshBufferSeconds;
287
+ if (refreshInSeconds <= 0) {
288
+ // Token is already expired or within buffer — refresh immediately
289
+ refreshNow().catch(()=>{
290
+ // Error handled inside refreshNow
291
+ });
292
+ return;
293
+ }
294
+ refreshTimer = setTimeout(()=>{
295
+ if (!destroyed && running) {
296
+ refreshNow().catch(()=>{
297
+ // Error handled inside refreshNow
298
+ });
299
+ }
300
+ }, refreshInSeconds * 1000);
301
+ }
302
+ function handleVisibilityChange() {
303
+ if (destroyed || !running) return;
304
+ if (typeof document === 'undefined') return;
305
+ if (document.visibilityState === 'visible') {
306
+ const accessToken = tokenManager.getAccessToken();
307
+ if (!accessToken) return;
308
+ const exp = decodeJwtExp(accessToken);
309
+ if (!exp) {
310
+ // No exp claim — refresh to be safe
311
+ refreshNow().catch(()=>{});
312
+ return;
313
+ }
314
+ const nowSeconds = Math.floor(Date.now() / 1000);
315
+ const secondsUntilExpiry = exp - nowSeconds;
316
+ // Refresh if expired or within buffer
317
+ if (secondsUntilExpiry <= refreshBufferSeconds) {
318
+ refreshNow().catch(()=>{});
319
+ } else {
320
+ // Reschedule proactive refresh (timer may have drifted during sleep)
321
+ scheduleRefresh();
322
+ }
323
+ }
324
+ }
325
+ function registerVisibilityListener() {
326
+ if (!enableVisibilityRefresh || !isBrowser$1() || visibilityHandler) return;
327
+ if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
328
+ visibilityHandler = handleVisibilityChange;
329
+ document.addEventListener('visibilitychange', visibilityHandler);
330
+ }
331
+ }
332
+ function removeVisibilityListener() {
333
+ if (visibilityHandler && typeof document !== 'undefined') {
334
+ document.removeEventListener('visibilitychange', visibilityHandler);
335
+ visibilityHandler = null;
336
+ }
337
+ }
338
+ async function refreshNow() {
339
+ if (destroyed) {
340
+ throw new Error('[23blocks] Token lifecycle manager has been destroyed');
341
+ }
342
+ // Concurrency lock — all callers share the same in-flight promise
343
+ if (refreshPromise) {
344
+ return refreshPromise;
345
+ }
346
+ refreshPromise = (async ()=>{
347
+ try {
348
+ const refreshToken = tokenManager.getRefreshToken();
349
+ if (!refreshToken) {
350
+ throw new Error('No refresh token available');
351
+ }
352
+ const result = await refreshFn(refreshToken);
353
+ // Store new tokens
354
+ tokenManager.setTokens(result.accessToken, result.refreshToken);
355
+ // Reschedule proactive refresh
356
+ scheduleRefresh();
357
+ notify('TOKEN_REFRESHED');
358
+ return result.accessToken;
359
+ } catch (error) {
360
+ // Refresh failed — session is dead
361
+ clearTimer();
362
+ tokenManager.clearTokens();
363
+ running = false;
364
+ notify('SESSION_EXPIRED');
365
+ throw error;
366
+ } finally{
367
+ refreshPromise = null;
368
+ }
369
+ })();
370
+ return refreshPromise;
371
+ }
372
+ function start() {
373
+ if (destroyed) return;
374
+ running = true;
375
+ scheduleRefresh();
376
+ registerVisibilityListener();
377
+ }
378
+ function stop() {
379
+ running = false;
380
+ clearTimer();
381
+ refreshPromise = null;
382
+ }
383
+ function destroy() {
384
+ destroyed = true;
385
+ stop();
386
+ removeVisibilityListener();
387
+ listeners.clear();
388
+ }
389
+ function onAuthStateChanged(listener) {
390
+ listeners.add(listener);
391
+ return ()=>{
392
+ listeners.delete(listener);
393
+ };
394
+ }
395
+ return {
396
+ start,
397
+ stop,
398
+ onAuthStateChanged,
399
+ refreshNow,
400
+ destroy
401
+ };
402
+ }
403
+ // ─────────────────────────────────────────────────────────────────────────────
404
+ // Retrying Transport Wrapper
405
+ // ─────────────────────────────────────────────────────────────────────────────
406
+ /**
407
+ * Wrap a transport with automatic 401 retry via token refresh.
408
+ *
409
+ * On a 401 BlockErrorException, the wrapper calls `getLifecycle().refreshNow()`
410
+ * to obtain a fresh token, then retries the request once.
411
+ *
412
+ * @param baseTransport - The underlying HTTP transport
413
+ * @param getLifecycle - Lazy getter for the lifecycle manager (supports React refs and late init)
414
+ */ function createRetryingTransport(baseTransport, getLifecycle) {
415
+ async function withRetry(fn) {
416
+ try {
417
+ return await fn();
418
+ } catch (error) {
419
+ if (error instanceof BlockErrorException && error.status === 401) {
420
+ const lifecycle = getLifecycle();
421
+ if (lifecycle) {
422
+ try {
423
+ await lifecycle.refreshNow();
424
+ // Retry once — transport reads fresh token from tokenManager on next call
425
+ return await fn();
426
+ } catch (e) {
427
+ // Refresh failed — throw original 401
428
+ throw error;
429
+ }
430
+ }
431
+ }
432
+ throw error;
433
+ }
434
+ }
435
+ return {
436
+ get (path, options) {
437
+ return withRetry(()=>baseTransport.get(path, options));
438
+ },
439
+ post (path, body, options) {
440
+ return withRetry(()=>baseTransport.post(path, body, options));
441
+ },
442
+ patch (path, body, options) {
443
+ return withRetry(()=>baseTransport.patch(path, body, options));
444
+ },
445
+ put (path, body, options) {
446
+ return withRetry(()=>baseTransport.put(path, body, options));
447
+ },
448
+ delete (path, options) {
449
+ return withRetry(()=>baseTransport.delete(path, options));
450
+ }
451
+ };
452
+ }
453
+
207
454
  /**
208
455
  * Detect browser environment.
209
456
  * Uses try/catch to handle edge runtimes (Lambda@Edge, Cloudflare Workers)
@@ -269,7 +516,7 @@ export { blockRag as rag };
269
516
  * });
270
517
  * ```
271
518
  */ function create23BlocksClient(config) {
272
- const { urls, apiKey, tenantId, authMode = 'token', storage = isBrowser() ? 'localStorage' : 'memory', headers: staticHeaders = {}, timeout } = config;
519
+ const { urls, apiKey, tenantId, authMode = 'token', storage = isBrowser() ? 'localStorage' : 'memory', headers: staticHeaders = {}, timeout, tokenLifecycle: lifecycleConfig = {} } = config;
273
520
  // Create token manager for token mode
274
521
  let tokenManager = null;
275
522
  if (authMode === 'token') {
@@ -279,8 +526,11 @@ export { blockRag as rag };
279
526
  storage
280
527
  });
281
528
  }
282
- // Factory to create transport for a specific service URL
283
- function createServiceTransport(baseUrl) {
529
+ // Token lifecycle manager (created lazily after auth block exists)
530
+ let lifecycle = null;
531
+ const lifecycleEnabled = authMode === 'token' && lifecycleConfig !== false;
532
+ // Factory to create base transport for a specific service URL
533
+ function createBaseTransport(baseUrl) {
284
534
  return createHttpTransport({
285
535
  baseUrl,
286
536
  timeout,
@@ -303,6 +553,14 @@ export { blockRag as rag };
303
553
  }
304
554
  });
305
555
  }
556
+ // Factory to create transport with optional 401 retry
557
+ function createServiceTransport(baseUrl) {
558
+ const base = createBaseTransport(baseUrl);
559
+ if (lifecycleEnabled) {
560
+ return createRetryingTransport(base, ()=>lifecycle);
561
+ }
562
+ return base;
563
+ }
306
564
  // Helper to create a proxy that throws when accessing unconfigured service
307
565
  function createUnconfiguredServiceProxy(serviceName, urlKey) {
308
566
  return new Proxy({}, {
@@ -335,12 +593,31 @@ export { blockRag as rag };
335
593
  const jarvisBlock = urls.jarvis ? createJarvisBlock(createServiceTransport(urls.jarvis), blockConfig) : null;
336
594
  const onboardingBlock = urls.onboarding ? createOnboardingBlock(createServiceTransport(urls.onboarding), blockConfig) : null;
337
595
  const universityBlock = urls.university ? createUniversityBlock(createServiceTransport(urls.university), blockConfig) : null;
596
+ // Create lifecycle manager if enabled and auth block is available
597
+ if (lifecycleEnabled && tokenManager && authenticationBlock) {
598
+ const lifecycleRefreshFn = async (refreshToken)=>{
599
+ const response = await authenticationBlock.auth.refreshToken({
600
+ refreshToken
601
+ });
602
+ return {
603
+ accessToken: response.accessToken,
604
+ refreshToken: response.refreshToken,
605
+ expiresIn: response.expiresIn
606
+ };
607
+ };
608
+ lifecycle = createTokenLifecycleManager(tokenManager, lifecycleRefreshFn, typeof lifecycleConfig === 'object' ? lifecycleConfig : {});
609
+ // Auto-start if tokens already exist (page reload scenario)
610
+ if (tokenManager.getAccessToken() && tokenManager.getRefreshToken()) {
611
+ lifecycle.start();
612
+ }
613
+ }
338
614
  // Create managed auth service with automatic token handling (only if auth URL configured)
339
615
  const managedAuth = authenticationBlock ? {
340
616
  async signIn (request) {
341
617
  const response = await authenticationBlock.auth.signIn(request);
342
618
  if (authMode === 'token' && tokenManager && response.accessToken) {
343
619
  tokenManager.setTokens(response.accessToken, response.refreshToken);
620
+ lifecycle == null ? void 0 : lifecycle.start();
344
621
  }
345
622
  return response;
346
623
  },
@@ -352,6 +629,7 @@ export { blockRag as rag };
352
629
  return response;
353
630
  },
354
631
  async signOut () {
632
+ lifecycle == null ? void 0 : lifecycle.stop();
355
633
  await authenticationBlock.auth.signOut();
356
634
  if (authMode === 'token' && tokenManager) {
357
635
  tokenManager.clearTokens();
@@ -361,6 +639,7 @@ export { blockRag as rag };
361
639
  const response = await authenticationBlock.auth.verifyMagicLink(request);
362
640
  if (authMode === 'token' && tokenManager && response.accessToken) {
363
641
  tokenManager.setTokens(response.accessToken, response.refreshToken);
642
+ lifecycle == null ? void 0 : lifecycle.start();
364
643
  }
365
644
  return response;
366
645
  },
@@ -368,6 +647,7 @@ export { blockRag as rag };
368
647
  const response = await authenticationBlock.auth.acceptInvitation(request);
369
648
  if (authMode === 'token' && tokenManager && response.accessToken) {
370
649
  tokenManager.setTokens(response.accessToken, response.refreshToken);
650
+ lifecycle == null ? void 0 : lifecycle.start();
371
651
  }
372
652
  return response;
373
653
  },
@@ -375,6 +655,7 @@ export { blockRag as rag };
375
655
  const response = await authenticationBlock.auth.verifyPasswordOtp(request);
376
656
  if (authMode === 'token' && tokenManager && response.accessToken) {
377
657
  tokenManager.setTokens(response.accessToken, response.refreshToken);
658
+ lifecycle == null ? void 0 : lifecycle.start();
378
659
  }
379
660
  return response;
380
661
  },
@@ -445,8 +726,25 @@ export { blockRag as rag };
445
726
  return null;
446
727
  }
447
728
  return tokenManager ? !!tokenManager.getAccessToken() : false;
729
+ },
730
+ onAuthStateChanged (listener) {
731
+ if (lifecycle) {
732
+ return lifecycle.onAuthStateChanged(listener);
733
+ }
734
+ // No lifecycle — return no-op unsubscribe
735
+ return ()=>{};
736
+ },
737
+ async refreshSession () {
738
+ if (!lifecycle) {
739
+ throw new Error('[23blocks] Token lifecycle is not available. ' + 'Ensure authMode is "token" and tokenLifecycle is not disabled.');
740
+ }
741
+ return lifecycle.refreshNow();
742
+ },
743
+ destroy () {
744
+ lifecycle == null ? void 0 : lifecycle.destroy();
745
+ lifecycle = null;
448
746
  }
449
747
  };
450
748
  }
451
749
 
452
- export { create23BlocksClient, createTokenManager };
750
+ export { create23BlocksClient, createRetryingTransport, createTokenLifecycleManager, createTokenManager, isBrowser$1 as isBrowser };
@@ -17,6 +17,7 @@ import { type JarvisBlock } from '@23blocks/block-jarvis';
17
17
  import { type OnboardingBlock } from '@23blocks/block-onboarding';
18
18
  import { type UniversityBlock } from '@23blocks/block-university';
19
19
  import { type StorageType } from './token-manager.js';
20
+ import { type TokenLifecycleConfig, type AuthStateListener } from './token-lifecycle.js';
20
21
  /**
21
22
  * Authentication mode
22
23
  * - 'token': Store tokens in browser storage, attach Authorization header
@@ -114,6 +115,16 @@ export interface ClientConfig {
114
115
  * @default 30000
115
116
  */
116
117
  timeout?: number;
118
+ /**
119
+ * Token lifecycle configuration for automatic refresh and 401 retry.
120
+ * - Pass an object to customize (e.g., `{ refreshBufferSeconds: 60 }`)
121
+ * - Pass `false` to disable entirely
122
+ * - Omit or pass `{}` to use defaults (enabled with 120s buffer)
123
+ *
124
+ * Only applies in token mode. Ignored in cookie mode.
125
+ * @default {} (enabled with defaults)
126
+ */
127
+ tokenLifecycle?: TokenLifecycleConfig | false;
117
128
  }
118
129
  /**
119
130
  * Auth service wrapper with automatic token management
@@ -286,6 +297,22 @@ export interface Blocks23Client {
286
297
  * In cookie mode: always returns null (check with validateToken instead)
287
298
  */
288
299
  isAuthenticated(): boolean | null;
300
+ /**
301
+ * Subscribe to auth state changes (token refreshed, session expired, etc.).
302
+ * Returns an unsubscribe function.
303
+ * Only active when tokenLifecycle is enabled (token mode).
304
+ */
305
+ onAuthStateChanged(listener: AuthStateListener): () => void;
306
+ /**
307
+ * Force an immediate token refresh.
308
+ * Returns the new access token. Throws if no lifecycle or refresh fails.
309
+ */
310
+ refreshSession(): Promise<string>;
311
+ /**
312
+ * Destroy the client — stops lifecycle timers and cleans up listeners.
313
+ * Call when the client is no longer needed (e.g., component unmount).
314
+ */
315
+ destroy(): void;
289
316
  }
290
317
  /**
291
318
  * Create a 23blocks client instance
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/client.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  export { create23BlocksClient, type AuthMode, type ClientConfig, type ServiceUrls, type Blocks23Client, type ManagedAuthService, type StorageType, type TokenManager, } from './client.js';
2
- export { createTokenManager } from './token-manager.js';
2
+ export { createTokenManager, isBrowser } from './token-manager.js';
3
+ export { createTokenLifecycleManager, createRetryingTransport, type AuthStateEvent, type AuthStateListener, type RefreshTokenFn, type TokenLifecycleConfig, type TokenLifecycleManager, } from './token-lifecycle.js';
3
4
  export * from '@23blocks/contracts';
4
5
  export * from '@23blocks/jsonapi-codec';
5
6
  export * from '@23blocks/transport-http';
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../../src/lib/sdk.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,oBAAoB,EACpB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAMxD,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AAqBzC,OAAO,KAAK,cAAc,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAC;AACrD,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAC3C,OAAO,KAAK,OAAO,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,WAAW,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,aAAa,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,SAAS,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,OAAO,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,OAAO,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../../src/lib/sdk.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,oBAAoB,EACpB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAM9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AAqBzC,OAAO,KAAK,cAAc,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,0BAA0B,CAAC;AACrD,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAC3C,OAAO,KAAK,OAAO,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,WAAW,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,aAAa,MAAM,+BAA+B,CAAC;AAC/D,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,SAAS,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,OAAO,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,OAAO,MAAM,yBAAyB,CAAC;AACnD,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,95 @@
1
+ import type { Transport } from '@23blocks/contracts';
2
+ import type { TokenManager } from './token-manager.js';
3
+ /**
4
+ * Auth state change events emitted by the lifecycle manager.
5
+ *
6
+ * - `SIGNED_IN` — User signed in and lifecycle started
7
+ * - `SIGNED_OUT` — User signed out and lifecycle stopped
8
+ * - `TOKEN_REFRESHED` — Access token was silently refreshed
9
+ * - `SESSION_EXPIRED` — Refresh failed, tokens cleared, user must re-authenticate
10
+ */
11
+ export type AuthStateEvent = 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'SESSION_EXPIRED';
12
+ /**
13
+ * Callback invoked when auth state changes
14
+ */
15
+ export type AuthStateListener = (event: AuthStateEvent) => void;
16
+ /**
17
+ * Function that performs the actual token refresh against the backend.
18
+ * Accepts the current refresh token and returns new credentials.
19
+ */
20
+ export type RefreshTokenFn = (refreshToken: string) => Promise<{
21
+ accessToken: string;
22
+ refreshToken?: string;
23
+ expiresIn?: number;
24
+ }>;
25
+ /**
26
+ * Configuration for automatic token lifecycle management
27
+ */
28
+ export interface TokenLifecycleConfig {
29
+ /**
30
+ * Seconds before token expiry to trigger a proactive refresh.
31
+ * @default 120
32
+ */
33
+ refreshBufferSeconds?: number;
34
+ /**
35
+ * Refresh token when tab becomes visible (handles laptop sleep/wake).
36
+ * @default true
37
+ */
38
+ enableVisibilityRefresh?: boolean;
39
+ /**
40
+ * Schedule proactive refresh based on JWT `exp` claim.
41
+ * @default true
42
+ */
43
+ enableProactiveRefresh?: boolean;
44
+ }
45
+ /**
46
+ * Token lifecycle manager interface.
47
+ * Manages automatic token refresh, tab visibility sync, and auth state notifications.
48
+ */
49
+ export interface TokenLifecycleManager {
50
+ /**
51
+ * Start the lifecycle — schedule proactive refresh and register visibility listener.
52
+ * Call after successful sign-in.
53
+ */
54
+ start(): void;
55
+ /**
56
+ * Stop the lifecycle — clear timers and listeners but keep the manager reusable.
57
+ * Call on sign-out.
58
+ */
59
+ stop(): void;
60
+ /**
61
+ * Subscribe to auth state changes.
62
+ * Returns an unsubscribe function.
63
+ */
64
+ onAuthStateChanged(listener: AuthStateListener): () => void;
65
+ /**
66
+ * Force an immediate token refresh.
67
+ * Multiple concurrent calls share the same in-flight promise (concurrency lock).
68
+ * Returns the new access token on success.
69
+ */
70
+ refreshNow(): Promise<string>;
71
+ /**
72
+ * Permanently destroy the manager — clears everything and prevents reuse.
73
+ */
74
+ destroy(): void;
75
+ }
76
+ /**
77
+ * Create a token lifecycle manager that automatically refreshes tokens,
78
+ * handles tab visibility, and notifies listeners of auth state changes.
79
+ *
80
+ * @param tokenManager - Token storage (read/write tokens)
81
+ * @param refreshFn - Function to call the backend refresh endpoint
82
+ * @param config - Lifecycle configuration
83
+ */
84
+ export declare function createTokenLifecycleManager(tokenManager: TokenManager, refreshFn: RefreshTokenFn, config?: TokenLifecycleConfig): TokenLifecycleManager;
85
+ /**
86
+ * Wrap a transport with automatic 401 retry via token refresh.
87
+ *
88
+ * On a 401 BlockErrorException, the wrapper calls `getLifecycle().refreshNow()`
89
+ * to obtain a fresh token, then retries the request once.
90
+ *
91
+ * @param baseTransport - The underlying HTTP transport
92
+ * @param getLifecycle - Lazy getter for the lifecycle manager (supports React refs and late init)
93
+ */
94
+ export declare function createRetryingTransport(baseTransport: Transport, getLifecycle: () => TokenLifecycleManager | null): Transport;
95
+ //# sourceMappingURL=token-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-lifecycle.d.ts","sourceRoot":"","sources":["../../../src/lib/token-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAOvD;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEhG;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,KAAK,IAAI,IAAI,CAAC;IAEd;;;OAGG;IACH,IAAI,IAAI,IAAI,CAAC;IAEb;;;OAGG;IACH,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;IAE5D;;;;OAIG;IACH,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B;;OAEG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAiDD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,cAAc,EACzB,MAAM,GAAE,oBAAyB,GAChC,qBAAqB,CAuLvB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,SAAS,EACxB,YAAY,EAAE,MAAM,qBAAqB,GAAG,IAAI,GAC/C,SAAS,CAuCX"}
@@ -47,6 +47,12 @@ export interface TokenManager {
47
47
  */
48
48
  onStorageChange(callback: () => void): () => void;
49
49
  }
50
+ /**
51
+ * Detect if we're running in a browser environment.
52
+ * Uses try/catch to handle edge runtimes (Lambda@Edge, Cloudflare Workers)
53
+ * that may throw on property access.
54
+ */
55
+ export declare function isBrowser(): boolean;
50
56
  /**
51
57
  * Create a token manager instance
52
58
  *
@@ -1 +1 @@
1
- {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../../src/lib/token-manager.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,gBAAgB,GAAG,QAAQ,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI,CAAC;IAEhC;;OAEG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5D;;OAEG;IACH,WAAW,IAAI,IAAI,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CACnD;AAkED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAwE3E"}
1
+ {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../../src/lib/token-manager.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,gBAAgB,GAAG,QAAQ,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI,CAAC;IAEhC;;OAEG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5D;;OAEG;IACH,WAAW,IAAI,IAAI,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CACnD;AA6BD;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CASnC;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAwE3E"}
package/llms.txt CHANGED
@@ -294,6 +294,13 @@ The client provides:
294
294
  - `client.authentication` - Full authentication block
295
295
  - `client.{blockName}` - All other blocks
296
296
  - `client.getAccessToken()`, `client.setTokens()`, `client.clearSession()` - Token utilities
297
+ - `client.onAuthStateChanged(listener)` - Subscribe to auth state changes (TOKEN_REFRESHED, SESSION_EXPIRED)
298
+ - `client.refreshSession()` - Force immediate token refresh
299
+ - `client.destroy()` - Cleanup lifecycle timers and listeners
300
+
301
+ ### Token Lifecycle (Auto-Refresh & 401 Retry)
302
+
303
+ Enabled by default in token mode. Handles proactive refresh (JWT exp decode, 120s buffer), 401 retry (refresh + retry once), tab visibility refresh (sleep/wake), concurrency lock, and session expiry (clears tokens, emits SESSION_EXPIRED). Disable with `tokenLifecycle: false`. Customize with `tokenLifecycle: { refreshBufferSeconds: 60 }`.
297
304
 
298
305
  ## Health Check
299
306
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@23blocks/sdk",
3
- "version": "13.4.0",
3
+ "version": "13.5.0",
4
4
  "description": "23blocks SDK - unified meta-package re-exporting all blocks and utilities",
5
5
  "license": "MIT",
6
6
  "author": "23blocks <hello@23blocks.com>",