From bc567d8591059377f8567490151f305cd45e5a11 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 14:44:41 +0000 Subject: [PATCH] Fix generateJWT closure capture to use updatable global reference The generateJWT callback passed to initialize() was captured in a closure and never updated, causing stale references in React apps where the callback depends on changing state (e.g., rotating user tokens). Changed all internal references from the closure-captured parameter to the module-level generateJWTGlobal variable, and added a setGenerateJWT() method to the WithJWT interface so users can update the function after initialization without reinitializing the entire SDK. Also improved the type of generateJWTGlobal from `any` to a proper type alias (GenerateJWTFunction) for better type safety, and added jest.clearAllTimers() in test beforeEach to fix timer leak between tests. Fixes #160 https://claude.ai/code/session_01TDMxsnQDkRoXn4181Vrv95 --- src/authorization/authorization.test.ts | 2 ++ src/authorization/authorization.ts | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/authorization/authorization.test.ts b/src/authorization/authorization.test.ts index 4cf8bbe1..14b54ef0 100644 --- a/src/authorization/authorization.test.ts +++ b/src/authorization/authorization.test.ts @@ -71,6 +71,7 @@ describe('API Key Interceptors', () => { }); beforeEach(() => { + jest.clearAllTimers(); setTypeOfAuthForTestingOnly('userID'); mockRequest.onPost('/users/update').reply(200, { @@ -1190,6 +1191,7 @@ describe('User Identification', () => { describe('refreshJwtToken', () => { beforeEach(() => { + jest.clearAllTimers(); mockRequest.resetHistory(); }); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 24a6bdb7..c8ffd603 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -41,7 +41,6 @@ let authIdentifier: null | string = null; let userInterceptor: number | null = null; let authInterceptor: number | null = null; let apiKey: null | string = null; -let generateJWTGlobal: any = null; const unknownUserManager = new UnknownUserEventManager(); export interface GenerateJWTPayload { @@ -49,6 +48,9 @@ export interface GenerateJWTPayload { userID?: string; } +type GenerateJWTFunction = (payload: GenerateJWTPayload) => Promise; +let generateJWTGlobal: GenerateJWTFunction | null = null; + export interface WithJWT { setEmail: ( email: string, @@ -61,6 +63,9 @@ export interface WithJWT { logout: () => void; refreshJwtToken: (authTypes: string) => Promise; clearRefresh: () => void; + setGenerateJWT: ( + newGenerateJWT: (payload: GenerateJWTPayload) => Promise + ) => void; setVisitorUsageTracked: (consent: boolean) => void; clearVisitorEventsAndUserData: () => void; } @@ -395,7 +400,7 @@ export function initialize( generateJWT?: (payload: GenerateJWTPayload) => Promise ) { apiKey = authToken; - generateJWTGlobal = generateJWT; + generateJWTGlobal = generateJWT ?? null; const logLevel = config.getConfig('logLevel'); if (!generateJWT && IS_PRODUCTION) { /* only let people use non-JWT mode if running the app locally */ @@ -706,7 +711,7 @@ export function initialize( baseAxiosRequest.interceptors.response.eject(responseInterceptor); } - return generateJWT(payload) + return (generateJWTGlobal as GenerateJWTFunction)(payload) .then((token) => { const authorizationToken = new AuthorizationToken(); authorizationToken.setToken(token); @@ -768,7 +773,9 @@ export function initialize( ? { email: newEmail } : { userID: authIdentifier ?? '' }; - return generateJWT(payloadToPass).then((newToken) => { + return (generateJWTGlobal as GenerateJWTFunction)( + payloadToPass + ).then((newToken) => { const authorizationToken = new AuthorizationToken(); authorizationToken.setToken(newToken); @@ -862,7 +869,7 @@ export function initialize( key if the Iterable API told us the JWT is invalid. */ if (error?.response?.status === 401) { - return generateJWT(payload) + return (generateJWTGlobal as GenerateJWTFunction)(payload) .then((newToken) => { const authorizationToken = new AuthorizationToken(); authorizationToken.setToken(newToken); @@ -1112,6 +1119,11 @@ export function initialize( } }); }, + setGenerateJWT: ( + newGenerateJWT: (payload: GenerateJWTPayload) => Promise + ) => { + generateJWTGlobal = newGenerateJWT; + }, setVisitorUsageTracked: (consent: boolean) => { /* if consent is true, we want to clear unknown user data and start tracking */ if (consent) {