-
Notifications
You must be signed in to change notification settings - Fork 137
/
Copy pathHeartbeatHandler.ts
126 lines (107 loc) · 4.83 KB
/
HeartbeatHandler.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { AppIdentifier } from "@kite9/fdc3-standard";
import { MessageHandler } from "../BasicFDC3Server";
import { AppRegistration, InstanceID, ServerContext, State } from "../ServerContext";
import { BrowserTypes } from "@kite9/fdc3-schema";
type HeartbeatDetails = {
instanceId: string,
time: number,
state: string
}
function convertToText(s?: State): string {
if (s == undefined) {
return "Unknown"
} else {
switch (s) {
case State.Pending:
return "Pending"
case State.Connected:
return "Connected"
case State.NotResponding:
return "Not Responding"
case State.Terminated:
return "Terminated"
}
}
}
/*
* Handles heartbeat pings and responses
*/
export class HeartbeatHandler implements MessageHandler {
private readonly contexts: ServerContext<AppRegistration>[] = []
private readonly lastHeartbeats: Map<InstanceID, number> = new Map()
private readonly timerFunction: NodeJS.Timeout
constructor(pingInterval: number = 1000, disconnectedAfter: number = 5000, deadAfter: number = 20000) {
this.timerFunction = setInterval(() => {
//console.log(`Contexts: ${this.contexts.length} Last Heartbeats: `, this.heartbeatTimes())
this.contexts.forEach(async (sc) => {
const apps = await sc.getAllApps()
apps
.filter(app => (app.state == State.Connected) || (app.state == State.NotResponding))
.forEach(app => {
const now = new Date().getTime()
this.sendHeartbeat(sc, app)
// check when the last heartbeat happened
const lastHeartbeat = this.lastHeartbeats.get(app.instanceId!!)
const currentState = app.state
if (lastHeartbeat != undefined) {
const timeSinceLastHeartbeat = now - lastHeartbeat
if ((timeSinceLastHeartbeat < disconnectedAfter) && (currentState != State.Connected)) {
console.error(`Heartbeat from ${app.instanceId} for ${timeSinceLastHeartbeat}ms. App is considered connected.`)
sc.setAppState(app.instanceId!!, State.Connected)
} else if ((timeSinceLastHeartbeat > disconnectedAfter) && (currentState == State.Connected)) {
console.error(`No heartbeat from ${app.instanceId} for ${timeSinceLastHeartbeat}ms. App is considered not responding.`)
sc.setAppState(app.instanceId!!, State.NotResponding)
} else if ((timeSinceLastHeartbeat > deadAfter) && (currentState == State.NotResponding)) {
console.error(`No heartbeat from ${app.instanceId} for ${timeSinceLastHeartbeat}ms. App is considered terminated.`)
sc.setAppState(app.instanceId!!, State.Terminated)
} else {
// no action
}
} else {
// start the clock
this.lastHeartbeats.set(app.instanceId!!, now)
}
})
})
}, pingInterval)
}
heartbeatTimes(): HeartbeatDetails[] {
const now = new Date().getTime()
return Array.from(this.lastHeartbeats).map(e => {
return {
instanceId: e[0], time: now - e[1], state: convertToText(this.contexts.map(sc => sc.getInstanceDetails(e[0])).reduce((a, b) => a || b)?.state)
} as HeartbeatDetails
}).filter(e => e.state != "Terminated")
}
shutdown(): void {
clearInterval(this.timerFunction)
}
accept(msg: any, sc: ServerContext<AppRegistration>, from: InstanceID): void {
if (!this.contexts.includes(sc)) {
this.contexts.push(sc)
}
if (msg.type == 'heartbeatAcknowledgementRequest') {
const app = sc.getInstanceDetails(from)
if (app) {
this.lastHeartbeats.set(app.instanceId!!, new Date().getTime())
}
}
if (msg.type == 'WCP5Shutdown') {
const app = sc.getInstanceDetails(from)
if (app) {
sc.setAppState(from, State.Terminated)
}
}
}
async sendHeartbeat(sc: ServerContext<AppRegistration>, app: AppIdentifier): Promise<void> {
sc.post({
type: 'heartbeatEvent',
meta: {
timestamp: new Date(),
eventUuid: sc.createUUID(),
},
payload: {
}
} as BrowserTypes.HeartbeatEvent, app.instanceId!!)
}
}