Skip to content

Commit 9ffa065

Browse files
committed
fix: /es/broadcast/subscribe 换成 /ws/broadcast ,解决 EventSource 在 http/1.1 的连接数限制;只有一个打开的窗口时不进行消息广播
close #29
1 parent f3d7343 commit 9ffa065

File tree

1 file changed

+139
-58
lines changed

1 file changed

+139
-58
lines changed

src/index.ts

Lines changed: 139 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)