{"version":3,"file":"SessionManager.mjs","sources":["../../../../../../core/src/SessionManager.ts"],"sourcesContent":["import { MULTIPLE_STYTCH_CLIENTS_DETECTED_WARNING } from './constants';\nimport {\n  B2BState,\n  ConsumerState,\n  IHeadlessB2BSessionClient,\n  IHeadlessSessionClient,\n  SessionAuthenticateOptions,\n  StytchProjectConfigurationInput,\n  UNRECOVERABLE_ERROR_TYPES,\n} from './public';\nimport { shouldTryRefresh } from './shouldTryRefresh';\nimport { IB2BSubscriptionService, IConsumerSubscriptionService } from './SubscriptionService';\nimport { SessionUpdateOptions } from './types';\nimport { logger } from './utils';\n\nclass SessionManagerRegistry {\n  private hasWarned = false;\n\n  private registry = new Map<string, ISessionManager>();\n\n  public register(key: string, sessionManager: ISessionManager) {\n    const otherManager = this.registry.get(key);\n\n    // If there appears to be another registered session manager, issue a\n    // warning and cancel its background refresh in favor the newer registration\n    if (otherManager && otherManager !== sessionManager) {\n      if (!this.hasWarned) {\n        logger.warn(MULTIPLE_STYTCH_CLIENTS_DETECTED_WARNING);\n        this.hasWarned = true;\n      }\n      otherManager.cancelBackgroundRefresh();\n    }\n    this.registry.set(key, sessionManager);\n  }\n\n  public unregister(publicToken: string, sessionManager: ISessionManager) {\n    const otherManager = this.registry.get(publicToken);\n    if (otherManager && otherManager === sessionManager) {\n      this.registry.delete(publicToken);\n    }\n  }\n}\n\nexport interface ISessionManager {\n  performBackgroundRefresh: () => void;\n  cancelBackgroundRefresh: () => void;\n}\n\nexport class SessionManager<TProjectConfiguration extends StytchProjectConfigurationInput> implements ISessionManager {\n  // Three minutes\n  private static REFRESH_INTERVAL_MS = 1000 * 60 * 3;\n  // When testing - it's often more useful to set to a shorter duration\n  // private static REFRESH_INTERVAL_MS = 1000 * 3;\n\n  private timeout: ReturnType<typeof setTimeout> | null = null;\n\n  /** In minutes */\n  private lastAuthenticationSessionDuration: number | undefined;\n\n  private static registry = new SessionManagerRegistry();\n\n  private register() {\n    SessionManager.registry.register(this._publicToken, this);\n  }\n\n  private unregister() {\n    SessionManager.registry.unregister(this._publicToken, this);\n  }\n\n  constructor(\n    private _subscriptionService:\n      | IConsumerSubscriptionService<TProjectConfiguration>\n      | IB2BSubscriptionService<TProjectConfiguration>,\n    private _headlessSessionClient:\n      | IHeadlessSessionClient<TProjectConfiguration>\n      | IHeadlessB2BSessionClient<TProjectConfiguration>,\n    private _publicToken: string,\n    private _options: { keepSessionAlive?: boolean },\n  ) {\n    this._subscriptionService.subscribeToState(this._onDataChange);\n  }\n\n  /**\n   * The core logic of the session refresh recursive trampoline\n   * - Refreshes the currently issued session\n   * - Schedules a future refresh if successful\n   */\n  performBackgroundRefresh() {\n    logger.debug('performing background refresh at ', Date.now());\n    this._reauthenticateWithBackoff()\n      .then(() => {\n        this.scheduleBackgroundRefresh();\n      })\n      .catch((error: unknown) => {\n        logger.warn('Session background refresh failed. Signalling to app that user is logged out.', { error });\n        this._subscriptionService.destroySession();\n      });\n  }\n\n  private scheduleBackgroundRefresh() {\n    /* Highlander rules - there can only ever be one */\n    this.cancelBackgroundRefresh();\n    this.register();\n    logger.debug('Scheduling bg refresh', Date.now());\n    this.timeout = setTimeout(() => {\n      this.performBackgroundRefresh();\n    }, SessionManager.REFRESH_INTERVAL_MS);\n  }\n\n  cancelBackgroundRefresh() {\n    if (this.timeout !== null) {\n      this.unregister();\n      logger.debug('Cancelling bg refresh', Date.now());\n      clearTimeout(this.timeout);\n      this.timeout = null;\n    }\n  }\n\n  /**\n   * We need to listen to a few types of events:\n   * - If the user logs in via invoking a .authenticate() call, we should start the background worker\n   * - If the user steps up their authentication via another .authenticate call(), we should restart the background worker\n   * - If the user logs out, we should terminate the worker\n   * - We should ignore session changes that we ourselves caused - so if we already have a timeout, leave it be!\n   */\n  private _onDataChange = (\n    state: (ConsumerState & SessionUpdateOptions) | (B2BState & SessionUpdateOptions) | null,\n  ) => {\n    if (state != null && state.sessionDurationMinutes) {\n      this.lastAuthenticationSessionDuration = state.sessionDurationMinutes;\n    }\n\n    if (shouldTryRefresh(state)) {\n      this.scheduleBackgroundRefresh();\n    } else {\n      this.cancelBackgroundRefresh();\n    }\n  };\n\n  // In cases where we cannot get a satisfactory request:\n  // - Stytch is hard-down\n  // - The user's network is disconnected for an extended period of time\n  // we will continue to retry every 4 minutes ad infinum\n  private _reauthenticateWithBackoff = async () => {\n    let count = 0;\n    while (true) {\n      try {\n        const options: SessionAuthenticateOptions = {\n          session_duration_minutes: this._options.keepSessionAlive ? this.lastAuthenticationSessionDuration : undefined,\n        };\n\n        return await this._headlessSessionClient.authenticate(options);\n      } catch (err) {\n        if (SessionManager.isUnrecoverableError(err)) {\n          return Promise.reject(err);\n        }\n        count++;\n        await new Promise((done) => setTimeout(done, SessionManager.timeoutForAttempt(count)));\n      }\n    }\n  };\n\n  // We start with a backoff of 2000ms and increase exponentially to ~4 minutes (+/- 175 ms for jitter)\n  // A short backoff initially helps increase the chance that we refresh the session before the JWT expires\n  static timeoutForAttempt(count: number) {\n    count = Math.min(count, 7);\n    const jitter = Math.floor(Math.random() * 350) - 175;\n    const delayMS = 2000 * 2 ** count;\n    return jitter + delayMS;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  static isUnrecoverableError(error: any) {\n    return UNRECOVERABLE_ERROR_TYPES.includes(error.error_type);\n  }\n}\n"],"names":["SessionManagerRegistry","hasWarned","registry","Map","register","key","sessionManager","otherManager","get","logger","warn","MULTIPLE_STYTCH_CLIENTS_DETECTED_WARNING","cancelBackgroundRefresh","set","unregister","publicToken","delete","SessionManager","REFRESH_INTERVAL_MS","timeout","lastAuthenticationSessionDuration","_publicToken","_subscriptionService","_headlessSessionClient","_options","subscribeToState","_onDataChange","performBackgroundRefresh","_reauthenticateWithBackoff","then","scheduleBackgroundRefresh","catch","error","destroySession","setTimeout","clearTimeout","state","sessionDurationMinutes","shouldTryRefresh","count","options","session_duration_minutes","keepSessionAlive","undefined","authenticate","err","isUnrecoverableError","Promise","reject","done","timeoutForAttempt","Math","min","jitter","floor","random","delayMS","UNRECOVERABLE_ERROR_TYPES","includes","error_type"],"mappings":"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"}