UNPKG

@bitrix24/b24jssdk

Version:

Bitrix24 REST API JavaScript SDK

1 lines • 13.3 kB
{"version":3,"file":"channel-manager.cjs","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":["LoggerFactory","SdkError"],"mappings":"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"}