@gamention/pulse-core 0.1.10 → 0.1.12

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
@@ -92,6 +92,8 @@ export declare class PulseClient extends Emitter {
92
92
  }
93
93
 
94
94
  export declare class StateManager extends Emitter {
95
+ /** HTTP base URL for resolving relative attachment paths (e.g. "http://localhost:4000"). */
96
+ baseUrl: string;
95
97
  private _user;
96
98
  private _users;
97
99
  private _presence;
@@ -103,6 +105,8 @@ export declare class StateManager extends Emitter {
103
105
  private _viewports;
104
106
  private _selections;
105
107
  get user(): PulseUser | null;
108
+ /** Optimistically remove a comment from local state (before server round-trip). */
109
+ removeComment(commentId: string): void;
106
110
  get presence(): PresenceUser[];
107
111
  get threads(): Thread[];
108
112
  get notifications(): Notification_2[];
@@ -114,6 +118,11 @@ export declare class StateManager extends Emitter {
114
118
  get viewports(): Map<string, ViewportInfo>;
115
119
  getViewport(userId: string): ViewportInfo | undefined;
116
120
  get selections(): Map<string, SelectionRange | null>;
121
+ /** Resolve a relative URL to absolute using baseUrl. */
122
+ private resolveUrl;
123
+ private resolveAttachments;
124
+ private resolveComment;
125
+ private resolveThread;
117
126
  handleMessage(msg: ServerMessage): void;
118
127
  reset(): void;
119
128
  }
@@ -1 +1 @@
1
- "use strict";var u=Object.defineProperty;var _=(a,n,e)=>n in a?u(a,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[n]=e;var i=(a,n,e)=>_(a,typeof n!="symbol"?n+"":n,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f="ws://localhost:4567",h=50,w=3e4,y=1e3,m=3e4;class c{constructor(){i(this,"handlers",new Map)}on(n,e){this.handlers.has(n)||this.handlers.set(n,new Set);const t=this.handlers.get(n);return t.add(e),()=>t.delete(e)}off(n,e){var t;(t=this.handlers.get(n))==null||t.delete(e)}emit(n,e){var t;(t=this.handlers.get(n))==null||t.forEach(s=>s(e))}removeAll(){this.handlers.clear()}}class d extends c{constructor(e){super();i(this,"ws",null);i(this,"endpoint");i(this,"reconnectAttempt",0);i(this,"reconnectTimer",null);i(this,"_state","disconnected");this.endpoint=e??f}get state(){return this._state}connect(){this.ws||(this._state="connecting",this.emit("state",this._state),this.ws=new WebSocket(this.endpoint),this.ws.addEventListener("open",()=>{this._state="connected",this.reconnectAttempt=0,this.emit("state",this._state)}),this.ws.addEventListener("message",e=>{const t=JSON.parse(e.data);this.emit("message",t)}),this.ws.addEventListener("close",()=>{this.ws=null,this._state="disconnected",this.emit("state",this._state),this.scheduleReconnect()}),this.ws.addEventListener("error",()=>{var e;(e=this.ws)==null||e.close()}))}disconnect(){var e;this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.reconnectAttempt=0,(e=this.ws)==null||e.close(),this.ws=null,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))}scheduleReconnect(){const e=Math.min(y*2**this.reconnectAttempt,m);this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},e)}}class p extends c{constructor(){super(...arguments);i(this,"_user",null);i(this,"_users",new Map);i(this,"_presence",new Map);i(this,"_threads",new Map);i(this,"_reactions",new Map);i(this,"_notifications",[]);i(this,"_activityLogs",[]);i(this,"_typing",new Map);i(this,"_viewports",new Map);i(this,"_selections",new Map)}get user(){return this._user}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}get activityLogs(){return this._activityLogs}getUser(e){return this._users.get(e)}getReactions(e){return this._reactions.get(e)??[]}getTypingUsers(e){const t=this._typing.get(e);if(!t)return[];const s=Date.now(),r=[];for(const[o,l]of t)s-l<3e3&&r.push(o);return r}get viewports(){return this._viewports}getViewport(e){return this._viewports.get(e)}get selections(){return this._selections}handleMessage(e){switch(e.type){case"auth:ok":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,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,e.thread),this.emit("threads",this.threads);break;case"comment:created":{const t=this._threads.get(e.threadId);t&&(t.comments.push(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(r=>r.id===e.comment.id);s!==-1&&(t.comments[s]=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(r=>r.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"error":this.emit("error",e);break}}reset(){this._user=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 I extends c{constructor(e){var s;super();i(this,"state");i(this,"connection");i(this,"config");i(this,"heartbeatTimer",null);i(this,"lastCursorSend",0);i(this,"pendingCursor",null);i(this,"cursorTimer",null);this.config=e,this.state=new p;const t=((s=e.endpoint)==null?void 0:s.replace(/^http/,"ws"))??void 0;this.connection=new d(t),this.connection.on("message",r=>{this.state.handleMessage(r),this.emit(r.type,r)}),this.connection.on("state",r=>{this.emit("connection",r),r==="connected"?(this.authenticate(),this.startHeartbeat()):r==="disconnected"&&this.stopHeartbeat()})}get connectionState(){return this.connection.state}connect(){this.connection.connect()}disconnect(){this.stopHeartbeat(),this.connection.disconnect(),this.state.reset()}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>=h?this.flushCursor():this.cursorTimer||(this.cursorTimer=setTimeout(()=>{this.cursorTimer=null,this.flushCursor()},h))}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=setInterval(()=>{this.send({type:"presence:update",status:"online"})},w)}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=[],r){const o=crypto.randomUUID();return this.send({type:"comment:create",threadId:e,id:o,body:t,mentions:s,attachmentIds:r}),o}editComment(e,t,s=[]){this.send({type:"comment:edit",commentId:e,body:t,mentions:s})}deleteComment(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.send({type:"notification:read",notificationId:e})}markAllRead(){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=(this.config.endpoint??window.location.origin).replace(/^ws(s?):/,"http$1:"),s=new FormData;s.append("file",e);const r=await fetch(`${t}/api/v1/upload`,{method:"POST",headers:{"X-Pulse-Key":this.config.apiKey,"X-Pulse-Token":this.config.token},body:s});if(!r.ok){const o=await r.json().catch(()=>({error:"Upload failed"}));throw new Error(o.error??"Upload failed")}return r.json()}setAppearOffline(e){e?(this.stopHeartbeat(),this.send({type:"presence:update",status:"idle"})):(this.startHeartbeat(),this.send({type:"presence:update",status:"online"}))}}exports.Connection=d;exports.Emitter=c;exports.PulseClient=I;exports.StateManager=p;
1
+ "use strict";var u=Object.defineProperty;var _=(o,n,t)=>n in o?u(o,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[n]=t;var i=(o,n,t)=>_(o,typeof n!="symbol"?n+"":n,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m="ws://localhost:4567",d=50,f=3e4,w=1e3,v=3e4;class h{constructor(){i(this,"handlers",new Map)}on(n,t){this.handlers.has(n)||this.handlers.set(n,new Set);const e=this.handlers.get(n);return e.add(t),()=>e.delete(t)}off(n,t){var e;(e=this.handlers.get(n))==null||e.delete(t)}emit(n,t){var e;(e=this.handlers.get(n))==null||e.forEach(s=>s(t))}removeAll(){this.handlers.clear()}}class l extends h{constructor(t){super();i(this,"ws",null);i(this,"endpoint");i(this,"reconnectAttempt",0);i(this,"reconnectTimer",null);i(this,"_state","disconnected");this.endpoint=t??m}get state(){return this._state}connect(){this.ws||(this._state="connecting",this.emit("state",this._state),this.ws=new WebSocket(this.endpoint),this.ws.addEventListener("open",()=>{this._state="connected",this.reconnectAttempt=0,this.emit("state",this._state)}),this.ws.addEventListener("message",t=>{const e=JSON.parse(t.data);this.emit("message",e)}),this.ws.addEventListener("close",()=>{this.ws=null,this._state="disconnected",this.emit("state",this._state),this.scheduleReconnect()}),this.ws.addEventListener("error",()=>{var t;(t=this.ws)==null||t.close()}))}disconnect(){var t;this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.reconnectAttempt=0,(t=this.ws)==null||t.close(),this.ws=null,this._state="disconnected",this.emit("state",this._state)}send(t){var e;((e=this.ws)==null?void 0:e.readyState)===WebSocket.OPEN&&this.ws.send(JSON.stringify(t))}scheduleReconnect(){const t=Math.min(w*2**this.reconnectAttempt,v);this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},t)}}class p extends h{constructor(){super(...arguments);i(this,"baseUrl","");i(this,"_user",null);i(this,"_users",new Map);i(this,"_presence",new Map);i(this,"_threads",new Map);i(this,"_reactions",new Map);i(this,"_notifications",[]);i(this,"_activityLogs",[]);i(this,"_typing",new Map);i(this,"_viewports",new Map);i(this,"_selections",new Map)}get user(){return this._user}removeComment(t){for(const[e,s]of this._threads){const r=s.comments.findIndex(a=>a.id===t);if(r!==-1){s.comments.splice(r,1),s.comments.length===0&&this._threads.delete(e),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(t=>!t.read).length}get activityLogs(){return this._activityLogs}getUser(t){return this._users.get(t)}getReactions(t){return this._reactions.get(t)??[]}getTypingUsers(t){const e=this._typing.get(t);if(!e)return[];const s=Date.now(),r=[];for(const[a,c]of e)s-c<3e3&&r.push(a);return r}get viewports(){return this._viewports}getViewport(t){return this._viewports.get(t)}get selections(){return this._selections}resolveUrl(t){return!this.baseUrl||!t||t.startsWith("http://")||t.startsWith("https://")?t:`${this.baseUrl}${t}`}resolveAttachments(t){return t.map(e=>({...e,url:this.resolveUrl(e.url),thumbnailUrl:e.thumbnailUrl?this.resolveUrl(e.thumbnailUrl):void 0}))}resolveComment(t){return!t.attachments||t.attachments.length===0?t:{...t,attachments:this.resolveAttachments(t.attachments)}}resolveThread(t){return{...t,comments:t.comments.map(e=>this.resolveComment(e))}}handleMessage(t){switch(t.type){case"auth:ok":this._user=t.user,this._users.clear();for(const e of t.users)this._users.set(e.id,e);this._presence.clear();for(const e of t.presence)this._presence.set(e.user.id,e),this._users.set(e.user.id,e.user);this._users.set(t.user.id,t.user),this._threads.clear();for(const e of t.threads)this._threads.set(e.id,this.resolveThread(e));this._notifications=t.notifications,this._reactions.clear();for(const e of t.reactions){const s=this._reactions.get(e.targetId)??[];s.push(e),this._reactions.set(e.targetId,s)}this._activityLogs=[...t.activityLogs],this.emit("auth",t.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(t.user.user.id,t.user),this._users.set(t.user.user.id,t.user.user),this.emit("presence",this.presence);break;case"presence:leave":this._presence.delete(t.userId),this._viewports.delete(t.userId),this._selections.delete(t.userId);for(const e of this._typing.values())e.delete(t.userId);this.emit("presence",this.presence);break;case"presence:update":{const e=this._presence.get(t.userId);e&&(e.status=t.status,this.emit("presence",this.presence));break}case"cursor:move":this.emit("cursor",{userId:t.userId,position:t.position});break;case"click:perform":this.emit("click",{userId:t.userId,position:t.position});break;case"thread:created":this._threads.set(t.thread.id,this.resolveThread(t.thread)),this.emit("threads",this.threads);break;case"comment:created":{const e=this._threads.get(t.threadId);e&&(e.comments.push(this.resolveComment(t.comment)),e.updatedAt=t.comment.createdAt,this.emit("threads",this.threads));break}case"comment:edited":{const e=this._threads.get(t.threadId);if(e){const s=e.comments.findIndex(r=>r.id===t.comment.id);s!==-1&&(e.comments[s]=this.resolveComment(t.comment)),this.emit("threads",this.threads)}break}case"comment:deleted":{const e=this._threads.get(t.threadId);e&&(e.comments=e.comments.filter(s=>s.id!==t.commentId),e.comments.length===0&&this._threads.delete(t.threadId),this.emit("threads",this.threads));break}case"thread:resolved":{const e=this._threads.get(t.threadId);e&&(e.resolved=t.resolved,this.emit("threads",this.threads));break}case"thread:deleted":this._threads.delete(t.threadId),this.emit("threads",this.threads);break;case"reaction:added":{const e=this._reactions.get(t.reaction.targetId)??[];e.push(t.reaction),this._reactions.set(t.reaction.targetId,e),this.emit("reactions",{targetId:t.reaction.targetId,reactions:e});break}case"reaction:removed":{const e=this._reactions.get(t.targetId);if(e){const s=e.filter(r=>r.id!==t.reactionId);this._reactions.set(t.targetId,s),this.emit("reactions",{targetId:t.targetId,reactions:s})}break}case"notification":this._notifications.unshift(t.notification),this.emit("notifications",this._notifications);break;case"typing:indicator":{this._typing.has(t.threadId)||this._typing.set(t.threadId,new Map),this._typing.get(t.threadId).set(t.userId,Date.now()),this.emit("typing",{threadId:t.threadId,userId:t.userId});break}case"viewport:update":{this._viewports.set(t.userId,{scrollX:t.scrollX,scrollY:t.scrollY,viewportWidth:t.viewportWidth,viewportHeight:t.viewportHeight,pageWidth:t.pageWidth,pageHeight:t.pageHeight}),this.emit("viewport",{userId:t.userId});break}case"selection:update":this._selections.set(t.userId,t.selection),this.emit("selection",{userId:t.userId,selection:t.selection});break;case"emoji:drop":this.emit("emoji-drop",{userId:t.userId,emoji:t.emoji,position:t.position});break;case"draw:stroke":this.emit("draw-stroke",{userId:t.userId,points:t.points,color:t.color,width:t.width});break;case"draw:clear":this.emit("draw-clear",{userId:t.userId});break;case"activity:logged":this._activityLogs.unshift(t.activityLog),this._activityLogs.length>100&&(this._activityLogs=this._activityLogs.slice(0,100)),this.emit("activity-logs",this._activityLogs);break;case"error":this.emit("error",t);break}}reset(){this._user=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 y extends h{constructor(t){var s;super();i(this,"state");i(this,"connection");i(this,"config");i(this,"heartbeatTimer",null);i(this,"lastCursorSend",0);i(this,"pendingCursor",null);i(this,"cursorTimer",null);this.config=t,this.state=new p,this.state.baseUrl=(t.endpoint??"").replace(/^ws(s?):/,"http$1:").replace(/\/$/,"");const e=((s=t.endpoint)==null?void 0:s.replace(/^http/,"ws"))??void 0;this.connection=new l(e),this.connection.on("message",r=>{this.state.handleMessage(r),this.emit(r.type,r)}),this.connection.on("state",r=>{this.emit("connection",r),r==="connected"?(this.authenticate(),this.startHeartbeat()):r==="disconnected"&&this.stopHeartbeat()})}get connectionState(){return this.connection.state}connect(){this.connection.connect()}disconnect(){this.stopHeartbeat(),this.connection.disconnect(),this.state.reset()}authenticate(){this.send({type:"auth",apiKey:this.config.apiKey,token:this.config.token,room:this.config.room})}send(t){this.connection.send(t)}moveCursor(t){const e=Date.now();this.pendingCursor=t,e-this.lastCursorSend>=d?this.flushCursor():this.cursorTimer||(this.cursorTimer=setTimeout(()=>{this.cursorTimer=null,this.flushCursor()},d))}flushCursor(){this.pendingCursor&&(this.send({type:"cursor:move",position:this.pendingCursor}),this.lastCursorSend=Date.now(),this.pendingCursor=null)}updatePresence(t){this.send({type:"presence:update",status:t})}startHeartbeat(){this.heartbeatTimer=setInterval(()=>{this.send({type:"presence:update",status:"online"})},f)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}createThread(t,e={}){const s=crypto.randomUUID();return this.send({type:"thread:create",id:s,body:t,mentions:e.mentions??[],position:e.position??null,attachmentIds:e.attachmentIds}),s}reply(t,e,s=[],r){const a=crypto.randomUUID();return this.send({type:"comment:create",threadId:t,id:a,body:e,mentions:s,attachmentIds:r}),a}editComment(t,e,s=[]){this.send({type:"comment:edit",commentId:t,body:e,mentions:s})}deleteComment(t){this.state.removeComment(t),this.send({type:"comment:delete",commentId:t})}resolveThread(t,e=!0){this.send({type:"thread:resolve",threadId:t,resolved:e})}addReaction(t,e,s){this.send({type:"reaction:add",targetId:t,targetType:e,emoji:s})}removeReaction(t){this.send({type:"reaction:remove",reactionId:t})}markRead(t){this.send({type:"notification:read",notificationId:t})}markAllRead(){this.send({type:"notification:read-all"})}performClick(t){this.send({type:"click:perform",position:t})}sendTyping(t){this.send({type:"typing:start",threadId:t})}updateViewport(t){this.send({type:"viewport:update",...t})}updateSelection(t){this.send({type:"selection:update",selection:t})}dropEmoji(t,e){this.send({type:"emoji:drop",emoji:t,position:e})}drawStroke(t,e,s){this.send({type:"draw:stroke",points:t,color:e,width:s})}clearDrawing(){this.send({type:"draw:clear"})}async uploadFile(t){const e=(this.config.endpoint??window.location.origin).replace(/^ws(s?):/,"http$1:"),s=new FormData;s.append("file",t);const r=await fetch(`${e}/api/v1/upload`,{method:"POST",headers:{"X-Pulse-Key":this.config.apiKey,"X-Pulse-Token":this.config.token},body:s});if(!r.ok){const c=await r.json().catch(()=>({error:"Upload failed"}));throw new Error(c.error??"Upload failed")}const a=await r.json();return a.url&&!a.url.startsWith("http")&&(a.url=`${e}${a.url}`),a.thumbnailUrl&&!a.thumbnailUrl.startsWith("http")&&(a.thumbnailUrl=`${e}${a.thumbnailUrl}`),a}setAppearOffline(t){t?(this.stopHeartbeat(),this.send({type:"presence:update",status:"idle"})):(this.startHeartbeat(),this.send({type:"presence:update",status:"online"}))}}exports.Connection=l;exports.Emitter=h;exports.PulseClient=y;exports.StateManager=p;
@@ -1,37 +1,37 @@
1
1
  var d = Object.defineProperty;
2
- var p = (a, n, e) => n in a ? d(a, n, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[n] = e;
3
- var i = (a, n, e) => p(a, typeof n != "symbol" ? n + "" : n, e);
4
- const l = "ws://localhost:4567";
2
+ var l = (o, n, t) => n in o ? d(o, n, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[n] = t;
3
+ var i = (o, n, t) => l(o, typeof n != "symbol" ? n + "" : n, t);
4
+ const p = "ws://localhost:4567";
5
5
  class c {
6
6
  constructor() {
7
7
  i(this, "handlers", /* @__PURE__ */ new Map());
8
8
  }
9
- on(n, e) {
9
+ on(n, t) {
10
10
  this.handlers.has(n) || this.handlers.set(n, /* @__PURE__ */ new Set());
11
- const t = this.handlers.get(n);
12
- return t.add(e), () => t.delete(e);
11
+ const e = this.handlers.get(n);
12
+ return e.add(t), () => e.delete(t);
13
13
  }
14
- off(n, e) {
15
- var t;
16
- (t = this.handlers.get(n)) == null || t.delete(e);
14
+ off(n, t) {
15
+ var e;
16
+ (e = this.handlers.get(n)) == null || e.delete(t);
17
17
  }
18
- emit(n, e) {
19
- var t;
20
- (t = this.handlers.get(n)) == null || t.forEach((s) => s(e));
18
+ emit(n, t) {
19
+ var e;
20
+ (e = this.handlers.get(n)) == null || e.forEach((s) => s(t));
21
21
  }
22
22
  removeAll() {
23
23
  this.handlers.clear();
24
24
  }
25
25
  }
26
26
  class u extends c {
27
- constructor(e) {
27
+ constructor(t) {
28
28
  super();
29
29
  i(this, "ws", null);
30
30
  i(this, "endpoint");
31
31
  i(this, "reconnectAttempt", 0);
32
32
  i(this, "reconnectTimer", null);
33
33
  i(this, "_state", "disconnected");
34
- this.endpoint = e ?? l;
34
+ this.endpoint = t ?? p;
35
35
  }
36
36
  get state() {
37
37
  return this._state;
@@ -39,37 +39,39 @@ class u extends c {
39
39
  connect() {
40
40
  this.ws || (this._state = "connecting", this.emit("state", this._state), this.ws = new WebSocket(this.endpoint), this.ws.addEventListener("open", () => {
41
41
  this._state = "connected", this.reconnectAttempt = 0, this.emit("state", this._state);
42
- }), this.ws.addEventListener("message", (e) => {
43
- const t = JSON.parse(e.data);
44
- this.emit("message", t);
42
+ }), this.ws.addEventListener("message", (t) => {
43
+ const e = JSON.parse(t.data);
44
+ this.emit("message", e);
45
45
  }), this.ws.addEventListener("close", () => {
46
46
  this.ws = null, this._state = "disconnected", this.emit("state", this._state), this.scheduleReconnect();
47
47
  }), this.ws.addEventListener("error", () => {
48
- var e;
49
- (e = this.ws) == null || e.close();
48
+ var t;
49
+ (t = this.ws) == null || t.close();
50
50
  }));
51
51
  }
52
52
  disconnect() {
53
- var e;
54
- this.reconnectTimer && (clearTimeout(this.reconnectTimer), this.reconnectTimer = null), this.reconnectAttempt = 0, (e = this.ws) == null || e.close(), this.ws = null, this._state = "disconnected", this.emit("state", this._state);
55
- }
56
- send(e) {
57
53
  var t;
58
- ((t = this.ws) == null ? void 0 : t.readyState) === WebSocket.OPEN && this.ws.send(JSON.stringify(e));
54
+ this.reconnectTimer && (clearTimeout(this.reconnectTimer), this.reconnectTimer = null), this.reconnectAttempt = 0, (t = this.ws) == null || t.close(), this.ws = null, this._state = "disconnected", this.emit("state", this._state);
55
+ }
56
+ send(t) {
57
+ var e;
58
+ ((e = this.ws) == null ? void 0 : e.readyState) === WebSocket.OPEN && this.ws.send(JSON.stringify(t));
59
59
  }
60
60
  scheduleReconnect() {
61
- const e = Math.min(
61
+ const t = Math.min(
62
62
  1e3 * 2 ** this.reconnectAttempt,
63
63
  3e4
64
64
  );
65
65
  this.reconnectAttempt++, this.reconnectTimer = setTimeout(() => {
66
66
  this.reconnectTimer = null, this.connect();
67
- }, e);
67
+ }, t);
68
68
  }
69
69
  }
70
70
  class _ extends c {
71
71
  constructor() {
72
72
  super(...arguments);
73
+ /** HTTP base URL for resolving relative attachment paths (e.g. "http://localhost:4000"). */
74
+ i(this, "baseUrl", "");
73
75
  i(this, "_user", null);
74
76
  i(this, "_users", /* @__PURE__ */ new Map());
75
77
  i(this, "_presence", /* @__PURE__ */ new Map());
@@ -84,6 +86,16 @@ class _ extends c {
84
86
  get user() {
85
87
  return this._user;
86
88
  }
89
+ /** Optimistically remove a comment from local state (before server round-trip). */
90
+ removeComment(t) {
91
+ for (const [e, s] of this._threads) {
92
+ const r = s.comments.findIndex((a) => a.id === t);
93
+ if (r !== -1) {
94
+ s.comments.splice(r, 1), s.comments.length === 0 && this._threads.delete(e), this.emit("threads", this.threads);
95
+ return;
96
+ }
97
+ }
98
+ }
87
99
  get presence() {
88
100
  return [...this._presence.values()];
89
101
  }
@@ -94,163 +106,183 @@ class _ extends c {
94
106
  return this._notifications;
95
107
  }
96
108
  get unreadCount() {
97
- return this._notifications.filter((e) => !e.read).length;
109
+ return this._notifications.filter((t) => !t.read).length;
98
110
  }
99
111
  get activityLogs() {
100
112
  return this._activityLogs;
101
113
  }
102
- getUser(e) {
103
- return this._users.get(e);
114
+ getUser(t) {
115
+ return this._users.get(t);
104
116
  }
105
- getReactions(e) {
106
- return this._reactions.get(e) ?? [];
117
+ getReactions(t) {
118
+ return this._reactions.get(t) ?? [];
107
119
  }
108
- getTypingUsers(e) {
109
- const t = this._typing.get(e);
110
- if (!t) return [];
120
+ getTypingUsers(t) {
121
+ const e = this._typing.get(t);
122
+ if (!e) return [];
111
123
  const s = Date.now(), r = [];
112
- for (const [o, h] of t)
113
- s - h < 3e3 && r.push(o);
124
+ for (const [a, h] of e)
125
+ s - h < 3e3 && r.push(a);
114
126
  return r;
115
127
  }
116
128
  get viewports() {
117
129
  return this._viewports;
118
130
  }
119
- getViewport(e) {
120
- return this._viewports.get(e);
131
+ getViewport(t) {
132
+ return this._viewports.get(t);
121
133
  }
122
134
  get selections() {
123
135
  return this._selections;
124
136
  }
125
- handleMessage(e) {
126
- switch (e.type) {
137
+ /** Resolve a relative URL to absolute using baseUrl. */
138
+ resolveUrl(t) {
139
+ return !this.baseUrl || !t || t.startsWith("http://") || t.startsWith("https://") ? t : `${this.baseUrl}${t}`;
140
+ }
141
+ resolveAttachments(t) {
142
+ return t.map((e) => ({
143
+ ...e,
144
+ url: this.resolveUrl(e.url),
145
+ thumbnailUrl: e.thumbnailUrl ? this.resolveUrl(e.thumbnailUrl) : void 0
146
+ }));
147
+ }
148
+ resolveComment(t) {
149
+ return !t.attachments || t.attachments.length === 0 ? t : { ...t, attachments: this.resolveAttachments(t.attachments) };
150
+ }
151
+ resolveThread(t) {
152
+ return {
153
+ ...t,
154
+ comments: t.comments.map((e) => this.resolveComment(e))
155
+ };
156
+ }
157
+ handleMessage(t) {
158
+ switch (t.type) {
127
159
  case "auth:ok":
128
- this._user = e.user, this._users.clear();
129
- for (const t of e.users) this._users.set(t.id, t);
160
+ this._user = t.user, this._users.clear();
161
+ for (const e of t.users) this._users.set(e.id, e);
130
162
  this._presence.clear();
131
- for (const t of e.presence)
132
- this._presence.set(t.user.id, t), this._users.set(t.user.id, t.user);
133
- this._users.set(e.user.id, e.user), this._threads.clear();
134
- for (const t of e.threads) this._threads.set(t.id, t);
135
- this._notifications = e.notifications, this._reactions.clear();
136
- for (const t of e.reactions) {
137
- const s = this._reactions.get(t.targetId) ?? [];
138
- s.push(t), this._reactions.set(t.targetId, s);
163
+ for (const e of t.presence)
164
+ this._presence.set(e.user.id, e), this._users.set(e.user.id, e.user);
165
+ this._users.set(t.user.id, t.user), this._threads.clear();
166
+ for (const e of t.threads) this._threads.set(e.id, this.resolveThread(e));
167
+ this._notifications = t.notifications, this._reactions.clear();
168
+ for (const e of t.reactions) {
169
+ const s = this._reactions.get(e.targetId) ?? [];
170
+ s.push(e), this._reactions.set(e.targetId, s);
139
171
  }
140
- 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);
172
+ this._activityLogs = [...t.activityLogs], this.emit("auth", t.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);
141
173
  break;
142
174
  case "presence:join":
143
- this._presence.set(e.user.user.id, e.user), this._users.set(e.user.user.id, e.user.user), this.emit("presence", this.presence);
175
+ this._presence.set(t.user.user.id, t.user), this._users.set(t.user.user.id, t.user.user), this.emit("presence", this.presence);
144
176
  break;
145
177
  case "presence:leave":
146
- this._presence.delete(e.userId), this._viewports.delete(e.userId), this._selections.delete(e.userId);
147
- for (const t of this._typing.values())
148
- t.delete(e.userId);
178
+ this._presence.delete(t.userId), this._viewports.delete(t.userId), this._selections.delete(t.userId);
179
+ for (const e of this._typing.values())
180
+ e.delete(t.userId);
149
181
  this.emit("presence", this.presence);
150
182
  break;
151
183
  case "presence:update": {
152
- const t = this._presence.get(e.userId);
153
- t && (t.status = e.status, this.emit("presence", this.presence));
184
+ const e = this._presence.get(t.userId);
185
+ e && (e.status = t.status, this.emit("presence", this.presence));
154
186
  break;
155
187
  }
156
188
  case "cursor:move":
157
- this.emit("cursor", { userId: e.userId, position: e.position });
189
+ this.emit("cursor", { userId: t.userId, position: t.position });
158
190
  break;
159
191
  case "click:perform":
160
- this.emit("click", { userId: e.userId, position: e.position });
192
+ this.emit("click", { userId: t.userId, position: t.position });
161
193
  break;
162
194
  case "thread:created":
163
- this._threads.set(e.thread.id, e.thread), this.emit("threads", this.threads);
195
+ this._threads.set(t.thread.id, this.resolveThread(t.thread)), this.emit("threads", this.threads);
164
196
  break;
165
197
  case "comment:created": {
166
- const t = this._threads.get(e.threadId);
167
- t && (t.comments.push(e.comment), t.updatedAt = e.comment.createdAt, this.emit("threads", this.threads));
198
+ const e = this._threads.get(t.threadId);
199
+ e && (e.comments.push(this.resolveComment(t.comment)), e.updatedAt = t.comment.createdAt, this.emit("threads", this.threads));
168
200
  break;
169
201
  }
170
202
  case "comment:edited": {
171
- const t = this._threads.get(e.threadId);
172
- if (t) {
173
- const s = t.comments.findIndex((r) => r.id === e.comment.id);
174
- s !== -1 && (t.comments[s] = e.comment), this.emit("threads", this.threads);
203
+ const e = this._threads.get(t.threadId);
204
+ if (e) {
205
+ const s = e.comments.findIndex((r) => r.id === t.comment.id);
206
+ s !== -1 && (e.comments[s] = this.resolveComment(t.comment)), this.emit("threads", this.threads);
175
207
  }
176
208
  break;
177
209
  }
178
210
  case "comment:deleted": {
179
- const t = this._threads.get(e.threadId);
180
- 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));
211
+ const e = this._threads.get(t.threadId);
212
+ e && (e.comments = e.comments.filter((s) => s.id !== t.commentId), e.comments.length === 0 && this._threads.delete(t.threadId), this.emit("threads", this.threads));
181
213
  break;
182
214
  }
183
215
  case "thread:resolved": {
184
- const t = this._threads.get(e.threadId);
185
- t && (t.resolved = e.resolved, this.emit("threads", this.threads));
216
+ const e = this._threads.get(t.threadId);
217
+ e && (e.resolved = t.resolved, this.emit("threads", this.threads));
186
218
  break;
187
219
  }
188
220
  case "thread:deleted":
189
- this._threads.delete(e.threadId), this.emit("threads", this.threads);
221
+ this._threads.delete(t.threadId), this.emit("threads", this.threads);
190
222
  break;
191
223
  case "reaction:added": {
192
- const t = this._reactions.get(e.reaction.targetId) ?? [];
193
- t.push(e.reaction), this._reactions.set(e.reaction.targetId, t), this.emit("reactions", {
194
- targetId: e.reaction.targetId,
195
- reactions: t
224
+ const e = this._reactions.get(t.reaction.targetId) ?? [];
225
+ e.push(t.reaction), this._reactions.set(t.reaction.targetId, e), this.emit("reactions", {
226
+ targetId: t.reaction.targetId,
227
+ reactions: e
196
228
  });
197
229
  break;
198
230
  }
199
231
  case "reaction:removed": {
200
- const t = this._reactions.get(e.targetId);
201
- if (t) {
202
- const s = t.filter((r) => r.id !== e.reactionId);
203
- this._reactions.set(e.targetId, s), this.emit("reactions", {
204
- targetId: e.targetId,
232
+ const e = this._reactions.get(t.targetId);
233
+ if (e) {
234
+ const s = e.filter((r) => r.id !== t.reactionId);
235
+ this._reactions.set(t.targetId, s), this.emit("reactions", {
236
+ targetId: t.targetId,
205
237
  reactions: s
206
238
  });
207
239
  }
208
240
  break;
209
241
  }
210
242
  case "notification":
211
- this._notifications.unshift(e.notification), this.emit("notifications", this._notifications);
243
+ this._notifications.unshift(t.notification), this.emit("notifications", this._notifications);
212
244
  break;
213
245
  case "typing:indicator": {
214
- this._typing.has(e.threadId) || this._typing.set(e.threadId, /* @__PURE__ */ new Map()), this._typing.get(e.threadId).set(e.userId, Date.now()), this.emit("typing", { threadId: e.threadId, userId: e.userId });
246
+ this._typing.has(t.threadId) || this._typing.set(t.threadId, /* @__PURE__ */ new Map()), this._typing.get(t.threadId).set(t.userId, Date.now()), this.emit("typing", { threadId: t.threadId, userId: t.userId });
215
247
  break;
216
248
  }
217
249
  case "viewport:update": {
218
- this._viewports.set(e.userId, {
219
- scrollX: e.scrollX,
220
- scrollY: e.scrollY,
221
- viewportWidth: e.viewportWidth,
222
- viewportHeight: e.viewportHeight,
223
- pageWidth: e.pageWidth,
224
- pageHeight: e.pageHeight
225
- }), this.emit("viewport", { userId: e.userId });
250
+ this._viewports.set(t.userId, {
251
+ scrollX: t.scrollX,
252
+ scrollY: t.scrollY,
253
+ viewportWidth: t.viewportWidth,
254
+ viewportHeight: t.viewportHeight,
255
+ pageWidth: t.pageWidth,
256
+ pageHeight: t.pageHeight
257
+ }), this.emit("viewport", { userId: t.userId });
226
258
  break;
227
259
  }
228
260
  case "selection:update":
229
- this._selections.set(e.userId, e.selection), this.emit("selection", { userId: e.userId, selection: e.selection });
261
+ this._selections.set(t.userId, t.selection), this.emit("selection", { userId: t.userId, selection: t.selection });
230
262
  break;
231
263
  case "emoji:drop":
232
264
  this.emit("emoji-drop", {
233
- userId: e.userId,
234
- emoji: e.emoji,
235
- position: e.position
265
+ userId: t.userId,
266
+ emoji: t.emoji,
267
+ position: t.position
236
268
  });
237
269
  break;
238
270
  case "draw:stroke":
239
271
  this.emit("draw-stroke", {
240
- userId: e.userId,
241
- points: e.points,
242
- color: e.color,
243
- width: e.width
272
+ userId: t.userId,
273
+ points: t.points,
274
+ color: t.color,
275
+ width: t.width
244
276
  });
245
277
  break;
246
278
  case "draw:clear":
247
- this.emit("draw-clear", { userId: e.userId });
279
+ this.emit("draw-clear", { userId: t.userId });
248
280
  break;
249
281
  case "activity:logged":
250
- this._activityLogs.unshift(e.activityLog), this._activityLogs.length > 100 && (this._activityLogs = this._activityLogs.slice(0, 100)), this.emit("activity-logs", this._activityLogs);
282
+ this._activityLogs.unshift(t.activityLog), this._activityLogs.length > 100 && (this._activityLogs = this._activityLogs.slice(0, 100)), this.emit("activity-logs", this._activityLogs);
251
283
  break;
252
284
  case "error":
253
- this.emit("error", e);
285
+ this.emit("error", t);
254
286
  break;
255
287
  }
256
288
  }
@@ -258,8 +290,8 @@ class _ extends c {
258
290
  this._user = 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();
259
291
  }
260
292
  }
261
- class w extends c {
262
- constructor(e) {
293
+ class f extends c {
294
+ constructor(t) {
263
295
  var s;
264
296
  super();
265
297
  i(this, "state");
@@ -269,9 +301,9 @@ class w extends c {
269
301
  i(this, "lastCursorSend", 0);
270
302
  i(this, "pendingCursor", null);
271
303
  i(this, "cursorTimer", null);
272
- this.config = e, this.state = new _();
273
- const t = ((s = e.endpoint) == null ? void 0 : s.replace(/^http/, "ws")) ?? void 0;
274
- this.connection = new u(t), this.connection.on("message", (r) => {
304
+ this.config = t, this.state = new _(), this.state.baseUrl = (t.endpoint ?? "").replace(/^ws(s?):/, "http$1:").replace(/\/$/, "");
305
+ const e = ((s = t.endpoint) == null ? void 0 : s.replace(/^http/, "ws")) ?? void 0;
306
+ this.connection = new u(e), this.connection.on("message", (r) => {
275
307
  this.state.handleMessage(r), this.emit(r.type, r);
276
308
  }), this.connection.on("state", (r) => {
277
309
  this.emit("connection", r), r === "connected" ? (this.authenticate(), this.startHeartbeat()) : r === "disconnected" && this.stopHeartbeat();
@@ -295,13 +327,13 @@ class w extends c {
295
327
  room: this.config.room
296
328
  });
297
329
  }
298
- send(e) {
299
- this.connection.send(e);
330
+ send(t) {
331
+ this.connection.send(t);
300
332
  }
301
333
  // ── Cursors ──
302
- moveCursor(e) {
303
- const t = Date.now();
304
- this.pendingCursor = e, t - this.lastCursorSend >= 50 ? this.flushCursor() : this.cursorTimer || (this.cursorTimer = setTimeout(() => {
334
+ moveCursor(t) {
335
+ const e = Date.now();
336
+ this.pendingCursor = t, e - this.lastCursorSend >= 50 ? this.flushCursor() : this.cursorTimer || (this.cursorTimer = setTimeout(() => {
305
337
  this.cursorTimer = null, this.flushCursor();
306
338
  }, 50));
307
339
  }
@@ -309,8 +341,8 @@ class w extends c {
309
341
  this.pendingCursor && (this.send({ type: "cursor:move", position: this.pendingCursor }), this.lastCursorSend = Date.now(), this.pendingCursor = null);
310
342
  }
311
343
  // ── Presence ──
312
- updatePresence(e) {
313
- this.send({ type: "presence:update", status: e });
344
+ updatePresence(t) {
345
+ this.send({ type: "presence:update", status: t });
314
346
  }
315
347
  startHeartbeat() {
316
348
  this.heartbeatTimer = setInterval(() => {
@@ -321,76 +353,76 @@ class w extends c {
321
353
  this.heartbeatTimer && (clearInterval(this.heartbeatTimer), this.heartbeatTimer = null);
322
354
  }
323
355
  // ── Threads & Comments ──
324
- createThread(e, t = {}) {
356
+ createThread(t, e = {}) {
325
357
  const s = crypto.randomUUID();
326
358
  return this.send({
327
359
  type: "thread:create",
328
360
  id: s,
329
- body: e,
330
- mentions: t.mentions ?? [],
331
- position: t.position ?? null,
332
- attachmentIds: t.attachmentIds
361
+ body: t,
362
+ mentions: e.mentions ?? [],
363
+ position: e.position ?? null,
364
+ attachmentIds: e.attachmentIds
333
365
  }), s;
334
366
  }
335
- reply(e, t, s = [], r) {
336
- const o = crypto.randomUUID();
337
- return this.send({ type: "comment:create", threadId: e, id: o, body: t, mentions: s, attachmentIds: r }), o;
367
+ reply(t, e, s = [], r) {
368
+ const a = crypto.randomUUID();
369
+ return this.send({ type: "comment:create", threadId: t, id: a, body: e, mentions: s, attachmentIds: r }), a;
338
370
  }
339
- editComment(e, t, s = []) {
340
- this.send({ type: "comment:edit", commentId: e, body: t, mentions: s });
371
+ editComment(t, e, s = []) {
372
+ this.send({ type: "comment:edit", commentId: t, body: e, mentions: s });
341
373
  }
342
- deleteComment(e) {
343
- this.send({ type: "comment:delete", commentId: e });
374
+ deleteComment(t) {
375
+ this.state.removeComment(t), this.send({ type: "comment:delete", commentId: t });
344
376
  }
345
- resolveThread(e, t = !0) {
346
- this.send({ type: "thread:resolve", threadId: e, resolved: t });
377
+ resolveThread(t, e = !0) {
378
+ this.send({ type: "thread:resolve", threadId: t, resolved: e });
347
379
  }
348
380
  // ── Reactions ──
349
- addReaction(e, t, s) {
350
- this.send({ type: "reaction:add", targetId: e, targetType: t, emoji: s });
381
+ addReaction(t, e, s) {
382
+ this.send({ type: "reaction:add", targetId: t, targetType: e, emoji: s });
351
383
  }
352
- removeReaction(e) {
353
- this.send({ type: "reaction:remove", reactionId: e });
384
+ removeReaction(t) {
385
+ this.send({ type: "reaction:remove", reactionId: t });
354
386
  }
355
387
  // ── Notifications ──
356
- markRead(e) {
357
- this.send({ type: "notification:read", notificationId: e });
388
+ markRead(t) {
389
+ this.send({ type: "notification:read", notificationId: t });
358
390
  }
359
391
  markAllRead() {
360
392
  this.send({ type: "notification:read-all" });
361
393
  }
362
394
  // ── Clicks ──
363
- performClick(e) {
364
- this.send({ type: "click:perform", position: e });
395
+ performClick(t) {
396
+ this.send({ type: "click:perform", position: t });
365
397
  }
366
398
  // ── Typing ──
367
- sendTyping(e) {
368
- this.send({ type: "typing:start", threadId: e });
399
+ sendTyping(t) {
400
+ this.send({ type: "typing:start", threadId: t });
369
401
  }
370
402
  // ── Viewport ──
371
- updateViewport(e) {
372
- this.send({ type: "viewport:update", ...e });
403
+ updateViewport(t) {
404
+ this.send({ type: "viewport:update", ...t });
373
405
  }
374
406
  // ── Selection ──
375
- updateSelection(e) {
376
- this.send({ type: "selection:update", selection: e });
407
+ updateSelection(t) {
408
+ this.send({ type: "selection:update", selection: t });
377
409
  }
378
410
  // ── Emoji Drops ──
379
- dropEmoji(e, t) {
380
- this.send({ type: "emoji:drop", emoji: e, position: t });
411
+ dropEmoji(t, e) {
412
+ this.send({ type: "emoji:drop", emoji: t, position: e });
381
413
  }
382
414
  // ── Drawing ──
383
- drawStroke(e, t, s) {
384
- this.send({ type: "draw:stroke", points: e, color: t, width: s });
415
+ drawStroke(t, e, s) {
416
+ this.send({ type: "draw:stroke", points: t, color: e, width: s });
385
417
  }
386
418
  clearDrawing() {
387
419
  this.send({ type: "draw:clear" });
388
420
  }
389
421
  // ── File Upload ──
390
- async uploadFile(e) {
391
- const t = (this.config.endpoint ?? window.location.origin).replace(/^ws(s?):/, "http$1:"), s = new FormData();
392
- s.append("file", e);
393
- const r = await fetch(`${t}/api/v1/upload`, {
422
+ async uploadFile(t) {
423
+ const e = (this.config.endpoint ?? window.location.origin).replace(/^ws(s?):/, "http$1:"), s = new FormData();
424
+ s.append("file", t);
425
+ const r = await fetch(`${e}/api/v1/upload`, {
394
426
  method: "POST",
395
427
  headers: {
396
428
  "X-Pulse-Key": this.config.apiKey,
@@ -399,19 +431,20 @@ class w extends c {
399
431
  body: s
400
432
  });
401
433
  if (!r.ok) {
402
- const o = await r.json().catch(() => ({ error: "Upload failed" }));
403
- throw new Error(o.error ?? "Upload failed");
434
+ const h = await r.json().catch(() => ({ error: "Upload failed" }));
435
+ throw new Error(h.error ?? "Upload failed");
404
436
  }
405
- return r.json();
437
+ const a = await r.json();
438
+ return a.url && !a.url.startsWith("http") && (a.url = `${e}${a.url}`), a.thumbnailUrl && !a.thumbnailUrl.startsWith("http") && (a.thumbnailUrl = `${e}${a.thumbnailUrl}`), a;
406
439
  }
407
440
  // ── Presence control ──
408
- setAppearOffline(e) {
409
- e ? (this.stopHeartbeat(), this.send({ type: "presence:update", status: "idle" })) : (this.startHeartbeat(), this.send({ type: "presence:update", status: "online" }));
441
+ setAppearOffline(t) {
442
+ t ? (this.stopHeartbeat(), this.send({ type: "presence:update", status: "idle" })) : (this.startHeartbeat(), this.send({ type: "presence:update", status: "online" }));
410
443
  }
411
444
  }
412
445
  export {
413
446
  u as Connection,
414
447
  c as Emitter,
415
- w as PulseClient,
448
+ f as PulseClient,
416
449
  _ as StateManager
417
450
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamention/pulse-core",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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",