@mindexec/cli 0.2.135 → 0.2.136
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/scripts/remote-frame-ws-smoke.mjs +14 -0
- package/server.js +133 -5
package/package.json
CHANGED
|
@@ -184,6 +184,20 @@ async function main() {
|
|
|
184
184
|
assert.ok(frame.metadata.frameSeq > 0, 'frameSeq should be positive');
|
|
185
185
|
assert.equal(frame.metadata.mimeType, 'image/png');
|
|
186
186
|
assert.ok(frame.payload.length > 0, 'payload should be non-empty');
|
|
187
|
+
|
|
188
|
+
const frameStatus = await fetchJson(`${bridge.baseUrl}/api/status?remoteFrames=ws`);
|
|
189
|
+
assert.equal(frameStatus.ok, true);
|
|
190
|
+
assert.ok(frameStatus.payload?.remoteFrameWs?.clientCount >= 1, JSON.stringify(frameStatus.payload?.remoteFrameWs));
|
|
191
|
+
assert.ok(frameStatus.payload?.remoteFrameWs?.framesSent >= 1, JSON.stringify(frameStatus.payload?.remoteFrameWs));
|
|
192
|
+
assert.equal(frameStatus.payload?.remoteFrameWs?.lastFrameDeviceId, device.deviceId);
|
|
193
|
+
assert.equal(frameStatus.payload?.remoteFrameWs?.lastFrameSeq, frame.metadata.frameSeq);
|
|
194
|
+
assert.ok(
|
|
195
|
+
frameStatus.payload?.remoteFrameWs?.clients?.some(client =>
|
|
196
|
+
Array.isArray(client.deviceIds)
|
|
197
|
+
&& client.deviceIds.includes(device.deviceId)
|
|
198
|
+
&& client.autoStartLive === true
|
|
199
|
+
&& client.sent >= 1),
|
|
200
|
+
JSON.stringify(frameStatus.payload?.remoteFrameWs));
|
|
187
201
|
ws.close();
|
|
188
202
|
|
|
189
203
|
console.log('Remote frame WebSocket smoke OK');
|
package/server.js
CHANGED
|
@@ -2128,6 +2128,35 @@ const remoteFrameWss = new WebSocketServer({ noServer: true });
|
|
|
2128
2128
|
const wsClients = new Set();
|
|
2129
2129
|
const remoteFrameClients = new Set();
|
|
2130
2130
|
const shellJobs = new Map();
|
|
2131
|
+
let remoteFrameClientSeq = 0;
|
|
2132
|
+
const remoteFrameWsDiagnostics = {
|
|
2133
|
+
opened: 0,
|
|
2134
|
+
closed: 0,
|
|
2135
|
+
errors: 0,
|
|
2136
|
+
framesReceived: 0,
|
|
2137
|
+
framesSent: 0,
|
|
2138
|
+
framesDropped: 0,
|
|
2139
|
+
framesInvalid: 0,
|
|
2140
|
+
framesNoClients: 0,
|
|
2141
|
+
framesNoSubscribers: 0,
|
|
2142
|
+
autoStartRequested: 0,
|
|
2143
|
+
autoStartStarted: 0,
|
|
2144
|
+
autoStartSkipped: 0,
|
|
2145
|
+
lastOpenAt: '',
|
|
2146
|
+
lastCloseAt: '',
|
|
2147
|
+
lastErrorAt: '',
|
|
2148
|
+
lastFrameAt: '',
|
|
2149
|
+
lastFrameDeviceId: '',
|
|
2150
|
+
lastFrameSeq: 0,
|
|
2151
|
+
lastFrameBytes: 0,
|
|
2152
|
+
lastFrameMatchedClients: 0,
|
|
2153
|
+
lastFrameSentClients: 0,
|
|
2154
|
+
lastFrameDroppedClients: 0,
|
|
2155
|
+
lastAutoStartAt: '',
|
|
2156
|
+
lastAutoStartRequested: 0,
|
|
2157
|
+
lastAutoStartStarted: 0,
|
|
2158
|
+
lastAutoStartSkipped: 0
|
|
2159
|
+
};
|
|
2131
2160
|
const REMOTE_FRAME_WS_AUTO_START_LIMIT = 120;
|
|
2132
2161
|
const REMOTE_FRAME_WS_DEFAULT_FPS = 12;
|
|
2133
2162
|
const REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH = 960;
|
|
@@ -2308,9 +2337,20 @@ function maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds = []) {
|
|
|
2308
2337
|
}
|
|
2309
2338
|
|
|
2310
2339
|
if (started.length > 0 || skipped.length > 0) {
|
|
2340
|
+
const now = new Date().toISOString();
|
|
2341
|
+
ws.remoteFrameLastAutoStartAt = now;
|
|
2342
|
+
ws.remoteFrameLastAutoStartStarted = started.length;
|
|
2343
|
+
ws.remoteFrameLastAutoStartSkipped = skipped.length;
|
|
2344
|
+
remoteFrameWsDiagnostics.autoStartRequested += Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT);
|
|
2345
|
+
remoteFrameWsDiagnostics.autoStartStarted += started.length;
|
|
2346
|
+
remoteFrameWsDiagnostics.autoStartSkipped += skipped.length;
|
|
2347
|
+
remoteFrameWsDiagnostics.lastAutoStartAt = now;
|
|
2348
|
+
remoteFrameWsDiagnostics.lastAutoStartRequested = Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT);
|
|
2349
|
+
remoteFrameWsDiagnostics.lastAutoStartStarted = started.length;
|
|
2350
|
+
remoteFrameWsDiagnostics.lastAutoStartSkipped = skipped.length;
|
|
2311
2351
|
sendRemoteFrameClientJson(ws, {
|
|
2312
2352
|
type: 'RemoteFrameLiveAutoStart',
|
|
2313
|
-
timestamp:
|
|
2353
|
+
timestamp: now,
|
|
2314
2354
|
requested: Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT),
|
|
2315
2355
|
started,
|
|
2316
2356
|
skipped: skipped.slice(0, 24),
|
|
@@ -2352,20 +2392,36 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
|
2352
2392
|
}
|
|
2353
2393
|
|
|
2354
2394
|
function broadcastRemoteBinaryFrame(frameEvent) {
|
|
2395
|
+
const frame = frameEvent?.frame || {};
|
|
2396
|
+
remoteFrameWsDiagnostics.framesReceived += 1;
|
|
2397
|
+
remoteFrameWsDiagnostics.lastFrameAt = new Date().toISOString();
|
|
2398
|
+
remoteFrameWsDiagnostics.lastFrameDeviceId = String(frameEvent?.deviceId || frame.deviceId || '').trim();
|
|
2399
|
+
remoteFrameWsDiagnostics.lastFrameSeq = Number(frame.frameSeq || 0) || 0;
|
|
2400
|
+
remoteFrameWsDiagnostics.lastFrameBytes = Number(frameEvent?.byteLength || frameEvent?.payload?.length || 0) || 0;
|
|
2401
|
+
remoteFrameWsDiagnostics.lastFrameMatchedClients = 0;
|
|
2402
|
+
remoteFrameWsDiagnostics.lastFrameSentClients = 0;
|
|
2403
|
+
remoteFrameWsDiagnostics.lastFrameDroppedClients = 0;
|
|
2404
|
+
|
|
2355
2405
|
if (remoteFrameClients.size === 0) {
|
|
2406
|
+
remoteFrameWsDiagnostics.framesNoClients += 1;
|
|
2356
2407
|
return;
|
|
2357
2408
|
}
|
|
2358
2409
|
|
|
2359
|
-
const deviceId =
|
|
2410
|
+
const deviceId = remoteFrameWsDiagnostics.lastFrameDeviceId;
|
|
2360
2411
|
if (!deviceId) {
|
|
2412
|
+
remoteFrameWsDiagnostics.framesInvalid += 1;
|
|
2361
2413
|
return;
|
|
2362
2414
|
}
|
|
2363
2415
|
|
|
2364
2416
|
const packet = buildRemoteFrameBinaryPacket(frameEvent);
|
|
2365
2417
|
if (!packet) {
|
|
2418
|
+
remoteFrameWsDiagnostics.framesInvalid += 1;
|
|
2366
2419
|
return;
|
|
2367
2420
|
}
|
|
2368
2421
|
|
|
2422
|
+
let matched = 0;
|
|
2423
|
+
let sent = 0;
|
|
2424
|
+
let dropped = 0;
|
|
2369
2425
|
for (const client of remoteFrameClients) {
|
|
2370
2426
|
if (client.readyState !== 1) {
|
|
2371
2427
|
continue;
|
|
@@ -2376,23 +2432,85 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
2376
2432
|
continue;
|
|
2377
2433
|
}
|
|
2378
2434
|
|
|
2435
|
+
matched += 1;
|
|
2379
2436
|
if (client.bufferedAmount > 8 * 1024 * 1024) {
|
|
2380
2437
|
client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
|
|
2438
|
+
client.remoteFrameLastDropAt = new Date().toISOString();
|
|
2439
|
+
remoteFrameWsDiagnostics.framesDropped += 1;
|
|
2440
|
+
dropped += 1;
|
|
2381
2441
|
continue;
|
|
2382
2442
|
}
|
|
2383
2443
|
|
|
2384
2444
|
try {
|
|
2385
2445
|
client.send(packet, { binary: true });
|
|
2386
2446
|
client.remoteFrameSentCount = (client.remoteFrameSentCount || 0) + 1;
|
|
2447
|
+
client.remoteFrameLastSentAt = new Date().toISOString();
|
|
2448
|
+
client.remoteFrameLastDeviceId = deviceId;
|
|
2449
|
+
client.remoteFrameLastSeq = remoteFrameWsDiagnostics.lastFrameSeq;
|
|
2450
|
+
remoteFrameWsDiagnostics.framesSent += 1;
|
|
2451
|
+
sent += 1;
|
|
2387
2452
|
} catch {
|
|
2388
2453
|
client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
|
|
2454
|
+
client.remoteFrameLastDropAt = new Date().toISOString();
|
|
2455
|
+
remoteFrameWsDiagnostics.framesDropped += 1;
|
|
2456
|
+
dropped += 1;
|
|
2389
2457
|
}
|
|
2390
2458
|
}
|
|
2459
|
+
|
|
2460
|
+
remoteFrameWsDiagnostics.lastFrameMatchedClients = matched;
|
|
2461
|
+
remoteFrameWsDiagnostics.lastFrameSentClients = sent;
|
|
2462
|
+
remoteFrameWsDiagnostics.lastFrameDroppedClients = dropped;
|
|
2463
|
+
if (matched === 0) {
|
|
2464
|
+
remoteFrameWsDiagnostics.framesNoSubscribers += 1;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
function serializeRemoteFrameWsDiagnostics() {
|
|
2469
|
+
const clients = [];
|
|
2470
|
+
for (const client of remoteFrameClients) {
|
|
2471
|
+
const deviceIds = client.remoteFrameDeviceIds instanceof Set
|
|
2472
|
+
? Array.from(client.remoteFrameDeviceIds)
|
|
2473
|
+
: [];
|
|
2474
|
+
clients.push({
|
|
2475
|
+
id: client.remoteFrameClientId || '',
|
|
2476
|
+
readyState: client.readyState,
|
|
2477
|
+
connectedAt: client.remoteFrameConnectedAt || '',
|
|
2478
|
+
subscribedAt: client.remoteFrameSubscriptionUpdatedAt || '',
|
|
2479
|
+
deviceCount: deviceIds.length,
|
|
2480
|
+
deviceIds: deviceIds.slice(0, 24),
|
|
2481
|
+
autoStartLive: client.remoteFrameAutoStartLive === true,
|
|
2482
|
+
autoStartOptions: client.remoteFrameAutoStartOptions || null,
|
|
2483
|
+
sent: Number(client.remoteFrameSentCount || 0),
|
|
2484
|
+
dropped: Number(client.remoteFrameDroppedCount || 0),
|
|
2485
|
+
bufferedAmount: Number(client.bufferedAmount || 0),
|
|
2486
|
+
lastSentAt: client.remoteFrameLastSentAt || '',
|
|
2487
|
+
lastDropAt: client.remoteFrameLastDropAt || '',
|
|
2488
|
+
lastDeviceId: client.remoteFrameLastDeviceId || '',
|
|
2489
|
+
lastFrameSeq: Number(client.remoteFrameLastSeq || 0) || 0,
|
|
2490
|
+
lastAutoStartAt: client.remoteFrameLastAutoStartAt || '',
|
|
2491
|
+
lastAutoStartStarted: Number(client.remoteFrameLastAutoStartStarted || 0),
|
|
2492
|
+
lastAutoStartSkipped: Number(client.remoteFrameLastAutoStartSkipped || 0)
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
return {
|
|
2497
|
+
protocol: 'mindexec.remote.frames.binary.v1',
|
|
2498
|
+
path: '/api/remote/frames/ws',
|
|
2499
|
+
clientCount: remoteFrameClients.size,
|
|
2500
|
+
...remoteFrameWsDiagnostics,
|
|
2501
|
+
clients
|
|
2502
|
+
};
|
|
2391
2503
|
}
|
|
2392
2504
|
|
|
2393
2505
|
remoteFrameWss.on('connection', (ws, req) => {
|
|
2394
2506
|
remoteFrameClients.add(ws);
|
|
2395
2507
|
ws.binaryType = 'arraybuffer';
|
|
2508
|
+
ws.remoteFrameClientId = `rfws-${++remoteFrameClientSeq}`;
|
|
2509
|
+
ws.remoteFrameConnectedAt = new Date().toISOString();
|
|
2510
|
+
ws.remoteFrameSentCount = 0;
|
|
2511
|
+
ws.remoteFrameDroppedCount = 0;
|
|
2512
|
+
remoteFrameWsDiagnostics.opened += 1;
|
|
2513
|
+
remoteFrameWsDiagnostics.lastOpenAt = ws.remoteFrameConnectedAt;
|
|
2396
2514
|
try {
|
|
2397
2515
|
const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
2398
2516
|
updateRemoteFrameClientSubscription(ws, {
|
|
@@ -2416,12 +2534,21 @@ remoteFrameWss.on('connection', (ws, req) => {
|
|
|
2416
2534
|
});
|
|
2417
2535
|
}
|
|
2418
2536
|
});
|
|
2419
|
-
ws.on('close', () =>
|
|
2420
|
-
|
|
2537
|
+
ws.on('close', () => {
|
|
2538
|
+
remoteFrameClients.delete(ws);
|
|
2539
|
+
remoteFrameWsDiagnostics.closed += 1;
|
|
2540
|
+
remoteFrameWsDiagnostics.lastCloseAt = new Date().toISOString();
|
|
2541
|
+
});
|
|
2542
|
+
ws.on('error', () => {
|
|
2543
|
+
remoteFrameClients.delete(ws);
|
|
2544
|
+
remoteFrameWsDiagnostics.errors += 1;
|
|
2545
|
+
remoteFrameWsDiagnostics.lastErrorAt = new Date().toISOString();
|
|
2546
|
+
});
|
|
2421
2547
|
sendRemoteFrameClientJson(ws, {
|
|
2422
2548
|
type: 'RemoteFrameSocketReady',
|
|
2423
2549
|
timestamp: new Date().toISOString(),
|
|
2424
|
-
protocol: 'mindexec.remote.frames.binary.v1'
|
|
2550
|
+
protocol: 'mindexec.remote.frames.binary.v1',
|
|
2551
|
+
clientId: ws.remoteFrameClientId
|
|
2425
2552
|
});
|
|
2426
2553
|
});
|
|
2427
2554
|
|
|
@@ -9286,6 +9413,7 @@ app.get('/api/status', async (req, res) => {
|
|
|
9286
9413
|
bridgeTokenHeader,
|
|
9287
9414
|
bridgeAuthRequired,
|
|
9288
9415
|
remoteHub: remoteHub.getStatus({ includeSecrets: false }),
|
|
9416
|
+
remoteFrameWs: serializeRemoteFrameWsDiagnostics(),
|
|
9289
9417
|
remoteAgent: serializeRemoteAgentState(),
|
|
9290
9418
|
remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
|
|
9291
9419
|
shellJobsPath: '/api/shell/jobs',
|