@bitrix24/b24rabbitmq
Version:
Library for integrating Bitrix24 applications with RabbitMQ
1 lines • 36.3 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../../src/logger.ts","../../src/base.ts","../../src/producer.ts","../../src/consumer.ts"],"sourcesContent":["import type { Logger } from './types'\n\n/**\n * Default `Logger` implementation: a thin wrapper over `console.*`.\n *\n * Picked when `RabbitMQConfig.logger` is not supplied — preserves the\n * out-of-the-box behaviour of earlier versions where this library\n * wrote diagnostics straight to the process's stdout/stderr.\n *\n * Consumers who want silence pass a noop logger explicitly; consumers\n * who want structured logging pass `pino`, `consola`, the\n * `@bitrix24/b24jssdk` logger, or any object that satisfies the\n * {@link Logger} interface.\n */\nexport const defaultLogger: Logger = {\n debug: (message: string, ...args: unknown[]) => { console.debug(message, ...args) },\n info: (message: string, ...args: unknown[]) => { console.info(message, ...args) },\n warn: (message: string, ...args: unknown[]) => { console.warn(message, ...args) },\n error: (message: string, ...args: unknown[]) => { console.error(message, ...args) }\n}\n\n/**\n * Regex matching `amqp://user:pass@host` and `amqps://user:pass@host`.\n * Used by {@link sanitizeUrl} via `String.prototype.replace`, which resets\n * `lastIndex` per call — safe to share at module scope. Do NOT call\n * `.test()` / `.exec()` on it directly; the `g` flag would carry state.\n */\nconst URL_CREDENTIAL_WITH_PASSWORD = /(amqps?:\\/\\/)([^:@]+):([^@]+)@/g\n\n/**\n * Regex matching `amqp://user@host` (username only, no password). Run\n * AFTER the with-password sweep so an already-scrubbed URL is not\n * matched again.\n */\nconst URL_CREDENTIAL_USER_ONLY = /(amqps?:\\/\\/)([^:@/]+)@/g\n\n/**\n * Replace `amqp://user:pass@host` and `amqp://user@host` with\n * `amqp://***:***@host` and `amqp://***@host`. Limitation: malformed\n * URLs with multiple literal `@` signs only have the first segment\n * scrubbed (RFC 3986 valid URLs never have unencoded `@` after the\n * authority).\n *\n * @param url any string that may contain an AMQP connection URL.\n * @returns the same string with credentials masked.\n */\nexport function sanitizeUrl(url: string): string {\n return url\n .replace(URL_CREDENTIAL_WITH_PASSWORD, '$1***:***@')\n .replace(URL_CREDENTIAL_USER_ONLY, '$1***@')\n}\n\n/**\n * Pull a loggable message out of an unknown thrown value and scrub any\n * credentials before returning. Use whenever a caught error is fed to\n * the logger.\n *\n * Walks the `error.cause` chain (as set by `new Error(msg, { cause })`,\n * which amqplib and our own `connect()` wrappers use) so credentials\n * hidden in a nested cause are scrubbed too, not just the top-level\n * `message`. The walk is bounded and cycle-safe.\n *\n * @param error any thrown value (Error, string, primitive, object).\n * @returns a scrubbed string suitable for the logger.\n */\nexport function safeErrorMessage(error: unknown): string {\n const parts: string[] = []\n const seen = new Set<unknown>()\n let current: unknown = error\n\n // Bound the walk: cycle guard via `seen`, plus a hard depth cap as a\n // belt-and-suspenders backstop against pathological chains.\n for (let depth = 0; depth < 10 && current != null && !seen.has(current); depth++) {\n seen.add(current)\n if (current instanceof Error) {\n parts.push(current.message)\n current = current.cause\n } else {\n parts.push(String(current))\n break\n }\n }\n\n if (parts.length === 0) {\n parts.push(String(error))\n }\n\n return sanitizeUrl(parts.join(': '))\n}\n","import amqp from 'amqplib'\nimport { defaultLogger } from './logger'\nimport type { ExchangeParams, Logger, QueueParams, RabbitMQConfig } from './types'\n\nexport abstract class RabbitMQBase {\n protected connection!: amqp.ChannelModel\n protected channel!: amqp.Channel\n protected config: RabbitMQConfig\n protected logger: Logger\n\n constructor(config: RabbitMQConfig) {\n this.config = {\n channel: { prefetchCount: 1 },\n ...config\n }\n this.logger = config.logger ?? defaultLogger\n }\n\n /**\n * Open the AMQP connection and channel. Not a TypeScript `abstract`\n * method — the base implementation throws unless a subclass overrides\n * it; `RabbitMQProducer` and `RabbitMQConsumer` do exactly that\n * (publish channel vs consumer channel + reconnect listener).\n */\n async connect(): Promise<void> {\n throw new Error('Need override this function')\n }\n\n /**\n * Iterate the config's `exchanges` array and assert each one against\n * the broker. Called by `initialize()`; idempotent on the broker side.\n * @protected\n */\n protected async setupExchanges(): Promise<void> {\n for (const exchange of this.config.exchanges) {\n await this.registerExchange(exchange)\n }\n }\n\n /**\n * Declare a single exchange on the active channel. `Producer` overrides\n * this to also cache the exchange in a local map.\n *\n * @param exchange the exchange to assert — see {@link ExchangeParams}.\n */\n async registerExchange(\n exchange: ExchangeParams\n ): Promise<void> {\n await this.channel.assertExchange(\n exchange.name,\n exchange.type,\n exchange.options || {}\n )\n }\n\n /**\n * Iterate the config's `queues` array, assert each queue and create\n * its bindings. Called by `Consumer.initialize()`.\n * @protected\n */\n protected async setupQueues(): Promise<void> {\n for (const queue of this.config.queues) {\n await this.registerQueue(queue)\n }\n }\n\n /**\n * Declare a single queue (and its bindings) on the active channel.\n *\n * The library merges three sources into the queue's `arguments`:\n * (1) library-injected `x-max-priority` (from `queue.maxPriority`,\n * default 10; omitted when set to 0),\n * (2) library-injected `x-dead-letter-exchange` / `x-dead-letter-routing-key`\n * (from `queue.deadLetter`),\n * (3) caller-supplied `queue.options.arguments`.\n *\n * On per-key conflict, the caller wins; sibling keys survive.\n *\n * @param queue the queue to assert — see {@link QueueParams}.\n * @returns the amqplib assertQueue reply (with the broker-assigned\n * queue name if `queue.name` was empty).\n */\n async registerQueue(\n queue: QueueParams\n ): Promise<amqp.Replies.AssertQueue> {\n const maxPriority = queue.maxPriority ?? 10\n\n // Build a single `arguments` object so x-max-priority, dead-letter args\n // and caller-supplied options.arguments all coexist. Earlier code spread\n // them in three separate steps and accidentally let later spreads wipe\n // the earlier ones; caller options.arguments now win per-key but do not\n // replace sibling keys.\n const mergedArguments: Record<string, unknown> = {}\n // AMQP rejects x-max-priority outside 1..255; treat 0 / negative as\n // \"no priority queue\" and omit the key so the broker accepts the assert.\n if (maxPriority > 0) {\n mergedArguments['x-max-priority'] = maxPriority\n }\n if (queue.deadLetter) {\n mergedArguments['x-dead-letter-exchange'] = queue.deadLetter.exchange\n mergedArguments['x-dead-letter-routing-key'] = queue.deadLetter.routingKey ?? ''\n }\n // Object.assign is a shallow copy — nested values share references with\n // the caller's config. Safe here because we hand the result straight to\n // assertQueue and never mutate after.\n const { arguments: callerArguments, ...callerRestOptions } = queue.options ?? {}\n if (callerArguments) {\n Object.assign(mergedArguments, callerArguments)\n }\n\n // We deliberately do NOT pass `maxPriority` as a top-level option:\n // amqplib translates it to x-max-priority internally, which would\n // silently shadow a caller's explicit override in options.arguments.\n const q = await this.channel.assertQueue(\n queue.name || '',\n {\n ...callerRestOptions,\n arguments: mergedArguments\n }\n )\n\n for (const binding of queue.bindings) {\n if (binding.headers) {\n await this.channel.bindQueue(\n q.queue,\n binding.exchange,\n binding.routingKey || '',\n binding.headers\n )\n } else {\n await this.channel.bindQueue(\n q.queue,\n binding.exchange,\n binding.routingKey || ''\n )\n }\n }\n\n return q\n }\n\n /**\n * Close the channel and the connection. Safe to call repeatedly or\n * before `initialize()` (the optional-chaining handles a missing\n * channel/connection). `RabbitMQConsumer` overrides this to also\n * clear its reconnect-tracking state.\n */\n async disconnect(): Promise<void> {\n await this.channel?.close()\n await this.connection?.close()\n this.logger.info('[RabbitMQ::Base] disconnect')\n }\n}\n","import amqp from 'amqplib'\nimport { RabbitMQBase } from './base'\nimport { safeErrorMessage } from './logger'\nimport type { ExchangeParams, MessageOptions } from './types'\n\nexport class RabbitMQProducer extends RabbitMQBase {\n private exchanges = new Map<string, ExchangeParams>()\n\n /**\n * Open the connection + publish channel and assert every exchange\n * from the config. Throws if the broker is unreachable or the\n * topology assertion fails. Call once before {@link publish}.\n */\n public async initialize(): Promise<void> {\n await this.connect()\n await this.setupExchanges()\n }\n\n /**\n * Open the AMQP connection and create a publish channel.\n *\n * Intentionally does NOT call `channel.prefetch()` — `prefetch` is a\n * consumer-side flow-control setting (limits unacked deliveries on the\n * channel) and has no effect on publishing.\n */\n override async connect(): Promise<void> {\n try {\n this.logger.info('[RabbitMQ::Producer] connect ...')\n\n this.connection = await amqp.connect(this.config.connection.url)\n this.channel = await this.connection.createChannel()\n this.logger.info('[RabbitMQ::Producer] connected successfully')\n } catch (error) {\n const problem = error instanceof Error ? error : new Error(`[RabbitMQ::Producer] connected error`, { cause: error })\n\n this.logger.error('[RabbitMQ::Producer] connect failed:', safeErrorMessage(problem))\n throw problem\n }\n }\n\n /**\n * Assert an exchange on the broker (delegates to `RabbitMQBase`) and\n * remember it in the Producer's local map. The cached entries are\n * informational — `publish()` does not re-assert before sending.\n */\n override async registerExchange(\n exchange: ExchangeParams\n ): Promise<void> {\n await super.registerExchange(exchange)\n this.exchanges.set(exchange.name, exchange)\n }\n\n /**\n * Publish a message to an exchange, serialized as JSON.\n *\n * @param exchangeName Target exchange (must be declared in the config).\n * @param routingKey Routing key for the broker to match against bindings.\n * @param message Payload — JSON-serialized into a Buffer.\n * @param options AMQP publish options. Defaults to `priority: 5`.\n *\n * @returns\n * The boolean returned by `amqplib`'s `channel.publish()`. This reflects\n * **only the client-side write buffer state**, not broker acknowledgment:\n * - `true` — the message was written to the channel's outgoing buffer.\n * - `false` — the buffer is full; the caller should wait for the channel's\n * `'drain'` event before publishing more (back-pressure signal).\n *\n * It does **not** mean the broker has received or persisted the message.\n * For at-least-once delivery you need publisher confirms (Track 4 capability,\n * post-v0.1) — switch to `createConfirmChannel()` and await\n * `channel.waitForConfirms()`.\n */\n async publish<T>(\n exchangeName: string,\n routingKey: string,\n message: T,\n options: MessageOptions = {}\n ): Promise<boolean> {\n return this.channel.publish(\n exchangeName,\n routingKey,\n Buffer.from(JSON.stringify(message)),\n {\n priority: 5,\n ...options\n }\n )\n }\n}\n","import amqp from 'amqplib'\nimport { RabbitMQBase } from './base'\nimport { safeErrorMessage } from './logger'\nimport type { MessageHandler } from './types'\n\nexport class RabbitMQConsumer extends RabbitMQBase {\n private retries = 0\n private handlers = new Map<string, MessageHandler>()\n /** Queue names we have an active `consume()` subscription on, so we can re-subscribe after reconnect. */\n private subscribedQueues = new Set<string>()\n /** queueName → active amqplib consumerTag, so `unRegisterHandler` can issue `basic.cancel`. */\n private consumerTags = new Map<string, string>()\n /** Guard so multiple 'close' events don't kick off concurrent reconnect loops. */\n private reconnectInProgress = false\n\n /**\n * Open the connection + consumer channel, assert every exchange and\n * queue (with bindings) from the config, then arm the reconnect\n * listener. Throws if the broker is unreachable or any assertion\n * fails. Call once before {@link registerHandler} + {@link consume}.\n */\n public async initialize(): Promise<void> {\n await this.connect()\n await this.setupExchanges()\n await this.setupQueues()\n }\n\n /**\n * Open a connection, create a channel, apply prefetch, and wire the\n * close-listener that drives reconnect. Throws on failure — the caller\n * (initialize or the reconnect loop) decides what to do with errors.\n */\n override async connect(): Promise<void> {\n this.logger.info('[RabbitMQ::Consumer] connect ...')\n\n this.connection = await amqp.connect(this.config.connection.url)\n this.channel = await this.connection.createChannel()\n await this.channel.prefetch(this.config.channel!.prefetchCount!)\n\n // The 'close' event fires when the broker drops the connection or the\n // peer side closes it. Each successful connect attaches its own listener\n // to the new connection object.\n this.connection.on('close', () => { void this.handleReconnect() })\n // 'error' typically precedes 'close' — we log here and let 'close' drive\n // the actual recovery so we don't double-trigger.\n this.connection.on('error', (error) => {\n this.logger.error('[RabbitMQ::Consumer] connection error', safeErrorMessage(error))\n })\n\n this.logger.info('[RabbitMQ::Consumer] connected successfully')\n this.retries = 0\n }\n\n /**\n * Bounded async reconnect loop. Triggered by the 'close' event on the\n * connection. Sleeps `reconnectInterval` between attempts, re-asserts the\n * topology and re-subscribes any queues that had active consume() calls.\n *\n * Never throws — a synchronous throw out of an event listener would be\n * uncatchable for the caller and cause an unhandled-exception crash.\n * On exhaustion we log and give up; the process stays alive and the\n * caller can decide to recreate the consumer.\n *\n * `maxRetries: 0` disables reconnect entirely (the loop never enters);\n * the log message in that case is \"reconnect disabled\" rather than\n * \"max retries exceeded\".\n */\n private async handleReconnect(): Promise<void> {\n if (this.reconnectInProgress) return\n this.reconnectInProgress = true\n // Start each reconnect series from a clean counter. `retries` is only\n // otherwise reset on a successful `connect()`; without this, a series\n // that exhausted `maxRetries` would leave the counter pinned at the\n // ceiling, so the NEXT 'close' event would find `retries >= maxRetries`\n // immediately and give up without a single attempt.\n this.retries = 0\n\n const maxRetries = this.config.connection.maxRetries ?? 5\n const interval = this.config.connection.reconnectInterval ?? 5000\n\n try {\n while (this.retries < maxRetries) {\n this.retries++\n this.logger.info(`[RabbitMQ::Consumer] reconnecting (attempt ${this.retries}/${maxRetries}) in ${interval}ms`)\n\n await new Promise<void>(resolve => setTimeout(resolve, interval))\n\n try {\n await this.connect()\n await this.setupExchanges()\n await this.setupQueues()\n for (const queueName of this.subscribedQueues) {\n const reply = await this.channel.consume(queueName, this.buildDeliveryCallback(queueName))\n this.consumerTags.set(queueName, reply.consumerTag)\n }\n this.logger.info('[RabbitMQ::Consumer] reconnected and re-subscribed')\n // Clear the guard BEFORE the implicit return so that a fresh\n // 'close' arriving right after success can re-enter cleanly.\n // (The `finally` below is then a harmless no-op double-clear.)\n this.reconnectInProgress = false\n return\n } catch (error) {\n this.logger.error(\n `[RabbitMQ::Consumer] reconnect attempt ${this.retries} failed:`,\n safeErrorMessage(error)\n )\n // fall through to the next iteration of the while-loop\n }\n }\n\n if (maxRetries === 0) {\n this.logger.error('[RabbitMQ::Consumer] reconnect disabled (maxRetries=0); the consumer is no longer connected.')\n } else {\n this.logger.error(`[RabbitMQ::Consumer] max connection retries (${maxRetries}) exceeded; giving up. The consumer is no longer connected.`)\n }\n } finally {\n this.reconnectInProgress = false\n }\n }\n\n /**\n * Register an async handler for a queue. Storage only — no broker\n * traffic happens here; the handler activates once {@link consume} is\n * called for the same `queueName`. Registering twice for the same\n * queue replaces the previous handler.\n *\n * The handler receives the JSON-parsed message body plus terminal\n * `ack` / `nack` callbacks. `nack` always sends `requeue=false` so\n * rejected messages route through any configured dead-letter\n * exchange — to replay, publish a fresh copy.\n *\n * Parse errors (invalid JSON) and uncaught handler rejections are\n * logged and `nack`'d by the library — the handler itself never sees\n * a parse failure.\n *\n * The library does NOT validate the parsed body — spreading it into\n * a trusted object (`{ ...trusted, ...msg }`) or assigning to\n * `Object.prototype`-adjacent keys is the caller's responsibility.\n * Validate the shape before merging.\n *\n * @typeParam T the parsed message body shape. The library does not\n * validate at runtime; supply a narrow type and validate at the\n * call site if needed.\n * @param queueName must match an asserted queue (see `RabbitMQConfig.queues`).\n * @param handler async callback invoked for every delivery.\n */\n registerHandler<T>(\n queueName: string,\n handler: MessageHandler<T>\n ): void {\n // Storage erases T: the map holds `MessageHandler<unknown>` (after the\n // `any → unknown` tightening of the type alias's default). The explicit\n // `<unknown>` makes the boundary visible at the call site and survives a\n // future change to the `MessageHandler` default. `buildDeliveryCallback`\n // invokes the handler with a parsed payload the caller chose to type as T.\n this.handlers.set(queueName, handler as MessageHandler<unknown>)\n }\n\n /**\n * Remove a previously registered handler, issue an AMQP `basic.cancel`\n * for the queue's active consumer (if any), and drop the queue from the\n * reconnect-resubscribe set.\n *\n * Issuing `basic.cancel` is what actually stops the broker delivering on\n * the live channel: without it the consumer tag stays open, deliveries\n * keep arriving at a now-handlerless callback, and — with\n * `prefetchCount=1` — the unacked delivery blocks the queue. Dropping the\n * queue from `subscribedQueues` covers the same hazard across a reconnect.\n *\n * Returns a promise because the cancel is a broker round-trip. The handler\n * map and tracking sets are cleared synchronously **before** the await, so\n * even an un-awaited call immediately stops dispatching to the handler.\n *\n * @param queueName the queue whose subscription to tear down. Unknown\n * queues are a no-op.\n */\n async unRegisterHandler(queueName: string): Promise<void> {\n this.handlers.delete(queueName)\n // Drop from subscribedQueues so a future reconnect doesn't re-subscribe\n // a queue with no handler (which would silently leave its deliveries\n // un-acked and, with prefetchCount=1, block the queue).\n this.subscribedQueues.delete(queueName)\n\n const consumerTag = this.consumerTags.get(queueName)\n if (consumerTag === undefined) return\n this.consumerTags.delete(queueName)\n try {\n await this.channel.cancel(consumerTag)\n } catch (error) {\n // A cancel failure (e.g. channel already closed) is non-fatal: the\n // handler is already detached and the tag forgotten. Log and move on.\n this.logger.warn(\n `[RabbitMQ::Consumer] basic.cancel for \"${queueName}\" failed:`,\n safeErrorMessage(error)\n )\n }\n }\n\n /**\n * Subscribe to a queue. Tracks the subscription so the reconnect\n * loop can re-attach after a broker drop. Each delivery is\n * JSON-parsed and forwarded to the handler registered via\n * {@link registerHandler}; if no handler exists for the queue, the\n * delivery is silently ignored (it stays un-acked until the channel\n * closes — register your handler **before** calling `consume`).\n *\n * @param queueName name of an already-asserted queue. Server-named\n * queues (where the broker assigns the name in `initialize()`)\n * should be looked up via the `assertQueue` reply if you need to\n * pass the generated name back in.\n * @returns the amqplib `Replies.Consume` (contains `consumerTag`).\n */\n async consume(\n queueName: string\n ): Promise<amqp.Replies.Consume> {\n this.subscribedQueues.add(queueName)\n const reply = await this.channel.consume(queueName, this.buildDeliveryCallback(queueName))\n this.consumerTags.set(queueName, reply.consumerTag)\n return reply\n }\n\n /**\n * Close the channel and connection, and reset the consumer to a clean\n * state so it can be safely re-`initialize()`d. Without this override\n * the reconnect tracking (`subscribedQueues`, `reconnectInProgress`,\n * `retries`) would leak across consumer lifecycles.\n */\n override async disconnect(): Promise<void> {\n this.subscribedQueues.clear()\n this.handlers.clear()\n this.consumerTags.clear()\n this.reconnectInProgress = false\n this.retries = 0\n await super.disconnect()\n }\n\n /**\n * Build the delivery callback for a queue. Extracted so the reconnect path\n * can use the same logic without duplicating the closure inline.\n *\n * Each invocation owns a per-delivery `terminated` flag that guards\n * `channel.ack` / `channel.nack` so only the FIRST terminal call wins:\n *\n * - `ack()` then `throw` → only `ack` fires; the catch's safety-net\n * `nack` is suppressed. amqplib treats a second terminal call on the\n * same delivery as a protocol error (channel close in production).\n * - `nack()` then `throw` → only `nack` fires.\n * - `ack(); ack()` → only the first ack fires.\n * - Handler throws without calling `ack`/`nack` → the catch nacks once.\n *\n * Idempotency is per-delivery: a separate `terminated` lives in each\n * outer-`return async (msg) => …` invocation, so two messages each get\n * their own first-call-wins guard.\n */\n private buildDeliveryCallback(queueName: string): (msg: amqp.ConsumeMessage | null) => Promise<void> {\n return async (msg) => {\n if (!msg) return\n\n const handler = this.handlers.get(queueName)\n if (!handler) return\n\n let terminated = false\n const ack = () => {\n if (terminated) {\n // `warn`, not `debug` — `ack(); ack()` (or `ack(); nack()`) without\n // a surrounding throw produces zero `error`-level output, so a\n // silent suppression would mask the handler bug at default log\n // levels. The rate is bounded by handler defects, not traffic.\n this.logger.warn('[RabbitMQ::Consumer] ack suppressed — delivery already terminated')\n return\n }\n // Call the broker FIRST, flip the flag on success only. If\n // `channel.ack` throws synchronously (channel closed mid-handler),\n // `terminated` stays false and the catch-safety-net below can\n // still nack — otherwise the delivery would sit unacked until the\n // reconnect re-asserts the channel.\n this.channel.ack(msg)\n terminated = true\n }\n const nack = () => {\n if (terminated) {\n this.logger.warn('[RabbitMQ::Consumer] nack suppressed — delivery already terminated')\n return\n }\n this.channel.nack(msg, false, false)\n terminated = true\n }\n\n try {\n const content = JSON.parse(msg.content.toString())\n await handler(content, ack, nack)\n } catch (error) {\n this.logger.error(`[RabbitMQ] Error processing message: ${safeErrorMessage(error)}`)\n // Safety-net: if the handler threw before reaching a terminal\n // call, nack the delivery. If a terminal call already fired,\n // skip — the `terminated` guard makes this idempotent. The\n // call-broker-first ordering above guarantees a synchronous\n // throw from `channel.ack/nack` leaves `terminated === false`,\n // so this path still nacks for that broker-error case.\n if (!terminated) {\n this.channel.nack(msg, false, false)\n terminated = true\n }\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}