@easemob-community/callkit-core 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/events/CallKitEvents.ts","../src/utils/logger.ts","../src/events/EventBus.ts","../src/types/callstate.types.ts","../src/state/SingleCallStateMachine.ts","../src/state/GroupCallSession.ts","../src/signaling/SignalRouter.ts","../src/signaling/SignalSender.ts","../src/signaling/SingleCallSignalHandler.ts","../src/signaling/GroupCallSignalHandler.ts","../src/im/IMListener.ts","../src/signaling/MessageBuilder.ts","../src/utils/callUtils.ts","../src/core/CallKitCore.ts","../src/index.ts"],"sourcesContent":["import type { CALL_TYPE } from '../types/callstate.types'\n\n// ────────────────────────────────────────────────\n// 基础事件结构\n// ────────────────────────────────────────────────\n\nexport interface BaseEvent {\n callId: string\n channel: string\n callType: CALL_TYPE\n callerUserId: string\n calleeUserId?: string\n groupId?: string\n}\n\n// ────────────────────────────────────────────────\n// 通用通话生命周期事件(向后兼容)\n// ────────────────────────────────────────────────\n\nexport interface IncomingCallEvent {\n type: 'incomingCall'\n payload: {\n callId: string\n callType: CALL_TYPE\n callerUserId: string\n callerDevId: string\n channel: string\n calleeUserId: string\n token?: string\n groupId?: string\n groupName?: string\n invitedMembers?: string[]\n callerInfo?: { nickname?: string; avatarURL?: string }\n }\n}\n\nexport interface CallInvitedEvent {\n type: 'callInvited'\n payload: BaseEvent & {\n isCaller: boolean\n }\n}\n\nexport interface SingleCallInvitedEvent {\n type: 'singleCallInvited'\n payload: BaseEvent & {\n isCaller: boolean\n }\n}\n\nexport interface GroupCallInvitedEvent {\n type: 'groupCallInvited'\n payload: BaseEvent & {\n isCaller: boolean\n }\n}\n\nexport interface CallStartedEvent {\n type: 'callStarted'\n payload: BaseEvent & {\n isCaller: boolean\n startTime: number\n }\n}\n\nexport interface CallAcceptedEvent {\n type: 'callAccepted'\n payload: BaseEvent & {\n isCaller: boolean\n }\n}\n\nexport interface CallConnectedEvent {\n type: 'callConnected'\n payload: BaseEvent\n}\n\nexport interface CallEndedEvent {\n type: 'callEnded'\n payload: BaseEvent & {\n reason: 'hangup' | 'cancel' | 'refuse' | 'busy' | 'timeout' | 'remoteHangup' | 'remoteCancel'\n duration?: number\n }\n}\n\nexport interface CallTimeoutEvent {\n type: 'callTimeout'\n payload: BaseEvent\n}\n\n// ────────────────────────────────────────────────\n// 单聊特定事件(UI 层精确订阅)\n// ────────────────────────────────────────────────\n\nexport interface SingleCallStartedEvent {\n type: 'singleCallStarted'\n payload: BaseEvent & {\n isCaller: boolean\n startTime: number\n }\n}\n\nexport interface SingleCallAcceptedEvent {\n type: 'singleCallAccepted'\n payload: BaseEvent & {\n isCaller: boolean\n }\n}\n\nexport interface SingleCallConnectedEvent {\n type: 'singleCallConnected'\n payload: BaseEvent\n}\n\nexport interface SingleCallEndedEvent {\n type: 'singleCallEnded'\n payload: BaseEvent & {\n reason: 'hangup' | 'cancel' | 'refuse' | 'busy' | 'timeout' | 'remoteHangup' | 'remoteCancel'\n duration?: number\n }\n}\n\nexport interface SingleCallTimeoutEvent {\n type: 'singleCallTimeout'\n payload: BaseEvent\n}\n\nexport interface SingleCallRefusedEvent {\n type: 'singleCallRefused'\n payload: BaseEvent & { isRemote: boolean }\n}\n\nexport interface SingleCallBusyEvent {\n type: 'singleCallBusy'\n payload: BaseEvent\n}\n\nexport interface SingleCallCanceledEvent {\n type: 'singleCallCanceled'\n payload: BaseEvent & { isRemote: boolean }\n}\n\n// ────────────────────────────────────────────────\n// 群聊特定事件(UI 层精确订阅)\n// ────────────────────────────────────────────────\n\nexport interface GroupCallStartedEvent {\n type: 'groupCallStarted'\n payload: BaseEvent & {\n isCaller: boolean\n startTime: number\n }\n}\n\nexport interface GroupCallAcceptedEvent {\n type: 'groupCallAccepted'\n payload: BaseEvent & {\n isCaller: boolean\n }\n}\n\nexport interface GroupCallConnectedEvent {\n type: 'groupCallConnected'\n payload: BaseEvent\n}\n\nexport interface GroupCallEndedEvent {\n type: 'groupCallEnded'\n payload: BaseEvent & {\n reason: 'hangup' | 'cancel' | 'refuse' | 'busy' | 'timeout' | 'remoteHangup' | 'remoteCancel'\n duration?: number\n }\n}\n\nexport interface GroupCallTimeoutEvent {\n type: 'groupCallTimeout'\n payload: BaseEvent\n}\n\nexport interface GroupCallRefusedEvent {\n type: 'groupCallRefused'\n payload: BaseEvent & { isRemote: boolean }\n}\n\nexport interface GroupCallBusyEvent {\n type: 'groupCallBusy'\n payload: BaseEvent\n}\n\nexport interface GroupCallCanceledEvent {\n type: 'groupCallCanceled'\n payload: BaseEvent & { isRemote: boolean }\n}\n\n// ────────────────────────────────────────────────\n// 状态/业务事件\n// ────────────────────────────────────────────────\n\nexport interface StatusChangedEvent {\n type: 'statusChanged'\n payload: BaseEvent & {\n from: string\n to: string\n }\n}\n\nexport interface CallRefusedEvent {\n type: 'callRefused'\n payload: BaseEvent & { isRemote: boolean }\n}\n\nexport interface CallBusyEvent {\n type: 'callBusy'\n payload: BaseEvent\n}\n\nexport interface CallCanceledEvent {\n type: 'callCanceled'\n payload: BaseEvent & { isRemote: boolean }\n}\n\n// ────────────────────────────────────────────────\n// RTC 指令事件\n// ────────────────────────────────────────────────\n\nexport interface ShouldJoinRtcEvent {\n type: 'shouldJoinRtc'\n payload: BaseEvent & {\n token: string\n uid: number | string\n /** Agora App ID(来自 IM 服务端 getRTCToken 返回),为空时上层使用初始化时传入的 appId */\n appId?: string\n role: 'caller' | 'callee'\n }\n}\n\nexport interface ShouldLeaveRtcEvent {\n type: 'shouldLeaveRtc'\n payload: BaseEvent & {\n reason: string\n }\n}\n\nexport interface ShouldPublishTracksEvent {\n type: 'shouldPublishTracks'\n payload: BaseEvent & {\n trackTypes: ('audio' | 'video')[]\n }\n}\n\n// ────────────────────────────────────────────────\n// 群聊特定事件\n// ────────────────────────────────────────────────\n\nexport interface GroupCallInitEvent {\n type: 'groupCallInit'\n payload: {\n callId: string\n groupId: string\n groupName: string\n channel: string\n callType: 'audio' | 'video'\n callerUserId: string\n invitedMembers: string[]\n }\n}\n\nexport interface ParticipantStateChangedEvent {\n type: 'participantStateChanged'\n payload: {\n callId: string\n userId: string\n state: 'invited' | 'accepted' | 'joinedRtc' | 'left'\n groupId?: string\n }\n}\n\nexport interface ParticipantJoinedEvent {\n type: 'participantJoined'\n payload: BaseEvent & {\n userId: string\n groupId?: string\n }\n}\n\nexport interface ParticipantLeftEvent {\n type: 'participantLeft'\n payload: BaseEvent & {\n userId: string\n reason: string\n groupId?: string\n }\n}\n\n// ────────────────────────────────────────────────\n// 媒体状态事件\n// ────────────────────────────────────────────────\n\nexport interface LocalAudioChangedEvent {\n type: 'localAudioChanged'\n payload: { enabled: boolean }\n}\n\nexport interface LocalVideoChangedEvent {\n type: 'localVideoChanged'\n payload: { enabled: boolean }\n}\n\n// ────────────────────────────────────────────────\n// RTC 上报事件(由 reportRtcEvent 触发)\n// ────────────────────────────────────────────────\n\nexport interface RtcReportEvent {\n type: 'rtcReport'\n payload: {\n type: string\n payload: Record<string, any>\n }\n}\n\n// ────────────────────────────────────────────────\n// 时长与错误事件\n// ────────────────────────────────────────────────\n\nexport interface CallDurationUpdatedEvent {\n type: 'callDurationUpdated'\n payload: BaseEvent & {\n duration: number\n }\n}\n\nexport interface CallErrorEvent {\n type: 'callError'\n payload: {\n /** 错误类型 */\n type: string\n /** 错误信息 */\n error: string\n /** 关联通话 ID */\n callId?: string\n /** 额外上下文 */\n context?: Record<string, any>\n }\n}\n\n// ────────────────────────────────────────────────\n// 联合类型\n// ────────────────────────────────────────────────\n\nexport type UIEvent =\n | IncomingCallEvent\n | CallInvitedEvent\n | CallStartedEvent\n | CallAcceptedEvent\n | CallConnectedEvent\n | CallEndedEvent\n | CallTimeoutEvent\n | StatusChangedEvent\n | CallRefusedEvent\n | CallBusyEvent\n | CallCanceledEvent\n | GroupCallInitEvent\n | ParticipantStateChangedEvent\n | ParticipantJoinedEvent\n | ParticipantLeftEvent\n | RtcReportEvent\n | CallDurationUpdatedEvent\n | CallErrorEvent\n // 精确单聊/群聊事件\n | SingleCallInvitedEvent\n | SingleCallStartedEvent\n | SingleCallAcceptedEvent\n | SingleCallConnectedEvent\n | SingleCallEndedEvent\n | SingleCallTimeoutEvent\n | SingleCallRefusedEvent\n | SingleCallBusyEvent\n | SingleCallCanceledEvent\n | GroupCallInvitedEvent\n | GroupCallStartedEvent\n | GroupCallAcceptedEvent\n | GroupCallConnectedEvent\n | GroupCallEndedEvent\n | GroupCallTimeoutEvent\n | GroupCallRefusedEvent\n | GroupCallBusyEvent\n | GroupCallCanceledEvent\n\nexport type RtcEvent =\n | ShouldJoinRtcEvent\n | ShouldLeaveRtcEvent\n | ShouldPublishTracksEvent\n | LocalAudioChangedEvent\n | LocalVideoChangedEvent\n\nexport type CallKitEvent = UIEvent | RtcEvent\n\n// ────────────────────────────────────────────────\n// 类型守卫(帮助按来源分发事件)\n// ────────────────────────────────────────────────\n\nconst rtcEventTypes: Set<CallKitEvent['type']> = new Set([\n 'shouldJoinRtc',\n 'shouldLeaveRtc',\n 'shouldPublishTracks',\n 'localAudioChanged',\n 'localVideoChanged',\n])\n\nconst uiEventTypes: Set<CallKitEvent['type']> = new Set([\n 'incomingCall',\n 'callInvited',\n 'callStarted',\n 'callAccepted',\n 'callConnected',\n 'callEnded',\n 'callTimeout',\n 'statusChanged',\n 'callRefused',\n 'callBusy',\n 'callCanceled',\n 'groupCallInit',\n 'participantStateChanged',\n 'participantJoined',\n 'participantLeft',\n 'rtcReport',\n 'callDurationUpdated',\n 'callError',\n // 精确单聊/群聊事件\n 'singleCallInvited',\n 'singleCallStarted',\n 'singleCallAccepted',\n 'singleCallConnected',\n 'singleCallEnded',\n 'singleCallTimeout',\n 'singleCallRefused',\n 'singleCallBusy',\n 'singleCallCanceled',\n 'groupCallInvited',\n 'groupCallStarted',\n 'groupCallAccepted',\n 'groupCallConnected',\n 'groupCallEnded',\n 'groupCallTimeout',\n 'groupCallRefused',\n 'groupCallBusy',\n 'groupCallCanceled',\n])\n\nexport function isUIEvent(event: CallKitEvent): event is UIEvent {\n return !rtcEventTypes.has(event.type)\n}\n\nexport function isRtcEvent(event: CallKitEvent): event is RtcEvent {\n return rtcEventTypes.has(event.type)\n}\n","/**\n * 轻量级日志接口\n * 核心库不依赖外部日志库(如 dexie),通过接口允许上层注入自定义 logger。\n */\n\nexport interface Logger {\n error(message: string, ...args: any[]): void\n warn(message: string, ...args: any[]): void\n info(message: string, ...args: any[]): void\n debug(message: string, ...args: any[]): void\n verbose(message: string, ...args: any[]): void\n signal?(direction: 'send' | 'recv', action: string, meta?: Record<string, any>): void\n stateChange?(from: any, to: any, meta?: Record<string, any>): void\n rtc?(event: string, meta?: Record<string, any>): void\n event?(event: string, meta?: Record<string, any>): void\n}\n\nconst defaultLogger: Logger = {\n error: (msg, ...args) => console.error(`[CallKitCore] ${msg}`, ...args),\n warn: (msg, ...args) => console.warn(`[CallKitCore] ${msg}`, ...args),\n info: (msg, ...args) => console.info(`[CallKitCore] ${msg}`, ...args),\n debug: (msg, ...args) => console.log(`[CallKitCore] ${msg}`, ...args),\n verbose: () => {}, // 默认关闭 verbose\n}\n\nlet globalLogger: Logger = defaultLogger\n\nexport function setLogger(logger: Logger): void {\n globalLogger = logger\n}\n\nexport function getLogger(): Logger {\n return globalLogger\n}\n","import type { Logger } from '../utils/logger'\nimport { getLogger } from '../utils/logger'\n\n/**\n * 轻量级事件总线\n * 不依赖外部库,内部使用 Map + Set 实现。\n */\nexport class EventBus<TEvents extends Record<string, any>> {\n private listeners = new Map<keyof TEvents, Set<(payload: any) => void>>()\n private logger: Logger\n\n constructor(logger?: Logger) {\n this.logger = logger || getLogger()\n }\n\n on<K extends keyof TEvents>(event: K, handler: (payload: TEvents[K]) => void): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(handler)\n this.logger.verbose?.(`[EventBus] 订阅事件: ${String(event)}`)\n return () => this.off(event, handler)\n }\n\n once<K extends keyof TEvents>(event: K, handler: (payload: TEvents[K]) => void): () => void {\n const onceHandler = (payload: TEvents[K]) => {\n this.off(event, onceHandler as unknown as (payload: TEvents[K]) => void)\n handler(payload)\n }\n return this.on(event, onceHandler as unknown as (payload: TEvents[K]) => void)\n }\n\n off<K extends keyof TEvents>(event: K, handler: (payload: TEvents[K]) => void): void {\n this.listeners.get(event)?.delete(handler as unknown as (payload: any) => void)\n }\n\n emit<K extends keyof TEvents>(event: K, payload: TEvents[K]): void {\n const handlers = this.listeners.get(event)\n if (!handlers || handlers.size === 0) {\n this.logger.verbose?.(`[EventBus] 事件 ${String(event)} 无订阅者,跳过`)\n return\n }\n this.logger.debug?.(`[EventBus] 触发事件: ${String(event)}`, payload)\n handlers.forEach((handler) => {\n try {\n handler(payload)\n } catch (error) {\n this.logger.error(`[EventBus] 事件 ${String(event)} 的 handler 执行失败:`, error)\n }\n })\n }\n\n clear(event?: keyof TEvents): void {\n if (event) {\n this.listeners.delete(event)\n } else {\n this.listeners.clear()\n }\n }\n\n listenerCount(event: keyof TEvents): number {\n return this.listeners.get(event)?.size ?? 0\n }\n}\n","export type CallMode = \"audio\" | \"video\" | \"group\";\n\nexport type CALL_STATUS_NAME =\n | \"idle\"\n | \"inviting\"\n | \"ringing\"\n | \"connecting\"\n | \"connected\"\n | \"ended\";\n\n// Call status type and constants\nexport type CALL_STATUS = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;\nexport const CALL_STATUS = {\n IDLE: 0,\n INVITING: 1,\n ALERTING: 2,\n CONFIRM_RING: 3,\n RECEIVED_CONFIRM_RING: 4,\n ANSWER_CALL: 5,\n CONFIRM_CALLEE: 6,\n IN_CALL: 7,\n} as const;\n// Callkit command message result type and constants\nexport type CALLKIT_CMD_MSG_RESULT_TYPE = \"accept\" | \"refuse\" | \"busy\";\nexport const CALLKIT_CMD_MSG_RESULT_TYPE = {\n ACCEPT: \"accept\",\n REFUSE: \"refuse\",\n BUSY: \"busy\",\n} as const;\nexport type CALLKIT_ACTION_MSG_TYPE = \"rtcCallWithAgora\";\nexport type CALLKIT_TEXT_MSG_ACTION = \"invite\";\n\nexport type CALLKIT_CMD_MSG_ACTION_TYPE =\n | \"confirmRing\"\n | \"alert\"\n | \"answerCall\"\n | \"leaveCall\"\n | \"confirmCallee\"\n | \"cancelCall\";\n// Call info interface\n// Call type and constants\nexport type CALL_TYPE = 0 | 1 | 2 | 3;\nexport const CALL_TYPE = {\n AUDIO_1V1: 0, // 一对一语音通话\n VIDEO_1V1: 1, // 一对一视频通话\n VIDEO_MULTI: 2, // 多人视频通话\n AUDIO_MULTI: 3, // 多人语音通话\n} as const;\nexport interface CALL_INFO {\n callId: string; // Call ID\n channel: string; // channelName\n token?: string; // Internal use\n type: CALL_TYPE; // Call type\n callerDevId: string; // Caller device ID (optional)\n calleeDevId?: string; // Callee device ID (optional)\n callerUserId: string; // Caller user ID\n calleeUserId?: string; // Callee user ID (optional)\n groupId?: string;\n groupName?: string;\n groupAvatar?: string;\n callerNickname?: string; // Caller user nickname (optional)\n invitedMembers?: string[]; // Internal use\n joinedMembers?: any[]; // Internal use\n inviteMessageId?: string; // Invitation message ID\n duration?: string; // Call duration, default 0\n state?: CALL_STATUS; // Call status, default idle\n}\n\n// Hang up reason type and constants\nexport type HANGUP_REASON =\n | \"hangup\"\n | \"cancel\"\n | \"remoteCancel\"\n | \"refuse\"\n | \"remoteRefuse\"\n | \"busy\"\n | \"noResponse\"\n | \"remoteNoResponse\"\n | \"handleOnOtherDevice\"\n | \"abnormalEnd\";\nexport const HANGUP_REASON = {\n HANGUP: \"hangup\", // Hang up call\n CANCEL: \"cancel\", // Cancel call\n REMOTE_CANCEL: \"remoteCancel\", // Remote cancel call\n REFUSE: \"refuse\", // Refuse call\n REMOTE_REFUSE: \"remoteRefuse\", // Remote refuse call\n BUSY: \"busy\", // Busy\n NO_RESPONSE: \"noResponse\", // No response (timeout)\n REMOTE_NO_RESPONSE: \"remoteNoResponse\", // Remote no response\n HANDLE_ON_OTHER_DEVICE: \"handleOnOtherDevice\", // Handled on other device\n ABNORMAL_END: \"abnormalEnd\", // Abnormal end\n} as const;\n","import { CALL_STATUS, CALL_TYPE, HANGUP_REASON } from '../types/callstate.types'\nimport type { Logger } from '../utils/logger'\nimport { getLogger } from '../utils/logger'\n\n// ────────────────────────────────────────────────\n// 状态与事件类型\n// ────────────────────────────────────────────────\n\nexport interface SingleCallState {\n status: CALL_STATUS\n callId: string\n channel: string\n token: string\n type: CALL_TYPE\n callerDevId: string\n calleeDevId: string\n callerUserId: string\n calleeUserId: string\n inviteTimeout: number\n inviteTimeoutTimer: ReturnType<typeof setTimeout> | null\n startTime: number | null\n audioEnabled: boolean\n videoEnabled: boolean\n}\n\nexport type DomainEvent =\n // ─── 单聊状态事件 ───\n | { type: 'STATUS_CHANGED'; from: CALL_STATUS; to: CALL_STATUS; callId: string }\n | { type: 'CALL_INVITED'; callId: string; isCaller: boolean; channel: string; callType: CALL_TYPE }\n | { type: 'CALL_STARTED'; callId: string; isCaller: boolean; channel: string; callType: CALL_TYPE }\n | { type: 'CALL_ACCEPTED'; callId: string; isCaller: boolean; channel: string; callType: CALL_TYPE }\n | { type: 'CALL_CONNECTED'; callId: string; channel: string; callType: CALL_TYPE }\n | { type: 'CALL_ENDED'; callId: string; reason: string; duration: number }\n | { type: 'CALL_TIMEOUT'; callId: string }\n | { type: 'CALL_REFUSED'; callId: string; isRemote: boolean }\n | { type: 'CALL_BUSY'; callId: string }\n | { type: 'CALL_CANCELED'; callId: string; isRemote: boolean }\n | {\n type: 'SHOULD_JOIN_RTC'\n callId: string\n channel: string\n token: string\n role: 'caller' | 'callee'\n callType: CALL_TYPE\n }\n // ─── 群聊事件 ───\n | {\n type: 'GROUP_CALL_INIT'\n callId: string\n groupId: string\n groupName: string\n channel: string\n callType: 'audio' | 'video'\n callerUserId: string\n invitedMembers: string[]\n }\n | {\n type: 'PARTICIPANT_STATE_CHANGED'\n callId: string\n userId: string\n state: 'invited' | 'accepted' | 'joinedRtc' | 'left'\n groupId?: string\n }\n | {\n type: 'PARTICIPANT_JOINED'\n callId: string\n userId: string\n channel: string\n callType: CALL_TYPE\n groupId?: string\n }\n | {\n type: 'PARTICIPANT_LEFT'\n callId: string\n userId: string\n channel: string\n callType: CALL_TYPE\n reason: string\n groupId?: string\n }\n // ─── 媒体状态事件 ───\n | { type: 'LOCAL_AUDIO_CHANGED'; callId: string; enabled: boolean }\n | { type: 'LOCAL_VIDEO_CHANGED'; callId: string; enabled: boolean }\n\nexport interface TransitionResult {\n ok: boolean\n events: DomainEvent[]\n}\n\n// ────────────────────────────────────────────────\n\nconst DEFAULT_TIMEOUT = 30000\n\nfunction createIdleState(preserve?: { callerDevId: string; callerUserId: string }): SingleCallState {\n return {\n status: CALL_STATUS.IDLE,\n callId: '',\n channel: '',\n token: '',\n type: CALL_TYPE.AUDIO_1V1,\n callerDevId: preserve?.callerDevId ?? '',\n calleeDevId: '',\n callerUserId: preserve?.callerUserId ?? '',\n calleeUserId: '',\n inviteTimeout: DEFAULT_TIMEOUT,\n inviteTimeoutTimer: null,\n startTime: null,\n audioEnabled: true,\n videoEnabled: true,\n }\n}\n\n/**\n * 单聊通话状态机\n *\n * 职责:\n * 1. 管理 IDLE → INVITING → ALERTING → RECEIVED_CONFIRM_RING → IN_CALL 的状态流转\n * 2. 校验 callId、deviceId、多端冲突(由 Handler 调用前预校验,状态机二次兜底)\n * 3. 管理邀请超时定时器\n * 4. 计算通话时长\n *\n * 不执行任何副作用(不发消息、不 join RTC),只返回领域事件。\n *\n * 状态流转规则从现有 lib/store/callState.ts + lib/signaling/SingleCallSignalHandler.ts 提取。\n */\nexport class SingleCallStateMachine {\n private state: SingleCallState = createIdleState()\n private logger: Logger\n\n constructor(logger?: Logger) {\n this.logger = logger || getLogger()\n }\n\n // ─── 查询 ───\n\n getState(): Readonly<SingleCallState> {\n return Object.freeze({ ...this.state })\n }\n\n isIdle(): boolean {\n return this.state.status === CALL_STATUS.IDLE\n }\n\n isInCall(): boolean {\n return this.state.status === CALL_STATUS.IN_CALL\n }\n\n /**\n * 当前是否处于可被接听的状态(被叫端弹窗显示区间)\n */\n isWaitingCalleeAction(): boolean {\n return (\n this.state.status === CALL_STATUS.ALERTING ||\n this.state.status === CALL_STATUS.CONFIRM_RING ||\n this.state.status === CALL_STATUS.RECEIVED_CONFIRM_RING\n )\n }\n\n /**\n * 当前是否处于活跃通话中(已进入 RTC 或即将进入)\n */\n isInActiveCall(): boolean {\n return (\n this.state.status === CALL_STATUS.ANSWER_CALL ||\n this.state.status === CALL_STATUS.CONFIRM_CALLEE ||\n this.state.status === CALL_STATUS.IN_CALL\n )\n }\n\n /**\n * 当前是否可以接听(被叫端按钮可点击)\n */\n canAccept(): boolean {\n return (\n this.state.status === CALL_STATUS.ALERTING ||\n this.state.status === CALL_STATUS.RECEIVED_CONFIRM_RING\n )\n }\n\n /**\n * 当前是否可以拒绝(被叫端按钮可点击)\n */\n canReject(): boolean {\n return (\n this.state.status === CALL_STATUS.ALERTING ||\n this.state.status === CALL_STATUS.RECEIVED_CONFIRM_RING\n )\n }\n\n /**\n * 当前是否可以挂断(主叫/被叫端的挂断按钮可点击)\n */\n canHangup(): boolean {\n return this.state.status !== CALL_STATUS.IDLE\n }\n\n /**\n * 当前是否处于呼叫/响铃中(主叫等待对方接听)\n */\n isCalling(): boolean {\n return (\n this.state.status === CALL_STATUS.INVITING ||\n this.state.status === CALL_STATUS.CONFIRM_RING\n )\n }\n\n isCallIdMatch(incomingCallId: string): boolean {\n return this.state.callId === incomingCallId\n }\n\n getDuration(): number {\n if (!this.state.startTime) return 0\n return Date.now() - this.state.startTime\n }\n\n // ─── 初始化 ───\n\n /**\n * 主叫方发起邀请\n */\n initInvite(params: {\n calleeUserId: string\n callType: CALL_TYPE\n callerDevId: string\n callerUserId: string\n callId: string\n channel: string\n token: string\n timeout?: number\n }): TransitionResult {\n this.clearTimeout()\n const oldStatus = this.state.status\n\n this.state = {\n ...createIdleState({\n callerDevId: params.callerDevId,\n callerUserId: params.callerUserId,\n }),\n status: CALL_STATUS.INVITING,\n calleeUserId: params.calleeUserId,\n type: params.callType,\n callId: params.callId,\n channel: params.channel,\n token: params.token,\n inviteTimeout: params.timeout ?? DEFAULT_TIMEOUT,\n }\n\n // 超时定时器由 CallKitCore 层管理,不在状态机内启动\n this.logger.stateChange?.(oldStatus, CALL_STATUS.INVITING, { callId: params.callId })\n\n return {\n ok: true,\n events: [\n {\n type: 'STATUS_CHANGED',\n from: oldStatus,\n to: CALL_STATUS.INVITING,\n callId: params.callId,\n },\n {\n type: 'CALL_INVITED',\n callId: params.callId,\n isCaller: true,\n channel: params.channel,\n callType: params.callType,\n },\n ],\n }\n }\n\n /**\n * 被叫方收到 invite,初始化响铃状态\n */\n initIncoming(params: {\n callId: string\n channel: string\n token: string\n callType: CALL_TYPE\n callerDevId: string\n callerUserId: string\n calleeDevId: string\n calleeUserId: string\n }): TransitionResult {\n this.clearTimeout()\n const oldStatus = this.state.status\n\n this.state = {\n ...createIdleState(),\n status: CALL_STATUS.ALERTING,\n callId: params.callId,\n channel: params.channel,\n token: params.token,\n type: params.callType,\n callerDevId: params.callerDevId,\n callerUserId: params.callerUserId,\n calleeDevId: params.calleeDevId,\n calleeUserId: params.calleeUserId,\n }\n\n // 超时定时器由 CallKitCore 层管理,不在状态机内启动\n this.logger.stateChange?.(oldStatus, CALL_STATUS.ALERTING, { callId: params.callId })\n\n return {\n ok: true,\n events: [\n {\n type: 'STATUS_CHANGED',\n from: oldStatus,\n to: CALL_STATUS.ALERTING,\n callId: params.callId,\n },\n {\n type: 'CALL_INVITED',\n callId: params.callId,\n isCaller: false,\n channel: params.channel,\n callType: params.callType,\n },\n ],\n }\n }\n\n // ─── 信令响应 ───\n\n /**\n * 主叫方收到 alert(被叫已响铃)\n *\n * 与 lib 对齐:收到 alert 后保持 INVITING 状态不变(lib 的 handleAlertSignalMessage\n * 不修改 callState.status),只记录 calleeDevId 和发送 confirmRing。\n * 状态直到收到 confirmRing 后才变为 RECEIVED_CONFIRM_RING。\n */\n receiveAlert(calleeDevId: string): TransitionResult {\n if (this.state.status !== CALL_STATUS.INVITING) {\n this.logger.warn('[SingleCallStateMachine] receiveAlert: 当前状态不是 INVITING,忽略', {\n status: this.state.status,\n })\n return { ok: false, events: [] }\n }\n\n // 不改变状态,只记录被叫设备 ID(与 lib 对齐)\n this.state.calleeDevId = calleeDevId\n this.logger.info('[SingleCallStateMachine] receiveAlert: 保持 INVITING,记录 calleeDevId', {\n callId: this.state.callId,\n calleeDevId,\n })\n\n return { ok: true, events: [] }\n }\n\n /**\n * 被叫方收到 confirmRing\n */\n receiveConfirmRing(status: boolean): TransitionResult {\n if (this.state.status < CALL_STATUS.ALERTING) {\n this.logger.warn('[SingleCallStateMachine] receiveConfirmRing: 当前状态 < ALERTING,忽略')\n return { ok: false, events: [] }\n }\n if (this.state.status === CALL_STATUS.RECEIVED_CONFIRM_RING) {\n this.logger.info('[SingleCallStateMachine] receiveConfirmRing: 已是 RECEIVED_CONFIRM_RING,忽略')\n return { ok: false, events: [] }\n }\n if (!status) {\n this.logger.warn('[SingleCallStateMachine] receiveConfirmRing: status=false,忽略')\n return { ok: false, events: [] }\n }\n\n this.clearTimeout()\n const oldStatus = this.state.status\n this.state.status = CALL_STATUS.RECEIVED_CONFIRM_RING\n this.logger.stateChange?.(oldStatus, CALL_STATUS.RECEIVED_CONFIRM_RING, { callId: this.state.callId })\n\n return {\n ok: true,\n events: [\n {\n type: 'STATUS_CHANGED',\n from: oldStatus,\n to: CALL_STATUS.RECEIVED_CONFIRM_RING,\n callId: this.state.callId,\n },\n ],\n }\n }\n\n /**\n * 收到 answerCall 信令\n */\n receiveAnswer(result: 'accept' | 'refuse' | 'busy', fromCaller?: boolean): TransitionResult {\n this.clearTimeout()\n\n // 已在通话中,忽略(防止重复 accept)\n if (this.state.status === CALL_STATUS.IN_CALL) {\n this.logger.info('[SingleCallStateMachine] receiveAnswer: 已在 IN_CALL,忽略')\n return { ok: false, events: [] }\n }\n\n if (result === 'accept') {\n const oldStatus = this.state.status\n this.state.status = CALL_STATUS.IN_CALL\n this.state.startTime = Date.now()\n this.logger.stateChange?.(oldStatus, CALL_STATUS.IN_CALL, { callId: this.state.callId })\n\n const isCaller = !fromCaller // 如果是主叫收到 accept,则 isCaller=true\n const events: DomainEvent[] = [\n {\n type: 'STATUS_CHANGED',\n from: oldStatus,\n to: CALL_STATUS.IN_CALL,\n callId: this.state.callId,\n },\n {\n type: 'CALL_ACCEPTED',\n callId: this.state.callId,\n isCaller,\n channel: this.state.channel,\n callType: this.state.type,\n },\n {\n type: 'CALL_STARTED',\n callId: this.state.callId,\n isCaller,\n channel: this.state.channel,\n callType: this.state.type,\n },\n {\n type: 'SHOULD_JOIN_RTC',\n callId: this.state.callId,\n channel: this.state.channel,\n token: this.state.token,\n role: fromCaller ? 'callee' : 'caller',\n callType: this.state.type,\n },\n ]\n return { ok: true, events }\n }\n\n // refuse / busy\n const oldStatus = this.state.status\n const duration = 0\n const reason = result === 'busy' ? HANGUP_REASON.BUSY : HANGUP_REASON.REMOTE_REFUSE\n const callId = this.state.callId\n this.resetCore()\n this.logger.stateChange?.(oldStatus, CALL_STATUS.IDLE, { callId, trigger: `answer:${result}` })\n\n const events: DomainEvent[] = [\n {\n type: 'CALL_ENDED',\n callId,\n reason,\n duration,\n },\n ]\n if (result === 'busy') {\n events.unshift({ type: 'CALL_BUSY', callId })\n } else {\n events.unshift({ type: 'CALL_REFUSED', callId, isRemote: true })\n }\n return { ok: true, events }\n }\n\n /**\n * 收到 cancelCall 信令\n * \n * 注:callId 校验和多端容错由 Handler 负责,状态机只处理匹配后的状态流转。\n */\n receiveCancel(): TransitionResult {\n const currentStatus = this.state.status\n const callId = this.state.callId\n\n if (currentStatus === CALL_STATUS.IDLE) {\n this.logger.warn('[SingleCallStateMachine] receiveCancel: 当前状态 IDLE,忽略')\n return { ok: false, events: [] }\n }\n\n this.clearTimeout()\n this.resetCore()\n this.logger.stateChange?.(currentStatus, CALL_STATUS.IDLE, { callId, trigger: 'cancel' })\n\n return {\n ok: true,\n events: [\n { type: 'CALL_CANCELED', callId, isRemote: true },\n { type: 'CALL_ENDED', callId, reason: HANGUP_REASON.REMOTE_CANCEL, duration: 0 },\n ],\n }\n }\n\n /**\n * 收到 leaveCall 信令\n * \n * 注:callId 校验和多端容错由 Handler 负责,状态机只处理匹配后的状态流转。\n */\n receiveLeave(): TransitionResult {\n const currentStatus = this.state.status\n const callId = this.state.callId\n\n if (currentStatus === CALL_STATUS.IDLE) {\n return { ok: false, events: [] }\n }\n if (currentStatus === CALL_STATUS.IN_CALL) {\n const duration = this.getDuration()\n this.resetCore()\n this.logger.stateChange?.(currentStatus, CALL_STATUS.IDLE, { callId, trigger: 'leave' })\n return {\n ok: true,\n events: [{ type: 'CALL_ENDED', callId, reason: HANGUP_REASON.HANGUP, duration }],\n }\n }\n // ALERTING/INVITING 状态下收到 leave → 也挂断(兼容 caller 在响铃阶段离开)\n this.resetCore()\n this.logger.stateChange?.(currentStatus, CALL_STATUS.IDLE, { callId, trigger: 'leave:alerting' })\n return {\n ok: true,\n events: [{ type: 'CALL_ENDED', callId, reason: HANGUP_REASON.HANGUP, duration: 0 }],\n }\n }\n\n /**\n * 收到 confirmCallee 信令(被叫方)\n */\n receiveConfirmCallee(): TransitionResult {\n // IDLE 状态下收到 confirmCallee → 忽略(可能已主动拒绝/挂断)\n if (this.state.status === CALL_STATUS.IDLE) {\n this.logger.warn('[SingleCallStateMachine] receiveConfirmCallee: 当前 IDLE,忽略')\n return { ok: false, events: [] }\n }\n\n // 已在 IN_CALL 状态 → 不重复派发事件(防止群聊被叫二次 join)\n if (this.state.status === CALL_STATUS.IN_CALL) {\n this.logger.info('[SingleCallStateMachine] receiveConfirmCallee: 已是 IN_CALL,跳过')\n return { ok: false, events: [] }\n }\n\n // 保存旧状态后再修改(修复 #7:from 字段永远是 IN_CALL 的 bug)\n const oldStatus = this.state.status\n this.state.status = CALL_STATUS.IN_CALL\n this.state.startTime = Date.now()\n this.clearTimeout()\n this.logger.stateChange?.(oldStatus, CALL_STATUS.IN_CALL, { callId: this.state.callId })\n\n const events: DomainEvent[] = [\n {\n type: 'STATUS_CHANGED',\n from: oldStatus,\n to: CALL_STATUS.IN_CALL,\n callId: this.state.callId,\n },\n {\n type: 'CALL_CONNECTED',\n callId: this.state.callId,\n channel: this.state.channel,\n callType: this.state.type,\n },\n {\n type: 'CALL_STARTED',\n callId: this.state.callId,\n isCaller: false,\n channel: this.state.channel,\n callType: this.state.type,\n },\n {\n type: 'SHOULD_JOIN_RTC',\n callId: this.state.callId,\n channel: this.state.channel,\n token: this.state.token,\n role: 'callee',\n callType: this.state.type,\n },\n ]\n return { ok: true, events }\n }\n\n // ─── 本地动作 ───\n\n /**\n * 本地挂断/取消\n */\n hangup(reason: string = HANGUP_REASON.HANGUP): TransitionResult {\n const currentStatus = this.state.status\n if (currentStatus === CALL_STATUS.IDLE) {\n this.logger.warn('[SingleCallStateMachine] hangup: 当前状态 IDLE,忽略')\n return { ok: false, events: [] }\n }\n\n const duration = currentStatus === CALL_STATUS.IN_CALL ? this.getDuration() : 0\n const callId = this.state.callId\n this.clearTimeout()\n this.resetCore()\n this.logger.stateChange?.(currentStatus, CALL_STATUS.IDLE, { callId, trigger: 'hangup' })\n\n return {\n ok: true,\n events: [{ type: 'CALL_ENDED', callId, reason, duration }],\n }\n }\n\n /**\n * 邀请超时\n */\n timeout(): TransitionResult {\n const currentStatus = this.state.status\n if (currentStatus === CALL_STATUS.IDLE || currentStatus === CALL_STATUS.IN_CALL) {\n return { ok: false, events: [] }\n }\n\n const callId = this.state.callId\n this.resetCore()\n this.logger.stateChange?.(currentStatus, CALL_STATUS.IDLE, { callId, trigger: 'timeout' })\n\n return {\n ok: true,\n events: [\n { type: 'CALL_TIMEOUT', callId },\n { type: 'CALL_ENDED', callId, reason: HANGUP_REASON.NO_RESPONSE, duration: 0 },\n ],\n }\n }\n\n /**\n * 强制重置状态机\n */\n reset(): void {\n this.clearTimeout()\n const oldStatus = this.state.status\n const preserved = { callerDevId: this.state.callerDevId, callerUserId: this.state.callerUserId }\n this.state = createIdleState(preserved)\n this.logger.stateChange?.(oldStatus, CALL_STATUS.IDLE, { trigger: 'forceReset' })\n }\n\n // ─── 超时管理(由外部调用) ───\n\n /**\n * 启动超时定时器。\n * 超时后自动调用 onTimeout 回调(由 CallKitCore 注册),确保事件不会被丢弃。\n */\n startTimeout(onTimeout?: (result: TransitionResult) => void): void {\n this.clearTimeout()\n this.state.inviteTimeoutTimer = setTimeout(() => {\n const result = this.timeout()\n if (onTimeout && result.ok) {\n onTimeout(result)\n }\n }, this.state.inviteTimeout)\n }\n\n private clearTimeout(): void {\n if (this.state.inviteTimeoutTimer) {\n clearTimeout(this.state.inviteTimeoutTimer)\n this.state.inviteTimeoutTimer = null\n }\n }\n\n /**\n * 核心重置:保留 callerDevId / callerUserId,其余清空\n * 与现有 callStateStore.resetCallState() 行为一致\n */\n private resetCore(): void {\n const preserved = { callerDevId: this.state.callerDevId, callerUserId: this.state.callerUserId }\n this.state = createIdleState(preserved)\n }\n\n // ─── 媒体状态 ───\n\n /**\n * 切换本地音频状态\n */\n toggleAudio(): TransitionResult {\n this.state.audioEnabled = !this.state.audioEnabled\n return {\n ok: true,\n events: [\n {\n type: 'LOCAL_AUDIO_CHANGED',\n callId: this.state.callId,\n enabled: this.state.audioEnabled,\n },\n ],\n }\n }\n\n /**\n * 切换本地视频状态\n */\n toggleVideo(): TransitionResult {\n this.state.videoEnabled = !this.state.videoEnabled\n return {\n ok: true,\n events: [\n {\n type: 'LOCAL_VIDEO_CHANGED',\n callId: this.state.callId,\n enabled: this.state.videoEnabled,\n },\n ],\n }\n }\n}\n","import type { Logger } from '../utils/logger'\nimport { getLogger } from '../utils/logger'\n\n/**\n * 群聊参与者\n */\nexport interface GroupParticipant {\n userId: string\n nickname: string\n avatarUrl?: string\n state: 'invited' | 'accepted' | 'joinedRtc' | 'left'\n isLocal: boolean\n isMuted: boolean\n isCameraOn: boolean\n isSpeaking: boolean\n}\n\n/**\n * 群聊会话状态\n */\nexport interface GroupSessionState {\n sessionId: string\n groupId: string\n groupName: string\n callType: 'audio' | 'video'\n status: 'inviting' | 'inCall' | 'ended'\n callerUserId: string\n startTime: number\n}\n\n/**\n * 群聊会话管理\n *\n * 职责:\n * 1. 管理群聊通话的会话元数据\n * 2. 维护参与者集合及其状态流转\n * 3. 不执行副作用,只维护内存状态\n */\nexport class GroupCallSession {\n private session: GroupSessionState | null = null\n private participants = new Map<string, GroupParticipant>()\n private logger: Logger\n\n constructor(logger?: Logger) {\n this.logger = logger || getLogger()\n }\n\n /**\n * 初始化会话\n */\n init(params: {\n sessionId: string\n groupId: string\n groupName: string\n callType: 'audio' | 'video'\n callerUserId: string\n }): void {\n this.session = {\n ...params,\n status: 'inviting',\n startTime: Date.now(),\n }\n this.participants.clear()\n this.logger.info('[GroupCallSession] 初始化', params)\n }\n\n /**\n * 添加参与者\n */\n addParticipant(info: GroupParticipant): void {\n this.participants.set(info.userId, { ...info })\n this.logger.info('[GroupCallSession] 添加参与者', { userId: info.userId, state: info.state })\n }\n\n /**\n * 移除参与者\n */\n removeParticipant(userId: string): boolean {\n const removed = this.participants.delete(userId)\n if (removed) {\n this.logger.info('[GroupCallSession] 移除参与者', { userId })\n }\n return removed\n }\n\n /**\n * 标记参与者状态\n */\n setParticipantState(userId: string, state: GroupParticipant['state']): boolean {\n const p = this.participants.get(userId)\n if (!p) return false\n p.state = state\n this.logger.info('[GroupCallSession] 更新参与者状态', { userId, state })\n return true\n }\n\n /**\n * 标记已接受\n */\n markAccepted(userId: string): boolean {\n return this.setParticipantState(userId, 'accepted')\n }\n\n /**\n * 标记已加入 RTC\n */\n markJoinedRtc(userId: string): boolean {\n const ok = this.setParticipantState(userId, 'joinedRtc')\n if (ok && this.session && this.session.status === 'inviting') {\n this.session.status = 'inCall'\n }\n return ok\n }\n\n /**\n * 标记已离开 RTC\n */\n markLeftRtc(userId: string): boolean {\n return this.setParticipantState(userId, 'left')\n }\n\n /**\n * 标记音频静音状态\n */\n markAudioMuted(userId: string, muted: boolean): boolean {\n const p = this.participants.get(userId)\n if (!p) return false\n p.isMuted = muted\n this.logger.info('[GroupCallSession] 更新音频状态', { userId, muted })\n return true\n }\n\n /**\n * 标记视频开关状态\n */\n markVideoOn(userId: string, on: boolean): boolean {\n const p = this.participants.get(userId)\n if (!p) return false\n p.isCameraOn = on\n this.logger.info('[GroupCallSession] 更新视频状态', { userId, on })\n return true\n }\n\n /**\n * 获取参与者\n */\n getParticipant(userId: string): Readonly<GroupParticipant> | undefined {\n const p = this.participants.get(userId)\n return p ? Object.freeze({ ...p }) : undefined\n }\n\n /**\n * 获取所有参与者\n */\n getAllParticipants(): Readonly<GroupParticipant>[] {\n return Array.from(this.participants.values()).map((p) => Object.freeze({ ...p }))\n }\n\n /**\n * 获取当前在线参与者(未离开)\n */\n getActiveParticipants(): Readonly<GroupParticipant>[] {\n return this.getAllParticipants().filter((p) => p.state !== 'left')\n }\n\n /**\n * 获取会话快照\n */\n getSnapshot(): Readonly<GroupSessionState> | null {\n return this.session ? Object.freeze({ ...this.session }) : null\n }\n\n /**\n * 结束会话\n */\n end(): void {\n if (this.session) {\n this.session.status = 'ended'\n }\n this.logger.info('[GroupCallSession] 会话结束')\n }\n\n /**\n * 销毁会话\n */\n destroy(): void {\n this.session = null\n this.participants.clear()\n this.logger.info('[GroupCallSession] 已销毁')\n }\n}\n","import type { Logger } from '../utils/logger'\nimport { getLogger } from '../utils/logger'\nimport type { SignalingExt } from '../types/signal.types'\nimport type { DomainEvent } from '../state/SingleCallStateMachine'\n\n/**\n * 信令消息体(从 useListenerManager 提取,去框架依赖)\n */\nexport interface CmdMsgBody {\n from?: string\n to?: string\n id?: string\n action?: string\n ext?: Partial<SignalingExt> & { [key: string]: any }\n [key: string]: any\n}\n\nexport interface SignalHandler {\n handle(message: CmdMsgBody): DomainEvent[]\n}\n\n/**\n * SignalRouter\n * 信令消息中央路由器\n * 职责:根据 action 将消息分发给已注册的 Handler\n */\nexport class SignalRouter {\n private handlers = new Map<string, SignalHandler[]>()\n private logger: Logger\n\n constructor(logger?: Logger) {\n this.logger = logger || getLogger()\n this.logger.warn('📡 [SignalRouter] 信令路由器已初始化')\n }\n\n register(action: string, handler: SignalHandler) {\n if (!this.handlers.has(action)) {\n this.handlers.set(action, [])\n }\n this.handlers.get(action)!.push(handler)\n }\n\n dispatch(message: CmdMsgBody): DomainEvent[] {\n const action = message.ext?.action\n if (!action) {\n this.logger.warn('[SignalRouter] 消息缺少 action,无法分发', message)\n return []\n }\n this.logger.signal?.('recv', action, {\n from: message.from,\n to: message.to,\n callId: message.ext?.callId,\n result: message.ext?.result,\n deviceId: message.ext?.callerDevId || message.ext?.calleeDevId,\n })\n const handlers = this.handlers.get(action) || []\n if (handlers.length === 0) {\n this.logger.warn(`[SignalRouter] 未注册 action \"${action}\" 的处理器`)\n return []\n }\n const allEvents: DomainEvent[] = []\n handlers.forEach((h) => {\n try {\n const result = h.handle(message)\n if (result && Array.isArray(result)) {\n allEvents.push(...result)\n }\n } catch (err) {\n this.logger.error('[SignalRouter] Handler 执行失败:', err)\n }\n })\n return allEvents\n }\n}\n","import type { EasemobConnection } from '../core/CallKitCore.types'\nimport type { SignalingExt } from '../types/signal.types'\nimport type { Logger } from '../utils/logger'\nimport { getLogger } from '../utils/logger'\n\n/**\n * SignalSender\n * 信令发送器,封装环信 IM SDK 的消息发送。\n *\n * 由 useSignalManager 改造而来,从 Vue Composable 退化为普通类。\n */\nexport class SignalSender {\n private imClient: EasemobConnection\n private logger: Logger\n private createMessageFn?: (options: any) => any\n\n constructor(imClient: EasemobConnection, logger?: Logger, createMessageFn?: (options: any) => any) {\n this.imClient = imClient\n this.logger = logger || getLogger()\n this.createMessageFn = createMessageFn\n }\n\n /**\n * 发送 invite 文本消息\n */\n async sendInviteMessage(\n targetId: string | string[],\n chatType: 'singleChat' | 'groupChat',\n message: string,\n ext: SignalingExt | Record<string, any>,\n groupId?: string\n ): Promise<any> {\n const isGroupChat = Array.isArray(targetId)\n const to = isGroupChat ? groupId || '' : targetId\n const msgBody: any = {\n type: 'txt',\n to,\n msg: message,\n chatType: isGroupChat ? 'groupChat' : chatType,\n ext,\n }\n if (isGroupChat && Array.isArray(targetId)) {\n msgBody.receiverList = targetId\n }\n\n const msg = this.createMessage(msgBody)\n this.logger.debug?.('[SignalSender] sendInviteMessage msgBody', JSON.parse(JSON.stringify(msgBody)))\n this.logger.debug?.('[SignalSender] sendInviteMessage ext', {\n action: msgBody.ext?.action,\n callId: msgBody.ext?.callId,\n callerIMName: msgBody.ext?.callerIMName,\n calleeIMName: msgBody.ext?.calleeIMName,\n callerDevId: msgBody.ext?.callerDevId,\n channelName: msgBody.ext?.channelName,\n chatType: msgBody.ext?.chatType,\n type: msgBody.ext?.type,\n msgType: msgBody.ext?.msgType,\n invitedMembers: msgBody.ext?.invitedMembers,\n em_push_ext: msgBody.ext?.em_push_ext,\n em_apns_ext: msgBody.ext?.em_apns_ext,\n ease_chat_uikit_user_info: msgBody.ext?.ease_chat_uikit_user_info,\n callkitGroupInfo: msgBody.ext?.callkitGroupInfo,\n })\n const result = await this.imClient.send(msg)\n this.logger.signal?.('send', 'invite', { to, callId: ext.callId })\n return result\n }\n\n /**\n * 发送 CMD 信令消息\n */\n async sendCmdMessage(\n targetId: string,\n chatType: 'singleChat' | 'groupChat',\n ext: SignalingExt | Record<string, any>,\n options?: {\n deliverOnlineOnly?: boolean\n receiverList?: string[]\n }\n ): Promise<any> {\n const msgBody: any = {\n type: 'cmd',\n to: targetId,\n chatType,\n action: 'rtcCall',\n ext,\n deliverOnlineOnly: options?.deliverOnlineOnly || false,\n }\n if (options?.receiverList) {\n msgBody.receiverList = options.receiverList\n }\n\n const msg = this.createMessage(msgBody)\n const result = await this.imClient.send(msg)\n this.logger.signal?.('send', ext.action, { to: targetId, callId: ext.callId })\n return result\n }\n\n /**\n * 兼容 full 版与 miniCore 版的消息创建\n * full 版: ChatSDK.message.create(options)\n * miniCore 版: client.Message.create(options)\n */\n private createMessage(options: any): any {\n // 优先使用外部传入的工厂函数\n if (this.createMessageFn) {\n return this.createMessageFn(options)\n }\n\n const client = this.imClient as any\n // 优先 full 版本静态 API\n if (typeof client !== 'undefined' && client.message?.create) {\n return client.message.create(options)\n }\n // fallback miniCore 实例 API\n if (client?.Message?.create) {\n return client.Message.create(options)\n }\n throw new Error(\n '[SignalSender] 无法创建消息:当前环境缺少 message.create API。' +\n '请确认 easemob-websdk 已安装(full 版),或 miniCore 已注册消息插件。'\n )\n }\n}\n","import type { CmdMsgBody, SignalHandler } from './SignalRouter'\nimport type { SingleCallStateMachine } from '../state/SingleCallStateMachine'\nimport type { DomainEvent } from '../state/SingleCallStateMachine'\nimport type { SignalSender } from './SignalSender'\nimport type { Logger } from '../utils/logger'\nimport { getLogger } from '../utils/logger'\nimport { CALL_STATUS, CALL_TYPE, HANGUP_REASON } from '../types/callstate.types'\n\n/**\n * SingleCallSignalHandler\n * 单聊域信令处理器\n *\n * 改造后:\n * - 不读写 Pinia Store → 注入 SingleCallStateMachine\n * - 不直接 join RTC → 通过 StateMachine 返回 SHOULD_JOIN_RTC 事件\n * - 不直接调用 CallService.hangup() → 返回 CALL_ENDED 事件\n * - 不直接 emit callKitEventBus → 返回 DomainEvent[]\n * - 保留直接发送响应信令(confirmRing / confirmCallee)→ 注入 SignalSender\n */\nexport class SingleCallSignalHandler implements SignalHandler {\n private stateMachine: SingleCallStateMachine\n private sender: SignalSender\n private getDeviceId: () => string\n private logger: Logger\n\n constructor(\n stateMachine: SingleCallStateMachine,\n sender: SignalSender,\n deviceIdProvider: (() => string) | string,\n logger?: Logger\n ) {\n this.stateMachine = stateMachine\n this.sender = sender\n // 兼容字符串入参(旧测试用例),运行时优先使用 provider 实时读取\n this.getDeviceId = typeof deviceIdProvider === 'function' ? deviceIdProvider : () => deviceIdProvider\n this.logger = logger || getLogger()\n this.logger.warn('📞 [SingleCallSignalHandler] 单聊信令处理器已初始化')\n }\n\n private get deviceId(): string {\n return this.getDeviceId()\n }\n\n handle(message: CmdMsgBody): DomainEvent[] {\n const action = message.ext?.action\n switch (action) {\n case 'alert':\n return this.handleAlert(message)\n case 'confirmRing':\n return this.handleConfirmRing(message)\n case 'answerCall':\n return this.handleAnswerCall(message)\n case 'cancelCall':\n return this.handleCancelCall(message)\n case 'leaveCall':\n return this.handleLeaveCall(message)\n case 'confirmCallee':\n return this.handleConfirmCallee(message)\n default:\n this.logger.warn(`[SingleCallSignalHandler] 未知 action: ${action}`)\n return []\n }\n }\n\n // ───────────────────────────────────────────────\n // alert — 主叫方收到被叫已响铃\n // ───────────────────────────────────────────────\n\n private handleAlert(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext\n if (!ext) return []\n\n const currentState = this.stateMachine.getState()\n const isGroupCall = currentState.type === CALL_TYPE.VIDEO_MULTI || currentState.type === CALL_TYPE.AUDIO_MULTI\n\n this.logger.signal?.('recv', 'alert', {\n from: message.from,\n callId: ext?.callId,\n callerDevId: ext?.callerDevId,\n isGroupCall,\n })\n\n // 多端校验:callerDevId 必须匹配当前设备\n if (ext.callerDevId !== this.deviceId) {\n this.logger.warn(\n `[SingleCallSignalHandler] 主叫多端: callerDevId(${ext.callerDevId}) ≠ 当前设备(${this.deviceId})`\n )\n return []\n }\n\n // 状态机流转(群聊也需要 alert → confirmRing 的流转)\n const stateResult = this.stateMachine.receiveAlert(ext.calleeDevId as string)\n\n // 群聊场景:主叫发 invite 后会立即进入 IN_CALL,导致状态机拒绝 alert 流转。\n // 但为了兼容 iOS/Android 被叫端,仍需回发 confirmRing,否则被叫会判定为\"对方已取消\"而不弹框。\n const shouldSendConfirmRing = stateResult.ok || isGroupCall\n\n if (shouldSendConfirmRing) {\n // 构建并发送 confirmRing 响应\n const confirmRingPayload = this.buildConfirmRingPayload(message)\n if (confirmRingPayload) {\n // 与旧版对齐:confirmRing 不设置 deliverOnlineOnly(默认 false)\n this.sender\n .sendCmdMessage(\n message.from as string,\n 'singleChat',\n {\n action: 'confirmRing',\n callId: ext.callId as string,\n status: confirmRingPayload.status,\n callerDevId: ext.callerDevId as string,\n calleeDevId: ext.calleeDevId as string,\n ts: Date.now(),\n msgType: 'rtcCallWithAgora',\n } as any\n )\n .catch(() => {})\n }\n }\n\n if (!stateResult.ok) {\n return []\n }\n\n return stateResult.events\n }\n\n private buildConfirmRingPayload(message: CmdMsgBody): { status: boolean } | null {\n const ext = message.ext\n if (!ext) return null\n\n const currentState = this.stateMachine.getState()\n if (!currentState.callId) {\n this.logger.warn('[SingleCallSignalHandler] 当前无通话,无法构建 confirmRing')\n return null\n }\n\n const isGroupCall =\n currentState.type === CALL_TYPE.VIDEO_MULTI || currentState.type === CALL_TYPE.AUDIO_MULTI\n\n let status = true\n if (ext.callId !== currentState.callId) {\n status = false\n this.logger.warn(\n `[SingleCallSignalHandler] callId 不匹配: ext(${ext.callId}) ≠ current(${currentState.callId})`\n )\n }\n\n // 群聊主叫发 invite 后会立即进入 IN_CALL,但仍需回发 status=true 的 confirmRing,\n // 否则 iOS/Android 被叫端会判定通话已取消。单聊状态超前时回发 status=false。\n if (\n currentState.status !== undefined &&\n currentState.status > CALL_STATUS.RECEIVED_CONFIRM_RING &&\n !isGroupCall\n ) {\n status = false\n this.logger.warn(\n `[SingleCallSignalHandler] 单聊状态已超前: ${currentState.status},confirmRing status=false`\n )\n }\n\n return { status }\n }\n\n // ───────────────────────────────────────────────\n // confirmRing — 被叫方收到主叫确认响铃\n // ───────────────────────────────────────────────\n\n private handleConfirmRing(message: CmdMsgBody): DomainEvent[] {\n const { ext } = message\n if (!ext) return []\n\n const currentState = this.stateMachine.getState()\n const isGroupCall = currentState.type === CALL_TYPE.VIDEO_MULTI || currentState.type === CALL_TYPE.AUDIO_MULTI\n\n // 多端校验:callerDevId 必须匹配当前设备(confirmRing 是发给主叫的,但被叫也会收到... 等等)\n // 实际上 confirmRing 是主叫发给被叫的。被叫收到时,callerDevId 是主叫的设备ID。\n // 但被叫需要确认这个 confirmRing 是否对应自己当前处理的通话。\n // 原始代码中:ext.callerDevId !== this.chatClientStore.getClientDeviceId → 这是检查主叫设备ID是否与当前设备ID一致\n // 但为什么要一致?confirmRing 是主叫发给被叫的,callerDevId 是主叫的,被叫的设备ID是 calleeDevId。\n // 啊,原始代码这里的逻辑是:主叫有两个设备时,被叫可能收到来自不同主叫设备的 confirmRing。\n // 但被叫怎么知道主叫的 deviceId?从 invite 中获取。\n // 在 StateMachine 中,callerDevId 已经存储了。\n\n if (ext.callerDevId !== currentState.callerDevId) {\n this.logger.warn(\n `[SingleCallSignalHandler] confirmRing 主叫设备不匹配: ext(${ext.callerDevId}) ≠ state(${currentState.callerDevId})`\n )\n return []\n }\n\n if (ext.calleeDevId !== this.deviceId) {\n this.logger.warn(\n `[SingleCallSignalHandler] confirmRing 被叫设备不匹配: ext(${ext.calleeDevId}) ≠ current(${this.deviceId})`\n )\n return []\n }\n\n const stateResult = this.stateMachine.receiveConfirmRing(!!ext.status)\n return stateResult.events\n }\n\n // ───────────────────────────────────────────────\n // answerCall — 主叫方收到被叫应答\n // ───────────────────────────────────────────────\n\n private handleAnswerCall(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext\n if (!ext) return []\n\n const currentState = this.stateMachine.getState()\n\n // callId 校验\n if (ext.callId !== currentState.callId) {\n this.logger.warn(\n `[SingleCallSignalHandler] answerCall callId 不匹配: ext(${ext.callId}) ≠ current(${currentState.callId})`\n )\n return []\n }\n\n // 群聊 answerCall 完全交给 GroupCallSignalHandler 处理:\n // 包括发送 confirmCallee、更新参与者状态、生成 PARTICIPANT_* 事件。\n // SingleCallSignalHandler 若参与处理,会导致:\n // 1. accept 时 confirmCallee 被发送两次;\n // 2. refuse/busy 时状态机被 reset 为 IDLE,整个群聊通话被挂断。\n const isGroupCall =\n currentState.type === CALL_TYPE.VIDEO_MULTI || currentState.type === CALL_TYPE.AUDIO_MULTI\n if (isGroupCall) {\n this.logger.debug('[SingleCallSignalHandler] 群聊 answerCall 由 GroupCallSignalHandler 处理,本 Handler 忽略')\n return []\n }\n\n // 已在通话中 → 忽略(单聊场景)\n if (currentState.status === CALL_STATUS.IN_CALL) {\n this.logger.debug('[SingleCallSignalHandler] 单聊已在 IN_CALL,忽略 answerCall')\n return []\n }\n\n // callerDevId 校验(多端)\n if (ext.callerDevId !== this.deviceId) {\n // 如果消息 from 是当前用户,说明被其他端处理了\n if (message.from === currentState.callerUserId) {\n const reason = ext.result === 'accept' ? '已被其他端接听' : '已被其他端拒绝'\n this.logger.warn(`[SingleCallSignalHandler] answerCall ${reason}`)\n return []\n }\n this.logger.warn(\n `[SingleCallSignalHandler] answerCall callerDevId 不匹配: ext(${ext.callerDevId}) ≠ current(${this.deviceId})`\n )\n return []\n }\n\n const allEvents: DomainEvent[] = []\n\n if (ext.result !== 'accept') {\n // ── 拒绝 / 忙线 分支 ──\n this.logger.signal?.('recv', 'answerCall', {\n from: message.from,\n result: ext.result,\n callId: ext.callId,\n })\n\n const stateResult = this.stateMachine.receiveAnswer(\n ext.result as 'refuse' | 'busy'\n )\n allEvents.push(...stateResult.events)\n\n // 发送 confirmCallee\n this.sendConfirmCallee(message.from as string, {\n callId: ext.callId as string,\n callerDevId: ext.callerDevId as string,\n calleeDevId: ext.calleeDevId as string,\n result: ext.result as string,\n })\n } else {\n // ── 接受 分支 ──\n this.logger.signal?.('recv', 'answerCall', {\n from: message.from,\n result: 'accept',\n callId: ext.callId,\n })\n\n // 发送 confirmCallee\n this.sendConfirmCallee(message.from as string, {\n callId: ext.callId as string,\n callerDevId: ext.callerDevId as string,\n calleeDevId: ext.calleeDevId as string,\n result: 'accept',\n })\n\n // 单聊:状态流转为 IN_CALL 并触发 SHOULD_JOIN_RTC\n this.logger.info('[SingleCallSignalHandler] 一对一通话接受,进入 IN_CALL')\n const stateResult = this.stateMachine.receiveAnswer('accept')\n allEvents.push(...stateResult.events)\n }\n\n return allEvents\n }\n\n // ───────────────────────────────────────────────\n // cancelCall\n // ───────────────────────────────────────────────\n\n private handleCancelCall(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext\n if (!ext) return []\n\n const currentState = this.stateMachine.getState()\n\n // callId 不匹配\n if (ext.callId !== currentState.callId) {\n this.logger.warn(\n `[SingleCallSignalHandler] cancelCall callId 不匹配: ext(${ext.callId}) ≠ current(${currentState.callId})`\n )\n\n if (currentState.status === CALL_STATUS.IDLE) {\n this.logger.info('[SingleCallSignalHandler] 当前 IDLE,忽略')\n return []\n }\n\n // 群聊分支已由 GroupCallSignalHandler 处理\n if (\n currentState.type === CALL_TYPE.VIDEO_MULTI ||\n currentState.type === CALL_TYPE.AUDIO_MULTI\n ) {\n this.logger.debug('[SingleCallSignalHandler] 群聊 cancelCall 由 GroupCallSignalHandler 处理')\n return []\n }\n\n // 单聊容错:ALERTING/INVITING 状态且来自主叫方 → 挂断\n const isFromCaller = message.from === currentState.callerUserId\n if (\n isFromCaller &&\n (currentState.status === CALL_STATUS.ALERTING ||\n currentState.status === CALL_STATUS.INVITING)\n ) {\n this.logger.info('[SingleCallSignalHandler] 单聊收到主叫方取消(callId 不匹配),执行挂断')\n const stateResult = this.stateMachine.receiveCancel()\n return stateResult.events\n }\n return []\n }\n\n // callId 匹配\n this.logger.signal?.('recv', 'cancelCall', {\n from: message.from,\n callId: ext.callId,\n })\n this.logger.info('[SingleCallSignalHandler] 收到对方取消')\n\n const stateResult = this.stateMachine.receiveCancel()\n return stateResult.events\n }\n\n // ───────────────────────────────────────────────\n // leaveCall\n // ───────────────────────────────────────────────\n\n private handleLeaveCall(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext\n if (!ext) return []\n\n const currentState = this.stateMachine.getState()\n\n // callId 不匹配\n if (ext.callId !== currentState.callId) {\n this.logger.warn(\n `[SingleCallSignalHandler] leaveCall callId 不匹配: ext(${ext.callId}) ≠ current(${currentState.callId})`\n )\n\n if (currentState.status === CALL_STATUS.IDLE) {\n return []\n }\n\n // 群聊分支\n if (\n currentState.type === CALL_TYPE.VIDEO_MULTI ||\n currentState.type === CALL_TYPE.AUDIO_MULTI\n ) {\n this.logger.debug('[SingleCallSignalHandler] 群聊 leaveCall 由 GroupCallSignalHandler 处理')\n return []\n }\n\n // 单聊容错\n if (currentState.status === CALL_STATUS.IN_CALL) {\n this.logger.info('[SingleCallSignalHandler] 通话中对方离开,执行挂断')\n const stateResult = this.stateMachine.receiveLeave()\n return stateResult.events\n } else if (\n currentState.status === CALL_STATUS.ALERTING &&\n message.from === currentState.callerUserId\n ) {\n this.logger.info('[SingleCallSignalHandler] ALERTING 状态收到主叫方离开,执行挂断')\n const stateResult = this.stateMachine.receiveLeave()\n return stateResult.events\n }\n return []\n }\n\n // callId 匹配\n this.logger.signal?.('recv', 'leaveCall', {\n from: message.from,\n callId: ext.callId,\n })\n\n // 群聊分支\n if (\n currentState.type === CALL_TYPE.VIDEO_MULTI ||\n currentState.type === CALL_TYPE.AUDIO_MULTI\n ) {\n this.logger.debug('[SingleCallSignalHandler] 群聊 leaveCall 由 GroupCallSignalHandler 处理')\n return []\n }\n\n if (currentState.status === CALL_STATUS.IDLE) {\n return []\n }\n\n // 与 lib 对齐:ALERTING/INVITING 状态下只有来自主叫方的 leaveCall 才挂断\n if (\n currentState.status === CALL_STATUS.ALERTING ||\n currentState.status === CALL_STATUS.INVITING\n ) {\n if (message.from !== currentState.callerUserId) {\n this.logger.warn('[SingleCallSignalHandler] leaveCall callId 匹配但发送者不是主叫方,忽略')\n return []\n }\n }\n\n const stateResult = this.stateMachine.receiveLeave()\n return stateResult.events\n }\n\n // ───────────────────────────────────────────────\n // confirmCallee — 被叫方收到主叫确认 callee 就绪\n // ───────────────────────────────────────────────\n\n private handleConfirmCallee(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext\n if (!ext) return []\n\n this.logger.signal?.('recv', 'confirmCallee', {\n from: message.from,\n callId: ext.callId,\n result: ext.result,\n })\n this.logger.info('[SingleCallSignalHandler] 收到 confirmCallee')\n\n const currentState = this.stateMachine.getState()\n\n if (ext.callId !== currentState.callId) {\n this.logger.warn(\n `[SingleCallSignalHandler] confirmCallee callId 不匹配: ext(${ext.callId}) ≠ current(${currentState.callId})`\n )\n return []\n }\n\n // confirmCallee 的 result 字段语义:accept 才进入通话,refuse/busy 应忽略\n if (ext.result && ext.result !== 'accept') {\n this.logger.info(`[SingleCallSignalHandler] confirmCallee result=${ext.result},忽略`)\n return []\n }\n\n const stateResult = this.stateMachine.receiveConfirmCallee()\n return stateResult.events\n }\n\n // ─── 私有辅助 ───\n\n private sendConfirmCallee(\n to: string,\n payload: {\n callId: string\n callerDevId: string\n calleeDevId: string\n result: string\n }\n ): void {\n // 与旧版对齐:confirmCallee 不设置 deliverOnlineOnly(默认 false)\n this.sender\n .sendCmdMessage(\n to,\n 'singleChat',\n {\n action: 'confirmCallee',\n callId: payload.callId,\n callerDevId: payload.callerDevId,\n calleeDevId: payload.calleeDevId,\n result: payload.result,\n ts: Date.now(),\n msgType: 'rtcCallWithAgora',\n } as any\n )\n .catch(() => {})\n }\n}\n","import type { CmdMsgBody, SignalHandler } from './SignalRouter'\nimport type { GroupCallSession } from '../state/GroupCallSession'\nimport type { SingleCallStateMachine } from '../state/SingleCallStateMachine'\nimport type { DomainEvent } from '../state/SingleCallStateMachine'\nimport type { SignalSender } from './SignalSender'\nimport type { Logger } from '../utils/logger'\nimport { getLogger } from '../utils/logger'\nimport { CALL_STATUS, CALL_TYPE } from '../types/callstate.types'\n\n/**\n * GroupCallSignalHandler\n * 群聊域信令处理器\n *\n * 改造后:\n * - 不读写 GroupCallStore → 注入 GroupCallSession\n * - 不直接调用 CallService → 返回 DomainEvent[]\n * - 不直接 emit callKitEventBus → 返回 DomainEvent[]\n * - 保留 SingleCallStateMachine 用于 callId / 状态校验(与原架构一致)\n */\nexport class GroupCallSignalHandler implements SignalHandler {\n private session: GroupCallSession\n private stateMachine: SingleCallStateMachine\n private sender: SignalSender\n private getUserId: () => string\n private logger: Logger\n\n constructor(\n session: GroupCallSession,\n stateMachine: SingleCallStateMachine,\n sender: SignalSender,\n userIdProvider: (() => string) | string,\n logger?: Logger\n ) {\n this.session = session\n this.stateMachine = stateMachine\n this.sender = sender\n // 兼容字符串入参(旧测试用例),运行时优先使用 provider 实时读取\n this.getUserId = typeof userIdProvider === 'function' ? userIdProvider : () => userIdProvider\n this.logger = logger || getLogger()\n this.logger.warn('👥 [GroupCallSignalHandler] 群聊信令处理器已初始化')\n }\n\n private get userId(): string {\n return this.getUserId()\n }\n\n /**\n * 处理 invite 文本消息中的群聊初始化\n * 由 IMListener 在收到 invite 文本消息时直接调用(不走 SignalRouter)\n */\n handleInviteTextMessage(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext as any\n const callerUserId = ext?.callerIMName || message.from || ''\n const groupId = ext?.callkitGroupInfo?.groupId || ''\n const groupName = ext?.callkitGroupInfo?.groupName || ''\n const channel = ext?.channelName || ''\n const callType = ext?.type === CALL_TYPE.VIDEO_MULTI ? 'video' : 'audio'\n const invitedMembers: string[] = ext?.invitedMembers || []\n const callId = ext?.callId || ''\n\n // 解析 caller 资料\n const callerUserInfo = ext?.ease_chat_uikit_user_info\n const callerNickname = callerUserInfo?.nickname || callerUserId\n const callerAvatar = callerUserInfo?.avatarURL || ''\n\n // 初始化群聊会话\n this.session.init({\n sessionId: channel,\n groupId,\n groupName,\n callType,\n callerUserId,\n })\n\n // 本地用户\n this.session.addParticipant({\n userId: this.userId,\n nickname: this.userId,\n avatarUrl: '',\n state: 'invited',\n isLocal: true,\n isMuted: false,\n isCameraOn: callType === 'video',\n isSpeaking: false,\n })\n\n // 主叫方\n if (callerUserId && callerUserId !== this.userId) {\n this.session.addParticipant({\n userId: callerUserId,\n nickname: callerNickname,\n avatarUrl: callerAvatar,\n state: 'joinedRtc',\n isLocal: false,\n isMuted: false,\n isCameraOn: false,\n isSpeaking: false,\n })\n }\n\n // 其他被邀请成员\n invitedMembers.forEach((m: string) => {\n if (m !== this.userId && m !== callerUserId) {\n this.session.addParticipant({\n userId: m,\n nickname: m,\n avatarUrl: '',\n state: 'invited',\n isLocal: false,\n isMuted: false,\n isCameraOn: false,\n isSpeaking: false,\n })\n }\n })\n\n this.logger.event?.('groupCallInit', {\n groupId,\n groupName,\n channel,\n callType,\n participants: this.session.getAllParticipants().map((p) => ({\n userId: p.userId,\n nickname: p.nickname,\n state: p.state,\n })),\n })\n\n return [\n {\n type: 'GROUP_CALL_INIT',\n callId,\n groupId,\n groupName,\n channel,\n callType,\n callerUserId,\n invitedMembers,\n },\n ]\n }\n\n handle(message: CmdMsgBody): DomainEvent[] {\n const action = message.ext?.action\n switch (action) {\n case 'answerCall':\n return this.handleAnswerCall(message)\n case 'cancelCall':\n return this.handleCancelCall(message)\n case 'leaveCall':\n return this.handleLeaveCall(message)\n default:\n this.logger.warn(`[GroupCallSignalHandler] 未知 action: ${action}`)\n return []\n }\n }\n\n // ───────────────────────────────────────────────\n // answerCall — 群聊成员应答\n // ───────────────────────────────────────────────\n\n private handleAnswerCall(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext\n if (!ext) return []\n\n const currentState = this.stateMachine.getState()\n\n // 只处理群聊类型\n if (\n currentState.type !== CALL_TYPE.VIDEO_MULTI &&\n currentState.type !== CALL_TYPE.AUDIO_MULTI\n ) {\n return []\n }\n\n // callId 校验\n if (ext.callId !== currentState.callId) {\n this.logger.warn(\n `[GroupCallSignalHandler] answerCall callId 不匹配: ext(${ext.callId}) ≠ current(${currentState.callId})`\n )\n return []\n }\n\n const fromUserId = message.from as string\n const callId = currentState.callId\n const channel = currentState.channel\n const groupSnapshot = this.session.getSnapshot()\n const groupId = groupSnapshot?.groupId\n\n if (ext.result !== 'accept') {\n // ── 拒绝 ──\n this.logger.signal?.('recv', 'answerCall', {\n from: fromUserId,\n result: ext.result,\n callId,\n type: 'group',\n })\n this.logger.info(`[GroupCallSignalHandler] 群聊成员拒绝: ${fromUserId}`)\n\n this.session.removeParticipant(fromUserId)\n\n return [\n {\n type: 'PARTICIPANT_LEFT',\n callId,\n userId: fromUserId,\n channel,\n callType: currentState.type,\n reason: ext.result === 'busy' ? 'busy' : 'refused',\n groupId,\n },\n ]\n }\n\n // ── 接受 ──\n this.logger.signal?.('recv', 'answerCall', {\n from: fromUserId,\n result: 'accept',\n callId,\n type: 'group',\n })\n this.logger.info(`[GroupCallSignalHandler] 群聊成员接受: ${fromUserId}`)\n\n this.session.markAccepted(fromUserId)\n\n // 与旧版对齐:群聊成员接受后,主叫方也发送 confirmCallee 给被叫方\n this.sendConfirmCallee(fromUserId, {\n callId,\n callerDevId: currentState.callerDevId || '',\n calleeDevId: ext.calleeDevId as string,\n result: 'accept',\n })\n\n return [\n {\n type: 'PARTICIPANT_STATE_CHANGED',\n callId,\n userId: fromUserId,\n state: 'accepted',\n groupId,\n },\n {\n type: 'PARTICIPANT_JOINED',\n callId,\n userId: fromUserId,\n channel,\n callType: currentState.type,\n groupId,\n },\n ]\n }\n\n // ─── 私有辅助 ───\n\n private sendConfirmCallee(\n to: string,\n payload: {\n callId: string\n callerDevId: string\n calleeDevId: string\n result: string\n }\n ): void {\n // 与旧版对齐:confirmCallee 不设置 deliverOnlineOnly(默认 false)\n this.sender\n .sendCmdMessage(\n to,\n 'singleChat',\n {\n action: 'confirmCallee',\n callId: payload.callId,\n callerDevId: payload.callerDevId,\n calleeDevId: payload.calleeDevId,\n result: payload.result,\n ts: Date.now(),\n msgType: 'rtcCallWithAgora',\n } as any\n )\n .catch(() => {})\n }\n\n // ───────────────────────────────────────────────\n // cancelCall — 群聊容错\n // ───────────────────────────────────────────────\n\n private handleCancelCall(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext\n if (!ext) return []\n\n const currentState = this.stateMachine.getState()\n\n // 只处理群聊类型\n if (\n currentState.type !== CALL_TYPE.VIDEO_MULTI &&\n currentState.type !== CALL_TYPE.AUDIO_MULTI\n ) {\n return []\n }\n\n const currentStatus = currentState.status\n const isFromCaller = message.from === currentState.callerUserId\n\n // callId 不匹配时的容错\n if (ext.callId !== currentState.callId) {\n if (\n isFromCaller &&\n (currentStatus === CALL_STATUS.ALERTING || currentStatus === CALL_STATUS.INVITING)\n ) {\n this.logger.info('[GroupCallSignalHandler] 群聊收到主叫方取消(callId 不匹配),执行挂断')\n const stateResult = this.stateMachine.receiveCancel()\n return stateResult.events\n }\n return []\n }\n\n // callId 匹配\n this.logger.signal?.('recv', 'cancelCall', {\n from: message.from,\n callId: ext.callId,\n type: 'group',\n })\n\n if (\n isFromCaller &&\n (currentStatus === CALL_STATUS.ALERTING || currentStatus === CALL_STATUS.INVITING)\n ) {\n this.logger.info('[GroupCallSignalHandler] 群聊收到主叫方 cancelCall,执行远程挂断')\n const stateResult = this.stateMachine.receiveCancel()\n return stateResult.events\n }\n\n this.logger.debug('[GroupCallSignalHandler] 群聊 cancelCall 状态不符,忽略')\n return []\n }\n\n // ───────────────────────────────────────────────\n // leaveCall — 群聊成员离开\n // ───────────────────────────────────────────────\n\n private handleLeaveCall(message: CmdMsgBody): DomainEvent[] {\n const ext = message.ext\n if (!ext) return []\n\n const currentState = this.stateMachine.getState()\n\n // 只处理群聊类型\n if (\n currentState.type !== CALL_TYPE.VIDEO_MULTI &&\n currentState.type !== CALL_TYPE.AUDIO_MULTI\n ) {\n return []\n }\n\n const currentStatus = currentState.status\n const isFromCaller = message.from === currentState.callerUserId\n const fromUserId = message.from as string\n\n // callId 不匹配时的容错\n if (ext.callId !== currentState.callId) {\n if (currentStatus === CALL_STATUS.IDLE) {\n this.logger.info('[GroupCallSignalHandler] 当前 IDLE,忽略 leaveCall')\n return []\n }\n if (currentStatus === CALL_STATUS.IN_CALL) {\n this.logger.info('[GroupCallSignalHandler] 通话中对方离开,继续处理(callId 不匹配)')\n // 继续执行下方逻辑\n } else if (\n currentStatus === CALL_STATUS.ALERTING &&\n isFromCaller\n ) {\n this.logger.info('[GroupCallSignalHandler] ALERTING 收到主叫方 leaveCall,继续处理')\n // 继续执行下方逻辑\n } else {\n this.logger.warn('[GroupCallSignalHandler] leaveCall callId 不匹配且状态不符,忽略')\n return []\n }\n }\n\n // 被叫方在 ALERTING/INVITING 状态收到主叫方 leaveCall → 挂断整个通话\n if (\n (currentStatus === CALL_STATUS.ALERTING || currentStatus === CALL_STATUS.INVITING) &&\n isFromCaller\n ) {\n this.logger.info(`[GroupCallSignalHandler] 被叫方收到主叫方(${fromUserId})离开,挂断通话`)\n const stateResult = this.stateMachine.receiveCancel()\n return stateResult.events\n }\n\n // 通话中状态:只移除离开的成员\n this.logger.signal?.('recv', 'leaveCall', {\n from: fromUserId,\n callId: ext.callId,\n type: 'group',\n })\n this.logger.info(`[GroupCallSignalHandler] 群聊成员离开: ${fromUserId}`)\n\n const groupSnapshot = this.session.getSnapshot()\n const groupId = groupSnapshot?.groupId\n\n this.session.setParticipantState(fromUserId, 'left')\n // 延迟移除(与原行为一致)\n setTimeout(() => this.session.removeParticipant(fromUserId), 2000)\n\n return [\n {\n type: 'PARTICIPANT_LEFT',\n callId: currentState.callId,\n userId: fromUserId,\n channel: currentState.channel,\n callType: currentState.type,\n reason: 'left',\n groupId,\n },\n ]\n }\n}\n","import type { EasemobConnection } from '../core/CallKitCore.types'\nimport type { Logger } from '../utils/logger'\nimport { getLogger } from '../utils/logger'\n\nexport interface IMListenerCallbacks {\n onTextMessage?: (msg: any) => void | Promise<void>\n onCmdMessage?: (msg: any) => void | Promise<void>\n onConnected?: () => void\n onDisconnected?: () => void\n}\n\n/**\n * IMListener\n * 环信 IM SDK 消息监听器薄壳。\n *\n * 职责:\n * 1. 挂载 onTextMessage / onCmdMessage 监听\n * 2. 收到后通过回调交给 CallKitCore 处理(不处理任何业务逻辑)\n */\nexport class IMListener {\n private imClient: EasemobConnection\n private callbacks: IMListenerCallbacks\n private logger: Logger\n private mounted = false\n private handlerId = 'callkit-core-listener'\n\n constructor(\n imClient: EasemobConnection,\n callbacks: IMListenerCallbacks,\n logger?: Logger\n ) {\n this.imClient = imClient\n this.callbacks = callbacks\n this.logger = logger || getLogger()\n }\n\n mount(): void {\n if (this.mounted) return\n this.mounted = true\n\n this.imClient.addEventHandler(this.handlerId, {\n onTextMessage: async (msg: any) => {\n this.logger.debug('[IMListener] onTextMessage', { from: msg.from, id: msg.id })\n await this.callbacks.onTextMessage?.(msg)\n },\n onCmdMessage: async (msg: any) => {\n this.logger.debug('[IMListener] onCmdMessage', { from: msg.from, action: msg.action })\n await this.callbacks.onCmdMessage?.(msg)\n },\n onConnected: () => {\n this.logger.info('[IMListener] IM 已连接')\n this.callbacks.onConnected?.()\n },\n onDisconnected: () => {\n this.logger.warn('[IMListener] IM 已断开')\n this.callbacks.onDisconnected?.()\n },\n })\n\n this.logger.warn('🔵 [IMListener] CORE 链路 IM 监听已挂载 | handlerId:', this.handlerId)\n }\n\n unmount(): void {\n if (!this.mounted) return\n this.mounted = false\n this.imClient.removeEventHandler(this.handlerId)\n this.logger.info('[IMListener] 监听已卸载')\n }\n}\n","import type {\n InviteSignalingExt,\n SignalingExt,\n AlertSignalingExt,\n ConfirmRingSignalingExt,\n AnswerCallSignalingExt,\n ConfirmCalleeSignalingExt,\n CancelCallSignalingExt,\n LeaveCallSignalingExt,\n} from '../types/signal.types'\nimport { CALL_TYPE } from '../types/callstate.types'\nimport type { CALLKIT_CMD_MSG_ACTION_TYPE, CALLKIT_CMD_MSG_RESULT_TYPE } from '../types/callstate.types'\n\n/**\n * MessageBuilder\n * 由 ChatService 改造而来的纯函数集合。\n *\n * 关键变化:\n * - 不再读取 callStateStore,所有数据由调用方显式传入\n * - 不再查询用户/群组资料,由调用方传入\n */\n\nexport interface BuildInviteMessageParams {\n callId: string\n callerUserId: string\n calleeUserId: string\n callerDevId: string\n channel: string\n callType: CALL_TYPE\n ts?: number\n invitedMembers?: string[]\n callerInfo?: { nickname?: string; avatarURL?: string }\n groupInfo?: { groupId: string; groupName?: string; groupAvatar?: string }\n}\n\nexport interface BuildCmdMessageParams {\n action: CALLKIT_CMD_MSG_ACTION_TYPE\n callId: string\n callerDevId?: string\n calleeDevId?: string\n result?: CALLKIT_CMD_MSG_RESULT_TYPE\n status?: boolean\n ts?: number\n}\n\nexport class MessageBuilder {\n /**\n * 构建 invite 文本消息的 ext\n */\n static buildInviteExt(params: BuildInviteMessageParams): InviteSignalingExt {\n const ts = params.ts ?? Date.now()\n // 与旧版 ChatService 对齐:空数组时不携带 invitedMembers 字段\n const invitedMembers =\n params.invitedMembers && params.invitedMembers.length > 0\n ? params.invitedMembers\n : undefined\n return {\n action: 'invite',\n callId: params.callId || '',\n callerIMName: params.callerUserId || '',\n calleeIMName: params.calleeUserId || '',\n callerDevId: params.callerDevId || '',\n channelName: params.channel || '',\n chatType: params.callType || CALL_TYPE.AUDIO_1V1,\n type: params.callType || CALL_TYPE.AUDIO_1V1,\n ts,\n msgType: 'rtcCallWithAgora',\n invitedMembers,\n em_push_ext: {\n type: 'call',\n custom: {\n action: 'invite',\n channelName: params.channel || '',\n type: params.callType || CALL_TYPE.AUDIO_1V1,\n callerDevId: params.callerDevId || '',\n callId: params.callId || '',\n ts,\n msgType: 'rtcCallWithAgora',\n callerIMName: params.callerUserId || '',\n calleeIMName: params.calleeUserId || '',\n // 与旧版 ChatService 对齐:无昵称时 fallback 到 callerUserId,避免 iOS/APNS 解析异常\n callerNickname: params.callerInfo?.nickname || params.callerUserId || '',\n chatType: params.callType || CALL_TYPE.AUDIO_1V1,\n },\n },\n em_apns_ext: {\n em_push_type: 'voip',\n },\n ease_chat_uikit_user_info: params.callerInfo\n ? {\n nickname: params.callerInfo.nickname || params.callerUserId || '',\n avatarURL: params.callerInfo.avatarURL || '',\n }\n : undefined,\n callkitGroupInfo: params.groupInfo,\n // 兼容新版 iOS EaseCallUIKit:ext 最外层携带 groupId / receiverList\n groupId: params.groupInfo?.groupId,\n receiverList: invitedMembers,\n }\n }\n\n /**\n * 构建 CMD 信令消息的 ext\n */\n static buildCmdExt(params: BuildCmdMessageParams): SignalingExt {\n const ts = params.ts ?? Date.now()\n const base = { callId: params.callId || '', ts, msgType: 'rtcCallWithAgora' }\n\n switch (params.action) {\n case 'alert':\n return {\n ...base,\n action: 'alert',\n calleeDevId: params.calleeDevId || '',\n callerDevId: params.callerDevId || '',\n } as AlertSignalingExt\n\n case 'confirmRing':\n return {\n ...base,\n action: 'confirmRing',\n status: params.status ?? false,\n callerDevId: params.callerDevId || '',\n calleeDevId: params.calleeDevId || '',\n } as ConfirmRingSignalingExt\n\n case 'answerCall':\n return {\n ...base,\n action: 'answerCall',\n result: (params.result as any) || 'busy',\n callerDevId: params.callerDevId || '',\n calleeDevId: params.calleeDevId || '',\n } as AnswerCallSignalingExt\n\n case 'confirmCallee':\n return {\n ...base,\n action: 'confirmCallee',\n result: (params.result as any) || 'accept',\n callerDevId: params.callerDevId || '',\n calleeDevId: params.calleeDevId || '',\n } as ConfirmCalleeSignalingExt\n\n case 'cancelCall':\n return {\n ...base,\n action: 'cancelCall',\n callerDevId: params.callerDevId || '',\n } as CancelCallSignalingExt\n\n case 'leaveCall':\n return {\n ...base,\n action: 'leaveCall',\n } as LeaveCallSignalingExt\n\n default:\n throw new Error(`[MessageBuilder] 未知的信令动作: ${params.action}`)\n }\n }\n}\n","/**\n * CallKit工具函数\n */\n\n/**\n * 生成随机channel字符串\n * @param length 字符串长度,默认8位\n * @returns 随机字符串\n */\nexport const generateRandomChannel = (length: number = 8): string => {\n const CHARS =\n \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\".split(\"\");\n let result = \"\";\n for (let i = 0; i < length; i++) {\n result += CHARS[Math.floor(Math.random() * CHARS.length)];\n }\n return result;\n};\n\n/**\n * 检查消息是否已过期\n * 与 lib/composables/useListenerManager.ts 中的 isMessageExpired 对齐\n * @param messageTime 消息时间戳(毫秒)\n * @param toleranceMs 容忍时间(毫秒),默认 40000(30s invite超时 + 10s 容差)\n * @returns 是否已过期\n */\nexport const isMessageExpired = (messageTime: number, toleranceMs: number = 40000): boolean => {\n if (!messageTime || messageTime <= 0) return false;\n return Date.now() - messageTime > toleranceMs;\n};\n\n/**\n * CMD 消息过期检查(与 lib 对齐,默认 60s)\n * @param messageTime 消息时间戳(毫秒)\n * @returns 是否已过期\n */\nexport const isCmdMessageExpired = (messageTime: number): boolean => {\n return isMessageExpired(messageTime, 60000);\n};\n/**\n * 格式化通话时间\n * @param seconds 秒数\n * @returns 格式化的时间字符串 (HH:MM:SS 或 MM:SS)\n */\nexport const formatCallDuration = (seconds: number): string => {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = seconds % 60;\n\n if (hours > 0) {\n return `${hours.toString().padStart(2, \"0\")}:${minutes\n .toString()\n .padStart(2, \"0\")}:${secs.toString().padStart(2, \"0\")}`;\n }\n return `${minutes.toString().padStart(2, \"0\")}:${secs\n .toString()\n .padStart(2, \"0\")}`;\n};\n","import type {\n CallKitCoreConfig,\n InviteCallParams,\n AnswerCallParams,\n HangupParams,\n InviteGroupCallParams,\n RtcReport,\n EasemobConnection,\n} from './CallKitCore.types'\nimport type { CallKitEvent } from '../events/CallKitEvents'\nimport { isUIEvent, isRtcEvent } from '../events/CallKitEvents'\nimport type { DomainEvent, SingleCallState } from '../state/SingleCallStateMachine'\nimport { getLogger } from '../utils/logger'\nimport type { Logger } from '../utils/logger'\nimport { EventBus } from '../events/EventBus'\nimport { SingleCallStateMachine } from '../state/SingleCallStateMachine'\nimport { GroupCallSession } from '../state/GroupCallSession'\nimport { SignalRouter } from '../signaling/SignalRouter'\nimport { SignalSender } from '../signaling/SignalSender'\nimport { SingleCallSignalHandler } from '../signaling/SingleCallSignalHandler'\nimport { GroupCallSignalHandler } from '../signaling/GroupCallSignalHandler'\nimport { IMListener } from '../im/IMListener'\nimport { MessageBuilder } from '../signaling/MessageBuilder'\nimport { generateRandomChannel, isMessageExpired, isCmdMessageExpired } from '../utils/callUtils'\nimport { CALL_STATUS, CALL_TYPE, HANGUP_REASON } from '../types/callstate.types'\n\n/**\n * CallKitCore\n * 框架无关的通话信令核心。\n *\n * 职责:\n * 1. 管理单聊/群聊通话生命周期\n * 2. 通过事件回调通知上层所有决策点\n * 3. 不直接操作任何 RTC SDK,RTC 由上层通过适配器或事件自行处理\n */\nexport class CallKitCore {\n private config: CallKitCoreConfig\n private logger: Logger\n private singleCallState: SingleCallStateMachine\n private groupCallSession: GroupCallSession\n private signalRouter: SignalRouter\n private signalSender: SignalSender\n private singleCallHandler: SingleCallSignalHandler\n private groupCallHandler: GroupCallSignalHandler\n private imListener: IMListener\n private destroyed = false\n private inviteTimer: ReturnType<typeof setTimeout> | null = null\n\n // 群聊被叫方在 fetchRtcToken 期间可能收到主叫的取消/离开信令,\n // 但此时 singleCallState 仍为 IDLE,SignalRouter 会忽略这些信令。\n // 用 Map 记录“待处理的 incoming invite”,在 token 返回后检查是否已被 abort。\n private pendingIncomingInvites = new Map<string, { aborted: boolean }>()\n\n // RTC token 元数据(来自 IM 服务端 getRTCToken)\n // 注意:Agora 加入频道时使用的 uid 必须是服务端返回的 RTCUId(数值型),而不是 IM 的字符串 userId\n private rtcAppId: string = ''\n private rtcUid: number = 0\n\n // 当前用户信息(实时读取:因 Provider 在登录前就 init,必须每次发送时从 imClient 取最新值,与旧版 lib/services/ChatService.ts 行为对齐)\n private inviteTimeoutMs: number\n\n // 通话时长计时器\n private durationTimer: ReturnType<typeof setInterval> | null = null\n private durationStartTime: number = 0\n private durationCallInfo: { callId: string; channel: string; callType: CALL_TYPE; callerUserId: string } | null = null\n\n private get userId(): string {\n return this.config.imClient.context?.userId || (this.config.imClient as any).user || ''\n }\n\n private get deviceId(): string {\n return this.config.imClient.context?.jid?.clientResource || ''\n }\n\n // 事件总线(供上层精细订阅)\n private eventBus: EventBus<{ callKitEvent: CallKitEvent }>\n\n constructor(config: CallKitCoreConfig) {\n this.config = config\n this.logger = config.logger || getLogger()\n this.eventBus = new EventBus<{ callKitEvent: CallKitEvent }>(this.logger)\n\n // userId / deviceId 通过 getter 实时读取,避免登录前快照导致 ext 字段为空\n this.inviteTimeoutMs = config.inviteTimeout || 30000\n\n // 初始化状态层\n this.singleCallState = new SingleCallStateMachine(this.logger)\n this.groupCallSession = new GroupCallSession(this.logger)\n\n // 初始化信令层\n this.signalRouter = new SignalRouter(this.logger)\n this.signalSender = new SignalSender(config.imClient, this.logger, config.createMessage)\n\n // 初始化 Handler(传入 provider 函数,确保每次校验/读取时都拿最新登录态)\n this.singleCallHandler = new SingleCallSignalHandler(\n this.singleCallState,\n this.signalSender,\n () => this.deviceId,\n this.logger\n )\n this.groupCallHandler = new GroupCallSignalHandler(\n this.groupCallSession,\n this.singleCallState,\n this.signalSender,\n () => this.userId,\n this.logger\n )\n\n // 注册到 Router\n this.signalRouter.register('alert', this.singleCallHandler)\n this.signalRouter.register('confirmRing', this.singleCallHandler)\n this.signalRouter.register('answerCall', this.singleCallHandler)\n this.signalRouter.register('cancelCall', this.singleCallHandler)\n this.signalRouter.register('leaveCall', this.singleCallHandler)\n this.signalRouter.register('confirmCallee', this.singleCallHandler)\n\n this.signalRouter.register('answerCall', this.groupCallHandler)\n this.signalRouter.register('cancelCall', this.groupCallHandler)\n this.signalRouter.register('leaveCall', this.groupCallHandler)\n\n // 初始化 IM 监听\n this.imListener = new IMListener(\n config.imClient,\n {\n onTextMessage: (msg) => this.handleTextMessage(msg),\n onCmdMessage: (msg) => this.handleCmdMessage(msg),\n onConnected: () => this.handleIMConnected(),\n onDisconnected: () => this.handleIMDisconnected(),\n },\n this.logger\n )\n this.imListener.mount()\n\n this.logger.info('[CallKitCore] 初始化完成(userId/deviceId 将在登录后实时读取)')\n this.logger.warn('🚀 ========== CallKitCore 链路已激活 ========== 🚀')\n this.logger.warn(' 版本: callkit-core | 当前用户:', this.userId || '(尚未登录)', '| 设备:', this.deviceId || '(尚未登录)')\n }\n\n // ───────────────────────────────────────────────\n // 单聊 API\n // ───────────────────────────────────────────────\n\n /**\n * 发起单聊通话\n */\n async inviteCall(params: InviteCallParams): Promise<void> {\n if (this.destroyed) throw new Error('CallKitCore 已销毁')\n\n const callId = generateRandomChannel(16)\n const channel = generateRandomChannel(12)\n\n // 获取 RTC token(兼容真实 SDK 返回 { data: { RTCToken, appId, RTCUId } } 与精简 mock)\n const token = await this.fetchRtcToken(channel)\n\n // 状态机流转\n const stateResult = this.singleCallState.initInvite({\n calleeUserId: params.calleeUserId,\n callType: params.callType,\n callerDevId: this.deviceId,\n callerUserId: this.userId,\n callId,\n channel,\n token,\n timeout: this.inviteTimeoutMs,\n })\n\n // 启动超时定时器(Critical #1:超时事件由 CallKitCore 消费)\n this.startInviteTimeout()\n\n // 发送 invite 文本消息\n // 优先使用本次邀请传入的 callerInfo,回退到初始化时的 userProfile\n const callerInfo = {\n ...this.config.userProfile,\n ...params.callerInfo,\n }\n const ext = MessageBuilder.buildInviteExt({\n callId,\n callerUserId: this.userId,\n calleeUserId: params.calleeUserId,\n callerDevId: this.deviceId,\n channel,\n callType: params.callType,\n callerInfo,\n })\n\n await this.signalSender.sendInviteMessage(\n params.calleeUserId,\n 'singleChat',\n '[通话邀请]',\n ext\n )\n\n // 处理并发出事件\n this.processEvents(stateResult.events, this.singleCallState.getState())\n }\n\n /**\n * 响应来电(接受/拒绝)\n */\n async answerCall(params: AnswerCallParams): Promise<void> {\n if (this.destroyed) throw new Error('CallKitCore 已销毁')\n\n const state = this.singleCallState.getState()\n\n // 兼容旧版 accept: boolean 参数\n const result = params.result ?? (params.accept ? 'accept' : 'refuse')\n const isGroupCall = state.type === CALL_TYPE.VIDEO_MULTI || state.type === CALL_TYPE.AUDIO_MULTI\n\n // 构建并发送 answerCall 信令(群聊也是点对点发给主叫方)\n const ext = MessageBuilder.buildCmdExt({\n action: 'answerCall',\n callId: state.callId,\n callerDevId: state.callerDevId,\n calleeDevId: this.deviceId,\n result,\n })\n\n await this.signalSender.sendCmdMessage(\n state.callerUserId,\n 'singleChat',\n ext,\n { deliverOnlineOnly: true }\n )\n\n if (result === 'accept') {\n if (isGroupCall) {\n // 群聊:被叫接受后不直接进入 IN_CALL,等待主叫方发送 confirmCallee\n // (修复 Critical #4:避免二次 SHOULD_JOIN_RTC)\n this.logger.info('[CallKitCore] 群聊接受,等待 confirmCallee 后再进入 IN_CALL')\n this.clearInviteTimeout()\n } else {\n // 单聊:保持 ALERTING,等待 confirmCallee 后再进入 IN_CALL\n this.logger.info('[CallKitCore] 单聊接受,等待 confirmCallee')\n }\n } else {\n // ── 拒绝/忙线分支 ──\n // 清理本地状态\n const hangupReason = result === 'busy' ? HANGUP_REASON.BUSY : HANGUP_REASON.REFUSE\n const hangupResult = this.singleCallState.hangup(hangupReason)\n this.processEvents(hangupResult.events, state)\n }\n }\n\n /**\n * 挂断/取消通话\n */\n async hangup(params?: HangupParams): Promise<void> {\n if (this.destroyed) throw new Error('CallKitCore 已销毁')\n\n const state = this.singleCallState.getState()\n const currentStatus = state.status\n\n if (currentStatus === CALL_STATUS.IDLE) {\n this.logger.warn('[CallKitCore] hangup: 当前 IDLE,忽略')\n return\n }\n\n // 根据状态决定发送 cancelCall 还是 leaveCall\n const isGroupCall =\n state.type === CALL_TYPE.VIDEO_MULTI || state.type === CALL_TYPE.AUDIO_MULTI\n\n if (currentStatus === CALL_STATUS.INVITING || currentStatus === CALL_STATUS.ALERTING) {\n this.clearInviteTimeout()\n // 发送 cancelCall\n const ext = MessageBuilder.buildCmdExt({\n action: 'cancelCall',\n callId: state.callId,\n callerDevId: state.callerDevId,\n })\n\n if (isGroupCall) {\n // 群聊:发送到群 groupId,携带 receiverList 定向给被邀请成员(与旧版 CallService 对齐)\n const groupSnapshot = this.groupCallSession.getSnapshot()\n const groupId = groupSnapshot?.groupId || state.calleeUserId\n const allParticipants = this.groupCallSession.getAllParticipants()\n const receiverList = allParticipants\n .filter((p) => p.userId !== this.userId && p.state === 'invited')\n .map((p) => p.userId)\n this.logger.warn(\n '[CallKitCore] 群聊取消: participants=',\n allParticipants.map((p) => ({ userId: p.userId, state: p.state })),\n '| receiverList=',\n receiverList\n )\n if (groupId && receiverList.length > 0) {\n this.logger.warn('[CallKitCore] 群聊取消: 发送 cancelCall 到', groupId, 'receiverList:', receiverList)\n await this.signalSender\n .sendCmdMessage(groupId, 'groupChat', ext, { receiverList })\n .catch((err) => {\n this.logger.error('[CallKitCore] 群聊取消: 发送 cancelCall 失败', err)\n })\n } else {\n this.logger.warn('[CallKitCore] 群聊取消: 无接收者,跳过发送 cancelCall')\n }\n } else {\n // 单聊\n const targetId = state.callerUserId === this.userId ? state.calleeUserId : state.callerUserId\n await this.signalSender.sendCmdMessage(targetId, 'singleChat', ext).catch(() => {})\n }\n } else if (currentStatus === CALL_STATUS.IN_CALL) {\n this.clearInviteTimeout()\n // 发送 leaveCall\n const ext = MessageBuilder.buildCmdExt({\n action: 'leaveCall',\n callId: state.callId,\n })\n\n if (isGroupCall) {\n // 群聊:发送到群 groupId,携带 receiverList 定向给通话中成员(与旧版 CallService 对齐)\n const groupSnapshot = this.groupCallSession.getSnapshot()\n const groupId = groupSnapshot?.groupId || state.calleeUserId\n const receiverList = this.groupCallSession\n .getAllParticipants()\n .filter((p) => p.userId !== this.userId && p.state !== 'left')\n .map((p) => p.userId)\n if (groupId && receiverList.length > 0) {\n await this.signalSender\n .sendCmdMessage(groupId, 'groupChat', ext, { receiverList })\n .catch(() => {})\n }\n } else {\n // 单聊\n const targetId = state.callerUserId === this.userId ? state.calleeUserId : state.callerUserId\n await this.signalSender.sendCmdMessage(targetId, 'singleChat', ext).catch(() => {})\n }\n }\n\n const reason = params?.reason === 'cancel'\n ? HANGUP_REASON.CANCEL\n : params?.reason === 'timeout'\n ? HANGUP_REASON.NO_RESPONSE\n : HANGUP_REASON.HANGUP\n\n const hangupResult = this.singleCallState.hangup(reason)\n this.clearInviteTimeout()\n this.processEvents(hangupResult.events, state)\n }\n\n // ───────────────────────────────────────────────\n // 群聊 API\n // ───────────────────────────────────────────────\n\n /**\n * 通话中邀请更多成员加入群聊通话\n */\n async inviteMoreParticipants(participantIds: string[]): Promise<void> {\n if (this.destroyed) throw new Error('CallKitCore 已销毁')\n\n const state = this.singleCallState.getState()\n if (state.status !== CALL_STATUS.IN_CALL && state.status !== CALL_STATUS.INVITING) {\n this.logger.warn('[CallKitCore] inviteMoreParticipants: 当前不在通话中,忽略')\n return\n }\n\n const isGroupCall = state.type === CALL_TYPE.VIDEO_MULTI || state.type === CALL_TYPE.AUDIO_MULTI\n if (!isGroupCall) {\n this.logger.warn('[CallKitCore] inviteMoreParticipants: 当前不是群聊通话,忽略')\n return\n }\n\n const groupSnapshot = this.groupCallSession.getSnapshot()\n const groupId = groupSnapshot?.groupId\n if (!groupId) {\n this.logger.warn('[CallKitCore] inviteMoreParticipants: 群聊会话未初始化')\n return\n }\n\n // 构建 invite 文本消息\n // 追加邀请时复用当前会话的群名称,避免退化成 groupId\n const ext = MessageBuilder.buildInviteExt({\n callId: state.callId,\n callerUserId: this.userId,\n calleeUserId: groupId,\n callerDevId: this.deviceId,\n channel: state.channel,\n callType: state.type,\n invitedMembers: participantIds,\n groupInfo: { groupId, groupName: groupSnapshot?.groupName || groupId },\n callerInfo: this.config.userProfile,\n })\n\n // 先发送邀请消息,成功后再添加参与者到会话\n try {\n await this.signalSender.sendInviteMessage(\n participantIds,\n 'groupChat',\n '[群通话邀请]',\n ext,\n groupId\n )\n } catch (err) {\n this.emitError('inviteMoreParticipantsFailed', err, { callId: state.callId, participantIds })\n this.logger.error('[CallKitCore] 发送追加邀请失败', err)\n throw err\n }\n\n // 发送成功后再添加参与者到会话\n participantIds.forEach((userId: string) => {\n if (!this.groupCallSession.getParticipant(userId)) {\n this.groupCallSession.addParticipant({\n userId,\n nickname: userId,\n avatarUrl: '',\n state: 'invited',\n isLocal: false,\n isMuted: false,\n isCameraOn: false,\n isSpeaking: false,\n })\n }\n })\n\n this.logger.info('[CallKitCore] 已发送追加邀请', { groupId, participantIds })\n }\n\n /**\n * 发起群聊通话\n */\n async inviteGroupCall(params: InviteGroupCallParams): Promise<void> {\n if (this.destroyed) throw new Error('CallKitCore 已销毁')\n\n const callId = generateRandomChannel(16)\n const channel = generateRandomChannel(12)\n const callTypeStr = params.callType === CALL_TYPE.VIDEO_MULTI ? 'video' : 'audio'\n\n // 获取 RTC token(兼容真实 SDK 返回 { data: { RTCToken, appId, RTCUId } } 与精简 mock)\n const token = await this.fetchRtcToken(channel)\n\n // 优先使用传入的群名称,回退到 groupId\n const groupName = params.ext?.groupName || params.groupId\n const groupAvatar = params.ext?.groupAvatar\n\n // 初始化群聊会话\n this.groupCallSession.init({\n sessionId: channel,\n groupId: params.groupId,\n groupName,\n callType: callTypeStr,\n callerUserId: this.userId,\n })\n\n // 添加主叫方自己\n this.groupCallSession.addParticipant({\n userId: this.userId,\n nickname: this.userId,\n avatarUrl: '',\n state: 'joinedRtc',\n isLocal: true,\n isMuted: false,\n isCameraOn: callTypeStr === 'video',\n isSpeaking: false,\n })\n\n // 添加被邀请成员\n params.participantIds.forEach((userId: string) => {\n this.groupCallSession.addParticipant({\n userId,\n nickname: userId,\n avatarUrl: '',\n state: 'invited',\n isLocal: false,\n isMuted: false,\n isCameraOn: false,\n isSpeaking: false,\n })\n })\n\n // 发出 GROUP_CALL_INIT 事件(主叫方也需要此事件来驱动 UI 显示)\n this.processEvents(\n [\n {\n type: 'GROUP_CALL_INIT',\n callId,\n groupId: params.groupId,\n groupName,\n channel,\n callType: callTypeStr,\n callerUserId: this.userId,\n invitedMembers: params.participantIds,\n },\n ],\n this.singleCallState.getState()\n )\n\n // 初始化单聊状态机(群聊也用它存储 callId/channel/token)\n // 群聊时 calleeUserId 使用 groupId,与旧版 lib/ 保持一致\n const stateResult = this.singleCallState.initInvite({\n calleeUserId: params.groupId,\n callType: params.callType,\n callerDevId: this.deviceId,\n callerUserId: this.userId,\n callId,\n channel,\n token,\n timeout: this.inviteTimeoutMs,\n })\n\n // 发送 invite 文本消息(groupChat)\n // calleeUserId 在群聊时使用 groupId,与旧版 lib/ 的 callStateStore.calleeUserId = groupId 对齐\n // 优先使用本次邀请传入的 callerInfo,回退到初始化时的 userProfile\n const callerInfo = {\n ...this.config.userProfile,\n ...params.callerInfo,\n }\n const ext = MessageBuilder.buildInviteExt({\n callId,\n callerUserId: this.userId,\n calleeUserId: params.groupId,\n callerDevId: this.deviceId,\n channel,\n callType: params.callType,\n invitedMembers: params.participantIds,\n groupInfo: { groupId: params.groupId, groupName, groupAvatar },\n callerInfo,\n })\n\n // 使用用户传入的 message 作为 txt 消息内容,与 v1.0.6 ChatService 行为对齐\n const inviteMessage = params.ext?.message || '[群通话邀请]'\n\n await this.signalSender.sendInviteMessage(\n params.participantIds,\n 'groupChat',\n inviteMessage,\n ext,\n params.groupId\n )\n\n this.processEvents(stateResult.events, this.singleCallState.getState())\n\n // 群聊主叫方:发送 invite 后立即进入 IN_CALL 并加入 RTC(与旧版行为对齐)\n this.logger.info('[CallKitCore] 群聊主叫方:进入 IN_CALL 并触发 RTC 加入')\n // fromCaller=false 表示“主叫方收到 accept”,使 isCaller=true / role='caller'\n const answerResult = this.singleCallState.receiveAnswer('accept', false)\n if (answerResult.ok) {\n this.processEvents(answerResult.events, this.singleCallState.getState())\n }\n }\n\n // ───────────────────────────────────────────────\n // 媒体控制\n // ───────────────────────────────────────────────\n\n /**\n * 切换本地音频(静音/取消静音)\n */\n toggleAudio(): void {\n if (this.destroyed) throw new Error('CallKitCore 已销毁')\n const stateResult = this.singleCallState.toggleAudio()\n this.processEvents(stateResult.events, this.singleCallState.getState())\n }\n\n /**\n * 切换本地视频(开启/关闭摄像头)\n */\n toggleVideo(): void {\n if (this.destroyed) throw new Error('CallKitCore 已销毁')\n const stateResult = this.singleCallState.toggleVideo()\n this.processEvents(stateResult.events, this.singleCallState.getState())\n }\n\n // ───────────────────────────────────────────────\n // RTC 反馈\n // ───────────────────────────────────────────────\n\n /**\n * 获取 RTC token(并缓存 appId / RTCUId)\n * - 环信真实 SDK:`{ data: { RTCToken, appId, RTCUId, expireIn } }`\n * - 早期 / 精简 mock:`{ accessToken, appId }`\n * 为了与旧版 lib/composables/useJoinChannel.ts 行为对齐,同步保存 rtcAppId、rtcUid供\n * shouldJoinRtc 事件透传给上层 RtcAdapter(Agora 的 join 必须使用服务端返回的数值型 uid)。\n */\n private async fetchRtcToken(channel: string): Promise<string> {\n try {\n const tokenRes: any = await this.config.imClient.getRTCToken(channel)\n const data: any = tokenRes?.data ?? tokenRes ?? {}\n const token: string = data.RTCToken ?? data.accessToken ?? tokenRes?.accessToken ?? ''\n const appId: string = data.appId ?? tokenRes?.appId ?? ''\n const rtcUid: number = Number(data.RTCUId ?? data.rtcUid ?? 0) || 0\n\n if (appId) this.rtcAppId = appId\n if (rtcUid) this.rtcUid = rtcUid\n\n if (!token) {\n this.logger.warn('[CallKitCore] getRTCToken 返回 token 为空', { tokenRes })\n } else {\n this.logger.info('[CallKitCore] 获取 RTC token 成功', {\n channel,\n appId: this.rtcAppId,\n rtcUid: this.rtcUid,\n hasToken: !!token,\n })\n }\n return token\n } catch (err) {\n this.emitError('rtcTokenFetchFailed', err, { channel })\n this.logger.warn('[CallKitCore] 获取 RTC token 失败,使用空 token', err)\n return ''\n }\n }\n\n /**\n * 上层调用 RTC SDK 后,通过此方法反馈 RTC 事件给核心库\n */\n reportRtcEvent(report: RtcReport): void {\n this.logger.info('[CallKitCore] reportRtcEvent', report)\n\n const { type, payload } = report\n\n // 群聊参与者 RTC 状态同步\n if (payload.userId) {\n switch (type) {\n case 'userJoined':\n case 'userPublished':\n this.groupCallSession.markJoinedRtc(payload.userId)\n break\n case 'userLeft':\n case 'userUnpublished':\n this.groupCallSession.markLeftRtc(payload.userId)\n break\n case 'userAudioMuted':\n this.groupCallSession.markAudioMuted(payload.userId, true)\n break\n case 'userAudioUnmuted':\n this.groupCallSession.markAudioMuted(payload.userId, false)\n break\n case 'userVideoMuted':\n this.groupCallSession.markVideoOn(payload.userId, false)\n break\n case 'userVideoUnmuted':\n this.groupCallSession.markVideoOn(payload.userId, true)\n break\n }\n }\n\n // 网络质量/说话状态/错误等事件直接透传给上层\n if (\n type === 'networkQuality' ||\n type === 'speaking' ||\n type === 'stoppedSpeaking' ||\n type === 'error'\n ) {\n const rtcEvent: CallKitEvent = {\n type: 'rtcReport',\n payload: { type: report.type, payload: report.payload },\n }\n this.emitEvent(rtcEvent)\n }\n }\n\n // ───────────────────────────────────────────────\n // 状态查询\n // ───────────────────────────────────────────────\n\n getSingleCallState() {\n return this.singleCallState.getState()\n }\n\n getGroupCallSession() {\n return this.groupCallSession.getSnapshot()\n }\n\n /**\n * 获取群聊通话的所有参与者(包含状态信息)\n */\n getGroupCallParticipants() {\n return this.groupCallSession.getAllParticipants()\n }\n\n /**\n * 当前是否在通话中(IN_CALL 状态)\n */\n isInCall(): boolean {\n return this.singleCallState.isInCall()\n }\n\n /**\n * 当前是否处于可被接听的状态(被叫端弹窗显示区间)\n */\n isWaitingCalleeAction(): boolean {\n return this.singleCallState.isWaitingCalleeAction()\n }\n\n /**\n * 当前是否处于活跃通话中(已进入 RTC 或即将进入)\n */\n isInActiveCall(): boolean {\n return this.singleCallState.isInActiveCall()\n }\n\n /**\n * 当前是否可以接听(被叫端按钮可点击)\n */\n canAccept(): boolean {\n return this.singleCallState.canAccept()\n }\n\n /**\n * 当前是否可以拒绝(被叫端按钮可点击)\n */\n canReject(): boolean {\n return this.singleCallState.canReject()\n }\n\n /**\n * 当前是否可以挂断(主叫/被叫端的挂断按钮可点击)\n */\n canHangup(): boolean {\n return this.singleCallState.canHangup()\n }\n\n /**\n * 当前是否在呼叫/响铃中(INVITING 或 ALERTING 状态)\n */\n isCalling(): boolean {\n return this.singleCallState.isCalling()\n }\n\n /**\n * 获取当前通话类型,无通话时返回 null\n */\n getCurrentCallType(): CALL_TYPE | null {\n const state = this.singleCallState.getState()\n return state.status === CALL_STATUS.IDLE ? null : state.type\n }\n\n /**\n * 获取当前通话 ID,无通话时返回空字符串\n */\n getCurrentCallId(): string {\n return this.singleCallState.getState().callId || ''\n }\n\n /**\n * 当前是否空闲\n */\n isIdle(): boolean {\n return this.singleCallState.isIdle()\n }\n\n // ───────────────────────────────────────────────\n // 事件订阅(供上层精细控制)\n // ───────────────────────────────────────────────\n\n /**\n * 订阅通话事件\n * @returns 取消订阅函数\n */\n onEvent(handler: (event: CallKitEvent) => void): () => void {\n return this.eventBus.on('callKitEvent', handler)\n }\n\n /**\n * 订阅单次通话事件\n */\n onceEvent(handler: (event: CallKitEvent) => void): () => void {\n return this.eventBus.once('callKitEvent', handler)\n }\n\n // ───────────────────────────────────────────────\n // 生命周期\n // ───────────────────────────────────────────────\n\n async destroy(): Promise<void> {\n if (this.destroyed) return\n this.destroyed = true\n\n this.clearInviteTimeout()\n this.stopDurationTimer()\n this.imListener.unmount()\n this.eventBus.clear()\n this.singleCallState.reset()\n this.groupCallSession.destroy()\n\n this.logger.info('[CallKitCore] 已销毁')\n }\n\n // ───────────────────────────────────────────────\n // 内部:消息处理\n // ───────────────────────────────────────────────\n\n private isSelfMessage(msg: any): boolean {\n return msg.from === this.userId\n }\n\n private async handleTextMessage(msg: any): Promise<void> {\n // 无条件日志:所有文本消息都记录,方便排查消息是否到达 core\n const rawExt = msg.ext as any\n const bodyExt = msg.body?.ext as any\n const ext = rawExt || bodyExt\n this.logger.warn('✉️ [CallKitCore] handleTextMessage 被调用', {\n from: msg.from,\n to: msg.to,\n msgType: msg.type,\n chatType: msg.chatType,\n hasRawExt: !!rawExt,\n hasBodyExt: !!bodyExt,\n extAction: ext?.action,\n extCallId: ext?.callId,\n self: this.isSelfMessage(msg),\n })\n\n if (this.isSelfMessage(msg)) {\n this.logger.warn('[CallKitCore] ❌ 忽略自己发送的文本消息')\n return\n }\n\n if (!ext || ext.action !== 'invite') {\n this.logger.warn('[CallKitCore] ❌ 忽略非 invite 文本消息 | ext.action=', ext?.action)\n return\n }\n\n const isGroupCall =\n ext.chatType === CALL_TYPE.VIDEO_MULTI ||\n ext.chatType === CALL_TYPE.AUDIO_MULTI ||\n ext.callkitGroupInfo?.groupId\n\n // ========= Critical #2: 忙线自动拒绝 =========\n const currentStatus = this.singleCallState.getState().status\n if (currentStatus > CALL_STATUS.IDLE) {\n this.logger.warn('[CallKitCore] ❌ 当前已在通话中,发送忙线拒绝 | currentStatus=', currentStatus)\n const busyExt = MessageBuilder.buildCmdExt({\n action: 'answerCall',\n callId: ext.callId as string,\n callerDevId: ext.callerDevId as string,\n calleeDevId: this.deviceId,\n result: 'busy',\n })\n this.signalSender\n .sendCmdMessage(msg.from as string, 'singleChat', busyExt as any, {\n deliverOnlineOnly: true,\n })\n .catch(() => {})\n return\n }\n\n // ========= Critical #3: 接收者校验 =========\n if (!isGroupCall) {\n // 单聊:检查消息是否发给当前用户\n if (msg.to && msg.to !== this.userId) {\n this.logger.warn('[CallKitCore] ❌ 单聊 invite 接收者不是当前用户 | msg.to=', msg.to)\n return\n }\n // 单聊:设备 ID 校验\n if (ext.calleeDevId && ext.calleeDevId !== this.deviceId) {\n this.logger.warn('[CallKitCore] ❌ 单聊 invite calleeDevId 不匹配 | ext=', ext.calleeDevId, '| my=', this.deviceId)\n return\n }\n } else {\n // 群聊:检查当前用户是否在被邀请列表中\n const invitedMembers: string[] = ext.invitedMembers || []\n if (invitedMembers.length > 0 && !invitedMembers.includes(this.userId)) {\n this.logger.warn('[CallKitCore] ❌ 群聊 invite 当前用户不在被邀请列表中')\n return\n }\n }\n\n // ========= Critical #3: 过期检查 =========\n const msgTime = msg.time || ext.ts\n if (msgTime && isMessageExpired(msgTime, this.inviteTimeoutMs + 10000)) {\n this.logger.warn('[CallKitCore] ❌ invite 消息已过期 | msgTime=', msgTime)\n return\n }\n\n this.logger.warn(\n isGroupCall\n ? '✉️ [CallKitCore] ✅ 【群聊】确认收到 invite 文本消息 → 进入群聊处理分支'\n : '✉️ [CallKitCore] ✅ 【单聊】确认收到 invite 文本消息 → 进入单聊处理分支',\n { from: msg.from, callId: ext.callId, channel: ext.channelName, type: ext.type }\n )\n\n if (isGroupCall) {\n // 群聊 invite:异步获取 token 并初始化\n await this.handleGroupCallInvite(msg, ext).catch((err) => {\n this.emitError('groupCallInviteHandlingFailed', err, { messageId: msg.id })\n this.logger.error('[CallKitCore] 群聊 invite 处理失败', err)\n })\n } else {\n // 单聊 invite\n await this.handleSingleCallInvite(msg, ext)\n }\n }\n\n private async handleGroupCallInvite(msg: any, ext: any): Promise<void> {\n // 群聊 invite:同时初始化 singleCallState(被叫方需要它来处理 confirmCallee / cancelCall / leaveCall)\n const callId = ext.callId as string\n const channel = ext.channelName as string\n const callType = ext.type as CALL_TYPE\n const callerDevId = ext.callerDevId as string\n const callerUserId = (ext.callerIMName as string) || (msg.from as string) || ''\n\n // 记录待处理 invite,用于在 fetchRtcToken 异步期间接收到取消/离开信令时能够 abort\n this.pendingIncomingInvites.set(callId, { aborted: false })\n\n // 获取 RTC token(群聊被叫方同样需要 token 加入 Agora 频道)\n const token = await this.fetchRtcToken(channel)\n\n // 检查是否在 fetch token 期间被主叫取消或离开\n const pending = this.pendingIncomingInvites.get(callId)\n this.pendingIncomingInvites.delete(callId)\n if (pending?.aborted) {\n this.logger.warn('[CallKitCore] 群聊 invite 在获取 token 期间已被取消/离开,跳过初始化')\n return\n }\n\n if (this.singleCallState.getState().status === CALL_STATUS.IDLE) {\n this.logger.warn('🔄 [CallKitCore] 群聊被叫方:singleCallState 从 IDLE → ALERTING')\n const groupId = ext?.callkitGroupInfo?.groupId || ext?.groupId || ''\n const stateResult = this.singleCallState.initIncoming({\n callId,\n channel,\n token,\n callType,\n callerDevId,\n callerUserId,\n calleeDevId: this.deviceId,\n calleeUserId: groupId, // 群聊时 calleeUserId 使用 groupId,与旧版对齐\n })\n this.logger.warn('[CallKitCore] initIncoming 返回事件数:', stateResult.events.length)\n // 处理状态机返回的 STATUS_CHANGED 事件,确保 callStateStore 同步到 ALERTING\n this.processEvents(stateResult.events, this.singleCallState.getState())\n // 启动超时定时器\n this.startInviteTimeout()\n } else {\n this.logger.warn(\n '[CallKitCore] ⚠️ 群聊被叫方:singleCallState 不是 IDLE,跳过 initIncoming | 当前状态=',\n this.singleCallState.getState().status\n )\n }\n\n const events = this.groupCallHandler.handleInviteTextMessage(msg)\n this.logger.warn('[CallKitCore] 群聊 invite 处理后事件:', events.map((e: any) => e.type))\n this.processEvents(events, this.singleCallState.getState())\n\n // 发出 incomingCall 事件(与单聊行为保持一致,确保 UI 层能弹出待接听状态栏)\n const incomingEvent: CallKitEvent = {\n type: 'incomingCall',\n payload: {\n callId,\n callType,\n callerUserId,\n callerDevId,\n channel,\n calleeUserId: ext?.callkitGroupInfo?.groupId || ext?.groupId || '',\n token: '',\n callerInfo: ext.ease_chat_uikit_user_info,\n groupId: ext?.callkitGroupInfo?.groupId || ext?.groupId,\n groupName: ext?.callkitGroupInfo?.groupName,\n },\n }\n this.logger.warn('[CallKitCore] 即将发出 incomingCall 事件:', { callId, callerUserId, callType })\n this.emitEvent(incomingEvent)\n this.logger.warn('[CallKitCore] incomingCall 事件已发出')\n\n // 被叫方收到 invite 后回发 alert CMD(与旧版 useListenerManager 对齐)\n this.sendAlertSignal(msg.from as string, ext.callId as string, ext.callerDevId as string)\n }\n\n private async handleSingleCallInvite(msg: any, ext: any): Promise<void> {\n const callId = ext.callId as string\n const channel = ext.channelName as string\n const callType = ext.type as CALL_TYPE\n const callerDevId = ext.callerDevId as string\n const callerUserId = (ext.callerIMName as string) || (msg.from as string) || ''\n const calleeUserId = (ext.calleeIMName as string) || (msg.to as string) || ''\n\n this.logger.debug('[CallKitCore] handleSingleCallInvite', {\n from: msg.from,\n callerIMName: ext.callerIMName,\n calleeIMName: ext.calleeIMName,\n resolvedCallerId: callerUserId,\n resolvedCalleeId: calleeUserId,\n })\n\n // 获取 RTC token(Warning #6:使用 await 确保 token 可用)\n const token = await this.fetchRtcToken(channel)\n\n // 状态机初始化\n const stateResult = this.singleCallState.initIncoming({\n callId,\n channel,\n token,\n callType,\n callerDevId,\n callerUserId,\n calleeDevId: this.deviceId,\n calleeUserId,\n })\n\n // 启动超时定时器\n this.startInviteTimeout()\n\n // 发出 incomingCall 事件\n const incomingEvent: CallKitEvent = {\n type: 'incomingCall',\n payload: {\n callId,\n callType,\n callerUserId,\n callerDevId,\n channel,\n calleeUserId,\n token,\n callerInfo: ext.ease_chat_uikit_user_info,\n },\n }\n this.emitEvent(incomingEvent)\n\n // 处理状态机返回的事件(STATUS_CHANGED)\n this.processEvents(stateResult.events, this.singleCallState.getState())\n\n // 被叫方收到 invite 后回发 alert CMD(与旧版 useListenerManager 对齐)\n this.sendAlertSignal(msg.from as string, callId, callerDevId)\n }\n\n /**\n * 发送 alert CMD 信令给主叫方\n */\n private sendAlertSignal(to: string, callId: string, callerDevId: string): void {\n const alertExt = MessageBuilder.buildCmdExt({\n action: 'alert',\n callId,\n callerDevId,\n calleeDevId: this.deviceId,\n })\n this.signalSender\n .sendCmdMessage(to, 'singleChat', alertExt as any, { deliverOnlineOnly: true })\n .catch(() => {})\n }\n\n private handleCmdMessage(msg: any): void {\n // 无条件日志:所有 CMD 消息都记录\n this.logger.warn('📨 [CallKitCore] handleCmdMessage 被调用', {\n from: msg.from,\n to: msg.to,\n action: msg.action,\n extAction: msg.ext?.action,\n callId: msg.ext?.callId,\n self: this.isSelfMessage(msg),\n })\n\n if (this.isSelfMessage(msg)) {\n this.logger.warn('[CallKitCore] ❌ 忽略自己发送的 CMD 消息')\n return\n }\n\n // ========= Critical #3: CMD 过滤 — 只处理 rtcCall 类型 =========\n if (msg.action !== 'rtcCall') {\n this.logger.warn('[CallKitCore] ❌ 忽略非 rtcCall CMD 消息 | action=', msg.action)\n return\n }\n\n // ========= Critical #3: CMD 过期检查 =========\n const cmdTime = msg.time || msg.ext?.ts\n if (cmdTime && isCmdMessageExpired(cmdTime)) {\n this.logger.warn('[CallKitCore] ❌ CMD 消息已过期 | cmdTime=', cmdTime)\n return\n }\n\n // ========= 待处理 invite 的取消/离开拦截 =========\n // 群聊被叫方在 fetchRtcToken 期间 singleCallState 仍为 IDLE,SignalRouter 会忽略 cancelCall/leaveCall。\n // 此处提前拦截并标记为 aborted,避免 token 返回后仍弹出已取消的邀请。\n const extAction = msg.ext?.action\n const extCallId = msg.ext?.callId\n if (\n extCallId &&\n (extAction === 'cancelCall' || extAction === 'leaveCall') &&\n this.pendingIncomingInvites.has(extCallId)\n ) {\n this.logger.warn('[CallKitCore] 待处理 invite 收到取消/离开信令,标记为 aborted', {\n callId: extCallId,\n action: extAction,\n })\n this.pendingIncomingInvites.get(extCallId)!.aborted = true\n return\n }\n\n const events = this.signalRouter.dispatch(msg)\n if (events.length > 0) {\n this.logger.warn('📨 [CallKitCore] ✅ CMD 消息处理后产生事件:', events.map((e) => e.type))\n this.processEvents(events, this.singleCallState.getState())\n } else {\n this.logger.warn('📨 [CallKitCore] ⚠️ CMD 消息未产生任何事件(可能被忽略或 handler 返回空)')\n }\n }\n\n // ───────────────────────────────────────────────\n // 内部:事件处理与映射\n // ───────────────────────────────────────────────\n\n private processEvents(events: DomainEvent[], snapshot: SingleCallState): void {\n events.forEach((event) => {\n const callKitEvents = this.mapDomainEvents(event, snapshot)\n callKitEvents.forEach((callKitEvent) => {\n this.emitEvent(callKitEvent)\n this.handleRtcEvent(callKitEvent)\n\n // 通话开始/连接时启动时长计时器;通话结束时停止\n if (callKitEvent.type === 'callStarted' || callKitEvent.type === 'callConnected') {\n this.startDurationTimer(\n callKitEvent.payload.callId,\n callKitEvent.payload.channel,\n callKitEvent.payload.callType,\n callKitEvent.payload.callerUserId\n )\n }\n if (callKitEvent.type === 'callEnded') {\n this.stopDurationTimer()\n }\n })\n })\n }\n\n /**\n * 当配置了 rtcAdapter 时,自动处理 RTC 相关事件\n */\n private handleRtcEvent(event: CallKitEvent): void {\n const adapter = this.config.rtcAdapter\n if (!adapter) return\n\n switch (event.type) {\n case 'shouldJoinRtc': {\n const p = event.payload\n adapter\n .joinChannel({\n channel: p.channel,\n token: p.token,\n uid: p.uid,\n appId: p.appId,\n })\n .catch((e) => {\n this.emitError('rtcJoinChannelFailed', e, { channel: p.channel, uid: p.uid })\n this.logger.error('[CallKitCore] rtcAdapter.joinChannel 失败:', e)\n })\n break\n }\n\n case 'shouldLeaveRtc': {\n adapter.leaveChannel().catch((e) => {\n this.emitError('rtcLeaveChannelFailed', e, { channel: event.payload.channel })\n })\n break\n }\n\n case 'shouldPublishTracks': {\n const p = event.payload\n adapter\n .publishLocalTracks(p.trackTypes)\n .catch((e) => {\n this.emitError('rtcPublishTracksFailed', e, { channel: p.channel, trackTypes: p.trackTypes })\n this.logger.error('[CallKitCore] rtcAdapter.publishLocalTracks 失败:', e)\n })\n break\n }\n\n case 'localAudioChanged': {\n adapter\n .setAudioEnabled(event.payload.enabled)\n .catch((e) => {\n this.emitError('rtcSetAudioEnabledFailed', e, { enabled: event.payload.enabled })\n this.logger.error('[CallKitCore] rtcAdapter.setAudioEnabled 失败:', e)\n })\n break\n }\n\n case 'localVideoChanged': {\n adapter\n .setVideoEnabled(event.payload.enabled)\n .catch((e) => {\n this.emitError('rtcSetVideoEnabledFailed', e, { enabled: event.payload.enabled })\n this.logger.error('[CallKitCore] rtcAdapter.setVideoEnabled 失败:', e)\n })\n break\n }\n }\n }\n\n private mapDomainEvents(event: DomainEvent, snapshot: SingleCallState): CallKitEvent[] {\n const base = {\n callId: event.callId,\n channel: snapshot.channel,\n callType: snapshot.type,\n callerUserId: snapshot.callerUserId,\n calleeUserId: snapshot.calleeUserId,\n }\n\n const isGroupCall = snapshot.type === CALL_TYPE.VIDEO_MULTI || snapshot.type === CALL_TYPE.AUDIO_MULTI\n\n switch (event.type) {\n case 'STATUS_CHANGED': {\n return [\n {\n type: 'statusChanged',\n payload: {\n ...base,\n from: String(event.from),\n to: String(event.to),\n },\n },\n ]\n }\n\n case 'CALL_INVITED': {\n const common = { ...base, isCaller: event.isCaller }\n return [\n { type: 'callInvited', payload: common },\n { type: isGroupCall ? 'groupCallInvited' : 'singleCallInvited', payload: common },\n ]\n }\n\n case 'CALL_STARTED': {\n const common = {\n ...base,\n isCaller: event.isCaller,\n startTime: Date.now(),\n }\n return [\n { type: 'callStarted', payload: common },\n { type: isGroupCall ? 'groupCallStarted' : 'singleCallStarted', payload: common },\n ]\n }\n\n case 'CALL_ACCEPTED': {\n const common = { ...base, isCaller: event.isCaller }\n return [\n { type: 'callAccepted', payload: common },\n { type: isGroupCall ? 'groupCallAccepted' : 'singleCallAccepted', payload: common },\n ]\n }\n\n case 'CALL_CONNECTED': {\n return [\n { type: 'callConnected', payload: base },\n { type: isGroupCall ? 'groupCallConnected' : 'singleCallConnected', payload: base },\n ]\n }\n\n case 'CALL_ENDED': {\n const common = {\n ...base,\n reason: event.reason as any,\n duration: event.duration,\n }\n return [\n { type: 'callEnded', payload: common },\n { type: isGroupCall ? 'groupCallEnded' : 'singleCallEnded', payload: common },\n ]\n }\n\n case 'CALL_TIMEOUT': {\n return [\n { type: 'callTimeout', payload: base },\n { type: isGroupCall ? 'groupCallTimeout' : 'singleCallTimeout', payload: base },\n ]\n }\n\n case 'CALL_REFUSED': {\n const common = { ...base, isRemote: event.isRemote }\n return [\n { type: 'callRefused', payload: common },\n { type: isGroupCall ? 'groupCallRefused' : 'singleCallRefused', payload: common },\n ]\n }\n\n case 'CALL_BUSY': {\n return [\n { type: 'callBusy', payload: base },\n { type: isGroupCall ? 'groupCallBusy' : 'singleCallBusy', payload: base },\n ]\n }\n\n case 'CALL_CANCELED': {\n const common = { ...base, isRemote: event.isRemote }\n return [\n { type: 'callCanceled', payload: common },\n { type: isGroupCall ? 'groupCallCanceled' : 'singleCallCanceled', payload: common },\n ]\n }\n\n case 'SHOULD_JOIN_RTC': {\n return [\n {\n type: 'shouldJoinRtc',\n payload: {\n ...base,\n token: event.token,\n // Agora 加入频道必须使用服务端返回的 RTCUId(数值型),\n // 无法获取时兑底为 IM userId(与旧版付费智能兑底逻辑一致)\n uid: this.rtcUid || this.userId,\n appId: this.rtcAppId || undefined,\n role: event.role,\n },\n },\n ]\n }\n\n case 'GROUP_CALL_INIT': {\n return [\n {\n type: 'groupCallInit',\n payload: {\n callId: event.callId,\n groupId: event.groupId,\n groupName: event.groupName,\n channel: event.channel,\n callType: event.callType,\n callerUserId: event.callerUserId,\n invitedMembers: event.invitedMembers,\n },\n },\n ]\n }\n\n case 'PARTICIPANT_STATE_CHANGED': {\n return [\n {\n type: 'participantStateChanged',\n payload: {\n callId: event.callId,\n userId: event.userId,\n state: event.state,\n groupId: event.groupId,\n },\n },\n ]\n }\n\n case 'PARTICIPANT_JOINED': {\n return [\n {\n type: 'participantJoined',\n payload: {\n ...base,\n userId: event.userId,\n groupId: event.groupId,\n },\n },\n ]\n }\n\n case 'PARTICIPANT_LEFT': {\n return [\n {\n type: 'participantLeft',\n payload: {\n ...base,\n userId: event.userId,\n reason: event.reason,\n groupId: event.groupId,\n },\n },\n ]\n }\n\n case 'LOCAL_AUDIO_CHANGED': {\n return [{ type: 'localAudioChanged', payload: { enabled: event.enabled } }]\n }\n\n case 'LOCAL_VIDEO_CHANGED': {\n return [{ type: 'localVideoChanged', payload: { enabled: event.enabled } }]\n }\n\n default:\n return []\n }\n }\n\n private emitEvent(event: CallKitEvent): void {\n this.logger.debug('[CallKitCore] emitEvent:', event.type, '| onEvent存在=', !!this.config.onEvent, '| onUIEvent存在=', !!this.config.onUIEvent)\n\n // 重复订阅检测:如果同时使用了 config.onEvent 和 core.onEvent(),发出警告\n const hasEventBusListeners = this.eventBus.listenerCount('callKitEvent') > 0\n const hasLegacyOnEvent = !!this.config.onEvent\n if (hasEventBusListeners && hasLegacyOnEvent) {\n this.logger.warn(\n '[CallKitCore] 同时使用了 config.onEvent 和 core.onEvent() 订阅,事件可能重复触发。' +\n '建议只使用其中一种订阅方式。'\n )\n }\n\n // 1. 通过 EventBus 分发(供上层 onEvent/offEvent 使用)\n this.eventBus.emit('callKitEvent', event)\n\n // 2. 兼容旧接口:onEvent 接收所有事件\n if (this.config.onEvent) {\n try {\n this.config.onEvent(event)\n this.logger.debug('[CallKitCore] onEvent 回调执行成功:', event.type)\n } catch (err) {\n this.logger.error('[CallKitCore] onEvent 回调执行失败:', err)\n }\n }\n\n // 3. 按来源分发到新接口\n if (isUIEvent(event) && this.config.onUIEvent) {\n try {\n this.config.onUIEvent(event)\n this.logger.debug('[CallKitCore] onUIEvent 回调执行成功:', event.type)\n } catch (err) {\n this.logger.error('[CallKitCore] onUIEvent 回调执行失败:', err)\n }\n }\n\n if (isRtcEvent(event) && this.config.onRtcEvent) {\n try {\n this.config.onRtcEvent(event)\n } catch (err) {\n this.logger.error('[CallKitCore] onRtcEvent 回调执行失败:', err)\n }\n }\n }\n\n private emitError(type: string, error: unknown, context?: Record<string, any>): void {\n const errMsg = error instanceof Error ? error.message : String(error)\n this.logger.error(`[CallKitCore] ${type}:`, error)\n this.emitEvent({\n type: 'callError',\n payload: {\n type,\n error: errMsg,\n callId: context?.callId,\n context,\n },\n })\n }\n\n private startDurationTimer(callId: string, channel: string, callType: CALL_TYPE, callerUserId: string): void {\n if (this.durationTimer) {\n clearInterval(this.durationTimer)\n }\n this.durationStartTime = Date.now()\n this.durationCallInfo = { callId, channel, callType, callerUserId }\n this.durationTimer = setInterval(() => {\n if (!this.durationCallInfo) return\n const duration = Date.now() - this.durationStartTime\n this.emitEvent({\n type: 'callDurationUpdated',\n payload: {\n callId: this.durationCallInfo.callId,\n channel: this.durationCallInfo.channel,\n callType: this.durationCallInfo.callType,\n callerUserId: this.durationCallInfo.callerUserId,\n duration,\n },\n })\n }, 1000)\n }\n\n private stopDurationTimer(): void {\n if (this.durationTimer) {\n clearInterval(this.durationTimer)\n this.durationTimer = null\n }\n this.durationStartTime = 0\n this.durationCallInfo = null\n }\n\n // ───────────────────────────────────────────────\n // 内部:IM 连接状态管理\n // ───────────────────────────────────────────────\n\n private handleIMConnected(): void {\n this.logger.info('[CallKitCore] IM 已重新连接')\n // IM 重连后,如果当前有进行中的通话,需要确保监听仍然有效\n // 环信 IM SDK 的 addEventHandler 是持久注册的,断连不会自动清除 handler\n // 但如果 SDK 版本行为不同,这里提供兜底 remount\n if (!this.destroyed && this.imListener) {\n this.logger.info('[CallKitCore] IM 重连恢复:监听状态正常')\n }\n }\n\n private handleIMDisconnected(): void {\n this.logger.warn('[CallKitCore] IM 已断开连接')\n // IM 断开后,通话状态保持不变,等待重连或超时\n // 如果通话正在进行,由 RTC 层维持音视频,信令暂时中断\n }\n\n // ───────────────────────────────────────────────\n // 内部:超时定时器管理(Critical #1)\n // ───────────────────────────────────────────────\n\n /**\n * 启动邀请超时定时器。\n * 超时后调用状态机的 timeout() 并通过 processEvents 消费事件,\n * 确保上层 UI 能收到 callTimeout + callEnded 事件。\n */\n private startInviteTimeout(): void {\n this.clearInviteTimeout()\n this.inviteTimer = setTimeout(() => {\n const result = this.singleCallState.timeout()\n if (result.ok) {\n this.processEvents(result.events, this.singleCallState.getState())\n }\n }, this.inviteTimeoutMs)\n }\n\n private clearInviteTimeout(): void {\n if (this.inviteTimer) {\n clearTimeout(this.inviteTimer)\n this.inviteTimer = null\n }\n }\n}\n","// ─── 版本号 ───\nexport const VERSION = '1.1.0'\n\n/**\n * @easemob-community/callkit-core\n *\n * 框架无关的通话信令核心库。\n * 仅依赖环信 IM SDK 作为信令通道,RTC 层通过抽象接口由上层自行接入。\n */\n\n// ─── 核心入口 ───\nexport { CallKitCore } from './core/CallKitCore'\nexport type {\n CallKitCoreConfig,\n InviteCallParams,\n AnswerCallParams,\n HangupParams,\n InviteGroupCallParams,\n RtcReport,\n EasemobConnection,\n} from './core/CallKitCore.types'\n\n// ─── 状态机 ───\nexport { SingleCallStateMachine } from './state/SingleCallStateMachine'\nexport type { SingleCallState, DomainEvent, TransitionResult } from './state/SingleCallStateMachine'\nexport { GroupCallSession } from './state/GroupCallSession'\nexport type { GroupParticipant, GroupSessionState } from './state/GroupCallSession'\n\n// ─── 信令 ───\nexport { SignalRouter } from './signaling/SignalRouter'\nexport type { CmdMsgBody, SignalHandler } from './signaling/SignalRouter'\nexport { SingleCallSignalHandler } from './signaling/SingleCallSignalHandler'\nexport { GroupCallSignalHandler } from './signaling/GroupCallSignalHandler'\nexport { MessageBuilder } from './signaling/MessageBuilder'\nexport type { BuildInviteMessageParams, BuildCmdMessageParams } from './signaling/MessageBuilder'\nexport { SignalSender } from './signaling/SignalSender'\n\n// ─── IM ───\nexport type { IMProvider, IMMessage } from './im/IMProvider'\nexport { IMListener } from './im/IMListener'\nexport type { IMListenerCallbacks } from './im/IMListener'\n\n// ─── RTC ───\nexport type { RtcAdapter, JoinRtcParams } from './rtc/RtcAdapter'\n\n// ─── 事件 ───\nexport type {\n CallKitEvent,\n UIEvent,\n RtcEvent,\n IncomingCallEvent,\n CallInvitedEvent,\n CallStartedEvent,\n CallAcceptedEvent,\n CallConnectedEvent,\n CallEndedEvent,\n CallTimeoutEvent,\n StatusChangedEvent,\n CallRefusedEvent,\n CallBusyEvent,\n CallCanceledEvent,\n SingleCallInvitedEvent,\n SingleCallStartedEvent,\n SingleCallAcceptedEvent,\n SingleCallConnectedEvent,\n SingleCallEndedEvent,\n SingleCallTimeoutEvent,\n SingleCallRefusedEvent,\n SingleCallBusyEvent,\n SingleCallCanceledEvent,\n GroupCallInvitedEvent,\n GroupCallStartedEvent,\n GroupCallAcceptedEvent,\n GroupCallConnectedEvent,\n GroupCallEndedEvent,\n GroupCallTimeoutEvent,\n GroupCallRefusedEvent,\n GroupCallBusyEvent,\n GroupCallCanceledEvent,\n ShouldJoinRtcEvent,\n ShouldLeaveRtcEvent,\n ShouldPublishTracksEvent,\n GroupCallInitEvent,\n ParticipantStateChangedEvent,\n ParticipantJoinedEvent,\n ParticipantLeftEvent,\n LocalAudioChangedEvent,\n LocalVideoChangedEvent,\n CallDurationUpdatedEvent,\n CallErrorEvent,\n} from './events/CallKitEvents'\nexport { isUIEvent, isRtcEvent } from './events/CallKitEvents'\nexport { EventBus } from './events/EventBus'\n\n// ─── 类型 ───\nexport { CALL_STATUS, CALL_TYPE, HANGUP_REASON } from './types/callstate.types'\nexport type {\n CALLKIT_CMD_MSG_ACTION_TYPE,\n CALLKIT_CMD_MSG_RESULT_TYPE,\n} from './types/callstate.types'\nexport type {\n SignalingExt,\n InviteSignalingExt,\n AlertSignalingExt,\n ConfirmRingSignalingExt,\n AnswerCallSignalingExt,\n ConfirmCalleeSignalingExt,\n CancelCallSignalingExt,\n LeaveCallSignalingExt,\n SignalMessageInviteExt,\n} from './types/signal.types'\n\n// ─── 工具 ───\nexport { generateRandomChannel, formatCallDuration, isMessageExpired, isCmdMessageExpired } from './utils/callUtils'\nexport { setLogger, getLogger } from './utils/logger'\nexport type { Logger } from './utils/logger'\n"],"names":["oldStatus","events","stateResult"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@easemob-community/callkit-core",
3
+ "version": "2.0.1",
4
+ "description": "Framework-agnostic call signaling core for Easemob IM",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "unpkg": "./dist/index.umd.js",
10
+ "jsdelivr": "./dist/index.umd.js",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ },
17
+ "./umd": "./dist/index.umd.js",
18
+ "./iife": "./dist/index.iife.js"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "directories": {
24
+ "doc": "skills"
25
+ },
26
+ "devDependencies": {
27
+ "@vitest/coverage-v8": "^4.1.5",
28
+ "terser": "^5.47.1",
29
+ "typedoc": "^0.28.19",
30
+ "typescript": "~5.8.3",
31
+ "vite": "^7.1.2",
32
+ "vite-plugin-dts": "^4.5.4",
33
+ "vitest": "^4.1.5"
34
+ },
35
+ "peerDependencies": {
36
+ "easemob-websdk": "^4.12.0"
37
+ },
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/Easemob-Community/easemob-uikit-callkit.git",
42
+ "directory": "packages/callkit-core"
43
+ },
44
+ "scripts": {
45
+ "build": "vite build",
46
+ "dev": "vite build --watch",
47
+ "typecheck": "tsc --noEmit",
48
+ "test": "vitest run",
49
+ "test:watch": "vitest",
50
+ "test:coverage": "vitest run --coverage",
51
+ "docs:build": "typedoc",
52
+ "docs:serve": "typedoc --watch"
53
+ }
54
+ }