import { BaseStateMachine } from '../../tools/BaseStateMachine'; import { EState } from './constants'; import { createCallMachine } from './createCallMachine'; import { TApiManagerEvents } from '../../ApiManager'; import { TEvents } from '../events'; import { TContext, TFullContext, TContextMap } from './types'; type TSnapshotContext = { raw: TContext; state: TState; }; type TCallEvent = { type: 'CALL.CONNECTING'; number: string; answer: boolean; extraHeaders?: string[]; } | { type: 'CALL.ENTER_ROOM'; room: string; participantName: string; token?: string; isDirectPeerToPeer?: boolean; } | { type: 'CALL.TOKEN_ISSUED'; token: string; conferenceForToken: string; participantName: string; } | { type: 'CALL.PRESENTATION_CALL'; } | { type: 'CALL.START_DISCONNECT'; } | { type: 'CALL.RESET'; }; export type TSnapshot = { value: EState.IDLE; context: TSnapshotContext; } | { value: EState.CONNECTING; context: TSnapshotContext; } | { value: EState.PRESENTATION_CALL; context: TSnapshotContext; } | { value: EState.ROOM_PENDING_AUTH; context: TSnapshotContext; } | { value: EState.PURGATORY; context: TSnapshotContext; } | { value: EState.P2P_ROOM; context: TSnapshotContext; } | { value: EState.DIRECT_P2P_ROOM; context: TSnapshotContext; } | { value: EState.IN_ROOM; context: TSnapshotContext; } | { value: EState.DISCONNECTING; context: TSnapshotContext; }; type TMachine = ReturnType; declare class CallStateMachine extends BaseStateMachine { constructor(machine: TMachine, events: TEvents); get isIdle(): boolean; get isConnecting(): boolean; get isPresentationCall(): boolean; get isRoomPendingAuth(): boolean; get isInPurgatory(): boolean; get isP2PRoom(): boolean; get isDirectP2PRoom(): boolean; get isInRoom(): boolean; get isDisconnecting(): boolean; /** Только в IDLE. См. `context` у актора для сырого значения. */ get idleContext(): TContextMap[EState.IDLE] | undefined; /** Только в CONNECTING: номер и ответ; без полей комнаты и токена в типе. */ get connectingContext(): TContextMap[EState.CONNECTING] | undefined; /** * Только в ROOM_PENDING_AUTH и при выполнении условий ожидания авторизации для обычной комнаты. * В сыром `context` могут быть лишние поля (например JWT до выравнивания с `room`). */ get roomPendingAuthContext(): TContextMap[EState.ROOM_PENDING_AUTH] | undefined; /** Только в PURGATORY. */ get purgatoryContext(): TContextMap[EState.PURGATORY] | undefined; /** Только в P2P_ROOM. */ get p2pRoomContext(): TContextMap[EState.P2P_ROOM] | undefined; /** Только в DIRECT_P2P_ROOM. */ get directP2pRoomContext(): TContextMap[EState.DIRECT_P2P_ROOM] | undefined; /** * Валидный для использования контекст звонка в комнате: только в IN_ROOM и только если * `conferenceForToken` согласован с `room` (для обычных комнат). Сырой `context` может * содержать устаревший JWT или несовпадающий `conferenceForToken` — полагаться на него напрямую нельзя. */ get inRoomContext(): TContextMap[EState.IN_ROOM] | undefined; get presentationCallContext(): TContextMap[EState.PRESENTATION_CALL] | undefined; /** Только в DISCONNECTING (контекст сброшен, опционально `pendingDisconnect`). */ get disconnectingContext(): TContextMap[EState.IDLE] | undefined; get isActive(): boolean; /** * Время первого попадания в одно из состояний «активной» фазы звонка: * `ROOM_PENDING_AUTH`, `PRESENTATION_CALL`, `PURGATORY`, `P2P_ROOM`, `DIRECT_P2P_ROOM`, `IN_ROOM`. * Сохраняется при переходах между ними; сбрасывается при `CALL.RESET` / disconnect. */ get startedTimestamp(): number | undefined; get number(): string | undefined; get token(): string | undefined; get isCallInitiator(): boolean; get isCallAnswerer(): boolean; /** * Пара `token` и `conferenceForToken` из нормализованного контекста комнаты. * Только в `IN_ROOM` (как `inRoomContext`); иначе `undefined`. */ getInRoomCredentials(): Pick | undefined; /** * Подписка на изменение пары `token` + `conferenceForToken` в нормализованном контексте `IN_ROOM` * (то же, что отдаёт `inRoomContext`). * * **Алгоритм (состояние только в замыкании подписки):** * * 1. Храним `previous` — последнюю пару учётных данных, о которой уже уведомили (`undefined` до * первого осмысленного уведомления). * 2. На каждом снимке актора: если `value === IN_ROOM`, читаем актуальную пару из * `snapshot.context.state` и сравниваем с `previous`. При отличии хотя бы одного поля — * вызываем `listener`, затем записываем в `previous` новую пару (чтобы не слать дубликаты). * 3. Обновление токена в комнате в машине идёт через переход в служебное состояние `evaluate` * и обратно в `IN_ROOM`. Пока мы «проезжаем» `evaluate`, сбрасывать `previous` нельзя: иначе * при возврате в `IN_ROOM` с **теми же** `token`/`conferenceForToken` пара снова считалась бы * «новой» и listener вызвался бы повторно. * 4. Если снимок не `IN_ROOM` и не `evaluate` (выход в другой доменный state: disconnect, другой * режим комнаты, IDLE и т.д.) — сбрасываем `previous`, чтобы при следующем входе в `IN_ROOM` * снова отдать актуальную пару, а не сравнивать с устаревшими данными прошлого сеанса. */ onInRoomCredentialsChange(listener: (payload: { token: string; conferenceForToken: string; }) => void): () => void; /** * Подписка на изменение `conferenceForToken` в нормализованном контексте `IN_ROOM`. * Изменения `token` сознательно игнорируются. */ onInRoomConferenceForTokenChange(listener: (payload: { conferenceForToken: string; }) => void): () => void; reset(): void; send(event: TCallEvent): void; subscribeToApiEvents(apiManager: TApiManagerEvents): void; private subscribeToEvents; } export declare const createCallStateMachine: (events: TEvents) => CallStateMachine; export type { CallStateMachine as ICallStateMachine };