@@ -88,9 +88,6 @@ export default class PluginSnippets extends Plugin {
8888 * 启用插件(进行各种初始化)
8989 */
9090 public async onload ( ) {
91- // 初始化 Broadcast Channel 用于跨窗口通信
92- this . initBroadcastChannel ( ) ;
93-
9491 if ( ! isVersionReach ( "3.3.0" ) ) {
9592 // 初始化 window.siyuan.jcsm
9693 window . siyuan . jcsm ??= { } ; // ??= 逻辑空赋值运算符 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_assignment
@@ -252,6 +249,9 @@ export default class PluginSnippets extends Plugin {
252249 // 获取已打开的所有自定义页签
253250 // this.getOpenedTab();
254251 }
252+
253+ // 初始化 Broadcast Channel 用于跨窗口通信(需要等插件设置加载完成)
254+ this . initBroadcastChannel ( ) ;
255255 }
256256
257257 /**
@@ -5797,9 +5797,9 @@ export default class PluginSnippets extends Plugin {
57975797 private windowId : string ;
57985798
57995799 /**
5800- * EventSource 连接用于接收广播消息
5800+ * WebSocket 连接用于接收广播消息
58015801 */
5802- private eventSource : EventSource | null = null ;
5802+ private websocket : WebSocket | null = null ;
58035803
58045804 /**
58055805 * 重连间隔(毫秒)
@@ -5811,62 +5811,86 @@ export default class PluginSnippets extends Plugin {
58115811 */
58125812 private reconnectTimer : number | null = null ;
58135813
5814+ /**
5815+ * 其他窗口 ID 集合,用于跟踪其他窗口的存在状态
5816+ */
5817+ private otherWindowIds : Set < string > = new Set ( ) ;
5818+
5819+
58145820 /**
58155821 * 初始化基于内核 API 的跨窗口通信
58165822 */
5817- private initBroadcastChannel ( ) {
5823+ private async initBroadcastChannel ( ) {
58185824 // 生成当前窗口的唯一标识符
58195825 this . windowId = BROADCAST_CHANNEL_NAME + "-" + window . Lute . NewNodeID ( ) ;
58205826
58215827 // 订阅广播频道
5822- this . subscribeToBroadcastChannel ( ) ;
5828+ await this . subscribeToBroadcastChannel ( ) ;
58235829
5830+ // console.log('Broadcast Channel has been initialized, Window ID:', this.windowId);
58245831 this . console . log ( 'Broadcast Channel has been initialized, Window ID:' , this . windowId ) ;
58255832
5826- // 发送初始化消息到其他窗口
5827- this . broadcastMessage ( 'window_ready ' , {
5833+ // 发送初始化消息到其他窗口(用于发现其他窗口,强制发送)
5834+ this . broadcastMessage ( 'window_online ' , {
58285835 windowId : this . windowId ,
58295836 timestamp : Date . now ( ) ,
5837+ } , true ) ;
5838+
5839+ // 监听页面卸载事件,确保窗口关闭时发送下线通知
5840+ window . addEventListener ( 'beforeunload' , ( ) => {
5841+ this . sendOfflineNotification ( ) ;
58305842 } ) ;
58315843 }
58325844
58335845 /**
58345846 * 订阅广播频道
58355847 */
5836- private subscribeToBroadcastChannel ( ) {
5837- try {
5838- // 构建订阅 URL
5839- const subscribeUrl = `/es/broadcast/subscribe?channel=${ encodeURIComponent ( BROADCAST_CHANNEL_NAME ) } &retry=${ this . reconnectInterval } ` ;
5840-
5841- // 创建 EventSource 连接
5842- this . eventSource = new EventSource ( subscribeUrl ) ;
5843-
5844- // 监听连接打开
5845- this . eventSource . onopen = ( ) => {
5846- this . console . log ( 'Broadcast channel connected' ) ;
5847- this . clearReconnectTimer ( ) ;
5848- } ;
5849-
5850- // 监听消息
5851- this . eventSource . addEventListener ( BROADCAST_CHANNEL_NAME , ( event ) => {
5852- try {
5853- const data = JSON . parse ( event . data ) ;
5854- this . handleBroadcastMessage ( data ) ;
5855- } catch ( error ) {
5856- this . console . error ( 'Failed to parse broadcast message:' , error ) ;
5857- }
5858- } ) ;
5859-
5860- // 监听连接错误
5861- this . eventSource . onerror = ( error ) => {
5862- this . console . error ( 'Broadcast channel connection error:' , error ) ;
5848+ private async subscribeToBroadcastChannel ( ) : Promise < void > {
5849+ return new Promise ( ( resolve , reject ) => {
5850+ try {
5851+ // 构建 WebSocket URL
5852+ const protocol = window . location . protocol === 'https:' ? 'wss:' : 'ws:' ;
5853+ const wsUrl = `${ protocol } //${ window . location . host } /ws/broadcast?channel=${ encodeURIComponent ( BROADCAST_CHANNEL_NAME ) } ` ;
5854+
5855+ // 创建 WebSocket 连接
5856+ this . websocket = new WebSocket ( wsUrl ) ;
5857+
5858+ // 监听连接打开
5859+ this . websocket . onopen = ( ) => {
5860+ this . console . log ( 'Broadcast channel connected' ) ;
5861+ this . clearReconnectTimer ( ) ;
5862+ resolve ( ) ; // 连接建立后 resolve Promise
5863+ } ;
5864+
5865+ // 监听消息
5866+ this . websocket . onmessage = ( event ) => {
5867+ try {
5868+ const data = JSON . parse ( event . data ) ;
5869+ this . handleBroadcastMessage ( data ) ;
5870+ } catch ( error ) {
5871+ this . console . error ( 'Failed to parse broadcast message:' , error ) ;
5872+ }
5873+ } ;
5874+
5875+ // 监听连接错误
5876+ this . websocket . onerror = ( error ) => {
5877+ this . console . error ( 'Broadcast channel connection error:' , error ) ;
5878+ this . scheduleReconnect ( ) ;
5879+ reject ( error ) ; // 连接错误时 reject Promise
5880+ } ;
5881+
5882+ // 监听连接关闭
5883+ this . websocket . onclose = ( event ) => {
5884+ this . console . log ( 'Broadcast channel connection closed:' , event . code , event . reason ) ;
5885+ this . scheduleReconnect ( ) ;
5886+ } ;
5887+
5888+ } catch ( error ) {
5889+ this . console . error ( 'Failed to subscribe to broadcast channel:' , error ) ;
58635890 this . scheduleReconnect ( ) ;
5864- } ;
5865-
5866- } catch ( error ) {
5867- this . console . error ( 'Failed to subscribe to broadcast channel:' , error ) ;
5868- this . scheduleReconnect ( ) ;
5869- }
5891+ reject ( error ) ;
5892+ }
5893+ } ) ;
58705894 }
58715895
58725896 /**
@@ -5890,15 +5914,53 @@ export default class PluginSnippets extends Plugin {
58905914 }
58915915 }
58925916
5917+
5918+
5919+ /**
5920+ * 处理窗口下线通知
5921+ * @param windowId 下线的窗口 ID
5922+ */
5923+ private handleWindowOffline ( windowId : string ) {
5924+ // 立即从跟踪列表中移除该窗口
5925+ this . otherWindowIds . delete ( windowId ) ;
5926+ this . console . log ( 'Window offline notification received, removed from tracking:' , windowId ) ;
5927+ }
5928+
5929+ /**
5930+ * 发送窗口下线通知
5931+ */
5932+ private sendOfflineNotification ( ) {
5933+ // 在页面卸载前发送下线通知
5934+ try {
5935+ this . broadcastMessage ( 'window_offline' , {
5936+ windowId : this . windowId ,
5937+ timestamp : Date . now ( ) ,
5938+ } , true ) ;
5939+ } catch ( error ) {
5940+ // 忽略错误,因为页面即将卸载
5941+ this . console . error ( 'Failed to send offline notification:' , error ) ;
5942+ }
5943+ }
5944+
58935945 /**
58945946 * 清理广播频道连接
58955947 */
58965948 private cleanupBroadcastChannel ( ) {
5949+ // 发送窗口下线通知
5950+ this . broadcastMessage ( 'window_offline' , {
5951+ windowId : this . windowId ,
5952+ timestamp : Date . now ( ) ,
5953+ } , true ) ;
5954+
58975955 this . clearReconnectTimer ( ) ;
58985956
5899- if ( this . eventSource ) {
5900- this . eventSource . close ( ) ;
5901- this . eventSource = null ;
5957+
5958+ // 清理窗口跟踪数据
5959+ this . otherWindowIds . clear ( ) ;
5960+
5961+ if ( this . websocket ) {
5962+ this . websocket . close ( ) ;
5963+ this . websocket = null ;
59025964 }
59035965 }
59045966
@@ -5907,14 +5969,33 @@ export default class PluginSnippets extends Plugin {
59075969 * @param data 消息数据
59085970 */
59095971 private handleBroadcastMessage ( data : any ) {
5972+ this . console . log ( 'Received broadcast message:' , data ) ;
5973+
59105974 // 忽略来自当前窗口的消息
59115975 if ( data . windowId === this . windowId ) {
5976+ this . console . log ( 'Ignoring message from current window:' , data . windowId ) ;
59125977 return ;
59135978 }
59145979
5980+ // 记录其他窗口 ID
5981+ this . otherWindowIds . add ( data . windowId ) ;
5982+
59155983 switch ( data . type ) {
5916- case 'window_ready ' :
5984+ case 'window_online ' :
59175985 this . console . log ( 'New window detected:' , data . windowId ) ;
5986+ // 向新上线的窗口发送反馈,告知自己的存在
5987+ this . broadcastMessage ( 'window_online_feedback' , {
5988+ windowId : this . windowId ,
5989+ timestamp : Date . now ( ) ,
5990+ } ) ;
5991+ break ;
5992+ case 'window_online_feedback' :
5993+ this . console . log ( 'Received online feedback from:' , data . windowId ) ;
5994+ // 将反馈的窗口 ID 添加到跟踪列表中
5995+ this . otherWindowIds . add ( data . windowId ) ;
5996+ break ;
5997+ case 'window_offline' :
5998+ this . handleWindowOffline ( data . windowId ) ;
59185999 break ;
59196000 case 'snippet_toggle' :
59206001 this . toggleSnippetSync ( data ) ;
@@ -5946,33 +6027,33 @@ export default class PluginSnippets extends Plugin {
59466027 * 发送广播消息到其他窗口
59476028 * @param type 消息类型
59486029 * @param data 消息数据
6030+ * @param force 是否强制发送(忽略其他窗口检查)
59496031 */
5950- private broadcastMessage ( type : string , data : any = { } ) {
6032+ private broadcastMessage ( type : string , data : any = { } , force : boolean = false ) {
6033+ // 如果不是强制发送且不存在其他窗口,则跳过广播
6034+ if ( ! force && this . otherWindowIds . size === 0 ) return ;
6035+
59516036 const message = {
59526037 type,
59536038 windowId : this . windowId ,
59546039 timestamp : Date . now ( ) ,
59556040 ...data
59566041 } ;
59576042
5958- // 使用思源内核的 broadcast API 发送消息
6043+ // 通过 WebSocket 连接发送消息
59596044 this . postBroadcastMessage ( JSON . stringify ( message ) ) ;
59606045 this . console . log ( 'Send cross-window message:' , message ) ;
59616046 }
59626047
59636048 /**
5964- * 通过内核 API 发送广播消息
6049+ * 通过 WebSocket 连接发送广播消息
59656050 * @param message 消息内容
59666051 */
59676052 private postBroadcastMessage ( message : string ) {
5968- // 使用思源前端的 fetchPost 方法调用内核 API
5969- fetchPost ( '/api/broadcast/postMessage' , {
5970- channel : BROADCAST_CHANNEL_NAME ,
5971- message : message
5972- } , ( response : any ) => {
5973- if ( response . code !== 0 ) {
5974- this . console . error ( 'Failed to send broadcast message:' , response . msg ) ;
5975- }
5976- } ) ;
6053+ if ( this . websocket && this . websocket . readyState === WebSocket . OPEN ) {
6054+ this . websocket . send ( message ) ;
6055+ } else {
6056+ this . console . error ( 'WebSocket connection is not ready, cannot send message' ) ;
6057+ }
59776058 }
59786059}
0 commit comments