{"version":3,"file":"auth.cjs","sources":["../../../src/frame/auth.ts"],"sourcesContent":["import type { AppFrame } from './frame'\nimport type { MessageManager } from './message'\nimport type { AuthActions, AuthData, RefreshAuthData, MessageInitData } from '../types/auth'\nimport type { ApiVersion } from '../types/b24'\nimport { MessageCommands } from './message'\nimport { SdkError } from '../core/sdk-error'\n\n// How long to wait for the parent window to answer a `refreshAuth` postMessage\n// before giving up. Without this the promise never settles if the parent is\n// slow / navigated away / blocked — and since #182 every 401 refreshes, so a\n// hung parent would hang the request. (#189)\nconst REFRESH_AUTH_TIMEOUT = 10_000\n\n/**\n * Authorization Manager\n */\nexport class AuthManager implements AuthActions {\n  #accessToken: null | string = null\n  #refreshId: null | string = null\n  #authExpires: number = 0\n  #authExpiresIn: number = 0\n  #memberId: null | string = null\n\n  #isAdmin: boolean = false\n\n  // The one in-flight `refreshAuth()`, shared by every caller (#532).\n  //\n  // `AbstractHttp` coalesces too, but per http client — and `B24Frame` builds\n  // TWO of them (v2 and v3) over ONE `AuthManager`, so a v2 call and a v3 call\n  // racing an expiry already refreshed twice. The keep-alive adds a third\n  // caller. Coalescing here, at the single point that actually talks to the\n  // parent window, covers all of them: refreshing too often is what Bitrix\n  // warns can get an application auto-blocked.\n  #pendingRefresh: null | Promise<AuthData> = null\n\n  #appFrame: AppFrame\n  #messageManager: MessageManager\n\n  constructor(\n    appFrame: AppFrame,\n    messageManager: MessageManager\n  ) {\n    this.#appFrame = appFrame\n    this.#messageManager = messageManager\n  }\n\n  /**\n   * Initializes the data received from the parent window message.\n   * @param data\n   */\n  public initData(data: MessageInitData): AuthManager {\n    if (data.AUTH_ID) {\n      this.#accessToken = data.AUTH_ID\n      this.#refreshId = data.REFRESH_ID\n      this.#authExpiresIn = Number.parseInt(data.AUTH_EXPIRES)\n      this.#authExpires = Date.now() + this.#authExpiresIn * 1_000\n\n      this.#isAdmin = data.IS_ADMIN\n      this.#memberId = data.MEMBER_ID || ''\n    }\n\n    return this\n  }\n\n  /**\n   * Returns authorization data\n   *\n   * @link https://apidocs.bitrix24.com/sdk/bx24-js-sdk/system-functions/bx24-get-auth.html\n   */\n  public getAuthData(): false | AuthData {\n    return this.#authExpires > Date.now()\n      ? ({\n          access_token: this.#accessToken,\n          refresh_token: this.#refreshId,\n          expires: this.#authExpires / 1_000,\n          expires_in: this.#authExpiresIn,\n          domain: this.#appFrame.getTargetOrigin(),\n          member_id: this.#memberId\n        } as AuthData)\n      : false\n  }\n\n  /**\n   * Updates authorization data through the parent window\n   *\n   * @link https://apidocs.bitrix24.com/sdk/bx24-js-sdk/system-functions/bx24-refresh-auth.html\n   */\n  public refreshAuth(): Promise<AuthData> {\n    if (this.#pendingRefresh !== null) {\n      return this.#pendingRefresh\n    }\n\n    // The slot is released inside the promise the callers await, not in a\n    // `.finally()` chained onto it — a chained handler runs one microtask AFTER\n    // the caller resumes, so `await refreshAuth(); refreshAuth()` would have\n    // been handed back the settled promise instead of refreshing.\n    //\n    // Clearing unconditionally is safe: the early return above means a second\n    // refresh cannot start while this one holds the slot, so the slot at settle\n    // time is always this refresh's own.\n    const pending = (async () => {\n      try {\n        return await this.#doRefreshAuth()\n      } finally {\n        this.#pendingRefresh = null\n      }\n    })()\n\n    this.#pendingRefresh = pending\n\n    return pending\n  }\n\n  async #doRefreshAuth(): Promise<AuthData> {\n    // Bound the wait: `MessageManager.send` has no timeout of its own here, and\n    // its `isSafely` mode auto-*resolves* with `{ isSafely: true }` — which is\n    // NOT valid `AuthData`. So race the send against a timer that *rejects*, so\n    // a hung parent surfaces a clean error instead of never settling. (#189)\n    let timer: ReturnType<typeof setTimeout> | undefined\n    const timeout = new Promise<never>((_resolve, reject) => {\n      timer = setTimeout(\n        () => reject(new SdkError({\n          code: 'JSSDK_FRAME_REFRESH_AUTH_TIMEOUT',\n          status: 408,\n          description: `refreshAuth: the parent window did not answer within ${REFRESH_AUTH_TIMEOUT}ms`\n        })),\n        REFRESH_AUTH_TIMEOUT\n      )\n    })\n\n    try {\n      const data = await Promise.race([\n        this.#messageManager.send(MessageCommands.refreshAuth, {}) as Promise<RefreshAuthData>,\n        timeout\n      ])\n\n      // Validate BEFORE writing anything. `Number.parseInt` on a missing, empty\n      // or non-numeric `AUTH_EXPIRES` yields NaN, which used to be written\n      // straight into `#authExpires`: `NaN > Date.now()` is false, so\n      // `getAuthData()` then returned `false` — for good — and this method\n      // handed that `false` back cast as `AuthData`. A single malformed answer\n      // therefore destroyed a still-valid token and left the manager\n      // permanently unauthenticated. Reject instead, and keep the old token:\n      // the caller can retry, and the keep-alive backs off. (#532)\n      const expiresIn = Number.parseInt(data?.AUTH_EXPIRES)\n\n      if (!Number.isFinite(expiresIn) || expiresIn <= 0) {\n        // The description names the field, never its value: an SdkError message\n        // is not redacted, and this payload carries the tokens. (#43)\n        throw new SdkError({\n          code: 'JSSDK_FRAME_REFRESH_AUTH_BAD_RESPONSE',\n          status: 500,\n          description: 'refreshAuth: the parent window answered without a usable AUTH_EXPIRES'\n        })\n      }\n\n      this.#accessToken = data.AUTH_ID\n      this.#refreshId = data.REFRESH_ID\n      this.#authExpiresIn = expiresIn\n      this.#authExpires = Date.now() + expiresIn * 1_000\n\n      return this.getAuthData() as AuthData\n    } finally {\n      // Clear the timer on either outcome so a resolved refresh doesn't leave a\n      // pending reject-timer running (and keep the event loop clean).\n      if (timer) {\n        clearTimeout(timer)\n      }\n    }\n  }\n\n  public getUniq(prefix: string): string {\n    return [prefix, this.#memberId || ''].join('_')\n  }\n\n  /**\n   * Determines whether the current user has administrator rights\n   *\n   * @link https://apidocs.bitrix24.com/sdk/bx24-js-sdk/additional-functions/bx24-is-admin.html\n   */\n  get isAdmin(): boolean {\n    return this.#isAdmin\n  }\n\n  /**\n   * @inheritDoc\n   */\n  public getTargetOrigin(): string {\n    return this.#appFrame.getTargetOrigin()\n  }\n\n  /**\n   * @inheritDoc\n   */\n  public getTargetOriginWithPath(): Map<ApiVersion, string> {\n    return this.#appFrame.getTargetOriginWithPath()\n  }\n}\n"],"names":["SdkError","MessageCommands"],"mappings":";;;;;;;;;;;;;;;AAWA,MAAM,oBAAA,GAAuB,GAAA;AAKtB,MAAM,WAAA,CAAmC;AAAA,EAhBhD;AAgBgD,IAAA,MAAA,CAAA,IAAA,EAAA,aAAA,CAAA;AAAA;AAAA,EAC9C,YAAA,GAA8B,IAAA;AAAA,EAC9B,UAAA,GAA4B,IAAA;AAAA,EAC5B,YAAA,GAAuB,CAAA;AAAA,EACvB,cAAA,GAAyB,CAAA;AAAA,EACzB,SAAA,GAA2B,IAAA;AAAA,EAE3B,QAAA,GAAoB,KAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUpB,eAAA,GAA4C,IAAA;AAAA,EAE5C,SAAA;AAAA,EACA,eAAA;AAAA,EAEA,WAAA,CACE,UACA,cAAA,EACA;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA;AACjB,IAAA,IAAA,CAAK,eAAA,GAAkB,cAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,SAAS,IAAA,EAAoC;AAClD,IAAA,IAAI,KAAK,OAAA,EAAS;AAChB,MAAA,IAAA,CAAK,eAAe,IAAA,CAAK,OAAA;AACzB,MAAA,IAAA,CAAK,aAAa,IAAA,CAAK,UAAA;AACvB,MAAA,IAAA,CAAK,cAAA,GAAiB,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,YAAY,CAAA;AACvD,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA,CAAK,GAAA,EAAI,GAAI,KAAK,cAAA,GAAiB,GAAA;AAEvD,MAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA;AACrB,MAAA,IAAA,CAAK,SAAA,GAAY,KAAK,SAAA,IAAa,EAAA;AAAA,IACrC;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,WAAA,GAAgC;AACrC,IAAA,OAAO,IAAA,CAAK,YAAA,GAAe,IAAA,CAAK,GAAA,EAAI,GAC/B;AAAA,MACC,cAAc,IAAA,CAAK,YAAA;AAAA,MACnB,eAAe,IAAA,CAAK,UAAA;AAAA,MACpB,OAAA,EAAS,KAAK,YAAA,GAAe,GAAA;AAAA,MAC7B,YAAY,IAAA,CAAK,cAAA;AAAA,MACjB,MAAA,EAAQ,IAAA,CAAK,SAAA,CAAU,eAAA,EAAgB;AAAA,MACvC,WAAW,IAAA,CAAK;AAAA,KAClB,GACA,KAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,WAAA,GAAiC;AACtC,IAAA,IAAI,IAAA,CAAK,oBAAoB,IAAA,EAAM;AACjC,MAAA,OAAO,IAAA,CAAK,eAAA;AAAA,IACd;AAUA,IAAA,MAAM,WAAW,YAAY;AAC3B,MAAA,IAAI;AACF,QAAA,OAAO,MAAM,KAAK,cAAA,EAAe;AAAA,MACnC,CAAA,SAAE;AACA,QAAA,IAAA,CAAK,eAAA,GAAkB,IAAA;AAAA,MACzB;AAAA,IACF,CAAA,GAAG;AAEH,IAAA,IAAA,CAAK,eAAA,GAAkB,OAAA;AAEvB,IAAA,OAAO,OAAA;AAAA,EACT;AAAA,EAEA,MAAM,cAAA,GAAoC;AAKxC,IAAA,IAAI,KAAA;AACJ,IAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAe,CAAC,UAAU,MAAA,KAAW;AACvD,MAAA,KAAA,GAAQ,UAAA;AAAA,QACN,MAAM,MAAA,CAAO,IAAIA,iBAAA,CAAS;AAAA,UACxB,IAAA,EAAM,kCAAA;AAAA,UACN,MAAA,EAAQ,GAAA;AAAA,UACR,WAAA,EAAa,wDAAwD,oBAAoB,CAAA,EAAA;AAAA,SAC1F,CAAC,CAAA;AAAA,QACF;AAAA,OACF;AAAA,IACF,CAAC,CAAA;AAED,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,IAAA,CAAK;AAAA,QAC9B,KAAK,eAAA,CAAgB,IAAA,CAAKC,wBAAA,CAAgB,WAAA,EAAa,EAAE,CAAA;AAAA,QACzD;AAAA,OACD,CAAA;AAUD,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,QAAA,CAAS,IAAA,EAAM,YAAY,CAAA;AAEpD,MAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,IAAK,aAAa,CAAA,EAAG;AAGjD,QAAA,MAAM,IAAID,iBAAA,CAAS;AAAA,UACjB,IAAA,EAAM,uCAAA;AAAA,UACN,MAAA,EAAQ,GAAA;AAAA,UACR,WAAA,EAAa;AAAA,SACd,CAAA;AAAA,MACH;AAEA,MAAA,IAAA,CAAK,eAAe,IAAA,CAAK,OAAA;AACzB,MAAA,IAAA,CAAK,aAAa,IAAA,CAAK,UAAA;AACvB,MAAA,IAAA,CAAK,cAAA,GAAiB,SAAA;AACtB,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,GAAA;AAE7C,MAAA,OAAO,KAAK,WAAA,EAAY;AAAA,IAC1B,CAAA,SAAE;AAGA,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EAEO,QAAQ,MAAA,EAAwB;AACrC,IAAA,OAAO,CAAC,MAAA,EAAQ,IAAA,CAAK,aAAa,EAAE,CAAA,CAAE,KAAK,GAAG,CAAA;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,OAAA,GAAmB;AACrB,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKO,eAAA,GAA0B;AAC/B,IAAA,OAAO,IAAA,CAAK,UAAU,eAAA,EAAgB;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKO,uBAAA,GAAmD;AACxD,IAAA,OAAO,IAAA,CAAK,UAAU,uBAAA,EAAwB;AAAA,EAChD;AACF;;;;"}