Skip to content

Develop - #6

Merged
boffart merged 19 commits into
masterfrom
develop
Apr 16, 2026
Merged

boffart merged 19 commits into
masterfrom
develop

Conversation

@boffart

@boffart boffart commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Alexey Portnov added 19 commits February 3, 2026 22:19
…al-каналов

enrichStatesWithConnections теперь проходит цепочку бриджей через
findBridgeChannel вместо прямого lookup партнёра, что корректно
разрешает номер при переводах через очереди.
… цикл перезапусков воркера

- collectActiveChannels: добавлена проверка Uniqueid === Linkedid для корректного
  определения инициатора звонка при follow-me на мобильный (порядок обхода каналов
  не гарантирован, исходящий транк мог ошибочно стать src_chan)
- Главный цикл: while(true) заменён на while($this->needRestart === false),
  чтобы воркер корректно завершался по SIGUSR1 без 3-минутного ожидания SIGTERM
Проблема: WorkerSafeScriptsCore пинговал воркер через AMI во время
тяжёлой инициализации (collectActiveChannels — 7-8 GetVar на канал),
воркер не успевал ответить на ping, получал SIGUSR1 и уходил в
бесконечный цикл рестартов каждые ~3 минуты.

Решение:
- CHECK_BY_AMI → CHECK_BY_PID_NOT_ALERT (core не пингует через AMI)
- Воркер пишет state-файл (/tmp/MonitorActiveCalls_worker.state)
  с pid, timestamp и status (starting/running)
- AsteriskManager: idle callback в waitUserEvent — вызывается каждые
  30 сек когда socket timeout + AMI ping OK (доказывает что процесс
  жив и AMI-соединение работает)
- safe.php: проверяет state-файл — status=starting допускает 120 сек,
  status=running допускает 90 сек без обновления, иначе kill + restart
…редей

- Добавлено поле direction (incoming/outgoing) в структуру channels
- Очереди теперь включены в общий список states с автоматическим расчётом состояния на основе доступности агентов
- Защита состояния очередей от перезаписи событием ExtensionStatus
Устранена проблема "зависших звонков" в веб-интерфейсе.

Проблема: При attended transfer, bridge masquerade или call pickup
Asterisk может изменить Linkedid канала. Событие Hangup приходит с
новым Linkedid, но канал хранится под старым — удаление не происходило.

Решение: В обработке Hangup добавлен fallback-поиск канала во всех
активных linkedId, если канал не найден в указанном linkedId из события.
Задача #11: Throttling/Debouncing обновлений (критично)
- Добавлена задержка 200ms перед отправкой WebSocket обновлений
- Накопление изменений: если за 200ms приходят новые события, отправляется только последнее состояние
- Burst из 32+ событий → 1 отправка вместо 32 (реальный тест на сервере)
- Ожидаемая экономия трафика: 83-95%
- Добавлено debug-логирование throttling событий (scheduled, pending, sent с метриками)

Задача #12: Убрать пустые channels (средний приоритет)
- Удаление ключа 'channels' если массив пустой (Idle/Unavailable статусы)
- Экономия ~200 байт на сообщение

Изменения в bin/WorkerActiveCalls.php:
- Добавлены свойства: $stateUpdateScheduled, $lastStateUpdateSent, $pendingUserStatesData
- Добавлена константа: STATE_UPDATE_DELAY (200ms)
- Изменена логика printActiveCalls():
  * Накопление изменений в $pendingUserStatesData
  * Проверка таймера при каждом вызове
  * Отправка через 200ms+ после первого изменения
  * Детальное логирование (delay, payload size, time since last)
- Изменена enrichStatesWithConnections():
  * Удаление пустых channels после continue
  * Удаление channels если обогащение не дало результата

Реальный результат (лог с сервера boffart.miko.ru):
- Входящий звонок с очередью: 32+ AMI событий за 20ms
- Отправлено: 1 WebSocket сообщение (вместо 32)
- Размер payload: 2633 байта (без пустых channels)
- Throttling работает корректно во время burst'а событий
…з idle callback

Проблема:
- Throttle механизм задерживает WebSocket обновления на 200ms
- Проверка "пора ли отправлять" выполнялась только при новых AMI событиях
- Если после Hangup событий больше не приходило, обновление зависало
- В UI завершённые звонки продолжали отображаться как активные

Решение:
- Выделена логика отправки в метод flushPendingStateUpdate()
- Добавлен вызов этого метода в idle callback (каждую секунду)
- Теперь даже без новых событий обновления отправляются через макс. 1 сек

Изменения:
- bin/WorkerActiveCalls.php:
  * Новый метод flushPendingStateUpdate() для отправки pending updates
  * Idle callback интервал уменьшен с 30 до 1 сек для быстрой отправки
  * Вызов flushPendingStateUpdate() добавлен в idle callback
Проблема:
- Socket timeout был 5 секунд (stream_set_timeout в AsteriskManager)
- Idle callback мог срабатывать максимум раз в 5 секунд
- WebSocket обновления после Hangup задерживались на ~5 секунд

Решение:
- Добавлен метод setSocketTimeout() в AsteriskManager
- После connect() устанавливается timeout 1 секунда
- Теперь idle callback срабатывает каждую секунду
- WebSocket updates отправляются максимум через 1 сек после события

