@gamention/pulse-core 0.3.4 → 0.3.5

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.
package/dist/index.d.ts CHANGED
@@ -116,6 +116,7 @@ export declare class PulseClient extends Emitter {
116
116
  private _p2pInstance;
117
117
  private p2pEnabled;
118
118
  private iceServers;
119
+ private signalingQueue;
119
120
  /** Current WebSocket connection state. */
120
121
  get connectionState(): ConnectionState;
121
122
  constructor(config: PulseConfig);
@@ -163,6 +164,7 @@ export declare class PulseClient extends Emitter {
163
164
  /** Lazy-loaded P2P manager. Returns a promise that resolves to the P2PManager instance. */
164
165
  get p2p(): Promise<P2PManager>;
165
166
  private initP2P;
167
+ private routeP2PMessage;
166
168
  }
167
169
 
168
170
  export declare interface SharedCounter {
@@ -1 +1 @@
1
- "use strict";var f=Object.defineProperty;var _=(h,a,e)=>a in h?f(h,a,{enumerable:!0,configurable:!0,writable:!0,value:e}):h[a]=e;var r=(h,a,e)=>_(h,typeof a!="symbol"?a+"":a,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("@gamention/pulse-shared");class d{constructor(){r(this,"handlers",new Map)}on(a,e){this.handlers.has(a)||this.handlers.set(a,new Set);const t=this.handlers.get(a);return t.add(e),()=>t.delete(e)}off(a,e){var t;(t=this.handlers.get(a))==null||t.delete(e)}emit(a,e){var t;(t=this.handlers.get(a))==null||t.forEach(i=>i(e))}removeAll(){this.handlers.clear()}}class p extends d{constructor(e){super();r(this,"ws",null);r(this,"endpoint");r(this,"reconnectAttempt",0);r(this,"reconnectTimer",null);r(this,"_state","disconnected");r(this,"permanentlyClosed",!1);r(this,"handleOpen",()=>{this._state="connected",this.reconnectAttempt=0,this.emit("state",this._state)});r(this,"handleMessage",e=>{try{const t=JSON.parse(e.data);this.emit("message",t)}catch{}});r(this,"handleClose",()=>{this.cleanupWs(),this._state="disconnected",this.emit("state",this._state),this.scheduleReconnect()});r(this,"handleError",()=>{var e;(e=this.ws)==null||e.close()});this.endpoint=e??c.DEFAULT_ENDPOINT}get state(){return this._state}connect(){this.ws||this.permanentlyClosed||(this._state="connecting",this.emit("state",this._state),this.ws=new WebSocket(this.endpoint),this.ws.addEventListener("open",this.handleOpen),this.ws.addEventListener("message",this.handleMessage),this.ws.addEventListener("close",this.handleClose),this.ws.addEventListener("error",this.handleError))}disconnect(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.reconnectAttempt=0,this.cleanupWs(),this._state="disconnected",this.emit("state",this._state)}send(e){var t;((t=this.ws)==null?void 0:t.readyState)===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}permanentDisconnect(){this.permanentlyClosed=!0,this.disconnect()}cleanupWs(){this.ws&&(this.ws.removeEventListener("open",this.handleOpen),this.ws.removeEventListener("message",this.handleMessage),this.ws.removeEventListener("close",this.handleClose),this.ws.removeEventListener("error",this.handleError),this.ws.close(),this.ws=null)}scheduleReconnect(){if(this.permanentlyClosed)return;const e=Math.min(c.RECONNECT_BASE_DELAY_MS*2**this.reconnectAttempt,c.RECONNECT_MAX_DELAY_MS);this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},e)}}class u extends d{constructor(){super(...arguments);r(this,"baseUrl","");r(this,"_user",null);r(this,"_config",{...c.DEFAULT_ENV_CONFIG});r(this,"_p2pConfig",null);r(this,"_users",new Map);r(this,"_presence",new Map);r(this,"_threads",new Map);r(this,"_reactions",new Map);r(this,"_notifications",[]);r(this,"_activityLogs",[]);r(this,"_typing",new Map);r(this,"_viewports",new Map);r(this,"_selections",new Map)}get user(){return this._user}get p2pConfig(){return this._p2pConfig}get config(){return this._config}removeComment(e){for(const[t,i]of this._threads){const s=i.comments.findIndex(n=>n.id===e);if(s!==-1){i.comments.splice(s,1),i.comments.length===0&&this._threads.delete(t),this.emit("threads",this.threads);return}}}get presence(){return[...this._presence.values()]}get threads(){return[...this._threads.values()]}get notifications(){return this._notifications}get unreadCount(){return this._notifications.filter(e=>!e.read).length}markNotificationRead(e){const t=this._notifications.find(i=>i.id===e);t&&!t.read&&(t.read=!0,this.emit("notifications",this._notifications))}markAllNotificationsRead(){let e=!1;for(const t of this._notifications)t.read||(t.read=!0,e=!0);e&&this.emit("notifications",this._notifications)}get activityLogs(){return this._activityLogs}getUser(e){return this._users.get(e)}get users(){return[...this._users.values()]}getReactions(e){return this._reactions.get(e)??[]}getTypingUsers(e){const t=this._typing.get(e);if(!t)return[];const i=Date.now(),s=[];for(const[n,o]of t)i-o<3e3&&s.push(n);return s}get viewports(){return this._viewports}getViewport(e){return this._viewports.get(e)}get selections(){return this._selections}resolveUrl(e){return!this.baseUrl||!e||e.startsWith("http://")||e.startsWith("https://")?e:`${this.baseUrl}${e}`}resolveAttachments(e){return e.map(t=>({...t,url:this.resolveUrl(t.url),thumbnailUrl:t.thumbnailUrl?this.resolveUrl(t.thumbnailUrl):void 0}))}resolveComment(e){return!e.attachments||e.attachments.length===0?e:{...e,attachments:this.resolveAttachments(e.attachments)}}resolveThread(e){return{...e,comments:e.comments.map(t=>this.resolveComment(t))}}handleMessage(e){switch(e.type){case"auth:ok":this._config=e.config??{...c.DEFAULT_ENV_CONFIG},this._p2pConfig=e.p2pConfig??null,this._user=e.user,this._users.clear();for(const t of e.users)this._users.set(t.id,t);this._presence.clear();for(const t of e.presence)this._presence.set(t.user.id,t),this._users.set(t.user.id,t.user);this._users.set(e.user.id,e.user),this._threads.clear();for(const t of e.threads)this._threads.set(t.id,this.resolveThread(t));this._notifications=e.notifications,this._reactions.clear();for(const t of e.reactions){const i=this._reactions.get(t.targetId)??[];i.push(t),this._reactions.set(t.targetId,i)}this._activityLogs=[...e.activityLogs],this.emit("auth",e.user),this.emit("presence",this.presence),this.emit("threads",this.threads),this.emit("notifications",this._notifications),this.emit("reactions",null),this.emit("activity-logs",this._activityLogs);break;case"presence:join":this._presence.set(e.user.user.id,e.user),this._users.set(e.user.user.id,e.user.user),this.emit("presence",this.presence);break;case"presence:leave":this._presence.delete(e.userId),this._viewports.delete(e.userId),this._selections.delete(e.userId);for(const t of this._typing.values())t.delete(e.userId);this.emit("presence",this.presence);break;case"presence:update":{const t=this._presence.get(e.userId);t&&(t.status=e.status,this.emit("presence",this.presence));break}case"cursor:move":this.emit("cursor",{userId:e.userId,position:e.position});break;case"click:perform":this.emit("click",{userId:e.userId,position:e.position});break;case"thread:created":this._threads.set(e.thread.id,this.resolveThread(e.thread)),this.emit("threads",this.threads);break;case"comment:created":{const t=this._threads.get(e.threadId);t&&(t.comments.push(this.resolveComment(e.comment)),t.updatedAt=e.comment.createdAt,this.emit("threads",this.threads));break}case"comment:edited":{const t=this._threads.get(e.threadId);if(t){const i=t.comments.findIndex(s=>s.id===e.comment.id);i!==-1&&(t.comments[i]=this.resolveComment(e.comment)),this.emit("threads",this.threads)}break}case"comment:deleted":{const t=this._threads.get(e.threadId);t&&(t.comments=t.comments.filter(i=>i.id!==e.commentId),t.comments.length===0&&this._threads.delete(e.threadId),this.emit("threads",this.threads));break}case"thread:resolved":{const t=this._threads.get(e.threadId);t&&(t.resolved=e.resolved,this.emit("threads",this.threads));break}case"thread:deleted":this._threads.delete(e.threadId),this.emit("threads",this.threads);break;case"reaction:added":{const t=this._reactions.get(e.reaction.targetId)??[];t.push(e.reaction),this._reactions.set(e.reaction.targetId,t),this.emit("reactions",{targetId:e.reaction.targetId,reactions:t});break}case"reaction:removed":{const t=this._reactions.get(e.targetId);if(t){const i=t.filter(s=>s.id!==e.reactionId);this._reactions.set(e.targetId,i),this.emit("reactions",{targetId:e.targetId,reactions:i})}break}case"notification":this._notifications.unshift(e.notification),this.emit("notifications",this._notifications);break;case"typing:indicator":{this._typing.has(e.threadId)||this._typing.set(e.threadId,new Map),this._typing.get(e.threadId).set(e.userId,Date.now()),this.emit("typing",{threadId:e.threadId,userId:e.userId});break}case"viewport:update":{this._viewports.set(e.userId,{scrollX:e.scrollX,scrollY:e.scrollY,viewportWidth:e.viewportWidth,viewportHeight:e.viewportHeight,pageWidth:e.pageWidth,pageHeight:e.pageHeight}),this.emit("viewport",{userId:e.userId});break}case"selection:update":this._selections.set(e.userId,e.selection),this.emit("selection",{userId:e.userId,selection:e.selection});break;case"emoji:drop":this.emit("emoji-drop",{userId:e.userId,emoji:e.emoji,position:e.position});break;case"draw:stroke":this.emit("draw-stroke",{userId:e.userId,points:e.points,color:e.color,width:e.width});break;case"draw:clear":this.emit("draw-clear",{userId:e.userId});break;case"activity:logged":this._activityLogs.unshift(e.activityLog),this._activityLogs.length>100&&(this._activityLogs=this._activityLogs.slice(0,100)),this.emit("activity-logs",this._activityLogs);break;case"auth:error":this.emit("auth:error",e);break;case"error":this.emit("error",e);break}}reset(){this._user=null,this._config={...c.DEFAULT_ENV_CONFIG},this._p2pConfig=null,this._users.clear(),this._presence.clear(),this._threads.clear(),this._reactions.clear(),this._notifications=[],this._activityLogs=[],this._typing.clear(),this._viewports.clear(),this._selections.clear()}}class m extends d{constructor(e){var i;super();r(this,"state");r(this,"connection");r(this,"config");r(this,"heartbeatTimer",null);r(this,"lastCursorSend",0);r(this,"pendingCursor",null);r(this,"cursorTimer",null);r(this,"_p2p",null);r(this,"_p2pInstance",null);r(this,"p2pEnabled");r(this,"iceServers");this.config=e,this.state=new u,this.state.baseUrl=(e.endpoint??"").replace(/^ws(s?):/,"http$1:").replace(/\/$/,"");const t=((i=e.endpoint)==null?void 0:i.replace(/^http/,"ws"))??void 0;this.connection=new p(t),this.p2pEnabled=e.p2p??!1,this.iceServers=e.iceServers??[{urls:"stun:stun.l.google.com:19302"}],this.connection.on("message",s=>{this.state.handleMessage(s),this.emit(s.type,s),s.type==="auth:error"&&this.connection.permanentDisconnect(),this._p2pInstance&&(s.type==="signal:offer"?this._p2pInstance.handleSignalOffer(s.fromUserId,s.sdp):s.type==="signal:answer"?this._p2pInstance.handleSignalAnswer(s.fromUserId,s.sdp):s.type==="signal:ice"?this._p2pInstance.handleIceCandidate(s.fromUserId,s.candidate):s.type==="p2p:sync"?this._p2pInstance.handleP2PSync(s.fromUserId,s.update):s.type==="presence:join"?this._p2pInstance.onPeerJoined(s.user.user.id):s.type==="presence:leave"&&this._p2pInstance.onPeerLeft(s.userId))}),this.connection.on("state",s=>{this.emit("connection",s),s==="connected"?(this.authenticate(),this.startHeartbeat()):s==="disconnected"&&this.stopHeartbeat()})}get connectionState(){return this.connection.state}connect(){this.connection.connect()}disconnect(){this.stopHeartbeat(),this.cursorTimer&&(clearTimeout(this.cursorTimer),this.cursorTimer=null),this.connection.disconnect(),this.state.reset()}destroy(){var e;(e=this._p2pInstance)==null||e.destroy(),this._p2pInstance=null,this._p2p=null,this.disconnect(),this.removeAll(),this.state.removeAll(),this.connection.removeAll()}authenticate(){this.send({type:"auth",apiKey:this.config.apiKey,token:this.config.token,room:this.config.room})}send(e){this.connection.send(e)}moveCursor(e){const t=Date.now();this.pendingCursor=e,t-this.lastCursorSend>=c.CURSOR_THROTTLE_MS?this.flushCursor():this.cursorTimer||(this.cursorTimer=setTimeout(()=>{this.cursorTimer=null,this.flushCursor()},c.CURSOR_THROTTLE_MS))}flushCursor(){this.pendingCursor&&(this.send({type:"cursor:move",position:this.pendingCursor}),this.lastCursorSend=Date.now(),this.pendingCursor=null)}updatePresence(e){this.send({type:"presence:update",status:e})}startHeartbeat(){this.heartbeatTimer||(this.heartbeatTimer=setInterval(()=>{this.send({type:"presence:update",status:"online"})},c.PRESENCE_HEARTBEAT_MS))}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}createThread(e,t={}){const i=crypto.randomUUID();return this.send({type:"thread:create",id:i,body:e,mentions:t.mentions??[],position:t.position??null,attachmentIds:t.attachmentIds}),i}reply(e,t,i=[],s){const n=crypto.randomUUID();return this.send({type:"comment:create",threadId:e,id:n,body:t,mentions:i,attachmentIds:s}),n}editComment(e,t,i=[]){this.send({type:"comment:edit",commentId:e,body:t,mentions:i})}deleteComment(e){this.state.removeComment(e),this.send({type:"comment:delete",commentId:e})}resolveThread(e,t=!0){this.send({type:"thread:resolve",threadId:e,resolved:t})}addReaction(e,t,i){this.send({type:"reaction:add",targetId:e,targetType:t,emoji:i})}removeReaction(e){this.send({type:"reaction:remove",reactionId:e})}markRead(e){this.state.markNotificationRead(e),this.send({type:"notification:read",notificationId:e})}markAllRead(){this.state.markAllNotificationsRead(),this.send({type:"notification:read-all"})}performClick(e){this.send({type:"click:perform",position:e})}sendTyping(e){this.send({type:"typing:start",threadId:e})}updateViewport(e){this.send({type:"viewport:update",...e})}updateSelection(e){this.send({type:"selection:update",selection:e})}dropEmoji(e,t){this.send({type:"emoji:drop",emoji:e,position:t})}drawStroke(e,t,i){this.send({type:"draw:stroke",points:e,color:t,width:i})}clearDrawing(){this.send({type:"draw:clear"})}async uploadFile(e){const t=typeof window<"u"?window.location.origin:"",i=(this.config.endpoint??t).replace(/^ws(s?):/,"http$1:"),s=new FormData;s.append("file",e);const n=await fetch(`${i}/api/v1/upload`,{method:"POST",headers:{"X-Pulse-Key":this.config.apiKey,"X-Pulse-Token":this.config.token},body:s});if(!n.ok){const l=await n.json().catch(()=>({error:"Upload failed"}));throw new Error(l.error??"Upload failed")}const o=await n.json();return o.url&&!o.url.startsWith("http")&&(o.url=`${i}${o.url}`),o.thumbnailUrl&&!o.thumbnailUrl.startsWith("http")&&(o.thumbnailUrl=`${i}${o.thumbnailUrl}`),o}setAppearOffline(e){e?(this.stopHeartbeat(),this.send({type:"presence:update",status:"idle"})):(this.startHeartbeat(),this.send({type:"presence:update",status:"online"}))}get p2p(){return this.p2pEnabled?(this._p2p||(this._p2p=this.initP2P()),this._p2p):Promise.reject(new Error("P2P is not enabled. Pass { p2p: true } in PulseConfig."))}async initP2P(){const e=await new Promise((n,o)=>{if(this.state.user&&this.state.p2pConfig){n({userId:this.state.user.id,p2pConfig:this.state.p2pConfig});return}const l=this.state.on("auth",()=>{if(l(),!this.state.p2pConfig){o(new Error("P2P is not enabled for this environment."));return}n({userId:this.state.user.id,p2pConfig:this.state.p2pConfig})})});let t=this.iceServers;try{const n=await fetch(`${this.state.baseUrl}/api/v1/turn-credentials`,{headers:{Authorization:`Bearer ${this.config.token}`}});n.ok&&(t=(await n.json()).iceServers)}catch{}const{P2PManager:i}=await Promise.resolve().then(()=>require("./P2PManager-e4ShL60A.cjs")),s=new i({roomId:this.config.room,userId:e.userId,baseUrl:this.state.baseUrl,authToken:this.config.token,p2pConfig:e.p2pConfig,iceServers:t,sendWS:n=>this.send(n)});this._p2pInstance=s,await s.initialize();for(const n of this.state.presence)n.user.id!==e.userId&&s.onPeerJoined(n.user.id);return s}}exports.Connection=p;exports.Emitter=d;exports.PulseClient=m;exports.StateManager=u;
1
+ "use strict";var f=Object.defineProperty;var _=(h,a,e)=>a in h?f(h,a,{enumerable:!0,configurable:!0,writable:!0,value:e}):h[a]=e;var r=(h,a,e)=>_(h,typeof a!="symbol"?a+"":a,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("@gamention/pulse-shared");class d{constructor(){r(this,"handlers",new Map)}on(a,e){this.handlers.has(a)||this.handlers.set(a,new Set);const t=this.handlers.get(a);return t.add(e),()=>t.delete(e)}off(a,e){var t;(t=this.handlers.get(a))==null||t.delete(e)}emit(a,e){var t;(t=this.handlers.get(a))==null||t.forEach(s=>s(e))}removeAll(){this.handlers.clear()}}class p extends d{constructor(e){super();r(this,"ws",null);r(this,"endpoint");r(this,"reconnectAttempt",0);r(this,"reconnectTimer",null);r(this,"_state","disconnected");r(this,"permanentlyClosed",!1);r(this,"handleOpen",()=>{this._state="connected",this.reconnectAttempt=0,this.emit("state",this._state)});r(this,"handleMessage",e=>{try{const t=JSON.parse(e.data);this.emit("message",t)}catch{}});r(this,"handleClose",()=>{this.cleanupWs(),this._state="disconnected",this.emit("state",this._state),this.scheduleReconnect()});r(this,"handleError",()=>{var e;(e=this.ws)==null||e.close()});this.endpoint=e??c.DEFAULT_ENDPOINT}get state(){return this._state}connect(){this.ws||this.permanentlyClosed||(this._state="connecting",this.emit("state",this._state),this.ws=new WebSocket(this.endpoint),this.ws.addEventListener("open",this.handleOpen),this.ws.addEventListener("message",this.handleMessage),this.ws.addEventListener("close",this.handleClose),this.ws.addEventListener("error",this.handleError))}disconnect(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.reconnectAttempt=0,this.cleanupWs(),this._state="disconnected",this.emit("state",this._state)}send(e){var t;((t=this.ws)==null?void 0:t.readyState)===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}permanentDisconnect(){this.permanentlyClosed=!0,this.disconnect()}cleanupWs(){this.ws&&(this.ws.removeEventListener("open",this.handleOpen),this.ws.removeEventListener("message",this.handleMessage),this.ws.removeEventListener("close",this.handleClose),this.ws.removeEventListener("error",this.handleError),this.ws.close(),this.ws=null)}scheduleReconnect(){if(this.permanentlyClosed)return;const e=Math.min(c.RECONNECT_BASE_DELAY_MS*2**this.reconnectAttempt,c.RECONNECT_MAX_DELAY_MS);this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},e)}}class u extends d{constructor(){super(...arguments);r(this,"baseUrl","");r(this,"_user",null);r(this,"_config",{...c.DEFAULT_ENV_CONFIG});r(this,"_p2pConfig",null);r(this,"_users",new Map);r(this,"_presence",new Map);r(this,"_threads",new Map);r(this,"_reactions",new Map);r(this,"_notifications",[]);r(this,"_activityLogs",[]);r(this,"_typing",new Map);r(this,"_viewports",new Map);r(this,"_selections",new Map)}get user(){return this._user}get p2pConfig(){return this._p2pConfig}get config(){return this._config}removeComment(e){for(const[t,s]of this._threads){const i=s.comments.findIndex(n=>n.id===e);if(i!==-1){s.comments.splice(i,1),s.comments.length===0&&this._threads.delete(t),this.emit("threads",this.threads);return}}}get presence(){return[...this._presence.values()]}get threads(){return[...this._threads.values()]}get notifications(){return this._notifications}get unreadCount(){return this._notifications.filter(e=>!e.read).length}markNotificationRead(e){const t=this._notifications.find(s=>s.id===e);t&&!t.read&&(t.read=!0,this.emit("notifications",this._notifications))}markAllNotificationsRead(){let e=!1;for(const t of this._notifications)t.read||(t.read=!0,e=!0);e&&this.emit("notifications",this._notifications)}get activityLogs(){return this._activityLogs}getUser(e){return this._users.get(e)}get users(){return[...this._users.values()]}getReactions(e){return this._reactions.get(e)??[]}getTypingUsers(e){const t=this._typing.get(e);if(!t)return[];const s=Date.now(),i=[];for(const[n,o]of t)s-o<3e3&&i.push(n);return i}get viewports(){return this._viewports}getViewport(e){return this._viewports.get(e)}get selections(){return this._selections}resolveUrl(e){return!this.baseUrl||!e||e.startsWith("http://")||e.startsWith("https://")?e:`${this.baseUrl}${e}`}resolveAttachments(e){return e.map(t=>({...t,url:this.resolveUrl(t.url),thumbnailUrl:t.thumbnailUrl?this.resolveUrl(t.thumbnailUrl):void 0}))}resolveComment(e){return!e.attachments||e.attachments.length===0?e:{...e,attachments:this.resolveAttachments(e.attachments)}}resolveThread(e){return{...e,comments:e.comments.map(t=>this.resolveComment(t))}}handleMessage(e){switch(e.type){case"auth:ok":this._config=e.config??{...c.DEFAULT_ENV_CONFIG},this._p2pConfig=e.p2pConfig??null,this._user=e.user,this._users.clear();for(const t of e.users)this._users.set(t.id,t);this._presence.clear();for(const t of e.presence)this._presence.set(t.user.id,t),this._users.set(t.user.id,t.user);this._users.set(e.user.id,e.user),this._threads.clear();for(const t of e.threads)this._threads.set(t.id,this.resolveThread(t));this._notifications=e.notifications,this._reactions.clear();for(const t of e.reactions){const s=this._reactions.get(t.targetId)??[];s.push(t),this._reactions.set(t.targetId,s)}this._activityLogs=[...e.activityLogs],this.emit("auth",e.user),this.emit("presence",this.presence),this.emit("threads",this.threads),this.emit("notifications",this._notifications),this.emit("reactions",null),this.emit("activity-logs",this._activityLogs);break;case"presence:join":this._presence.set(e.user.user.id,e.user),this._users.set(e.user.user.id,e.user.user),this.emit("presence",this.presence);break;case"presence:leave":this._presence.delete(e.userId),this._viewports.delete(e.userId),this._selections.delete(e.userId);for(const t of this._typing.values())t.delete(e.userId);this.emit("presence",this.presence);break;case"presence:update":{const t=this._presence.get(e.userId);t&&(t.status=e.status,this.emit("presence",this.presence));break}case"cursor:move":this.emit("cursor",{userId:e.userId,position:e.position});break;case"click:perform":this.emit("click",{userId:e.userId,position:e.position});break;case"thread:created":this._threads.set(e.thread.id,this.resolveThread(e.thread)),this.emit("threads",this.threads);break;case"comment:created":{const t=this._threads.get(e.threadId);t&&(t.comments.push(this.resolveComment(e.comment)),t.updatedAt=e.comment.createdAt,this.emit("threads",this.threads));break}case"comment:edited":{const t=this._threads.get(e.threadId);if(t){const s=t.comments.findIndex(i=>i.id===e.comment.id);s!==-1&&(t.comments[s]=this.resolveComment(e.comment)),this.emit("threads",this.threads)}break}case"comment:deleted":{const t=this._threads.get(e.threadId);t&&(t.comments=t.comments.filter(s=>s.id!==e.commentId),t.comments.length===0&&this._threads.delete(e.threadId),this.emit("threads",this.threads));break}case"thread:resolved":{const t=this._threads.get(e.threadId);t&&(t.resolved=e.resolved,this.emit("threads",this.threads));break}case"thread:deleted":this._threads.delete(e.threadId),this.emit("threads",this.threads);break;case"reaction:added":{const t=this._reactions.get(e.reaction.targetId)??[];t.push(e.reaction),this._reactions.set(e.reaction.targetId,t),this.emit("reactions",{targetId:e.reaction.targetId,reactions:t});break}case"reaction:removed":{const t=this._reactions.get(e.targetId);if(t){const s=t.filter(i=>i.id!==e.reactionId);this._reactions.set(e.targetId,s),this.emit("reactions",{targetId:e.targetId,reactions:s})}break}case"notification":this._notifications.unshift(e.notification),this.emit("notifications",this._notifications);break;case"typing:indicator":{this._typing.has(e.threadId)||this._typing.set(e.threadId,new Map),this._typing.get(e.threadId).set(e.userId,Date.now()),this.emit("typing",{threadId:e.threadId,userId:e.userId});break}case"viewport:update":{this._viewports.set(e.userId,{scrollX:e.scrollX,scrollY:e.scrollY,viewportWidth:e.viewportWidth,viewportHeight:e.viewportHeight,pageWidth:e.pageWidth,pageHeight:e.pageHeight}),this.emit("viewport",{userId:e.userId});break}case"selection:update":this._selections.set(e.userId,e.selection),this.emit("selection",{userId:e.userId,selection:e.selection});break;case"emoji:drop":this.emit("emoji-drop",{userId:e.userId,emoji:e.emoji,position:e.position});break;case"draw:stroke":this.emit("draw-stroke",{userId:e.userId,points:e.points,color:e.color,width:e.width});break;case"draw:clear":this.emit("draw-clear",{userId:e.userId});break;case"activity:logged":this._activityLogs.unshift(e.activityLog),this._activityLogs.length>100&&(this._activityLogs=this._activityLogs.slice(0,100)),this.emit("activity-logs",this._activityLogs);break;case"auth:error":this.emit("auth:error",e);break;case"error":this.emit("error",e);break}}reset(){this._user=null,this._config={...c.DEFAULT_ENV_CONFIG},this._p2pConfig=null,this._users.clear(),this._presence.clear(),this._threads.clear(),this._reactions.clear(),this._notifications=[],this._activityLogs=[],this._typing.clear(),this._viewports.clear(),this._selections.clear()}}class m extends d{constructor(e){var s;super();r(this,"state");r(this,"connection");r(this,"config");r(this,"heartbeatTimer",null);r(this,"lastCursorSend",0);r(this,"pendingCursor",null);r(this,"cursorTimer",null);r(this,"_p2p",null);r(this,"_p2pInstance",null);r(this,"p2pEnabled");r(this,"iceServers");r(this,"signalingQueue",[]);this.config=e,this.state=new u,this.state.baseUrl=(e.endpoint??"").replace(/^ws(s?):/,"http$1:").replace(/\/$/,"");const t=((s=e.endpoint)==null?void 0:s.replace(/^http/,"ws"))??void 0;this.connection=new p(t),this.p2pEnabled=e.p2p??!1,this.iceServers=e.iceServers??[{urls:"stun:stun.l.google.com:19302"}],this.connection.on("message",i=>{this.state.handleMessage(i),this.emit(i.type,i),i.type==="auth:error"&&this.connection.permanentDisconnect(),i.type==="signal:offer"||i.type==="signal:answer"||i.type==="signal:ice"||i.type==="p2p:sync"?this._p2pInstance?this.routeP2PMessage(i):this.p2pEnabled&&this.signalingQueue.push(i):this._p2pInstance&&(i.type==="presence:join"?this._p2pInstance.onPeerJoined(i.user.user.id):i.type==="presence:leave"&&this._p2pInstance.onPeerLeft(i.userId))}),this.connection.on("state",i=>{this.emit("connection",i),i==="connected"?(this.authenticate(),this.startHeartbeat()):i==="disconnected"&&this.stopHeartbeat()})}get connectionState(){return this.connection.state}connect(){this.connection.connect()}disconnect(){this.stopHeartbeat(),this.cursorTimer&&(clearTimeout(this.cursorTimer),this.cursorTimer=null),this.connection.disconnect(),this.state.reset()}destroy(){var e;(e=this._p2pInstance)==null||e.destroy(),this._p2pInstance=null,this._p2p=null,this.disconnect(),this.removeAll(),this.state.removeAll(),this.connection.removeAll()}authenticate(){this.send({type:"auth",apiKey:this.config.apiKey,token:this.config.token,room:this.config.room})}send(e){this.connection.send(e)}moveCursor(e){const t=Date.now();this.pendingCursor=e,t-this.lastCursorSend>=c.CURSOR_THROTTLE_MS?this.flushCursor():this.cursorTimer||(this.cursorTimer=setTimeout(()=>{this.cursorTimer=null,this.flushCursor()},c.CURSOR_THROTTLE_MS))}flushCursor(){this.pendingCursor&&(this.send({type:"cursor:move",position:this.pendingCursor}),this.lastCursorSend=Date.now(),this.pendingCursor=null)}updatePresence(e){this.send({type:"presence:update",status:e})}startHeartbeat(){this.heartbeatTimer||(this.heartbeatTimer=setInterval(()=>{this.send({type:"presence:update",status:"online"})},c.PRESENCE_HEARTBEAT_MS))}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}createThread(e,t={}){const s=crypto.randomUUID();return this.send({type:"thread:create",id:s,body:e,mentions:t.mentions??[],position:t.position??null,attachmentIds:t.attachmentIds}),s}reply(e,t,s=[],i){const n=crypto.randomUUID();return this.send({type:"comment:create",threadId:e,id:n,body:t,mentions:s,attachmentIds:i}),n}editComment(e,t,s=[]){this.send({type:"comment:edit",commentId:e,body:t,mentions:s})}deleteComment(e){this.state.removeComment(e),this.send({type:"comment:delete",commentId:e})}resolveThread(e,t=!0){this.send({type:"thread:resolve",threadId:e,resolved:t})}addReaction(e,t,s){this.send({type:"reaction:add",targetId:e,targetType:t,emoji:s})}removeReaction(e){this.send({type:"reaction:remove",reactionId:e})}markRead(e){this.state.markNotificationRead(e),this.send({type:"notification:read",notificationId:e})}markAllRead(){this.state.markAllNotificationsRead(),this.send({type:"notification:read-all"})}performClick(e){this.send({type:"click:perform",position:e})}sendTyping(e){this.send({type:"typing:start",threadId:e})}updateViewport(e){this.send({type:"viewport:update",...e})}updateSelection(e){this.send({type:"selection:update",selection:e})}dropEmoji(e,t){this.send({type:"emoji:drop",emoji:e,position:t})}drawStroke(e,t,s){this.send({type:"draw:stroke",points:e,color:t,width:s})}clearDrawing(){this.send({type:"draw:clear"})}async uploadFile(e){const t=typeof window<"u"?window.location.origin:"",s=(this.config.endpoint??t).replace(/^ws(s?):/,"http$1:"),i=new FormData;i.append("file",e);const n=await fetch(`${s}/api/v1/upload`,{method:"POST",headers:{"X-Pulse-Key":this.config.apiKey,"X-Pulse-Token":this.config.token},body:i});if(!n.ok){const l=await n.json().catch(()=>({error:"Upload failed"}));throw new Error(l.error??"Upload failed")}const o=await n.json();return o.url&&!o.url.startsWith("http")&&(o.url=`${s}${o.url}`),o.thumbnailUrl&&!o.thumbnailUrl.startsWith("http")&&(o.thumbnailUrl=`${s}${o.thumbnailUrl}`),o}setAppearOffline(e){e?(this.stopHeartbeat(),this.send({type:"presence:update",status:"idle"})):(this.startHeartbeat(),this.send({type:"presence:update",status:"online"}))}get p2p(){return this.p2pEnabled?(this._p2p||(this._p2p=this.initP2P()),this._p2p):Promise.reject(new Error("P2P is not enabled. Pass { p2p: true } in PulseConfig."))}async initP2P(){const e=await new Promise((n,o)=>{if(this.state.user&&this.state.p2pConfig){n({userId:this.state.user.id,p2pConfig:this.state.p2pConfig});return}const l=this.state.on("auth",()=>{if(l(),!this.state.p2pConfig){o(new Error("P2P is not enabled for this environment."));return}n({userId:this.state.user.id,p2pConfig:this.state.p2pConfig})})});let t=this.iceServers;try{const n=await fetch(`${this.state.baseUrl}/api/v1/turn-credentials`,{headers:{Authorization:`Bearer ${this.config.token}`}});n.ok&&(t=(await n.json()).iceServers)}catch{}const{P2PManager:s}=await Promise.resolve().then(()=>require("./P2PManager-e4ShL60A.cjs")),i=new s({roomId:this.config.room,userId:e.userId,baseUrl:this.state.baseUrl,authToken:this.config.token,p2pConfig:e.p2pConfig,iceServers:t,sendWS:n=>this.send(n)});this._p2pInstance=i,await i.initialize();for(const n of this.state.presence)n.user.id!==e.userId&&i.onPeerJoined(n.user.id);for(const n of this.signalingQueue)this.routeP2PMessage(n);return this.signalingQueue=[],i}routeP2PMessage(e){this._p2pInstance&&(e.type==="signal:offer"?this._p2pInstance.handleSignalOffer(e.fromUserId,e.sdp):e.type==="signal:answer"?this._p2pInstance.handleSignalAnswer(e.fromUserId,e.sdp):e.type==="signal:ice"?this._p2pInstance.handleIceCandidate(e.fromUserId,e.candidate):e.type==="p2p:sync"&&this._p2pInstance.handleP2PSync(e.fromUserId,e.update))}}exports.Connection=p;exports.Emitter=d;exports.PulseClient=m;exports.StateManager=u;
@@ -1,7 +1,7 @@
1
1
  var u = Object.defineProperty;
