import { EventEmitter } from 'node:events'; import { type Logger } from 'zapo-js'; import { type BinaryNode } from 'zapo-js/transport'; import type { WaCallMediaEvent, WaCallMediaMessage } from '@zapo-js/voip-media'; import { type WaVideoUpgradeResult } from '../signaling/signaling.js'; import { type CallOfferOptions, EndCallReason, type WaVoipDeps, type WaVoipStores } from '../types.js'; import { CallInfo } from './call-state.js'; export interface WaCallManagerConfig { deps: WaVoipDeps; stores: WaVoipStores; logger?: Logger; maxConcurrentCalls?: number; useOriginalRelayPort?: boolean; useRawUdpTransport?: boolean; /** Where call media runs; see `WaVoipCoordinatorOptions.media`. */ mediaMode?: WaCallMediaMode; } /** `local` carries the media in this process; `remote` hands its plan to a host elsewhere. */ export type WaCallMediaMode = 'local' | 'remote'; export declare class WaCallManager extends EventEmitter { private readonly deps; private readonly stores; private readonly logger; private readonly maxConcurrentCalls; /** Unset, each call picks by session: the advertised port on a companion, 3480 on a primary. */ private readonly useOriginalRelayPort; private readonly useRawUdpTransport; private readonly mediaMode; private readonly calls; /** Set by `destroy`: an offer still resolving when it ran must not make a call. */ private destroyed; /** * Call ids a `` ended before their offer made a call, with when that * expires: the offer can still be decrypting when the terminate lands. */ private readonly terminatedBeforeOffer; /** Calls the app knows of: an outgoing one from its start, an incoming one once announced. */ private readonly announcedCalls; constructor(config: WaCallManagerConfig); /** Throws once the manager is destroyed, even mid-way: no offer goes out after `destroy`. */ startCall(options: CallOfferOptions): Promise; acceptCall(callId: string): Promise; rejectCall(callId: string, reason?: EndCallReason): Promise; endCall(callId: string, reason?: EndCallReason): Promise; setMute(callId: string, muted: boolean): void; setHandRaised(callId: string, raised: boolean): Promise; setScreenShare(callId: string, sharing: boolean): Promise; /** Sends an emoji reaction. `false` when nothing went on the wire. */ sendReaction(callId: string, reaction: string): boolean; requestVideoUpgrade(callId: string): Promise; acceptVideoUpgrade(callId: string): Promise; rejectVideoUpgrade(callId: string): Promise; cancelVideoUpgrade(callId: string): Promise; loadAudio(callId: string, audioPath: string): Promise; setExternalAudioMode(callId: string, enabled: boolean): void; feedLiveAudio(callId: string, data: Float32Array): number; feedLiveVideo(callId: string, data: Uint8Array, timestampUs: number): number; getLiveBufferMs(callId: string): number; getFeedWatermarksMs(): { pauseMs: number; resumeMs: number; }; getCall(callId: string): CallInfo | null; getCalls(): readonly CallInfo[]; handleCallOffer(node: BinaryNode, peerJid: string): Promise; /** * An `` from another device of this account on an incoming call ends it (answered * elsewhere) and frees its slot. */ handleCallAccept(node: BinaryNode, peerJid: string): Promise; handleCallPreaccept(node: BinaryNode, peerJid: string): Promise; handleCallTransport(node: BinaryNode, peerJid: string): Promise; handleCallAck(node: BinaryNode): Promise; handleCallRelaylatency(node: BinaryNode, peerJid: string): void; handleRelayElection(node: BinaryNode): void; handleCallMuteV2(node: BinaryNode, peerJid: string): void; handleCallUserAction(node: BinaryNode, peerJid: string): void; /** * A top-level ``: a message type of its own rather than a variant of * ``, and both are live on the wire, so both have to route. */ handleCallRaiseHand(node: BinaryNode, peerJid: string): void; handleCallScreenShare(node: BinaryNode): void; handleCallVideoState(node: BinaryNode): void; /** A terminate that finds no call is kept, so the offer still decrypting for it is dropped. */ handleCallTerminate(node: BinaryNode, peerJid?: string): Promise; destroy(): void; private throwIfDestroyed; /** Terminates an offer that left while `destroy` ran; best effort, the socket may be closing. */ private withdrawOffer; /** Whether a terminate already ended this call before its offer was handled; consumes it. */ private takeTerminatedBeforeOffer; private get activeCallCount(); private createSession; /** * Hands an event from a remote media host to its call. Events for a call that no longer * exists are dropped: the host may still be winding down. */ handleMediaEvent(callId: string, event: WaCallMediaEvent): void; /** * The whole media plan of a call, for a host that joins late or lost track of * the messages. `null` for an unknown call, or when the media runs locally. */ getMediaSnapshot(callId: string): WaCallMediaMessage | null; private createMediaLink; private getSessionOrThrow; private resolveSessionFromNode; private resolveSessionForOfferAck; private announceIncoming; /** The lifecycle of a call the app was never told about stays silent. */ private emitState; private emitEnded; private resolvePeerLid; private resolvePeerDeviceJids; private maybeUnblockWaitingCalls; private activateWaitingIncoming; /** * Whether a `` or `` handled concurrently ended the call mid-setup; that * path already reported the end, so setup stops and cleans up again. */ private endedDuringSetup; }