Изменения:
- Lib/AsteriskManager.php: новый метод setSocketTimeout()
- bin/WorkerActiveCalls.php: вызов setSocketTimeout(1) после connect()
Оптимизация:
- Socket timeout 300ms согласуется с STATE_UPDATE_DELAY (200ms)
- Максимальная задержка WebSocket обновлений: 200-500ms (было 200-1200ms)
- Среднее время отклика: ~350ms (было ~600ms)
- Idle callback срабатывает ~3-4 раза в секунду (было ~1 раз в секунду)

Результат:
- UI обновляется в 2 раза быстрее после завершения звонков
- Минимальная дополнительная нагрузка на CPU
…ройств

Когда на одном номере зарегистрировано несколько устройств (например, PJSIP/233-00001603&PJSIP/233-00001604),
и отвечает не первое устройство, первый канал уничтожается Hangup'ом. Теперь модуль автоматически обновляет
src_chan на активный канал с тем же endpoint, предотвращая ссылки на несуществующие каналы.
…p *8XXX)

Asterisk автоматически меняет linkedid канала при выполнении PickupChan. Канал перехватчика
создаётся с новым linkedid, но при входе в bridge получает linkedid исходного звонка.
Это приводило к тому, что модуль не мог найти канал и не отправлял информацию о собеседнике
в WebSocket (/pbxcore/api/module-softphone-backend/v1/sub/users-state).

Решение:
- Добавлено отслеживание pickup-каналов по extension *8XXX при Newchannel/Newstate
- При BridgeEnter для pickup-канала проверяется изменение linkedid
- Если linkedid изменился, канал автоматически мигрируется в новый linkedid
- Добавлен метод migrateChannel() для переноса данных канала между linkedId
- Добавлен cleanup старых маппингов в channelAdditionalControl()

После миграции метод enrichStatesWithConnections() корректно определяет собеседника
и направление звонка через findBridgeChannel().
Проблема:
При перехвате вызова (Call Pickup *8233) каналы клиента и сотрудника,
перехватившего звонок, оказывались с разными linkedid в одном bridge.
Метод findBridgeChannel() искал связанные каналы только внутри одного
linkedid, поэтому не находил канал перехватившего сотрудника.
Результат: поле "Кому" в таблице становилось пустым.

Решение:
1. Добавлен механизм связывания linkedId через массив linkedIdAliases
2. Метод findBridgeChannel() теперь ищет каналы во всех связанных linkedId
3. При BridgeEnter с SwapUniqueid автоматически создается алиас между
   linkedId исходного звонка и linkedId pickup-канала
4. Добавлена очистка устаревших алиасов при Hangup и в channelAdditionalControl()

Изменения в WorkerActiveCalls.php:
- Добавлен массив $linkedIdAliases для отслеживания связей
- Добавлен метод resolveLinkedId() для разрешения алиасов
- Обновлен findBridgeChannel() для поиска в связанных linkedId
- Обновлена обработка BridgeEnter для создания алиасов
- Обновлена обработка Hangup для очистки алиасов
- Обновлен enrichStatesWithConnections() для использования канонических linkedId
Критические изменения:
- Метод migrateChannel теперь переносит callType, spyerChannels, activeBridges
- Добавлена очистка пустых linkedId для предотвращения дублирования звонков
- Добавлена проверка $oldLinkedId === $newLinkedId для оптимизации

Code cleanup:
- Все комментарии переписаны на английский
- Убрано избыточное debug логирование в BridgeEnter
- Улучшена читаемость логики обработки pickup

Результат: перехваченные звонки (*8XXX) теперь корректно отображаются
как один звонок с полными данными о pickup-канале.
- Заменён createLoginResponse('1','admin') на createServiceToken() (требует добавления метода в ModuleSoftphoneBackend)
- JS: добавлен refreshAuthToken() через /auth/refresh вместо повторной генерации токенов
- Старые refresh-токены теперь попадают в blacklist на сервере
…а от падений

- AMI reconnect только при потере соединения (ensureAmiConnected) вместо disconnect+connect на каждый запрос
- Белый список разрешённых методов (ALLOWED_API_METHODS) вместо method_exists
- try/catch в главном цикле start() с reconnect Beanstalk
- try/catch и валидация данных в restAPICallback(), success после проверки ответа AMI
- try/catch в onEvents() вокруг выполнения метода
- WorkerActiveCalls: прямой вызов GetChannels вместо invokeApi
GetChannels() теперь возвращает null при ошибке AMI вместо пустого массива,
channelAdditionalControl() различает "нет каналов" от "ошибка связи" и
корректно очищает зависшие каналы при валидном пустом ответе.
Asterisk меняет Linkedid канала при входе в bridge в сценариях call
pickup *8, interception-bridge (originate+merge) и attended transfer.
Существующий механизм отслеживал только *8-pickup по Exten — из-за
этого при перехвате через interception-bridge метаданные канала
оставались под старым Linkedid, web-UI показывал фантомную строку
«сотрудник разговаривает сам с собой» и в логах сыпались ERROR
"Undefined array key" из printActiveCalls.

Фикс: в обработчике BridgeEnter перед pickup-специфичной логикой
детектируем смену Linkedid по наличию канала в activeChannels под
другим linkedId и мигрируем метаданные через существующий
migrateChannel(). Защита от цикла в linkedIdAliases и фильтр
Local-каналов во избежание лишних обходов. Плюс defensive isset/??
в printActiveCalls как страховка от будущих экзотических сценариев.
@boffart
boffart merged commit c047e89 into master Apr 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant