{"version":3,"file":"channel-manager.mjs","sources":["../../../src/pullClient/channel-manager.ts"],"sourcesContent":["import type { AjaxResult } from '../core/http/ajax-result'\nimport type { TypeChanel, TypeChannelManagerParams, TypePublicIdDescriptor } from '../types/pull'\nimport type { TypeB24 } from '../types/b24'\nimport type { SuccessPayload } from '../types/payloads'\nimport type { LoggerInterface } from '../logger'\nimport { LoggerFactory } from '../logger'\nimport { SdkError } from '../core/sdk-error'\n\n/** Named once: it is compared against as well as thrown. */\nconst PUBLIC_IDS_UNAVAILABLE = 'JSSDK_PULL_PUBLIC_IDS_UNAVAILABLE'\n\nexport class ChannelManager {\n  private _logger: LoggerInterface\n  private _publicIds: Map<number, TypeChanel>\n  private _restClient: TypeB24\n  private _getPublicListMethod: string\n\n  constructor(params: TypeChannelManagerParams) {\n    this._logger = LoggerFactory.createNullLogger()\n    this._publicIds = new Map()\n\n    this._restClient = params.b24\n    // Defaulted here as well as in `PullClient`, which is the only caller that\n    // passes it today. Without this the name reaches the failure message as\n    // `undefined`, and that message exists precisely to tell the reader which\n    // method was refused.\n    this._getPublicListMethod = params.getPublicListMethod || 'pull.channel.public.list'\n  }\n\n  setLogger(logger: LoggerInterface): void {\n    this._logger = logger\n  }\n\n  getLogger(): LoggerInterface {\n    return this._logger\n  }\n\n  /**\n   * @param {Array} users Array of user ids.\n   * @return {Promise}\n   */\n  async getPublicIds(users: number[]): Promise<Record<number, TypeChanel>> {\n    const now = new Date()\n\n    const result: Record<number, TypeChanel> = {}\n    const unknownUsers: number[] = []\n\n    for (const userId of users) {\n      const chanel = this._publicIds.get(userId)\n\n      if (chanel && chanel.end > now) {\n        result[chanel.userId] = chanel\n      } else {\n        unknownUsers.push(userId)\n      }\n    }\n\n    if (unknownUsers.length === 0) {\n      return Promise.resolve(result)\n    }\n\n    /**\n     * This REJECTS on failure, and that is the point.\n     *\n     * It used to log and resolve `{}`, which turned a refused request into an\n     * empty channel map. The only caller then encoded a message with no\n     * receivers, the push server dropped it without a word, and\n     * `sendMessage()` reported success. Measured against a live portal: the\n     * message was \"accepted\" and never arrived, with nothing anywhere saying\n     * why.\n     *\n     * The failure this most often is: `pull.channel.public.list` is not part\n     * of the application REST surface. An application's Pull client is\n     * documented as RECEIVE-ONLY — its back end puts messages into the channel\n     * with `pull.application.event.add`, and the front end subscribes. So for\n     * an application this rejection is usually the correct, permanent answer,\n     * and the message it carries says so rather than leaving the caller to\n     * guess.\n     *\n     * Usually, not always, and the difference is the cache above rather than\n     * anything here. `PullClient` seeds it from `config.publicChannels`,\n     * returned by the config call it makes at startup —\n     * `pull.application.config.get` in an application, `pull.config.get`\n     * otherwise. That map carries the current user's own channel, so a send\n     * addressed to SELF commonly finds every recipient cached and unexpired,\n     * this method makes no request at all, and there is nothing to refuse: the\n     * batch is encoded and handed to the socket. A send to another user\n     * normally still needs the lookup, and still gets the rejection.\n     *\n     * Two things that follows does NOT prove, both measured rather than\n     * reasoned. The cache admits a channel on `end > now` by the CLIENT's\n     * clock, with no signature check, so a rotated or server-side-invalidated\n     * channel can still satisfy it — the frame then encodes and is addressed\n     * to something the server does not recognise. And reaching the transport\n     * is not a delivery receipt in any case: both connectors return `true` as\n     * soon as they have handed the bytes over, long-polling without even\n     * reading the response.\n     */\n    return new Promise((resolve, reject) => {\n      // Was `callMethod`, removed in 3.0.0 (#277). `pull.server.time` and the\n      // channel methods are `restApi:v2` only, which is what `callMethod`\n      // resolved to anyway — so this is the same request, spelled the way the\n      // SDK spells every other one.\n      this._restClient.actions.v2.call\n        .make({\n          method: this._getPublicListMethod,\n          params: { users: unknownUsers }\n        })\n        .then((response: AjaxResult) => {\n          // A refusal reaches here in TWO shapes, and only one of them throws.\n          // `AbstractHttp.call()` rejects with an `AjaxError` for most\n          // failures, but RESOLVES a non-success `AjaxResult` whenever\n          // `isSoftError()` is true — the built-in soft codes, anything a\n          // caller added through `softErrorCodes`, and v3-envelope 4xx outside\n          // 401/408/429. Without this branch that delivery fell through to\n          // `getData()` returning `undefined`, and the rejection below happened\n          // only because reading `.result` off it threw a `TypeError`. It\n          // worked, by accident, and it logged the `TypeError` instead of the\n          // portal's own message — so the one thing this change exists to\n          // provide, an answer to \"why did my message go nowhere?\", was exactly\n          // what it did not provide on that path.\n          if (!response.isSuccess) {\n            return reject(this.publicIdsUnavailable(\n              new Error(response.getErrorMessages().join('; '))\n            ))\n          }\n\n          const payload = response.getData() as undefined | SuccessPayload<Record<string, TypePublicIdDescriptor>>\n          if (payload?.result) {\n            this.setPublicIds(Object.values(payload.result))\n          }\n\n          for (const userId of unknownUsers) {\n            const chanel = this._publicIds.get(userId)\n            if (chanel) {\n              result[chanel.userId] = chanel\n            }\n          }\n\n          // A successful answer that yielded no channel at all is the silent\n          // drop one level up: the caller asked for recipients, the portal did\n          // not refuse, and there is still nobody to send to. It reaches here\n          // when the body carries no usable descriptor — a 200 with no\n          // `result`, for instance, which `getData()` normalises into a\n          // result-shaped object rather than failing.\n          //\n          // Only the all-or-nothing case rejects. A PARTIAL answer still\n          // resolves, and that is a deliberate gap rather than a decision:\n          // sending to the subset that resolved is what the old code did, and\n          // changing it is a separate question from the one this fixes.\n          if (Object.keys(result).length === 0) {\n            return reject(this.publicIdsUnavailable(\n              new Error(`\\`${this._getPublicListMethod}\\` answered without a channel for any requested user`)\n            ))\n          }\n\n          resolve(result)\n        })\n        .catch((error: unknown) => {\n          // `reject` above lands here too, so the already-wrapped error is\n          // passed through rather than wrapped twice. The test is the CODE, not\n          // `instanceof SdkError`: `AjaxError extends SdkError`, so an\n          // instance check would let every transport failure through unwrapped\n          // — which is exactly what it did on first run.\n          if (error instanceof SdkError && error.code === PUBLIC_IDS_UNAVAILABLE) {\n            return reject(error)\n          }\n\n          return reject(this.publicIdsUnavailable(error))\n        })\n    })\n  }\n\n  /**\n   * The one failure this class reports, built in one place.\n   *\n   * `originalError` carries the cause. It is deliberately non-enumerable on\n   * `SdkError`, so it does not reach a serializer, but it is what lets a caller\n   * tell a permanent refusal (the method is not in the application surface —\n   * retrying is pointless) from a transient one (a 503, a rate limit — retrying\n   * is right). The description alone asserts the former and cannot distinguish\n   * them.\n   *\n   * No caller value is interpolated into the description: `SdkError` does not\n   * run it through the log redaction, so only the method name — a constant —\n   * goes in.\n   */\n  private publicIdsUnavailable(cause: unknown): SdkError {\n    this.getLogger().error('some error in getPublicIds', { error: cause }).catch(() => {})\n\n    return new SdkError({\n      code: PUBLIC_IDS_UNAVAILABLE,\n      description: `Pull: could not resolve channel ids through \\`${this._getPublicListMethod}\\`, so there is nobody to send to. Publishing from the client needs that method; that method is not part of the application REST surface — put messages into the channel with \\`pull.application.event.add\\` from your back end instead.`,\n      status: 0,\n      ...(cause instanceof Error ? { originalError: cause } : {})\n    })\n  }\n\n  /**\n   * @param {TypePublicIdDescriptor[]} publicIds\n   */\n  public setPublicIds(publicIds: TypePublicIdDescriptor[]): void {\n    publicIds.forEach((publicIdDescriptor: TypePublicIdDescriptor) => {\n      const userId = Number(publicIdDescriptor.user_id)\n      this._publicIds.set(userId, {\n        userId: userId,\n        publicId: publicIdDescriptor.public_id,\n        signature: publicIdDescriptor.signature,\n        start: new Date(publicIdDescriptor.start),\n        end: new Date(publicIdDescriptor.end)\n      } as TypeChanel)\n    })\n  }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AASA,MAAM,sBAAA,GAAyB,mCAAA;AAExB,MAAM,cAAA,CAAe;AAAA,EAX5B;AAW4B,IAAA,MAAA,CAAA,IAAA,EAAA,gBAAA,CAAA;AAAA;AAAA,EAClB,OAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,oBAAA;AAAA,EAER,YAAY,MAAA,EAAkC;AAC5C,IAAA,IAAA,CAAK,OAAA,GAAU,cAAc,gBAAA,EAAiB;AAC9C,IAAA,IAAA,CAAK,UAAA,uBAAiB,GAAA,EAAI;AAE1B,IAAA,IAAA,CAAK,cAAc,MAAA,CAAO,GAAA;AAK1B,IAAA,IAAA,CAAK,oBAAA,GAAuB,OAAO,mBAAA,IAAuB,0BAAA;AAAA,EAC5D;AAAA,EAEA,UAAU,MAAA,EAA+B;AACvC,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,EACjB;AAAA,EAEA,SAAA,GAA6B;AAC3B,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAa,KAAA,EAAsD;AACvE,IAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AAErB,IAAA,MAAM,SAAqC,EAAC;AAC5C,IAAA,MAAM,eAAyB,EAAC;AAEhC,IAAA,KAAA,MAAW,UAAU,KAAA,EAAO;AAC1B,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,MAAM,CAAA;AAEzC,MAAA,IAAI,MAAA,IAAU,MAAA,CAAO,GAAA,GAAM,GAAA,EAAK;AAC9B,QAAA,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,GAAI,MAAA;AAAA,MAC1B,CAAA,MAAO;AACL,QAAA,YAAA,CAAa,KAAK,MAAM,CAAA;AAAA,MAC1B;AAAA,IACF;AAEA,IAAA,IAAI,YAAA,CAAa,WAAW,CAAA,EAAG;AAC7B,MAAA,OAAO,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAAA,IAC/B;AAuCA,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AAKtC,MAAA,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,EAAA,CAAG,IAAA,CACzB,IAAA,CAAK;AAAA,QACJ,QAAQ,IAAA,CAAK,oBAAA;AAAA,QACb,MAAA,EAAQ,EAAE,KAAA,EAAO,YAAA;AAAa,OAC/B,CAAA,CACA,IAAA,CAAK,CAAC,QAAA,KAAyB;AAa9B,QAAA,IAAI,CAAC,SAAS,SAAA,EAAW;AACvB,UAAA,OAAO,OAAO,IAAA,CAAK,oBAAA;AAAA,YACjB,IAAI,KAAA,CAAM,QAAA,CAAS,kBAAiB,CAAE,IAAA,CAAK,IAAI,CAAC;AAAA,WACjD,CAAA;AAAA,QACH;AAEA,QAAA,MAAM,OAAA,GAAU,SAAS,OAAA,EAAQ;AACjC,QAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,UAAA,IAAA,CAAK,YAAA,CAAa,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAC,CAAA;AAAA,QACjD;AAEA,QAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,UAAA,MAAM,MAAA,GAAS,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,MAAM,CAAA;AACzC,UAAA,IAAI,MAAA,EAAQ;AACV,YAAA,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,GAAI,MAAA;AAAA,UAC1B;AAAA,QACF;AAaA,QAAA,IAAI,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,CAAE,WAAW,CAAA,EAAG;AACpC,UAAA,OAAO,OAAO,IAAA,CAAK,oBAAA;AAAA,YACjB,IAAI,KAAA,CAAM,CAAA,EAAA,EAAK,IAAA,CAAK,oBAAoB,CAAA,oDAAA,CAAsD;AAAA,WAC/F,CAAA;AAAA,QACH;AAEA,QAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,MAChB,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,KAAmB;AAMzB,QAAA,IAAI,KAAA,YAAiB,QAAA,IAAY,KAAA,CAAM,IAAA,KAAS,sBAAA,EAAwB;AACtE,UAAA,OAAO,OAAO,KAAK,CAAA;AAAA,QACrB;AAEA,QAAA,OAAO,MAAA,CAAO,IAAA,CAAK,oBAAA,CAAqB,KAAK,CAAC,CAAA;AAAA,MAChD,CAAC,CAAA;AAAA,IACL,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBQ,qBAAqB,KAAA,EAA0B;AACrD,IAAA,IAAA,CAAK,SAAA,EAAU,CAAE,KAAA,CAAM,4BAAA,EAA8B,EAAE,OAAO,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAErF,IAAA,OAAO,IAAI,QAAA,CAAS;AAAA,MAClB,IAAA,EAAM,sBAAA;AAAA,MACN,WAAA,EAAa,CAAA,8CAAA,EAAiD,IAAA,CAAK,oBAAoB,CAAA,6OAAA,CAAA;AAAA,MACvF,MAAA,EAAQ,CAAA;AAAA,MACR,GAAI,KAAA,YAAiB,KAAA,GAAQ,EAAE,aAAA,EAAe,KAAA,KAAU;AAAC,KAC1D,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,aAAa,SAAA,EAA2C;AAC7D,IAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,kBAAA,KAA+C;AAChE,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,kBAAA,CAAmB,OAAO,CAAA;AAChD,MAAA,IAAA,CAAK,UAAA,CAAW,IAAI,MAAA,EAAQ;AAAA,QAC1B,MAAA;AAAA,QACA,UAAU,kBAAA,CAAmB,SAAA;AAAA,QAC7B,WAAW,kBAAA,CAAmB,SAAA;AAAA,QAC9B,KAAA,EAAO,IAAI,IAAA,CAAK,kBAAA,CAAmB,KAAK,CAAA;AAAA,QACxC,GAAA,EAAK,IAAI,IAAA,CAAK,kBAAA,CAAmB,GAAG;AAAA,OACvB,CAAA;AAAA,IACjB,CAAC,CAAA;AAAA,EACH;AACF;;;;"}