ApiWs

src/chathouse/components/ladies/ApiWs.ts

Observer на [[services/chathouse/services/lady-runner]]. Підключається до партнерського API через WebSocket (Centrifuge) від імені TU і в реальному часі обробляє вхідні події — повідомлення, typing, сповіщення, зміни лімітів.


Технологія

Використовує бібліотеку Centrifuge. Після підключення підписується на канал api#${ladyUlid}. Всі події надходять через publication handler.


Підключення (connect())

  1. Якщо centrifuge.state === 'connected' — пропустити (ідемпотентність).
  2. Отримати broadcast-токен через authService.broadcastingToken(initiatorId).
  3. Якщо токен не вдалося отримати — почекати 20 сек і повторити. Ліміт: 5 спроб. Після 5-ї невдачі — disconnectOperator (примусово відключає оператора).
  4. Створити Centrifuge з токеном, підписатися на канал api#${ladyUlid}.
  5. Запустити прослуховування подій publication.

Reconnect при розриві з’єднання обробляє сама бібліотека Centrifuge (вбудована логіка).


Оброблювані типи подій

ТипЩо означаєДія
SendRU надіслав повідомлення в чатФорвардинг + створення таску
InMailSendRU надіслав mailФорвардинг + створення таску
TypingRU набирає текстTyping-таск з кеш-гардом
NewNotificationRU онлайн або зробив покупкуnotificationLogic
LimitChangedЗмінились ліміти в діалозіФорвардинг якщо є хоч один ліміт

Всі інші типи (GiftOpened, Read, DeleteMessage тощо) — ігноруються.


Обробка publication (покрокова логіка)

  1. Дедуплікація. Кожне повідомлення має fingerprint (action:messageId або action:manUlid:sent_at). Якщо збігається з попереднім — ігнорується.

  2. Нормалізація часу. WebSocket надсилає мікросекунди (2025-02-04T15:14:25.301443Z), HTTP — нулі (...000000Z). ApiWs обрізає до .000000Z щоб час збігався при подальшому порівнянні.

  3. LimitChanged: якщо є хоча б один ліміт (chat або mail) — форвардується клієнту як apiSocket-подія. Якщо лімітів нема — ігнорується.

  4. NewNotificationnotificationLogic() (детальніше нижче).

  5. Фільтр вхідних повідомлень: якщо тип не в allowedActionTypes або повідомлення не є вхідним (isIncomingMessage) — ігнорується.

  6. Send + “like message body” (повідомлення-реакція, не текст): додаткова перевірка — чи є ліміти в діалозі з цим RU (API-запит). Якщо лімітів нема — ігнорується.

  7. Форвардинг клієнту — raw-дані надсилаються оператору як apiSocket-подія.

  8. Створення таску через TaskWsFactory.create():

    • TypingTask: перевірити cacheTypingTasksGuard (1 хв cooldown на manUlid). Якщо вже є таск в сторі — пропустити. Перевірити ліміти в діалозі. Додати до task store.
    • ITask (звичайний таск): якщо вже є таск з тим самим eventTriggerTask по цій парі — пропустити. Зберегти в БД і додати до task store.

notificationLogic()

Спрацьовує на NewNotification з типами ONLINE_NOW або RECENT_PURCHASE і дією NEW або REPEAT.

  1. Отримати повідомлення діалогу з цим RU.
  2. Якщо є chat-ліміти → створити NeedToWriteWsNotificationTask.
  3. Якщо є тільки mail-ліміти → створити NeedToWriteMailWsNotificationTask.
  4. Якщо таск з тим самим eventTriggerTask вже в сторі → пропустити.
  5. Зберегти в БД і додати до task store.

Кеш typing (cacheTypingTasksGuard)

Map<manUlid, {timestamp}> — гард проти дублікатів typing-тасків.

  • Якщо typing від цього RU прийшов менше 1 хв тому — новий typing-таск не створюється.
  • Очищується setInterval кожну 1 хв — видаляються записи старші за 1 хв.

stop()

  1. isStopped = true.
  2. Очищаються всі таймери (timeout, cacheCleanupInterval).
  3. centrifuge.removeAllListeners() + centrifuge.disconnect().
  4. subscription.unsubscribe().
  5. cacheTypingTasksGuard.clear().
  6. runner.removeObserver(this).
  7. Всі посилання на сервіси → null (запобігання memory leaks).

Нюанси

  • connect() викликається з LadyRunner.start() після логіну TU — токен отримується вже під авторизованою сесією.
  • Centrifuge сам управляє reconnect при розриві з’єднання — ApiWs не додає власної reconnect-логіки для WebSocket-розривів (тільки для помилок отримання токена).
  • previousMessage зберігає fingerprint одного попереднього повідомлення — захищає тільки від миттєвих дублікатів.