herdr-remote-relay 0.2.3 → 0.2.5
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/package.json +1 -1
- package/src/metrics.js +32 -2
- package/src/relay-server.js +28 -3
package/package.json
CHANGED
package/src/metrics.js
CHANGED
|
@@ -12,6 +12,29 @@ function bytesPerSecond(current, previous, elapsedMs) {
|
|
|
12
12
|
return Math.max(0, (current - previous) * 1000 / elapsedMs);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* How many people are attached, rather than how many sockets are open.
|
|
17
|
+
*
|
|
18
|
+
* A phone and a laptop belonging to one person are two connections but one
|
|
19
|
+
* paired device each, and one person opening three browser windows is three
|
|
20
|
+
* connections and one device — so the distinct paired device is the closest
|
|
21
|
+
* thing the relay has to a user. Connections that predate device pairing carry
|
|
22
|
+
* no `deviceId`; they fall back to their own connection id so they still count
|
|
23
|
+
* once instead of collapsing into a single anonymous user.
|
|
24
|
+
*/
|
|
25
|
+
function countActiveUsers(clients = []) {
|
|
26
|
+
const identities = new Set();
|
|
27
|
+
for (const client of clients) {
|
|
28
|
+
if (!client) continue;
|
|
29
|
+
identities.add(
|
|
30
|
+
typeof client.deviceId === 'string' && client.deviceId
|
|
31
|
+
? `device:${client.deviceId}`
|
|
32
|
+
: `client:${client.id}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return identities.size;
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
class RelayMetrics {
|
|
16
39
|
constructor({ version = '0.1.0', protocolVersion = 1 } = {}) {
|
|
17
40
|
this.version = version;
|
|
@@ -143,7 +166,13 @@ class RelayMetrics {
|
|
|
143
166
|
};
|
|
144
167
|
}
|
|
145
168
|
|
|
146
|
-
|
|
169
|
+
/**
|
|
170
|
+
* `activeUserCount` is passed in rather than derived from `clients`: the
|
|
171
|
+
* public `/api/status` rows deliberately omit `deviceId`, so only the caller
|
|
172
|
+
* still holding the full connection records can count devices correctly.
|
|
173
|
+
* Omitting it falls back to counting whatever the given rows identify.
|
|
174
|
+
*/
|
|
175
|
+
snapshot({ clients = [], hosts = [], ptys = [], sample = true, scopeHostId = null, activeUserCount = null } = {}) {
|
|
147
176
|
const now = Date.now();
|
|
148
177
|
const elapsedMs = now - this.lastSample.at;
|
|
149
178
|
const throughput = {
|
|
@@ -181,6 +210,7 @@ class RelayMetrics {
|
|
|
181
210
|
clientCount: clients.length,
|
|
182
211
|
hostCount: hosts.length,
|
|
183
212
|
ptyCount: ptys.length,
|
|
213
|
+
activeUserCount: Number.isFinite(activeUserCount) ? activeUserCount : countActiveUsers(clients),
|
|
184
214
|
throughput: scopedThroughput,
|
|
185
215
|
cpu: {
|
|
186
216
|
load1m: finite(load[0]),
|
|
@@ -208,4 +238,4 @@ class RelayMetrics {
|
|
|
208
238
|
}
|
|
209
239
|
}
|
|
210
240
|
|
|
211
|
-
module.exports = { RelayMetrics };
|
|
241
|
+
module.exports = { RelayMetrics, countActiveUsers };
|
package/src/relay-server.js
CHANGED
|
@@ -9,7 +9,7 @@ const { URL } = require('node:url');
|
|
|
9
9
|
const { WebSocketServer, WebSocket } = require('ws');
|
|
10
10
|
const { loadRelayConfig, defaultStateDir, PACKAGE_ROOT } = require('./relay-config');
|
|
11
11
|
const { AuthStore } = require('./auth-store');
|
|
12
|
-
const { RelayMetrics } = require('./metrics');
|
|
12
|
+
const { RelayMetrics, countActiveUsers } = require('./metrics');
|
|
13
13
|
const { unpackStreamFrame, packStreamFrame, sanitizeTerminalPalette } = require('./stream-frame');
|
|
14
14
|
const { ensureDir } = require('./state');
|
|
15
15
|
|
|
@@ -1203,6 +1203,15 @@ class RelayServer {
|
|
|
1203
1203
|
bytesSent: client.bytesSent,
|
|
1204
1204
|
ip: client.ip,
|
|
1205
1205
|
}));
|
|
1206
|
+
// The roster is read once and used twice: the operator response carries it
|
|
1207
|
+
// whole, and every response carries the per-host tally derived from it. A
|
|
1208
|
+
// public caller learns how many devices a workstation has paired, never
|
|
1209
|
+
// which ones.
|
|
1210
|
+
const pairedDevices = this.auth.listDevices();
|
|
1211
|
+
const pairedPerHost = new Map();
|
|
1212
|
+
for (const device of pairedDevices) {
|
|
1213
|
+
pairedPerHost.set(device.hostId, (pairedPerHost.get(device.hostId) || 0) + 1);
|
|
1214
|
+
}
|
|
1206
1215
|
const hosts = scopedHosts.map((host) => ({
|
|
1207
1216
|
id: host.id,
|
|
1208
1217
|
hostname: host.hostname,
|
|
@@ -1211,6 +1220,12 @@ class RelayServer {
|
|
|
1211
1220
|
status: host.reconnecting ? 'reconnecting' : host.clients.size ? 'busy' : 'online',
|
|
1212
1221
|
connectedAt: host.connectedAt,
|
|
1213
1222
|
activePtyCount: host.ptys.length,
|
|
1223
|
+
// Distinct devices attached to this workstation, not open sockets: a
|
|
1224
|
+
// phone with two tabs open is one device on the operator's board.
|
|
1225
|
+
connectedDeviceCount: countActiveUsers(
|
|
1226
|
+
[...host.clients].map((id) => this.clients.get(id)).filter(Boolean)
|
|
1227
|
+
),
|
|
1228
|
+
pairedDeviceCount: pairedPerHost.get(host.id) || 0,
|
|
1214
1229
|
load: host.load,
|
|
1215
1230
|
}));
|
|
1216
1231
|
// The workstation counts one PTY per stream and cannot know how many
|
|
@@ -1225,9 +1240,19 @@ class RelayServer {
|
|
|
1225
1240
|
// The paired-device roster identifies people's hardware, so it is served to
|
|
1226
1241
|
// the relay operator only — never on /api/status, which any paired device
|
|
1227
1242
|
// may read.
|
|
1228
|
-
const devices = includeDevices ?
|
|
1243
|
+
const devices = includeDevices ? pairedDevices : undefined;
|
|
1229
1244
|
return {
|
|
1230
|
-
...this.metrics.snapshot({
|
|
1245
|
+
...this.metrics.snapshot({
|
|
1246
|
+
clients,
|
|
1247
|
+
hosts,
|
|
1248
|
+
ptys,
|
|
1249
|
+
sample,
|
|
1250
|
+
scopeHostId,
|
|
1251
|
+
// Counted from the unredacted records: the mapped rows above only carry
|
|
1252
|
+
// `deviceId` for the operator, and a public caller must still see the
|
|
1253
|
+
// same number of users the operator does.
|
|
1254
|
+
activeUserCount: countActiveUsers(scopedClients),
|
|
1255
|
+
}),
|
|
1231
1256
|
...(devices ? { devices } : {}),
|
|
1232
1257
|
relayMode: this.relayMode,
|
|
1233
1258
|
isRemoteRelay: this.relayMode === 'remote',
|