forked from robtaussig/react-use-websocket
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathheartbeat.ts
55 lines (47 loc) · 1.91 KB
/
heartbeat.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import { MutableRefObject } from "react";
import { DEFAULT_HEARTBEAT } from "./constants";
import { HeartbeatOptions } from "./types";
function getLastMessageTime(lastMessageTime: MutableRefObject<number> | MutableRefObject<number>[]): number {
if (Array.isArray(lastMessageTime)) {
return lastMessageTime.reduce((p, c) => { return (p.current > c.current) ? p : c; }).current;
}
return lastMessageTime.current
}
export function heartbeat(ws: WebSocket, lastMessageTime: MutableRefObject<number> | MutableRefObject<number>[], options?: HeartbeatOptions): () => void {
const {
interval = DEFAULT_HEARTBEAT.interval,
timeout = DEFAULT_HEARTBEAT.timeout,
message = DEFAULT_HEARTBEAT.message,
} = options || {};
// how often check interval between ping messages
// minimum is 100ms
// maximum is ${interval / 10}ms
const intervalCheck = Math.max(100, interval / 10);
let lastPingSentAt = Date.now();
const heartbeatInterval = setInterval(() => {
const timeNow = Date.now();
const lastMessageReceivedAt = getLastMessageTime(lastMessageTime);
if (lastMessageReceivedAt + timeout <= timeNow) {
console.warn(`Heartbeat timed out, closing connection, last message received ${timeNow - lastMessageReceivedAt}ms ago, last ping sent ${timeNow - lastPingSentAt}ms ago`);
ws.close();
} else {
if (lastMessageReceivedAt + interval <= timeNow && lastPingSentAt + interval <= timeNow) {
try {
if (typeof message === 'function') {
ws.send(message());
} else {
ws.send(message);
}
lastPingSentAt = timeNow;
} catch (err: unknown) {
console.error(`Heartbeat failed, closing connection`, err instanceof Error ? err.message : err);
ws.close();
}
}
}
}, intervalCheck);
ws.addEventListener("close", () => {
clearInterval(heartbeatInterval);
});
return () => { };
}