@quitting/core 1.0.2 → 1.2.0

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.
@@ -1,2 +1,2 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("centrifuge"),require("livekit-client")):typeof define==`function`&&define.amd?define([`exports`,`centrifuge`,`livekit-client`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ImCore={},e.Centrifuge,e.LiveKit))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=class{listeners=new Map;on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>this.off(e,t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){let n=this.listeners.get(e);if(n)for(let r of n)try{r(t)}catch(t){console.error(`[im] event handler error: ${e}`,t)}}removeAllListeners(){this.listeners.clear()}},i=class extends Error{code;httpStatus;constructor(e,t,n){super(n),this.name=`ApiError`,this.code=e,this.httpStatus=t}},a=class{apiBase;tokenGetter;constructor(e,t){this.apiBase=e.replace(/\/$/,``),this.tokenGetter=t}async request(e,t={}){let n=`${this.apiBase}${e}`,r={"Content-Type":`application/json`,...t.headers},a=this.tokenGetter();a&&(r.Authorization=`Bearer ${a}`),r[`X-Trace-Id`]=crypto.randomUUID?.()||Math.random().toString(36).slice(2);let o=await fetch(n,{...t,headers:r});if(!o.ok){let e=0,t=`HTTP ${o.status}`;try{let n=await o.json();n?.code&&(e=Number(n.code)),n?.message&&(t=n.message)}catch{}throw new i(e,o.status,t)}return o.json()}async sendBeacon(e,t){let n=`${this.apiBase}${e}`,r={"Content-Type":`application/json`},i=this.tokenGetter();i&&(r.Authorization=`Bearer ${i}`);try{await fetch(n,{method:`POST`,headers:r,body:JSON.stringify(t),keepalive:!0})}catch(e){console.warn(`[im] sendBeacon 失败(页面卸载中可能正常)`,e)}}getClientConfig(){return this.request(`/client/config`)}login(e,t,n=4,r){return this.request(`/auth/login`,{method:`POST`,body:JSON.stringify({appId:e,externalId:t,clientType:n,deviceName:r})})}refresh(){return this.request(`/auth/refresh`,{method:`POST`})}getCentrifugoToken(e,t){let n=new URLSearchParams;return e&&n.set(`name`,e),t&&n.set(`avatar`,t),this.request(`/auth/centrifugo-token?${n.toString()}`)}getSessionState(e){let t=e?`?uid=${encodeURIComponent(e)}`:``;return this.request(`/session/state${t}`)}usersByExternal(e){return this.request(`/users/by-external?ids=${e.map(encodeURIComponent).join(`,`)}`)}callInit(e,t){return this.request(`/call/init`,{method:`POST`,body:JSON.stringify({calleeUid:e,type:t})})}callAccept(e){return this.request(`/call/accept`,{method:`POST`,body:JSON.stringify({callId:e})})}callEnd(e,t){return this.request(`/call/end`,{method:`POST`,body:JSON.stringify({callId:e,reason:t})})}meetingCreate(e){return this.request(`/meeting/create`,{method:`POST`,body:JSON.stringify(e)})}meetingJoin(e){return this.request(`/meeting/join`,{method:`POST`,body:JSON.stringify({meetingId:e})})}meetingRespond(e,t){return this.request(`/meeting/${e}/respond`,{method:`POST`,body:JSON.stringify({action:t})})}meetingKick(e,t){return this.request(`/meeting/${e}/kick`,{method:`POST`,body:JSON.stringify({uid:t})})}meetingReinvite(e,t){return this.request(`/meeting/${e}/reinvite`,{method:`POST`,body:JSON.stringify({uid:t})})}meetingLeave(e){return this.request(`/meeting/${e}/leave`,{method:`POST`})}meetingDissolve(e){return this.request(`/meeting/${e}/dissolve`,{method:`POST`})}meetingParticipantCount(e){return this.request(`/meeting/${e}/participant-count`)}meetingPendingInvites(){return this.request(`/meetings/pending-invites`)}meetingMembers(e){return this.request(`/meeting/${e}/members`)}meetingTransferHost(e,t){return this.request(`/meeting/${e}/transfer-host`,{method:`POST`,body:JSON.stringify({newHostUid:t})})}sendMessage(e){return this.request(`/messages`,{method:`POST`,body:JSON.stringify(e)})}ackMessage(e){return this.request(`/messages/${e}/ack`,{method:`POST`})}revokeMessage(e){return this.request(`/messages/${e}/revoke`,{method:`POST`})}listMessages(e,t=0,n=50){return this.request(`/conversations/${e}/messages?sinceSeq=${t}&limit=${n}`)}markRead(e,t){return this.request(`/conversations/${e}/read`,{method:`POST`,body:JSON.stringify({lastReadSeq:t})})}listConversations(){return this.request(`/conversations`)}getUploadUrl(e){return this.request(`/media/upload-url`,{method:`POST`,body:JSON.stringify(e)})}checkMd5(e){return this.request(`/media/check-md5`,{method:`POST`,body:JSON.stringify({md5:e})})}},o=class{transport;sessionToken=null;centrifugoToken=null;expiresAt=0;refreshTimer=null;refreshAheadMs=60*1e3;constructor(e){this.transport=e}getToken(){return this.sessionToken}isExpired(){return Date.now()>=this.expiresAt-this.refreshAheadMs}async login(e,t,n=4,r){let i=await this.transport.login(e,t,n,r);return this.sessionToken=i.token,this.expiresAt=i.expiresAt*1e3,this.scheduleRefresh(),i}setExternalToken(e,t){this.sessionToken=e,this.expiresAt=t*1e3,this.scheduleRefresh()}async getCentrifugoToken(e,t){if(this.centrifugoToken)return this.centrifugoToken;let n=await this.transport.getCentrifugoToken(e,t);return this.centrifugoToken=n.token,setTimeout(()=>{this.centrifugoToken=null},240*1e3),n.token}async refresh(){for(let e=0;e<3;e++)try{let e=await this.transport.refresh();this.sessionToken=e.token,this.expiresAt=Date.now()+1440*60*1e3,this.scheduleRefresh();return}catch(t){let n=String(t?.message||t);if(n.includes(`401`)||n.includes(`kicked`)||n.includes(`expired`)){console.error(`[im] refresh 失败(session 失效),需重新登录`,t),this.logout();return}console.warn(`[im] refresh token 失败(第 ${e+1} 次),5s 后重试`,t),await new Promise(e=>setTimeout(e,5e3))}console.error(`[im] refresh token 重试 3 次仍失败,清 token`),this.logout()}scheduleRefresh(){this.refreshTimer&&clearTimeout(this.refreshTimer);let e=Math.max(this.expiresAt-Date.now()-this.refreshAheadMs,60*1e3);this.refreshTimer=setTimeout(()=>this.refresh(),e)}logout(){this.refreshTimer&&=(clearTimeout(this.refreshTimer),null),this.sessionToken=null,this.centrifugoToken=null}},s=class extends r{client=null;subscriptions=new Map;refCount=new Map;wsUrl;uid=``;tokenGetter;currentState=`disconnected`;constructor(e,t){super(),this.wsUrl=e,this.tokenGetter=t}get state(){return this.currentState}async connect(e){this.uid=e,this.client=new t.Centrifuge(this.wsUrl,{getToken:async()=>await this.tokenGetter()}),this.client.on(`connected`,()=>{this.currentState=`connected`,this.emit(`connection:state`,`connected`),this.subscribe(`personal#${e}`)}),this.client.on(`disconnected`,e=>{this.currentState=`disconnected`,this.emit(`connection:state`,`disconnected`),console.warn(`[im] centrifugo disconnected`,e?.reason)}),this.client.on(`error`,e=>{console.error(`[im] centrifugo error`,e)}),this.client.connect()}async subscribe(e){if(!this.client)return null;let t=this.refCount.get(e)||0;if(this.refCount.set(e,t+1),this.subscriptions.has(e))return this.subscriptions.get(e);let n=this.client.newSubscription(e);return n.on(`publication`,t=>{this.handlePublication(e,t)}),n.subscribe(),this.subscriptions.set(e,n),n}unsubscribe(e){let t=this.refCount.get(e)||0;t<=1?this.refCount.delete(e):this.refCount.set(e,t-1)}async publish(e,t){let n=this.subscriptions.get(e);if(!n)throw Error(`未订阅通道: ${e}`);await n.publish(t)}handlePublication(e,t){let n=t.data,r=n.type;switch(r){case`call_invite`:this.emit(`call:invite`,n);break;case`call_accepted`:this.emit(`call:accepted`,n);break;case`call_rejected`:this.emit(`call:rejected`,n);break;case`call_cancelled`:this.emit(`call:cancelled`,n);break;case`call_busy`:this.emit(`call:busy`,n);break;case`call_no_answer`:this.emit(`call:no_answer`,n);break;case`call_ended`:this.emit(`call:ended`,n);break;case`meeting_invite`:this.emit(`meeting:invite`,n);break;case`meeting_kicked`:this.emit(`meeting:kicked`,n);break;case`meeting_host_changed`:this.emit(`meeting:hostChanged`,n);break;case`meeting_dissolved`:this.emit(`meeting:dissolved`,n);break;case`message`:this.emit(`message`,n);break;case`message_preview`:this.emit(`message_preview`,n);break;case`message_revoked`:this.emit(`message:revoked`,n);break;case`read_receipt`:this.emit(`read_receipt`,n);break;case`presence:update`:{let e=n.changes||[];for(let t of e)this.emit(`presence:update`,{uid:t.uid,online:t.online})}break;case`session:kicked`:this.emit(`session:kicked`,n);break;case`group:memberJoined`:this.emit(`group:memberJoined`,n);break;case`group:kicked`:this.emit(`group:kicked`,n);break;case`group:memberLeft`:this.emit(`group:memberLeft`,n);break;case`group:profileChanged`:this.emit(`group:profileChanged`,n);break;default:console.debug(`[im] 未知 publication type`,r,e)}}disconnect(){this.client&&=(this.client.disconnect(),null),this.subscriptions.clear(),this.currentState=`disconnected`}},c=class extends r{callId;callType;roomName;identity;peerIdentity;room;livekitWs;iceServers;ended=!1;sessionListeners=new Map;constructor(e){super(),this.callId=e.callId,this.callType=e.callType,this.roomName=e.roomName,this.identity=e.identity,this.token=e.token,this.livekitWs=e.livekitWs,this.iceServers=e.iceServers,this.room=new n.Room({adaptiveStream:!0,dynacast:!0})}async connect(){this.room.on(`participantConnected`,e=>{this.peerIdentity=e.identity,this.emitSession(`remoteConnected`,{identity:e.identity})}).on(`participantDisconnected`,e=>{this.emitSession(`remoteDisconnected`,{identity:e.identity}),this.end(`user_left`)}).on(`disconnected`,()=>{this.ended||this.end(`user_left`)}).on(`trackSubscribed`,(e,t,r)=>{e.kind===n.Track.Kind.Audio&&e.attach(),this.emitSession(`remoteTrackSubscribed`,{kind:e.kind,identity:r.identity})}),await this.room.connect(this.livekitWs,this.token,{rtcConfig:{iceServers:this.iceServers}}),this.emitSession(`connected`,{})}async enableDevices(){let e=[],t=!1,n=!1;try{await this.room.localParticipant.setMicrophoneEnabled(!0),t=!0}catch(t){let n=t?.message||String(t);e.push(`mic: ${n}`),console.warn(`[im] 麦克风开启失败,通话继续(音频不可用)`,t)}if(this.callType===`video`)try{await this.room.localParticipant.setCameraEnabled(!0),n=!0}catch(t){let n=t?.message||String(t);e.push(`camera: ${n}`),console.warn(`[im] 摄像头开启失败,通话继续(视频不可用)`,t)}return this.emitSession(`devices`,{mic:t,camera:n,errors:e}),{mic:t,camera:n,errors:e}}attachLocalVideo(e){let t=this.room.localParticipant.getTrackPublication(n.Track.Source.Camera);t&&t.track&&t.track.attach(e)}attachRemoteVideo(e){for(let[,t]of this.room.remoteParticipants){let r=t.getTrackPublication(n.Track.Source.Camera);if(r){if(r.track){r.track.attach(e);return}r.isSubscribed||r.setSubscribed(!0)}}}detachAllVideoFromEl(e){if(!e)return;let t=this.room.localParticipant.getTrackPublication(n.Track.Source.Camera);t&&t.track&&t.track.detach(e);for(let[,t]of this.room.remoteParticipants){let r=t.getTrackPublication(n.Track.Source.Camera);r&&r.track&&r.track.detach(e)}}async setMicEnabled(e){await this.room.localParticipant.setMicrophoneEnabled(e)}async setCameraEnabled(e){await this.room.localParticipant.setCameraEnabled(e)}async hangup(e,t=`user_left`){if(!this.ended){this.ended=!0;try{await e.callEnd(this.callId,t)}catch(e){console.error(`[im] callEnd 失败`,e)}(0,n.setLogLevel)(`silent`);try{await this.room.disconnect()}finally{(0,n.setLogLevel)(`warn`)}this.emitSession(`ended`,{reason:t})}}async end(e){if(!this.ended){this.ended=!0,(0,n.setLogLevel)(`silent`);try{await this.room.disconnect()}finally{(0,n.setLogLevel)(`warn`)}this.emitSession(`ended`,{reason:e})}}registerRpcMethod(e,t){this.room.localParticipant.registerRpcMethod(e,e=>t(e.payload))}async performRpc(e,t,n){return await this.room.localParticipant.performRpc({destinationIdentity:e,method:t,payload:n})}emitSession(e,t){let n=this.sessionListeners.get(e);if(n)for(let r of n)try{r(t)}catch(t){console.error(`[im] session handler error: ${e}`,t)}}onSession(e,t){this.sessionListeners.has(e)||this.sessionListeners.set(e,new Set),this.sessionListeners.get(e).add(t)}get isConnected(){return this.room.state===`connected`}getToken(){return this.token}token},l=class{transport;livekitWs;identity;iceServers;currentSession=null;constructor(e,t,n,r){this.transport=e,this.livekitWs=t,this.identity=n,this.iceServers=r}async init(e,t){let n=await this.transport.callInit(e,t),r=new c({callId:n.callId,callType:t,roomName:n.roomName,identity:this.identity,token:n.token,livekitWs:this.livekitWs,iceServers:this.iceServers});return this.currentSession=r,r}async accept(e,t){let n=await this.transport.callAccept(e),r=new c({callId:e,callType:t,roomName:n.roomName,identity:this.identity,token:n.token,livekitWs:this.livekitWs,iceServers:this.iceServers});this.currentSession=r;try{await r.connect()}catch(t){throw this.currentSession=null,this.transport.callEnd(e,`user_left`).catch(()=>{}),t}return r.enableDevices().catch(e=>{console.warn(`[im] enableDevices 异常`,e)}),r}async end(e=`user_left`,t){if(this.currentSession)await this.currentSession.hangup(this.transport,e),this.currentSession=null;else if(t)try{await this.transport.callEnd(t,e)}catch(e){console.warn(`[im] callEnd 失败(不阻塞)`,e)}}};(0,n.setLogLevel)(`warn`);async function u(e){(0,n.setLogLevel)(`silent`);try{await e.disconnect()}finally{(0,n.setLogLevel)(`warn`)}}var d=class e extends r{meetingId;title;roomName;identity;isHost;hostIdentity;room;livekitWs;token;iceServers;ended=!1;sessionListeners=new Map;membersCache=[];membersCacheAt=0;static MEMBERS_CACHE_TTL=8e3;pendingAttach=new Map;constructor(e){super(),this.meetingId=e.meetingId,this.title=e.title,this.roomName=e.roomName,this.identity=e.identity,this.token=e.token,this.isHost=e.isHost,this.hostIdentity=e.hostIdentity,this.livekitWs=e.livekitWs,this.iceServers=e.iceServers,this.room=new n.Room({adaptiveStream:!0,dynacast:!0})}get isConnected(){return this.room.state===`connected`}async connect(){this.room.on(`participantConnected`,e=>{if(this.pendingAttach.has(e.identity)){let t=e.getTrackPublication(n.Track.Source.Camera);t&&!t.isSubscribed&&t.setSubscribed(!0)}this.emitSession(`participantJoined`,{identity:e.identity})}).on(`participantDisconnected`,e=>{this.emitSession(`participantLeft`,{identity:e.identity,reason:`leave`})}).on(`disconnected`,()=>{this.ended||this.end()}).on(`trackSubscribed`,(e,t,r)=>{if(e.kind===`video`){let t=this.pendingAttach.get(r.identity);t&&(e.attach(t),this.pendingAttach.delete(r.identity))}else e.kind===n.Track.Kind.Audio&&e.attach();this.emitSession(`participantTrackSubscribed`,{kind:e.kind,identity:r.identity})}).on(`activeSpeakersChanged`,e=>{let t=e.map(e=>e.identity);this.emitSession(`activeSpeakersChanged`,{identities:t})}),await this.room.connect(this.livekitWs,this.token,{rtcConfig:{iceServers:this.iceServers}});let e=this.room.localParticipant.identity;e&&(this.identity=e,this.hostIdentity||=e),this.emitSession(`connected`,{})}async enableDevices(){let e=[],t=!1,n=!1;try{await this.room.localParticipant.setMicrophoneEnabled(!0),t=!0}catch(t){e.push(`mic: ${t?.message||String(t)}`),console.warn(`[im] 麦克风开启失败,会议继续(音频不可用)`,t)}try{await this.room.localParticipant.setCameraEnabled(!0),n=!0}catch(t){e.push(`camera: ${t?.message||String(t)}`),console.warn(`[im] 摄像头开启失败,会议继续(视频不可用)`,t)}return this.emitSession(`devices`,{mic:t,camera:n,errors:e}),{mic:t,camera:n,errors:e}}async kick(e,t){await e.meetingKick(this.meetingId,t)}async leave(e){if(!this.ended){this.ended=!0;try{await e.meetingLeave(this.meetingId)}catch(e){console.error(`[im] meetingLeave 失败`,e)}await u(this.room),this.emitSession(`ended`,{})}}async end(){this.ended||(this.ended=!0,await u(this.room),this.emitSession(`ended`,{}))}getRemoteParticipants(){let e=[];for(let[,t]of this.room.remoteParticipants)e.push({identity:t.identity,audioEnabled:t.isMicrophoneEnabled,videoEnabled:t.isCameraEnabled,isSpeaking:t.isSpeaking});return e}attachLocalVideo(e){if((()=>{let t=this.room.localParticipant.getTrackPublication(n.Track.Source.Camera);return t&&t.track?(t.track.attach(e),!0):!1})())return;let t=r=>{r.source===n.Track.Source.Camera&&r.track&&(r.track.attach(e),this.room.localParticipant.off(`localTrackPublished`,t))};this.room.localParticipant.on(`localTrackPublished`,t)}attachParticipantVideo(e,t){let r=this.room.remoteParticipants.get(e);if(!r){this.pendingAttach.set(e,t);return}let i=r.getTrackPublication(n.Track.Source.Camera);if(!i){this.pendingAttach.set(e,t);return}if(i.track){i.track.attach(t),this.pendingAttach.delete(e);return}this.pendingAttach.set(e,t),i.isSubscribed||i.setSubscribed(!0)}async setMicEnabled(e){await this.room.localParticipant.setMicrophoneEnabled(e)}async setCameraEnabled(e){await this.room.localParticipant.setCameraEnabled(e)}async setSpeakerMuted(e){for(let[,t]of this.room.remoteParticipants)for(let[,r]of t.audioTrackPublications){let t=r.track;t&&t.kind===n.Track.Kind.Audio&&t.setVolume(+!e)}}async refreshMembers(t,n=!1){let r=Date.now();if(!n&&r-this.membersCacheAt<e.MEMBERS_CACHE_TTL&&this.membersCache.length>0)return this.membersCache;let i=await t.meetingMembers(this.meetingId);return this.membersCache=i.members||[],this.membersCacheAt=r,i.hostUid&&i.hostUid!==this.hostIdentity&&(this.hostIdentity=i.hostUid),this.membersCache}getMembers(){return this.membersCache}async transferHost(e,t){await e.meetingTransferHost(this.meetingId,t),this.hostIdentity=t,this.emitSession(`hostChanged`,{hostUid:t})}onSession(e,t){this.sessionListeners.has(e)||this.sessionListeners.set(e,new Set),this.sessionListeners.get(e).add(t)}emitSession(e,t){let n=this.sessionListeners.get(e);if(n)for(let r of n)try{r(t)}catch(t){console.error(`[im] session handler error: ${e}`,t)}}},f=class{transport;livekitWs;identity;iceServers;currentSession=null;constructor(e,t,n,r){this.transport=e,this.livekitWs=t,this.identity=n,this.iceServers=r}async create(e,t){let n=await this.transport.meetingCreate({inviteeUids:e,title:t}),r=new d({meetingId:n.meetingId,title:t,roomName:n.roomName,identity:this.identity,token:n.token,isHost:!0,hostIdentity:n.hostUid||this.identity,livekitWs:this.livekitWs,iceServers:this.iceServers});this.currentSession=r;try{await r.connect(),r.enableDevices().catch(e=>console.warn(`[im] enableDevices 异常`,e))}catch(e){throw this.currentSession=null,this.transport.meetingLeave(n.meetingId).catch(()=>{}),e}return{meetingId:n.meetingId,session:r,busyInvitees:n.busyInvitees}}async join(e,t=``){let n=await this.transport.meetingJoin(e),r=new d({meetingId:e,title:t,roomName:n.roomName,identity:this.identity,token:n.token,isHost:!1,hostIdentity:n.hostUid||``,livekitWs:this.livekitWs,iceServers:this.iceServers});this.currentSession=r;try{await r.connect(),r.enableDevices().catch(e=>console.warn(`[im] enableDevices 异常`,e))}catch(t){throw this.currentSession=null,this.transport.meetingLeave(e).catch(()=>{}),t}return r}async respond(e,t,n){let r=await this.transport.meetingRespond(e,t);if(t!==`accept`||!r?.token||!r?.roomName)return null;let i=new d({meetingId:e,title:r.title||n||``,roomName:r.roomName,identity:this.identity,token:r.token,isHost:!1,hostIdentity:r.hostUid||``,livekitWs:this.livekitWs,iceServers:this.iceServers});this.currentSession=i;try{await i.connect(),i.enableDevices().catch(e=>console.warn(`[im] enableDevices 异常`,e))}catch(t){throw this.currentSession=null,this.transport.meetingLeave(e).catch(()=>{}),t}return i}async leave(){this.currentSession&&=(await this.currentSession.leave(this.transport),null)}async dissolve(){if(!this.currentSession)return!1;let e=this.currentSession.meetingId;return await this.transport.meetingDissolve(e),this.currentSession=null,!0}async getParticipantCount(){return this.currentSession?(await this.transport.meetingParticipantCount(this.currentSession.meetingId)).count:0}async fetchPendingInvites(){try{return(await this.transport.meetingPendingInvites()).invites||[]}catch(e){return console.warn(`[im] fetchPendingInvites 失败`,e),[]}}async reinvite(e,t){await this.transport.meetingReinvite(e,t)}async getMembers(e=!1){return this.currentSession?this.currentSession.refreshMembers(this.transport,e):[]}},p=class{transport;realtime;uid;peerUidCache=new Map;constructor(e,t,n){this.transport=e,this.realtime=t,this.uid=n}async resolvePeerUUID(e){let t=this.peerUidCache.get(e);if(t)return t;let n=(await this.transport.usersByExternal([e])).users[e];if(!n||!n.uid)throw Error(`peer not found: ${e}`);return this.peerUidCache.set(e,n.uid),n.uid}async send(e){let t={to:e.to,type:e.type,body:e.body,clientMid:crypto.randomUUID(),mediaId:e.mediaId,replyToId:e.replyToId},n=await this.transport.sendMessage(t);return await this.transport.ackMessage(n.msgId).catch(()=>{}),n}async sendMedia(e,t){let{mediaId:n}=await t.upload(e.file,e.file.type);return await this.send({to:e.to,type:e.type,mediaId:n})}async history(e,t=0,n=50){return(await this.transport.listMessages(e,t,n)).messages}async ack(e){await this.transport.ackMessage(e)}async markRead(e,t){await this.transport.markRead(e,t)}async revoke(e){await this.transport.revokeMessage(e)}async typing(e,t){let n=await this.resolvePeerUUID(e),r=this.dialogChannel(n);await this.realtime.publish(r,{type:`typing`,from:``,isTyping:t})}async listConversations(){return(await this.transport.listConversations()).conversations}async openDialog(e){let t=await this.resolvePeerUUID(e),n=this.dialogChannel(t);await this.realtime.subscribe(n)}closeDialog(e){let t=this.peerUidCache.get(e);if(!t)return;let n=this.dialogChannel(t);this.realtime.unsubscribe(n)}dialogChannel(e){return`dialog#${[this.uid,e].sort()[0]},${[this.uid,e].sort()[1]}`}},m=class{transport;constructor(e){this.transport=e}async computeMD5(e){let t=await e.arrayBuffer(),n=await crypto.subtle.digest(`SHA-256`,t);return Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,`0`)).join(``)}async upload(e,t){let n=await this.computeMD5(e),r=await this.transport.checkMd5(n);if(r.hit&&r.mediaId)return{mediaId:r.mediaId};let i=await this.transport.getUploadUrl({fileName:e.name,contentType:t||e.type,fileSize:e.size,md5:n});if(i.uploadUrl&&i.uploadUrl.startsWith(`http`)){let n=await fetch(i.uploadUrl,{method:`PUT`,body:e,headers:{"Content-Type":t||e.type}});if(!n.ok)throw Error(`上传对象存储失败: HTTP ${n.status}`)}return{mediaId:i.mediaId}}},h=[`message`,`message_preview`,`message:revoked`,`read_receipt`,`typing`,`call:invite`,`call:accepted`,`call:rejected`,`call:cancelled`,`call:busy`,`call:no_answer`,`call:ended`,`meeting:invite`,`meeting:kicked`,`meeting:hostChanged`,`meeting:dissolved`,`group:memberJoined`,`group:memberLeft`,`group:kicked`,`group:announcement`,`group:profileChanged`,`session:kicked`,`connection:state`,`presence:update`],g=class extends r{auth;transport;realtime;call;meeting;message;media;uid=``;appId;sessionState=`idle`;clientConfig=null;livekitWs;apiBase;name=``;avatar=``;constructor(e){super(),this.appId=e.appId,this.apiBase=e.apiBase,this.livekitWs=e.livekitWs,this.auth=new o(null),this.transport=new a(e.apiBase,()=>this.auth.getToken()),this.auth=new o(this.transport),this.realtime=new s(e.centrifugoWs,async()=>await this.auth.getCentrifugoToken(this.name,this.avatar))}async connect(e){e.name&&(this.name=e.name),e.avatar&&(this.avatar=e.avatar);let t=await this.auth.login(this.appId,e.externalId,e.clientType??4,e.deviceName);return this.uid=t.uid,t.name&&(this.name=t.name),t.avatarUrl&&(this.avatar=t.avatarUrl),await this.initAfterAuth(),t}async connectWithExternalJwt(e){e.name&&(this.name=e.name),e.avatar&&(this.avatar=e.avatar),this.uid=e.uid,this.auth.setExternalToken(e.jwt,e.expiresAt),await this.initAfterAuth()}async initAfterAuth(){await this.loadClientConfig();let e=this.clientConfig?.iceServers??[];this.call=new l(this.transport,this.livekitWs,this.uid,e),this.meeting=new f(this.transport,this.livekitWs,this.uid,e),this.message=new p(this.transport,this.realtime,this.uid),this.media=new m(this.transport),await this.realtime.connect(this.uid);try{let e=await this.transport.getSessionState();this.sessionState=e.state,this.emit(`session:state`,{state:e.state,callId:e.id,meetingId:e.id})}catch(e){console.warn(`[im] 同步 sessionState 失败`,e)}this.realtime.on(`call:invite`,()=>{}),this.realtime.on(`call:busy`,()=>{this.call.currentSession=null}),this.realtime.on(`call:no_answer`,()=>{this.call.currentSession=null}),this.realtime.on(`meeting:hostChanged`,e=>{let t=this.meeting?.currentSession;t&&e?.meetingId===t.meetingId&&e?.hostUid&&(t.hostIdentity=e.hostUid)}),this.realtime.on(`meeting:dissolved`,e=>{let t=this.meeting?.currentSession;t&&e?.meetingId===t.meetingId&&t.end().catch(e=>console.warn(`[im] dissolved end session 失败`,e))}),this.realtime.on(`meeting:kicked`,e=>{let t=this.meeting?.currentSession;t&&e?.meetingId===t.meetingId&&t.end().catch(e=>console.warn(`[im] kicked end session 失败`,e))}),this.realtime.on(`session:kicked`,()=>{console.warn(`[im] 会话被踢(同端新登录),断开连接`);try{this.realtime.disconnect()}catch{}this.auth.logout(),this.uid=``}),this.forwardRealtimeEvents(),this.fetchAndEmitPendingInvites()}async loadClientConfig(){if(!this.clientConfig)try{this.clientConfig=await this.transport.getClientConfig()}catch(e){console.warn(`[im] 拉取 client/config 失败,使用空 iceServers 兜底`,e),this.clientConfig={iceServers:[]}}}getConfig(){return this.clientConfig}async fetchAndEmitPendingInvites(){if(this.meeting)try{let e=await this.meeting.fetchPendingInvites(),t=this.meeting.currentSession?.meetingId;for(let n of e)n.meetingId!==t&&this.emit(`meeting:invite`,{meetingId:n.meetingId,from:n.from,title:n.title,ts:n.ts,variant:n.variant,reinvite:n.variant===`reinvite`})}catch(e){console.warn(`[im] fetchAndEmitPendingInvites 失败`,e)}}forwardRealtimeEvents(){for(let e of h)this.realtime.on(e,t=>{this.emit(e,t)})}async disconnect(){this.call?.currentSession&&await this.call.end(`user_left`).catch(()=>{}),this.meeting?.currentSession&&await this.meeting.leave().catch(()=>{}),this.realtime.disconnect(),this.auth.logout(),this.removeAllListeners()}};e.ApiError=i,e.AuthManager=o,e.CallController=l,e.CallSession=c,e.EventEmitter=r,e.ImClient=g,e.MediaUploader=m,e.MeetingController=f,e.MeetingSession=d,e.MessageController=p,e.RealtimeConnection=s,e.Transport=a;var _=require("@quitting/contract");Object.keys(_).forEach(function(t){t!=="default"&&!Object.prototype.hasOwnProperty.call(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:function(){return _[t]}})})});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("centrifuge"),require("livekit-client")):typeof define==`function`&&define.amd?define([`exports`,`centrifuge`,`livekit-client`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ImCore={},e.Centrifuge,e.LiveKit))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=class{listeners=new Map;on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>this.off(e,t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){let n=this.listeners.get(e);if(n)for(let r of n)try{r(t)}catch(t){console.error(`[im] event handler error: ${e}`,t)}}removeAllListeners(){this.listeners.clear()}},i=class extends Error{code;httpStatus;constructor(e,t,n){super(n),this.name=`ApiError`,this.code=e,this.httpStatus=t}},a=class{apiBase;tokenGetter;constructor(e,t){this.apiBase=e.replace(/\/$/,``),this.tokenGetter=t}async request(e,t={}){let n=`${this.apiBase}${e}`,r={"Content-Type":`application/json`,...t.headers},a=this.tokenGetter();a&&(r.Authorization=`Bearer ${a}`),r[`X-Trace-Id`]=crypto.randomUUID?.()||Math.random().toString(36).slice(2);let o=await fetch(n,{...t,headers:r});if(!o.ok){let e=0,t=`HTTP ${o.status}`;try{let n=await o.json();n?.code&&(e=Number(n.code)),n?.message&&(t=n.message)}catch{}throw new i(e,o.status,t)}return o.json()}async sendBeacon(e,t){let n=`${this.apiBase}${e}`,r={"Content-Type":`application/json`},i=this.tokenGetter();i&&(r.Authorization=`Bearer ${i}`);try{await fetch(n,{method:`POST`,headers:r,body:JSON.stringify(t),keepalive:!0})}catch(e){console.warn(`[im] sendBeacon 失败(页面卸载中可能正常)`,e)}}getClientConfig(){return this.request(`/client/config`)}login(e,t,n=4,r){return this.request(`/auth/login`,{method:`POST`,body:JSON.stringify({appId:e,externalId:t,clientType:n,deviceName:r})})}refresh(){return this.request(`/auth/refresh`,{method:`POST`})}getCentrifugoToken(e,t){let n=new URLSearchParams;return e&&n.set(`name`,e),t&&n.set(`avatar`,t),this.request(`/auth/centrifugo-token?${n.toString()}`)}getSessionState(e){let t=e?`?uid=${encodeURIComponent(e)}`:``;return this.request(`/session/state${t}`)}usersByExternal(e){return this.request(`/users/by-external?ids=${e.map(encodeURIComponent).join(`,`)}`)}listUsers(){return this.request(`/users`)}callInit(e,t){return this.request(`/call/init`,{method:`POST`,body:JSON.stringify({calleeUid:e,type:t})})}callAccept(e){return this.request(`/call/accept`,{method:`POST`,body:JSON.stringify({callId:e})})}callEnd(e,t){return this.request(`/call/end`,{method:`POST`,body:JSON.stringify({callId:e,reason:t})})}meetingCreate(e){return this.request(`/meeting/create`,{method:`POST`,body:JSON.stringify(e)})}meetingJoin(e){return this.request(`/meeting/join`,{method:`POST`,body:JSON.stringify({meetingId:e})})}meetingRespond(e,t){return this.request(`/meeting/${e}/respond`,{method:`POST`,body:JSON.stringify({action:t})})}meetingKick(e,t){return this.request(`/meeting/${e}/kick`,{method:`POST`,body:JSON.stringify({uid:t})})}meetingReinvite(e,t){return this.request(`/meeting/${e}/reinvite`,{method:`POST`,body:JSON.stringify({uid:t})})}meetingLeave(e){return this.request(`/meeting/${e}/leave`,{method:`POST`})}meetingDissolve(e){return this.request(`/meeting/${e}/dissolve`,{method:`POST`})}meetingParticipantCount(e){return this.request(`/meeting/${e}/participant-count`)}meetingPendingInvites(){return this.request(`/meetings/pending-invites`)}meetingMembers(e){return this.request(`/meeting/${e}/members`)}meetingTransferHost(e,t){return this.request(`/meeting/${e}/transfer-host`,{method:`POST`,body:JSON.stringify({newHostUid:t})})}sendMessage(e){return this.request(`/messages`,{method:`POST`,body:JSON.stringify(e)})}ackMessage(e){return this.request(`/messages/${e}/ack`,{method:`POST`})}revokeMessage(e){return this.request(`/messages/${e}/revoke`,{method:`POST`})}listMessages(e,t=0,n=50){return this.request(`/conversations/${e}/messages?sinceSeq=${t}&limit=${n}`)}markRead(e,t){return this.request(`/conversations/${e}/read`,{method:`POST`,body:JSON.stringify({lastReadSeq:t})})}listConversations(){return this.request(`/conversations`)}getUploadUrl(e){return this.request(`/media/upload-url`,{method:`POST`,body:JSON.stringify(e)})}checkMd5(e){return this.request(`/media/check-md5`,{method:`POST`,body:JSON.stringify({md5:e})})}},o=class{transport;sessionToken=null;centrifugoToken=null;expiresAt=0;refreshTimer=null;refreshAheadMs=60*1e3;constructor(e){this.transport=e}getToken(){return this.sessionToken}isExpired(){return Date.now()>=this.expiresAt-this.refreshAheadMs}async login(e,t,n=4,r){let i=await this.transport.login(e,t,n,r);return this.sessionToken=i.token,this.expiresAt=i.expiresAt*1e3,this.scheduleRefresh(),i}setExternalToken(e,t){this.sessionToken=e,this.expiresAt=t*1e3,this.scheduleRefresh()}async getCentrifugoToken(e,t){if(this.centrifugoToken)return this.centrifugoToken;let n=await this.transport.getCentrifugoToken(e,t);return this.centrifugoToken=n.token,setTimeout(()=>{this.centrifugoToken=null},240*1e3),n.token}async refresh(){for(let e=0;e<3;e++)try{let e=await this.transport.refresh();this.sessionToken=e.token,this.expiresAt=Date.now()+1440*60*1e3,this.scheduleRefresh();return}catch(t){let n=String(t?.message||t);if(n.includes(`401`)||n.includes(`kicked`)||n.includes(`expired`)){console.error(`[im] refresh 失败(session 失效),需重新登录`,t),this.logout();return}console.warn(`[im] refresh token 失败(第 ${e+1} 次),5s 后重试`,t),await new Promise(e=>setTimeout(e,5e3))}console.error(`[im] refresh token 重试 3 次仍失败,清 token`),this.logout()}scheduleRefresh(){this.refreshTimer&&clearTimeout(this.refreshTimer);let e=Math.max(this.expiresAt-Date.now()-this.refreshAheadMs,60*1e3);this.refreshTimer=setTimeout(()=>this.refresh(),e)}logout(){this.refreshTimer&&=(clearTimeout(this.refreshTimer),null),this.sessionToken=null,this.centrifugoToken=null}},s=class extends r{client=null;subscriptions=new Map;refCount=new Map;wsUrl;uid=``;tokenGetter;currentState=`disconnected`;constructor(e,t){super(),this.wsUrl=e,this.tokenGetter=t}get state(){return this.currentState}async connect(e){this.uid=e,this.client&&this.disconnect(),this.client=new t.Centrifuge(this.wsUrl,{getToken:async()=>await this.tokenGetter()}),this.client.on(`connected`,()=>{this.currentState=`connected`,this.emit(`connection:state`,`connected`),this.subscribe(`personal#${e}`)}),this.client.on(`disconnected`,e=>{this.currentState=`disconnected`,this.emit(`connection:state`,`disconnected`),console.warn(`[im] centrifugo disconnected`,e?.reason)}),this.client.on(`error`,e=>{console.error(`[im] centrifugo error`,e)}),this.client.connect()}async subscribe(e){if(!this.client)return null;let t=this.refCount.get(e)||0;if(this.refCount.set(e,t+1),this.subscriptions.has(e))return this.subscriptions.get(e);let n=this.client.newSubscription(e);return n.on(`publication`,t=>{this.handlePublication(e,t)}),n.subscribe(),this.subscriptions.set(e,n),n}unsubscribe(e){let t=this.refCount.get(e)||0;t<=1?this.refCount.delete(e):this.refCount.set(e,t-1)}async publish(e,t){let n=this.subscriptions.get(e);if(!n)throw Error(`未订阅通道: ${e}`);await n.publish(t)}handlePublication(e,t){let n=t.data,r=n.type;switch(r){case`call_invite`:this.emit(`call:invite`,n);break;case`call_accepted`:this.emit(`call:accepted`,n);break;case`call_rejected`:this.emit(`call:rejected`,n);break;case`call_cancelled`:this.emit(`call:cancelled`,n);break;case`call_busy`:this.emit(`call:busy`,n);break;case`call_no_answer`:this.emit(`call:no_answer`,n);break;case`call_ended`:this.emit(`call:ended`,n);break;case`meeting_invite`:this.emit(`meeting:invite`,n);break;case`meeting_kicked`:this.emit(`meeting:kicked`,n);break;case`meeting_host_changed`:this.emit(`meeting:hostChanged`,n);break;case`meeting_dissolved`:this.emit(`meeting:dissolved`,n);break;case`message`:this.emit(`message`,n);break;case`message_preview`:this.emit(`message_preview`,n);break;case`message_revoked`:this.emit(`message:revoked`,n);break;case`read_receipt`:this.emit(`read_receipt`,n);break;case`typing`:this.emit(`typing`,{from:n.from||``,isTyping:!!n.isTyping});break;case`presence:update`:{let e=n.changes||[];for(let t of e)this.emit(`presence:update`,{uid:t.uid,online:t.online})}break;case`session:kicked`:this.emit(`session:kicked`,n);break;case`group:memberJoined`:this.emit(`group:memberJoined`,n);break;case`group:kicked`:this.emit(`group:kicked`,n);break;case`group:memberLeft`:this.emit(`group:memberLeft`,n);break;case`group:profileChanged`:this.emit(`group:profileChanged`,n);break;default:console.debug(`[im] 未知 publication type`,r,e)}}disconnect(){this.client&&=(this.client.disconnect(),null),this.subscriptions.clear(),this.refCount.clear(),this.currentState=`disconnected`}},c=null;async function l(e){(0,n.setLogLevel)(`silent`),c&&=(clearTimeout(c),null);try{await e.disconnect()}finally{c=setTimeout(()=>{(0,n.setLogLevel)(`warn`),c=null},1500)}}var u=class extends r{callId;callType;roomName;identity;peerIdentity;room;livekitWs;iceServers;ended=!1;sessionListeners=new Map;constructor(e){super(),this.callId=e.callId,this.callType=e.callType,this.roomName=e.roomName,this.identity=e.identity,this.token=e.token,this.livekitWs=e.livekitWs,this.iceServers=e.iceServers,this.room=new n.Room({adaptiveStream:!0,dynacast:!0})}async connect(){this.room.on(`participantConnected`,e=>{this.peerIdentity=e.identity,this.emitSession(`remoteConnected`,{identity:e.identity})}).on(`participantDisconnected`,e=>{this.emitSession(`remoteDisconnected`,{identity:e.identity}),this.end(`user_left`)}).on(`disconnected`,()=>{this.ended||this.end(`user_left`)}).on(`trackSubscribed`,(e,t,r)=>{e.kind===n.Track.Kind.Audio&&e.attach(),this.emitSession(`remoteTrackSubscribed`,{kind:e.kind,identity:r.identity})}),await this.room.connect(this.livekitWs,this.token,{rtcConfig:{iceServers:this.iceServers}}),this.emitSession(`connected`,{})}async enableDevices(){let e=[],t=!1,n=!1;try{await this.room.localParticipant.setMicrophoneEnabled(!0),t=!0}catch(t){let n=t?.message||String(t);e.push(`mic: ${n}`),console.warn(`[im] 麦克风开启失败,通话继续(音频不可用)`,t)}if(this.callType===`video`)try{await this.room.localParticipant.setCameraEnabled(!0),n=!0}catch(t){let n=t?.message||String(t);e.push(`camera: ${n}`),console.warn(`[im] 摄像头开启失败,通话继续(视频不可用)`,t)}return this.emitSession(`devices`,{mic:t,camera:n,errors:e}),{mic:t,camera:n,errors:e}}attachLocalVideo(e){let t=this.room.localParticipant.getTrackPublication(n.Track.Source.Camera);t&&t.track&&t.track.attach(e)}attachRemoteVideo(e){for(let[,t]of this.room.remoteParticipants){let r=t.getTrackPublication(n.Track.Source.Camera);if(r){if(r.track){r.track.attach(e);return}r.isSubscribed||r.setSubscribed(!0)}}}detachAllVideoFromEl(e){if(!e)return;let t=this.room.localParticipant.getTrackPublication(n.Track.Source.Camera);t&&t.track&&t.track.detach(e);for(let[,t]of this.room.remoteParticipants){let r=t.getTrackPublication(n.Track.Source.Camera);r&&r.track&&r.track.detach(e)}}async setMicEnabled(e){await this.room.localParticipant.setMicrophoneEnabled(e)}async setCameraEnabled(e){await this.room.localParticipant.setCameraEnabled(e)}async hangup(e,t=`user_left`){if(!this.ended){this.ended=!0;try{await e.callEnd(this.callId,t)}catch(e){console.error(`[im] callEnd 失败`,e)}await l(this.room),this.emitSession(`ended`,{reason:t})}}async end(e){this.ended||(this.ended=!0,await l(this.room),this.emitSession(`ended`,{reason:e}))}registerRpcMethod(e,t){this.room.localParticipant.registerRpcMethod(e,e=>t(e.payload))}async performRpc(e,t,n){return await this.room.localParticipant.performRpc({destinationIdentity:e,method:t,payload:n})}emitSession(e,t){let n=this.sessionListeners.get(e);if(n)for(let r of n)try{r(t)}catch(t){console.error(`[im] session handler error: ${e}`,t)}}onSession(e,t){this.sessionListeners.has(e)||this.sessionListeners.set(e,new Set),this.sessionListeners.get(e).add(t)}get isConnected(){return this.room.state===`connected`}getToken(){return this.token}token},d=class{transport;livekitWs;identity;iceServers;currentSession=null;constructor(e,t,n,r){this.transport=e,this.livekitWs=t,this.identity=n,this.iceServers=r}async init(e,t){let n=await this.transport.callInit(e,t),r=new u({callId:n.callId,callType:t,roomName:n.roomName,identity:this.identity,token:n.token,livekitWs:this.livekitWs,iceServers:this.iceServers});return this.currentSession=r,r}async accept(e,t){let n=await this.transport.callAccept(e),r=new u({callId:e,callType:t,roomName:n.roomName,identity:this.identity,token:n.token,livekitWs:this.livekitWs,iceServers:this.iceServers});this.currentSession=r;try{await r.connect()}catch(t){throw this.currentSession=null,this.transport.callEnd(e,`user_left`).catch(()=>{}),t}return r.enableDevices().catch(e=>{console.warn(`[im] enableDevices 异常`,e)}),r}async end(e=`user_left`,t){if(this.currentSession)await this.currentSession.hangup(this.transport,e),this.currentSession=null;else if(t)try{await this.transport.callEnd(t,e)}catch(e){console.warn(`[im] callEnd 失败(不阻塞)`,e)}}};(0,n.setLogLevel)(`warn`);var f=class e extends r{meetingId;title;roomName;identity;isHost;hostIdentity;room;livekitWs;token;iceServers;ended=!1;sessionListeners=new Map;membersCache=[];membersCacheAt=0;static MEMBERS_CACHE_TTL=8e3;pendingAttach=new Map;constructor(e){super(),this.meetingId=e.meetingId,this.title=e.title,this.roomName=e.roomName,this.identity=e.identity,this.token=e.token,this.isHost=e.isHost,this.hostIdentity=e.hostIdentity,this.livekitWs=e.livekitWs,this.iceServers=e.iceServers,this.room=new n.Room({adaptiveStream:!0,dynacast:!0})}get isConnected(){return this.room.state===`connected`}async connect(){this.room.on(`participantConnected`,e=>{if(this.pendingAttach.has(e.identity)){let t=e.getTrackPublication(n.Track.Source.Camera);t&&!t.isSubscribed&&t.setSubscribed(!0)}this.emitSession(`participantJoined`,{identity:e.identity})}).on(`participantDisconnected`,e=>{this.emitSession(`participantLeft`,{identity:e.identity,reason:`leave`})}).on(`disconnected`,()=>{this.ended||this.end()}).on(`trackSubscribed`,(e,t,r)=>{if(e.kind===`video`){let t=this.pendingAttach.get(r.identity);t&&(e.attach(t),this.pendingAttach.delete(r.identity))}else e.kind===n.Track.Kind.Audio&&e.attach();this.emitSession(`participantTrackSubscribed`,{kind:e.kind,identity:r.identity})}).on(`activeSpeakersChanged`,e=>{let t=e.map(e=>e.identity);this.emitSession(`activeSpeakersChanged`,{identities:t})}).on(`trackMuted`,(e,t)=>{e.source===n.Track.Source.Microphone?this.emitSession(`participantMuteChanged`,{identity:t.identity,muted:!0,kind:`audio`}):e.source===n.Track.Source.Camera&&this.emitSession(`participantMuteChanged`,{identity:t.identity,muted:!0,kind:`video`})}).on(`trackUnmuted`,(e,t)=>{e.source===n.Track.Source.Microphone?this.emitSession(`participantMuteChanged`,{identity:t.identity,muted:!1,kind:`audio`}):e.source===n.Track.Source.Camera&&this.emitSession(`participantMuteChanged`,{identity:t.identity,muted:!1,kind:`video`})}),await this.room.connect(this.livekitWs,this.token,{rtcConfig:{iceServers:this.iceServers}});let e=this.room.localParticipant.identity;e&&(this.identity=e,this.hostIdentity||=e),this.emitSession(`connected`,{})}async enableDevices(){let e=[],t=!1,n=!1;try{await this.room.localParticipant.setMicrophoneEnabled(!0),t=!0}catch(t){e.push(`mic: ${t?.message||String(t)}`),console.warn(`[im] 麦克风开启失败,会议继续(音频不可用)`,t)}try{await this.room.localParticipant.setCameraEnabled(!0),n=!0}catch(t){e.push(`camera: ${t?.message||String(t)}`),console.warn(`[im] 摄像头开启失败,会议继续(视频不可用)`,t)}return this.emitSession(`devices`,{mic:t,camera:n,errors:e}),{mic:t,camera:n,errors:e}}async kick(e,t){await e.meetingKick(this.meetingId,t)}async leave(e){if(!this.ended){this.ended=!0;try{await e.meetingLeave(this.meetingId)}catch(e){console.error(`[im] meetingLeave 失败`,e)}await l(this.room),this.emitSession(`ended`,{})}}async end(){this.ended||(this.ended=!0,await l(this.room),this.emitSession(`ended`,{}))}getRemoteParticipants(){let e=[];for(let[,t]of this.room.remoteParticipants)e.push({identity:t.identity,audioEnabled:t.isMicrophoneEnabled,videoEnabled:t.isCameraEnabled,isSpeaking:t.isSpeaking});return e}attachLocalVideo(e){if((()=>{let t=this.room.localParticipant.getTrackPublication(n.Track.Source.Camera);return t&&t.track?(t.track.attach(e),!0):!1})())return;let t=r=>{r.source===n.Track.Source.Camera&&r.track&&(r.track.attach(e),this.room.localParticipant.off(`localTrackPublished`,t))};this.room.localParticipant.on(`localTrackPublished`,t)}attachParticipantVideo(e,t){let r=this.room.remoteParticipants.get(e);if(!r){this.pendingAttach.set(e,t);return}let i=r.getTrackPublication(n.Track.Source.Camera);if(!i){this.pendingAttach.set(e,t);return}if(i.track){i.track.attach(t),this.pendingAttach.delete(e);return}this.pendingAttach.set(e,t),i.isSubscribed||i.setSubscribed(!0)}async setMicEnabled(e){await this.room.localParticipant.setMicrophoneEnabled(e)}async setCameraEnabled(e){await this.room.localParticipant.setCameraEnabled(e)}async setSpeakerMuted(e){for(let[,t]of this.room.remoteParticipants)for(let[,r]of t.audioTrackPublications){let t=r.track;t&&t.kind===n.Track.Kind.Audio&&t.setVolume(+!e)}}async refreshMembers(t,n=!1){let r=Date.now();if(!n&&r-this.membersCacheAt<e.MEMBERS_CACHE_TTL&&this.membersCache.length>0)return this.membersCache;let i=await t.meetingMembers(this.meetingId);return this.membersCache=i.members||[],this.membersCacheAt=r,i.hostUid&&i.hostUid!==this.hostIdentity&&(this.hostIdentity=i.hostUid),this.membersCache}getMembers(){return this.membersCache}async transferHost(e,t){await e.meetingTransferHost(this.meetingId,t),this.hostIdentity=t,this.emitSession(`hostChanged`,{hostUid:t})}onSession(e,t){this.sessionListeners.has(e)||this.sessionListeners.set(e,new Set),this.sessionListeners.get(e).add(t)}emitSession(e,t){let n=this.sessionListeners.get(e);if(n)for(let r of n)try{r(t)}catch(t){console.error(`[im] session handler error: ${e}`,t)}}},p=class{transport;livekitWs;identity;iceServers;currentSession=null;constructor(e,t,n,r){this.transport=e,this.livekitWs=t,this.identity=n,this.iceServers=r}async create(e,t){let n=await this.transport.meetingCreate({inviteeUids:e,title:t}),r=new f({meetingId:n.meetingId,title:t,roomName:n.roomName,identity:this.identity,token:n.token,isHost:!0,hostIdentity:n.hostUid||this.identity,livekitWs:this.livekitWs,iceServers:this.iceServers});this.currentSession=r;try{await r.connect(),r.enableDevices().catch(e=>console.warn(`[im] enableDevices 异常`,e))}catch(e){throw this.currentSession=null,this.transport.meetingLeave(n.meetingId).catch(()=>{}),e}return{meetingId:n.meetingId,session:r,busyInvitees:n.busyInvitees}}async join(e,t=``){let n=await this.transport.meetingJoin(e),r=new f({meetingId:e,title:t,roomName:n.roomName,identity:this.identity,token:n.token,isHost:!1,hostIdentity:n.hostUid||``,livekitWs:this.livekitWs,iceServers:this.iceServers});this.currentSession=r;try{await r.connect(),r.enableDevices().catch(e=>console.warn(`[im] enableDevices 异常`,e))}catch(t){throw this.currentSession=null,this.transport.meetingLeave(e).catch(()=>{}),t}return r}async respond(e,t,n){let r=await this.transport.meetingRespond(e,t);if(t!==`accept`||!r?.token||!r?.roomName)return null;let i=new f({meetingId:e,title:r.title||n||``,roomName:r.roomName,identity:this.identity,token:r.token,isHost:!1,hostIdentity:r.hostUid||``,livekitWs:this.livekitWs,iceServers:this.iceServers});this.currentSession=i;try{await i.connect(),i.enableDevices().catch(e=>console.warn(`[im] enableDevices 异常`,e))}catch(t){throw this.currentSession=null,this.transport.meetingLeave(e).catch(()=>{}),t}return i}async leave(){this.currentSession&&=(await this.currentSession.leave(this.transport),null)}async dissolve(){if(!this.currentSession)return!1;let e=this.currentSession;return await this.transport.meetingDissolve(e.meetingId),this.currentSession=null,await e.end().catch(()=>{}),!0}async getParticipantCount(){return this.currentSession?(await this.transport.meetingParticipantCount(this.currentSession.meetingId)).count:0}async fetchPendingInvites(){try{return(await this.transport.meetingPendingInvites()).invites||[]}catch(e){return console.warn(`[im] fetchPendingInvites 失败`,e),[]}}async reinvite(e,t){await this.transport.meetingReinvite(e,t)}async getMembers(e=!1){return this.currentSession?this.currentSession.refreshMembers(this.transport,e):[]}},m=class{transport;realtime;uid;peerUidCache=new Map;constructor(e,t,n){this.transport=e,this.realtime=t,this.uid=n}async resolvePeerUUID(e){let t=this.peerUidCache.get(e);if(t)return t;let n=(await this.transport.usersByExternal([e])).users[e];if(!n||!n.uid)throw Error(`peer not found: ${e}`);return this.peerUidCache.set(e,n.uid),n.uid}async send(e){let t={to:e.to,type:e.type,body:e.body,clientMid:crypto.randomUUID(),mediaId:e.mediaId,replyToId:e.replyToId},n=await this.transport.sendMessage(t);return await this.transport.ackMessage(n.msgId).catch(()=>{}),n}async sendMedia(e,t){let{mediaId:n}=await t.upload(e.file,e.file.type);return await this.send({to:e.to,type:e.type,mediaId:n})}async history(e,t=0,n=50){return(await this.transport.listMessages(e,t,n)).messages}async ack(e){await this.transport.ackMessage(e)}async markRead(e,t){await this.transport.markRead(e,t)}async revoke(e){await this.transport.revokeMessage(e)}async typing(e,t){let n=await this.resolvePeerUUID(e),r=this.dialogChannel(n);await this.realtime.publish(r,{type:`typing`,from:``,isTyping:t})}async listConversations(){return(await this.transport.listConversations()).conversations}async openDialog(e){let t=await this.resolvePeerUUID(e),n=this.dialogChannel(t);await this.realtime.subscribe(n)}closeDialog(e){let t=this.peerUidCache.get(e);if(!t)return;let n=this.dialogChannel(t);this.realtime.unsubscribe(n)}dialogChannel(e){return`dialog#${[this.uid,e].sort()[0]},${[this.uid,e].sort()[1]}`}},h=class{transport;constructor(e){this.transport=e}async computeMD5(e){let t=await e.arrayBuffer(),n=await crypto.subtle.digest(`SHA-256`,t);return Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,`0`)).join(``)}async upload(e,t){let n=await this.computeMD5(e),r=await this.transport.checkMd5(n);if(r.hit&&r.mediaId)return{mediaId:r.mediaId};let i=await this.transport.getUploadUrl({fileName:e.name,contentType:t||e.type,fileSize:e.size,md5:n});if(i.uploadUrl&&i.uploadUrl.startsWith(`http`)){let n=await fetch(i.uploadUrl,{method:`PUT`,body:e,headers:{"Content-Type":t||e.type}});if(!n.ok)throw Error(`上传对象存储失败: HTTP ${n.status}`)}return{mediaId:i.mediaId}}},g=class{transport;byExternal=new Map;byUid=new Map;loaded=!1;loading=null;constructor(e){this.transport=e}async list(e=!1){return this.loaded&&!e?[...this.byExternal.values()]:(this.loading||=this.transport.listUsers().then(e=>{for(let t of e.users||[])this.upsert(t);this.loaded=!0}).finally(()=>{this.loading=null}),await this.loading,[...this.byExternal.values()])}async resolve(e){let t=this.byExternal.get(e)||this.byUid.get(e);if(t)return t;if(!this.loaded)return await this.list(),this.byExternal.get(e)||this.byUid.get(e)}upsert(e){this.byExternal.set(e.externalId,e),this.byUid.set(e.uid,e)}updateOnline(e,t){let n=this.byExternal.get(e);n&&n.online!==t&&(n.online=t)}},_=[`message`,`message_preview`,`message:revoked`,`read_receipt`,`typing`,`call:invite`,`call:accepted`,`call:rejected`,`call:cancelled`,`call:busy`,`call:no_answer`,`call:ended`,`meeting:invite`,`meeting:kicked`,`meeting:hostChanged`,`meeting:dissolved`,`group:memberJoined`,`group:memberLeft`,`group:kicked`,`group:announcement`,`group:profileChanged`,`session:kicked`,`connection:state`,`presence:update`],v=class extends r{auth;transport;realtime;call;meeting;message;media;users;uid=``;appId;sessionState=`idle`;clientType=4;realtimeBound=!1;clientConfig=null;livekitWs;apiBase;name=``;avatar=``;constructor(e){super(),this.appId=e.appId,this.apiBase=e.apiBase,this.livekitWs=e.livekitWs,this.auth=new o(null),this.transport=new a(e.apiBase,()=>this.auth.getToken()),this.auth=new o(this.transport),this.realtime=new s(e.centrifugoWs,async()=>await this.auth.getCentrifugoToken(this.name,this.avatar))}async connect(e){e.name&&(this.name=e.name),e.avatar&&(this.avatar=e.avatar),this.clientType=e.clientType??4;let t=await this.auth.login(this.appId,e.externalId,e.clientType??4,e.deviceName);return this.uid=t.uid,t.name&&(this.name=t.name),t.avatarUrl&&(this.avatar=t.avatarUrl),await this.initAfterAuth(),t}async connectWithExternalJwt(e){e.name&&(this.name=e.name),e.avatar&&(this.avatar=e.avatar),this.uid=e.uid,this.clientType=e.clientType??4,this.auth.setExternalToken(e.jwt,e.expiresAt),await this.initAfterAuth()}async initAfterAuth(){await this.loadClientConfig();let e=this.clientConfig?.iceServers??[];this.call=new d(this.transport,this.livekitWs,this.uid,e),this.meeting=new p(this.transport,this.livekitWs,this.uid,e),this.message=new m(this.transport,this.realtime,this.uid),this.media=new h(this.transport),this.users=new g(this.transport),await this.realtime.connect(this.uid);try{let e=await this.transport.getSessionState();this.sessionState=e.state,this.emit(`session:state`,{state:e.state,callId:e.id,meetingId:e.id})}catch(e){console.warn(`[im] 同步 sessionState 失败`,e)}this.realtimeBound||(this.realtimeBound=!0,this.realtime.on(`call:busy`,()=>{this.call.currentSession=null}),this.realtime.on(`call:no_answer`,()=>{this.call.currentSession=null}),this.realtime.on(`meeting:hostChanged`,e=>{let t=this.meeting?.currentSession;t&&e?.meetingId===t.meetingId&&e?.hostUid&&(t.hostIdentity=e.hostUid)}),this.realtime.on(`meeting:dissolved`,e=>{let t=this.meeting?.currentSession;t&&e?.meetingId===t.meetingId&&t.end().catch(e=>console.warn(`[im] dissolved end session 失败`,e))}),this.realtime.on(`meeting:kicked`,e=>{let t=this.meeting?.currentSession;t&&e?.meetingId===t.meetingId&&t.end().catch(e=>console.warn(`[im] kicked end session 失败`,e))}),this.realtime.on(`session:kicked`,e=>{if(!(e?.clientType!==void 0&&e.clientType!==this.clientType)){console.warn(`[im] 会话被踢(同端新登录),断开连接`);try{this.realtime.disconnect()}catch{}this.auth.logout(),this.uid=``}}),this.realtime.on(`presence:update`,e=>{e?.uid&&this.users?.updateOnline(e.uid,!!e.online)}),this.forwardRealtimeEvents()),this.fetchAndEmitPendingInvites()}async loadClientConfig(){if(!this.clientConfig)try{this.clientConfig=await this.transport.getClientConfig()}catch(e){console.warn(`[im] 拉取 client/config 失败,使用空 iceServers 兜底`,e),this.clientConfig={iceServers:[]}}}getConfig(){return this.clientConfig}async fetchAndEmitPendingInvites(){if(this.meeting)try{let e=await this.meeting.fetchPendingInvites(),t=this.meeting.currentSession?.meetingId;for(let n of e)n.meetingId!==t&&this.emit(`meeting:invite`,{meetingId:n.meetingId,from:n.from,title:n.title,ts:n.ts,variant:n.variant,reinvite:n.variant===`reinvite`})}catch(e){console.warn(`[im] fetchAndEmitPendingInvites 失败`,e)}}forwardRealtimeEvents(){for(let e of _)this.realtime.on(e,t=>{this.emit(e,t)})}async disconnect(){this.call?.currentSession&&await this.call.end(`user_left`).catch(()=>{}),this.meeting?.currentSession&&await this.meeting.leave().catch(()=>{}),this.realtime.disconnect(),this.auth.logout(),this.removeAllListeners()}};e.ApiError=i,e.AuthManager=o,e.CallController=d,e.CallSession=u,e.EventEmitter=r,e.ImClient=v,e.MediaUploader=h,e.MeetingController=p,e.MeetingSession=f,e.MessageController=m,e.RealtimeConnection=s,e.Transport=a,e.UserDirectory=g;var y=require("@quitting/contract");Object.keys(y).forEach(function(t){t!=="default"&&!Object.prototype.hasOwnProperty.call(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:function(){return y[t]}})})});
2
2
  //# sourceMappingURL=im-core.umd.js.map