fluxy-bot 0.1.22 → 0.1.24
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/client/src/hooks/useWebSocket.ts +3 -5
- package/client/src/lib/ws-client.ts +67 -3
- package/dist/assets/{index-CW83Kbey.js → index-CW6a7xtX.js} +29 -29
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/supervisor/index.ts +36 -1
- package/supervisor/tunnel.ts +14 -0
- package/worker/index.ts +6 -1
|
@@ -8,14 +8,12 @@ export function useWebSocket() {
|
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
const client = new WsClient();
|
|
10
10
|
clientRef.current = client;
|
|
11
|
-
client.connect();
|
|
12
11
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
}, 1000);
|
|
12
|
+
const unsub = client.onStatus(setConnected);
|
|
13
|
+
client.connect();
|
|
16
14
|
|
|
17
15
|
return () => {
|
|
18
|
-
|
|
16
|
+
unsub();
|
|
19
17
|
client.disconnect();
|
|
20
18
|
};
|
|
21
19
|
}, []);
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
type MessageHandler = (msg: any) => void;
|
|
2
|
+
type StatusHandler = (connected: boolean) => void;
|
|
3
|
+
|
|
4
|
+
interface QueuedMessage {
|
|
5
|
+
type: string;
|
|
6
|
+
data: any;
|
|
7
|
+
}
|
|
2
8
|
|
|
3
9
|
export class WsClient {
|
|
4
10
|
private ws: WebSocket | null = null;
|
|
5
11
|
private handlers = new Map<string, Set<MessageHandler>>();
|
|
12
|
+
private statusHandlers = new Set<StatusHandler>();
|
|
6
13
|
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
14
|
+
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
7
15
|
private url: string;
|
|
16
|
+
private queue: QueuedMessage[] = [];
|
|
17
|
+
private intentionalClose = false;
|
|
18
|
+
private reconnectDelay = 1000;
|
|
19
|
+
private static MAX_RECONNECT_DELAY = 8000;
|
|
8
20
|
|
|
9
21
|
constructor(url?: string) {
|
|
10
22
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
@@ -13,23 +25,42 @@ export class WsClient {
|
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
connect(): void {
|
|
28
|
+
this.intentionalClose = false;
|
|
16
29
|
this.ws = new WebSocket(this.url);
|
|
17
30
|
|
|
31
|
+
this.ws.onopen = () => {
|
|
32
|
+
this.reconnectDelay = 1000;
|
|
33
|
+
this.notifyStatus(true);
|
|
34
|
+
this.flushQueue();
|
|
35
|
+
this.startHeartbeat();
|
|
36
|
+
};
|
|
37
|
+
|
|
18
38
|
this.ws.onmessage = (e) => {
|
|
39
|
+
// Ignore pong frames
|
|
40
|
+
if (e.data === 'pong') return;
|
|
19
41
|
const msg = JSON.parse(e.data);
|
|
20
42
|
const handlers = this.handlers.get(msg.type);
|
|
21
43
|
handlers?.forEach((h) => h(msg.data));
|
|
22
44
|
};
|
|
23
45
|
|
|
24
46
|
this.ws.onclose = () => {
|
|
25
|
-
this.
|
|
47
|
+
this.stopHeartbeat();
|
|
48
|
+
this.notifyStatus(false);
|
|
49
|
+
if (!this.intentionalClose) {
|
|
50
|
+
this.reconnectTimer = setTimeout(() => {
|
|
51
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, WsClient.MAX_RECONNECT_DELAY);
|
|
52
|
+
this.connect();
|
|
53
|
+
}, this.reconnectDelay);
|
|
54
|
+
}
|
|
26
55
|
};
|
|
27
56
|
|
|
28
57
|
this.ws.onerror = () => this.ws?.close();
|
|
29
58
|
}
|
|
30
59
|
|
|
31
60
|
disconnect(): void {
|
|
32
|
-
|
|
61
|
+
this.intentionalClose = true;
|
|
62
|
+
if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; }
|
|
63
|
+
this.stopHeartbeat();
|
|
33
64
|
this.ws?.close();
|
|
34
65
|
this.ws = null;
|
|
35
66
|
}
|
|
@@ -40,13 +71,46 @@ export class WsClient {
|
|
|
40
71
|
return () => this.handlers.get(type)?.delete(handler);
|
|
41
72
|
}
|
|
42
73
|
|
|
74
|
+
onStatus(handler: StatusHandler): () => void {
|
|
75
|
+
this.statusHandlers.add(handler);
|
|
76
|
+
return () => this.statusHandlers.delete(handler);
|
|
77
|
+
}
|
|
78
|
+
|
|
43
79
|
send(type: string, data: any): void {
|
|
80
|
+
const message = { type, data };
|
|
44
81
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
45
|
-
this.ws.send(JSON.stringify(
|
|
82
|
+
this.ws.send(JSON.stringify(message));
|
|
83
|
+
} else {
|
|
84
|
+
// Queue for delivery on reconnect
|
|
85
|
+
this.queue.push(message);
|
|
46
86
|
}
|
|
47
87
|
}
|
|
48
88
|
|
|
49
89
|
get connected(): boolean {
|
|
50
90
|
return this.ws?.readyState === WebSocket.OPEN;
|
|
51
91
|
}
|
|
92
|
+
|
|
93
|
+
private flushQueue(): void {
|
|
94
|
+
while (this.queue.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
|
|
95
|
+
const msg = this.queue.shift()!;
|
|
96
|
+
this.ws.send(JSON.stringify(msg));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private notifyStatus(connected: boolean): void {
|
|
101
|
+
this.statusHandlers.forEach((h) => h(connected));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private startHeartbeat(): void {
|
|
105
|
+
this.stopHeartbeat();
|
|
106
|
+
this.heartbeatTimer = setInterval(() => {
|
|
107
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
108
|
+
this.ws.send('ping');
|
|
109
|
+
}
|
|
110
|
+
}, 25_000);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private stopHeartbeat(): void {
|
|
114
|
+
if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; }
|
|
115
|
+
}
|
|
52
116
|
}
|