@linkshell/gateway 0.3.2 → 0.3.4

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/src/sessions.ts CHANGED
@@ -33,12 +33,16 @@ export interface HostDevice {
33
33
  }
34
34
 
35
35
  const OUTPUT_BUFFER_CAPACITY = 200;
36
+ const OUTPUT_BUFFER_MAX_PAYLOAD_BYTES = Number(
37
+ process.env.OUTPUT_BUFFER_MAX_PAYLOAD_BYTES ?? 64 * 1024,
38
+ );
36
39
  const HOST_RECONNECT_WINDOW = 60_000;
37
40
  const CLEANUP_INTERVAL = 30_000;
38
41
 
39
42
  export class DeviceManager {
40
43
  private devices = new Map<string, HostDevice>();
41
44
  private cleanupTimer: ReturnType<typeof setInterval>;
45
+ private droppedClients = 0;
42
46
 
43
47
  constructor() {
44
48
  this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL);
@@ -106,7 +110,7 @@ export class DeviceManager {
106
110
  removeClient(hostDeviceId: string, deviceId: string): void {
107
111
  const device = this.devices.get(hostDeviceId);
108
112
  if (!device) return;
109
- device.clients.delete(deviceId);
113
+ if (device.clients.delete(deviceId)) this.droppedClients++;
110
114
  if (device.controllerId === deviceId) {
111
115
  const next = device.clients.keys().next();
112
116
  device.controllerId = next.done ? undefined : next.value;
@@ -126,6 +130,7 @@ export class DeviceManager {
126
130
  device.clients.delete(deviceId);
127
131
  closed++;
128
132
  }
133
+ this.droppedClients += closed;
129
134
  if (device.controllerId && !device.clients.has(device.controllerId)) {
130
135
  const next = device.clients.keys().next();
131
136
  device.controllerId = next.done ? undefined : next.value;
@@ -154,6 +159,14 @@ export class DeviceManager {
154
159
  bufferOutput(hostDeviceId: string, envelope: Envelope): void {
155
160
  const device = this.devices.get(hostDeviceId);
156
161
  if (!device) return;
162
+ const payload = envelope.payload as { data?: unknown } | undefined;
163
+ if (
164
+ typeof payload?.data === "string" &&
165
+ Buffer.byteLength(payload.data, "utf8") > OUTPUT_BUFFER_MAX_PAYLOAD_BYTES
166
+ ) {
167
+ device.lastActivity = Date.now();
168
+ return;
169
+ }
157
170
  const terminalId = envelope.terminalId ?? "default";
158
171
  let buffer = device.outputBuffers.get(terminalId);
159
172
  if (!buffer) {
@@ -257,6 +270,29 @@ export class DeviceManager {
257
270
  };
258
271
  }
259
272
 
273
+ getStats() {
274
+ let clientCount = 0;
275
+ let bufferedTerminalFrames = 0;
276
+ let hostAbsentDevices = 0;
277
+ let terminalCount = 0;
278
+ for (const device of this.devices.values()) {
279
+ clientCount += device.clients.size;
280
+ terminalCount += device.outputBuffers.size;
281
+ bufferedTerminalFrames += [...device.outputBuffers.values()].reduce((sum, buf) => sum + buf.length, 0);
282
+ if (!device.host || device.host.socket.readyState !== device.host.socket.OPEN) hostAbsentDevices++;
283
+ }
284
+ return {
285
+ devices: this.devices.size,
286
+ activeDevices: this.listActive().length,
287
+ clients: clientCount,
288
+ droppedClients: this.droppedClients,
289
+ hostAbsentDevices,
290
+ terminalsWithReplay: terminalCount,
291
+ bufferedTerminalFrames,
292
+ bufferedAgentFrames: 0,
293
+ };
294
+ }
295
+
260
296
  setMetadata(
261
297
  hostDeviceId: string,
262
298
  _provider?: string,