2
2
  var f = (h, a, e) => a in h ? u(h, a, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[a] = e;
3
3
  var r = (h, a, e) => f(h, typeof a != "symbol" ? a + "" : a, e);
4
- import { DEFAULT_ENDPOINT as _, RECONNECT_BASE_DELAY_MS as m, RECONNECT_MAX_DELAY_MS as v, DEFAULT_ENV_CONFIG as d, CURSOR_THROTTLE_MS as p, PRESENCE_HEARTBEAT_MS as w } from "@gamention/pulse-shared";
4
+ import { DEFAULT_ENDPOINT as _, RECONNECT_BASE_DELAY_MS as m, RECONNECT_MAX_DELAY_MS as w, DEFAULT_ENV_CONFIG as d, CURSOR_THROTTLE_MS as p, PRESENCE_HEARTBEAT_MS as v } from "@gamention/pulse-shared";
5
5
  class l {
6
6
  constructor() {
7
7
  r(this, "handlers", /* @__PURE__ */ new Map());
@@ -17,7 +17,7 @@ class l {
17
17
  }
18
18
  emit(a, e) {
19
19
  var t;
20
- (t = this.handlers.get(a)) == null || t.forEach((i) => i(e));
20
+ (t = this.handlers.get(a)) == null || t.forEach((s) => s(e));
21
21
  }
22
22
  removeAll() {
23
23
  this.handlers.clear();
@@ -75,7 +75,7 @@ class y extends l {
75
75
  if (this.permanentlyClosed) return;
76
76
  const e = Math.min(
77
77
  m * 2 ** this.reconnectAttempt,
78
- v
78
+ w
79
79
  );
80
80
  this.reconnectAttempt++, this.reconnectTimer = setTimeout(() => {
81
81
  this.reconnectTimer = null, this.connect();
@@ -111,10 +111,10 @@ class I extends l {
111
111
  }
112
112
  /** Optimistically remove a comment from local state (before server round-trip). */
113
113
  removeComment(e) {
114
- for (const [t, i] of this._threads) {
115
- const s = i.comments.findIndex((n) => n.id === e);
116
- if (s !== -1) {
117
- i.comments.splice(s, 1), i.comments.length === 0 && this._threads.delete(t), this.emit("threads", this.threads);
114
+ for (const [t, s] of this._threads) {
115
+ const i = s.comments.findIndex((n) => n.id === e);
116
+ if (i !== -1) {
117
+ s.comments.splice(i, 1), s.comments.length === 0 && this._threads.delete(t), this.emit("threads", this.threads);
118
118
  return;
119
119
  }
120
120
  }
@@ -133,7 +133,7 @@ class I extends l {
133
133
  }
134
134
  /** Optimistically mark a single notification as read. */
135
135
  markNotificationRead(e) {
136
- const t = this._notifications.find((i) => i.id === e);
136
+ const t = this._notifications.find((s) => s.id === e);
137
137
  t && !t.read && (t.read = !0, this.emit("notifications", this._notifications));
138
138
  }
139
139
  /** Optimistically mark all notifications as read. */
@@ -159,10 +159,10 @@ class I extends l {
159
159
  getTypingUsers(e) {
160
160
  const t = this._typing.get(e);
161
161
  if (!t) return [];
162
- const i = Date.now(), s = [];
162
+ const s = Date.now(), i = [];
163
163
  for (const [n, o] of t)
164
- i - o < 3e3 && s.push(n);
165
- return s;
164
+ s - o < 3e3 && i.push(n);
165
+ return i;
166
166
  }
167
167
  get viewports() {
168
168
  return this._viewports;
@@ -205,8 +205,8 @@ class I extends l {
205
205
  for (const t of e.threads) this._threads.set(t.id, this.resolveThread(t));
206
206
  this._notifications = e.notifications, this._reactions.clear();
207
207
  for (const t of e.reactions) {
208
- const i = this._reactions.get(t.targetId) ?? [];
209
- i.push(t), this._reactions.set(t.targetId, i);
208
+ const s = this._reactions.get(t.targetId) ?? [];
209
+ s.push(t), this._reactions.set(t.targetId, s);
210
210
  }
211
211
  this._activityLogs = [...e.activityLogs], this.emit("auth", e.user), this.emit("presence", this.presence), this.emit("threads", this.threads), this.emit("notifications", this._notifications), this.emit("reactions", null), this.emit("activity-logs", this._activityLogs);
212
212
  break;
@@ -241,14 +241,14 @@ class I extends l {
241
241
  case "comment:edited": {
242
242
  const t = this._threads.get(e.threadId);
243
243
  if (t) {
244
- const i = t.comments.findIndex((s) => s.id === e.comment.id);
245
- i !== -1 && (t.comments[i] = this.resolveComment(e.comment)), this.emit("threads", this.threads);
244
+ const s = t.comments.findIndex((i) => i.id === e.comment.id);
245
+ s !== -1 && (t.comments[s] = this.resolveComment(e.comment)), this.emit("threads", this.threads);
246
246
  }
247
247
  break;
248
248
  }
249
249
  case "comment:deleted": {
250
250
  const t = this._threads.get(e.threadId);
251
- t && (t.comments = t.comments.filter((i) => i.id !== e.commentId), t.comments.length === 0 && this._threads.delete(e.threadId), this.emit("threads", this.threads));
251
+ t && (t.comments = t.comments.filter((s) => s.id !== e.commentId), t.comments.length === 0 && this._threads.delete(e.threadId), this.emit("threads", this.threads));
252
252
  break;
253
253
  }
254
254
  case "thread:resolved": {
@@ -270,10 +270,10 @@ class I extends l {
270
270
  case "reaction:removed": {
271
271
  const t = this._reactions.get(e.targetId);
272
272
  if (t) {
273
- const i = t.filter((s) => s.id !== e.reactionId);
274
- this._reactions.set(e.targetId, i), this.emit("reactions", {
273
+ const s = t.filter((i) => i.id !== e.reactionId);
274
+ this._reactions.set(e.targetId, s), this.emit("reactions", {
275
275
  targetId: e.targetId,
276
- reactions: i
276
+ reactions: s
277
277
  });
278
278
  }
279
279
  break;
@@ -332,9 +332,9 @@ class I extends l {
332
332
  this._user = null, this._config = { ...d }, this._p2pConfig = null, this._users.clear(), this._presence.clear(), this._threads.clear(), this._reactions.clear(), this._notifications = [], this._activityLogs = [], this._typing.clear(), this._viewports.clear(), this._selections.clear();
333
333
  }
334
334
  }
335
- class g extends l {
335
+ class C extends l {
336
336
  constructor(e) {
337
- var i;
337
+ var s;
338
338
  super();
339
339
  r(this, "state");
340
340
  r(this, "connection");
@@ -347,12 +347,13 @@ class g extends l {
347
347
  r(this, "_p2pInstance", null);
348
348
  r(this, "p2pEnabled");
349
349
  r(this, "iceServers");
350
+ r(this, "signalingQueue", []);
350
351
  this.config = e, this.state = new I(), this.state.baseUrl = (e.endpoint ?? "").replace(/^ws(s?):/, "http$1:").replace(/\/$/, "");
351
- const t = ((i = e.endpoint) == null ? void 0 : i.replace(/^http/, "ws")) ?? void 0;
352
- this.connection = new y(t), this.p2pEnabled = e.p2p ?? !1, this.iceServers = e.iceServers ?? [{ urls: "stun:stun.l.google.com:19302" }], this.connection.on("message", (s) => {
353
- this.state.handleMessage(s), this.emit(s.type, s), s.type === "auth:error" && this.connection.permanentDisconnect(), this._p2pInstance && (s.type === "signal:offer" ? this._p2pInstance.handleSignalOffer(s.fromUserId, s.sdp) : s.type === "signal:answer" ? this._p2pInstance.handleSignalAnswer(s.fromUserId, s.sdp) : s.type === "signal:ice" ? this._p2pInstance.handleIceCandidate(s.fromUserId, s.candidate) : s.type === "p2p:sync" ? this._p2pInstance.handleP2PSync(s.fromUserId, s.update) : s.type === "presence:join" ? this._p2pInstance.onPeerJoined(s.user.user.id) : s.type === "presence:leave" && this._p2pInstance.onPeerLeft(s.userId));
354
- }), this.connection.on("state", (s) => {
355
- this.emit("connection", s), s === "connected" ? (this.authenticate(), this.startHeartbeat()) : s === "disconnected" && this.stopHeartbeat();
352
+ const t = ((s = e.endpoint) == null ? void 0 : s.replace(/^http/, "ws")) ?? void 0;
353
+ this.connection = new y(t), this.p2pEnabled = e.p2p ?? !1, this.iceServers = e.iceServers ?? [{ urls: "stun:stun.l.google.com:19302" }], this.connection.on("message", (i) => {
354
+ this.state.handleMessage(i), this.emit(i.type, i), i.type === "auth:error" && this.connection.permanentDisconnect(), i.type === "signal:offer" || i.type === "signal:answer" || i.type === "signal:ice" || i.type === "p2p:sync" ? this._p2pInstance ? this.routeP2PMessage(i) : this.p2pEnabled && this.signalingQueue.push(i) : this._p2pInstance && (i.type === "presence:join" ? this._p2pInstance.onPeerJoined(i.user.user.id) : i.type === "presence:leave" && this._p2pInstance.onPeerLeft(i.userId));
355
+ }), this.connection.on("state", (i) => {
356
+ this.emit("connection", i), i === "connected" ? (this.authenticate(), this.startHeartbeat()) : i === "disconnected" && this.stopHeartbeat();
356
357
  });
357
358
  }
358
359
  /** Current WebSocket connection state. */
@@ -397,29 +398,29 @@ class g extends l {
397
398
  startHeartbeat() {
398
399
  this.heartbeatTimer || (this.heartbeatTimer = setInterval(() => {
399
400
  this.send({ type: "presence:update", status: "online" });
400
- }, w));
401
+ }, v));
401
402
  }
402
403
  stopHeartbeat() {
403
404
  this.heartbeatTimer && (clearInterval(this.heartbeatTimer), this.heartbeatTimer = null);
404
405
  }
405
406
  // ── Threads & Comments ──
406
407
  createThread(e, t = {}) {
407
- const i = crypto.randomUUID();
408
+ const s = crypto.randomUUID();
408
409
  return this.send({
409
410
  type: "thread:create",
410
- id: i,
411
+ id: s,
411
412
  body: e,
412
413
  mentions: t.mentions ?? [],
413
414
  position: t.position ?? null,
414
415
  attachmentIds: t.attachmentIds
415
- }), i;
416
+ }), s;
416
417
  }
417
- reply(e, t, i = [], s) {
418
+ reply(e, t, s = [], i) {
418
419
  const n = crypto.randomUUID();
419
- return this.send({ type: "comment:create", threadId: e, id: n, body: t, mentions: i, attachmentIds: s }), n;
420
+ return this.send({ type: "comment:create", threadId: e, id: n, body: t, mentions: s, attachmentIds: i }), n;
420
421
  }
421
- editComment(e, t, i = []) {
422
- this.send({ type: "comment:edit", commentId: e, body: t, mentions: i });
422
+ editComment(e, t, s = []) {
423
+ this.send({ type: "comment:edit", commentId: e, body: t, mentions: s });
423
424
  }
424
425
  deleteComment(e) {
425
426
  this.state.removeComment(e), this.send({ type: "comment:delete", commentId: e });
@@ -428,8 +429,8 @@ class g extends l {
428
429
  this.send({ type: "thread:resolve", threadId: e, resolved: t });
429
430
  }
430
431
  // ── Reactions ──
431
- addReaction(e, t, i) {
432
- this.send({ type: "reaction:add", targetId: e, targetType: t, emoji: i });
432
+ addReaction(e, t, s) {
433
+ this.send({ type: "reaction:add", targetId: e, targetType: t, emoji: s });
433
434
  }
434
435
  removeReaction(e) {
435
436
  this.send({ type: "reaction:remove", reactionId: e });
@@ -462,30 +463,30 @@ class g extends l {
462
463
  this.send({ type: "emoji:drop", emoji: e, position: t });
463
464
  }
464
465
  // ── Drawing ──
465
- drawStroke(e, t, i) {
466
- this.send({ type: "draw:stroke", points: e, color: t, width: i });
466
+ drawStroke(e, t, s) {
467
+ this.send({ type: "draw:stroke", points: e, color: t, width: s });
467
468
  }
468
469
  clearDrawing() {
469
470
  this.send({ type: "draw:clear" });
470
471
  }
471
472
  // ── File Upload ──
472
473
  async uploadFile(e) {
473
- const t = typeof window < "u" ? window.location.origin : "", i = (this.config.endpoint ?? t).replace(/^ws(s?):/, "http$1:"), s = new FormData();
474
- s.append("file", e);
475
- const n = await fetch(`${i}/api/v1/upload`, {
474
+ const t = typeof window < "u" ? window.location.origin : "", s = (this.config.endpoint ?? t).replace(/^ws(s?):/, "http$1:"), i = new FormData();
475
+ i.append("file", e);
476
+ const n = await fetch(`${s}/api/v1/upload`, {
476
477
  method: "POST",
477
478
  headers: {
478
479
  "X-Pulse-Key": this.config.apiKey,
479
480
  "X-Pulse-Token": this.config.token
480
481
  },
481
- body: s
482
+ body: i
482
483
  });
483
484
  if (!n.ok) {
484
485
  const c = await n.json().catch(() => ({ error: "Upload failed" }));
485
486
  throw new Error(c.error ?? "Upload failed");
486
487
  }
487
488
  const o = await n.json();
488
- return o.url && !o.url.startsWith("http") && (o.url = `${i}${o.url}`), o.thumbnailUrl && !o.thumbnailUrl.startsWith("http") && (o.thumbnailUrl = `${i}${o.thumbnailUrl}`), o;
489
+ return o.url && !o.url.startsWith("http") && (o.url = `${s}${o.url}`), o.thumbnailUrl && !o.thumbnailUrl.startsWith("http") && (o.thumbnailUrl = `${s}${o.thumbnailUrl}`), o;
489
490
  }
490
491
  // ── Presence control ──
491
492
  setAppearOffline(e) {
@@ -526,7 +527,7 @@ class g extends l {
526
527
  n.ok && (t = (await n.json()).iceServers);
527
528
  } catch {
528
529
  }
529
- const { P2PManager: i } = await import("./P2PManager-CwR8sbb_.js"), s = new i({
530
+ const { P2PManager: s } = await import("./P2PManager-CwR8sbb_.js"), i = new s({
530
531
  roomId: this.config.room,
531
532
  userId: e.userId,
532
533
  baseUrl: this.state.baseUrl,
@@ -535,15 +536,20 @@ class g extends l {
535
536
  iceServers: t,
536
537
  sendWS: (n) => this.send(n)
537
538
  });
538
- this._p2pInstance = s, await s.initialize();
539
+ this._p2pInstance = i, await i.initialize();
539
540
  for (const n of this.state.presence)
540
- n.user.id !== e.userId && s.onPeerJoined(n.user.id);
541
- return s;
541
+ n.user.id !== e.userId && i.onPeerJoined(n.user.id);
542
+ for (const n of this.signalingQueue)
543
+ this.routeP2PMessage(n);
544
+ return this.signalingQueue = [], i;
545
+ }
546
+ routeP2PMessage(e) {
547
+ this._p2pInstance && (e.type === "signal:offer" ? this._p2pInstance.handleSignalOffer(e.fromUserId, e.sdp) : e.type === "signal:answer" ? this._p2pInstance.handleSignalAnswer(e.fromUserId, e.sdp) : e.type === "signal:ice" ? this._p2pInstance.handleIceCandidate(e.fromUserId, e.candidate) : e.type === "p2p:sync" && this._p2pInstance.handleP2PSync(e.fromUserId, e.update));
542
548
  }
543
549
  }
544
550
  export {
545
551
  y as Connection,
546
552
  l as Emitter,
547
- g as PulseClient,
553
+ C as PulseClient,
548
554
  I as StateManager
549
555
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamention/pulse-core",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Core client SDK for Pulse — WebSocket connection, state management, and API for real-time collaboration",
5
5
  "type": "module",
6
6
  "main": "./dist/pulse-core.cjs",