@mindexec/cli 0.2.134 → 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-fast-mdm-browser-smoke.mjs +13 -17
- package/scripts/remote-frame-ws-smoke.mjs +25 -9
- package/server.js +227 -5
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +50 -4
- package/wwwroot/service-worker-assets.js +2 -2
- package/wwwroot/service-worker.js +1 -1
package/package.json
CHANGED
|
@@ -161,14 +161,14 @@ async function launchBrowser(chromium) {
|
|
|
161
161
|
throw new Error(`Unable to launch a Chromium browser: ${lastError?.message || 'unknown error'}`);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
async function paintRemoteFrameInBrowser(page, wsUrl, deviceId) {
|
|
164
|
+
async function paintRemoteFrameInBrowser(page, wsUrl, deviceId, fps = REQUESTED_FPS) {
|
|
165
165
|
await page.setContent(`
|
|
166
166
|
<!doctype html>
|
|
167
167
|
<meta charset="utf-8">
|
|
168
168
|
<canvas id="screen" width="320" height="180"></canvas>
|
|
169
169
|
`);
|
|
170
170
|
|
|
171
|
-
return await page.evaluate(async ({ wsUrl, deviceId }) => {
|
|
171
|
+
return await page.evaluate(async ({ wsUrl, deviceId, fps }) => {
|
|
172
172
|
const canvas = document.getElementById('screen');
|
|
173
173
|
const context = canvas.getContext('2d', { alpha: false, desynchronized: true }) || canvas.getContext('2d');
|
|
174
174
|
if (!context) {
|
|
@@ -203,7 +203,15 @@ async function paintRemoteFrameInBrowser(page, wsUrl, deviceId) {
|
|
|
203
203
|
const ws = new WebSocket(wsUrl);
|
|
204
204
|
ws.binaryType = 'arraybuffer';
|
|
205
205
|
ws.onopen = () => {
|
|
206
|
-
ws.send(JSON.stringify({
|
|
206
|
+
ws.send(JSON.stringify({
|
|
207
|
+
type: 'subscribe',
|
|
208
|
+
deviceIds: [deviceId],
|
|
209
|
+
autoStartLive: true,
|
|
210
|
+
fps,
|
|
211
|
+
maxWidth: 960,
|
|
212
|
+
maxHeight: 540,
|
|
213
|
+
quality: 60
|
|
214
|
+
}));
|
|
207
215
|
};
|
|
208
216
|
ws.onerror = () => {
|
|
209
217
|
clearTimeout(timeout);
|
|
@@ -262,7 +270,7 @@ async function paintRemoteFrameInBrowser(page, wsUrl, deviceId) {
|
|
|
262
270
|
}
|
|
263
271
|
};
|
|
264
272
|
});
|
|
265
|
-
}, { wsUrl, deviceId });
|
|
273
|
+
}, { wsUrl, deviceId, fps });
|
|
266
274
|
}
|
|
267
275
|
|
|
268
276
|
async function main() {
|
|
@@ -320,23 +328,11 @@ async function main() {
|
|
|
320
328
|
&& item.capabilities?.binaryFrames === true) || null;
|
|
321
329
|
}, 15000, `RemoteFast device registration\n${hostBridge.details()}\n${clientBridge.details()}`);
|
|
322
330
|
|
|
323
|
-
const live = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices/${encodeURIComponent(device.deviceId)}/live/start`, {
|
|
324
|
-
method: 'POST',
|
|
325
|
-
body: JSON.stringify({
|
|
326
|
-
fps: REQUESTED_FPS,
|
|
327
|
-
maxWidth: 960,
|
|
328
|
-
maxHeight: 540,
|
|
329
|
-
quality: 60
|
|
330
|
-
})
|
|
331
|
-
});
|
|
332
|
-
assert.equal(live.ok, true, JSON.stringify(live.payload));
|
|
333
|
-
assert.equal(live.payload?.ok, true, JSON.stringify(live.payload));
|
|
334
|
-
|
|
335
331
|
const chromium = await loadPlaywrightChromium();
|
|
336
332
|
browser = await launchBrowser(chromium);
|
|
337
333
|
const page = await browser.newPage();
|
|
338
334
|
const wsUrl = `${hostBridge.baseUrl.replace(/^http:/, 'ws:')}/api/remote/frames/ws?token=${encodeURIComponent(BRIDGE_TOKEN)}`;
|
|
339
|
-
const painted = await paintRemoteFrameInBrowser(page, wsUrl, device.deviceId);
|
|
335
|
+
const painted = await paintRemoteFrameInBrowser(page, wsUrl, device.deviceId, REQUESTED_FPS);
|
|
340
336
|
assert.ok(painted.frameSeq > 0, JSON.stringify(painted));
|
|
341
337
|
assert.equal(painted.fps, REQUESTED_FPS, JSON.stringify(painted));
|
|
342
338
|
assert.ok(painted.width > 0 && painted.height > 0, JSON.stringify(painted));
|
|
@@ -151,7 +151,7 @@ async function main() {
|
|
|
151
151
|
|
|
152
152
|
const seed = await fetchJson(`${bridge.baseUrl}/api/remote/synthetic/seed`, {
|
|
153
153
|
method: 'POST',
|
|
154
|
-
body: JSON.stringify({ count: 4, connectedRatio: 1,
|
|
154
|
+
body: JSON.stringify({ count: 4, connectedRatio: 1, thumbnailRatio: 1, liveCount: 0 })
|
|
155
155
|
});
|
|
156
156
|
assert.equal(seed.ok, true);
|
|
157
157
|
|
|
@@ -165,23 +165,39 @@ async function main() {
|
|
|
165
165
|
ws.once('open', resolve);
|
|
166
166
|
ws.once('error', reject);
|
|
167
167
|
});
|
|
168
|
-
ws.send(JSON.stringify({
|
|
168
|
+
ws.send(JSON.stringify({
|
|
169
|
+
type: 'subscribe',
|
|
170
|
+
deviceIds: [device.deviceId],
|
|
171
|
+
autoStartLive: true,
|
|
172
|
+
fps: 12,
|
|
173
|
+
maxWidth: 960,
|
|
174
|
+
maxHeight: 540,
|
|
175
|
+
quality: 60
|
|
176
|
+
}));
|
|
169
177
|
|
|
170
178
|
const framePromise = waitForBinaryFrame(ws);
|
|
171
|
-
const live = await fetchJson(`${bridge.baseUrl}/api/remote/devices/${encodeURIComponent(device.deviceId)}/live/start`, {
|
|
172
|
-
method: 'POST',
|
|
173
|
-
body: JSON.stringify({ fps: 20 })
|
|
174
|
-
});
|
|
175
|
-
assert.equal(live.ok, true);
|
|
176
|
-
|
|
177
179
|
const frame = await framePromise;
|
|
178
180
|
assert.equal(frame.metadata.type, 'remote.frame.binary');
|
|
179
181
|
assert.equal(frame.metadata.kind, 'live');
|
|
180
182
|
assert.equal(frame.metadata.deviceId, device.deviceId);
|
|
181
|
-
assert.equal(frame.metadata.fps,
|
|
183
|
+
assert.equal(frame.metadata.fps, 12);
|
|
182
184
|
assert.ok(frame.metadata.frameSeq > 0, 'frameSeq should be positive');
|
|
183
185
|
assert.equal(frame.metadata.mimeType, 'image/png');
|
|
184
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));
|
|
185
201
|
ws.close();
|
|
186
202
|
|
|
187
203
|
console.log('Remote frame WebSocket smoke OK');
|
package/server.js
CHANGED
|
@@ -2128,6 +2128,40 @@ 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
|
+
};
|
|
2160
|
+
const REMOTE_FRAME_WS_AUTO_START_LIMIT = 120;
|
|
2161
|
+
const REMOTE_FRAME_WS_DEFAULT_FPS = 12;
|
|
2162
|
+
const REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH = 960;
|
|
2163
|
+
const REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT = 540;
|
|
2164
|
+
const REMOTE_FRAME_WS_DEFAULT_QUALITY = 60;
|
|
2131
2165
|
|
|
2132
2166
|
httpServer.on('upgrade', (req, socket, head) => {
|
|
2133
2167
|
try {
|
|
@@ -2217,12 +2251,112 @@ function updateRemoteFrameClientSubscription(ws, payload = {}) {
|
|
|
2217
2251
|
const deviceIds = normalizeRemoteFrameWsDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
|
|
2218
2252
|
ws.remoteFrameDeviceIds = new Set(deviceIds);
|
|
2219
2253
|
ws.remoteFrameSubscriptionUpdatedAt = new Date().toISOString();
|
|
2254
|
+
ws.remoteFrameAutoStartLive = /^(1|true|yes|on|live)$/i.test(String(payload.autoStartLive ?? payload.startLive ?? ''));
|
|
2255
|
+
ws.remoteFrameAutoStartOptions = normalizeRemoteFrameWsLiveOptions(payload);
|
|
2220
2256
|
sendRemoteFrameClientJson(ws, {
|
|
2221
2257
|
type: 'RemoteFrameSubscription',
|
|
2222
2258
|
timestamp: ws.remoteFrameSubscriptionUpdatedAt,
|
|
2223
2259
|
deviceIds,
|
|
2224
|
-
mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices'
|
|
2260
|
+
mode: deviceIds.length > 0 ? 'selected-devices' : 'all-devices',
|
|
2261
|
+
autoStartLive: ws.remoteFrameAutoStartLive === true
|
|
2225
2262
|
});
|
|
2263
|
+
maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
function clampRemoteFrameWsNumber(value, min, max, fallback) {
|
|
2267
|
+
const number = Number(value);
|
|
2268
|
+
if (!Number.isFinite(number)) {
|
|
2269
|
+
return fallback;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
return Math.max(min, Math.min(max, Math.round(number)));
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
function normalizeRemoteFrameWsLiveOptions(payload = {}) {
|
|
2276
|
+
return {
|
|
2277
|
+
fps: clampRemoteFrameWsNumber(payload.fps, 1, 24, REMOTE_FRAME_WS_DEFAULT_FPS),
|
|
2278
|
+
maxWidth: clampRemoteFrameWsNumber(payload.maxWidth, 320, 2560, REMOTE_FRAME_WS_DEFAULT_MAX_WIDTH),
|
|
2279
|
+
maxHeight: clampRemoteFrameWsNumber(payload.maxHeight, 180, 1440, REMOTE_FRAME_WS_DEFAULT_MAX_HEIGHT),
|
|
2280
|
+
quality: clampRemoteFrameWsNumber(payload.quality, 20, 95, REMOTE_FRAME_WS_DEFAULT_QUALITY),
|
|
2281
|
+
mode: String(payload.mode || 'remote-fast').trim() || 'remote-fast'
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
function getRemoteFrameWsDeviceLookup() {
|
|
2286
|
+
const devices = remoteHub.listDevices({ includeDataUrl: false });
|
|
2287
|
+
return new Map(devices.map(device => [String(device?.deviceId || '').trim(), device]).filter(([id]) => id));
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
function maybeAutoStartRemoteFrameLiveStreams(ws, deviceIds = []) {
|
|
2291
|
+
if (!ws || ws.remoteFrameAutoStartLive !== true || !Array.isArray(deviceIds) || deviceIds.length === 0) {
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
const options = ws.remoteFrameAutoStartOptions || normalizeRemoteFrameWsLiveOptions();
|
|
2296
|
+
const lookup = getRemoteFrameWsDeviceLookup();
|
|
2297
|
+
const started = [];
|
|
2298
|
+
const skipped = [];
|
|
2299
|
+
for (const deviceId of deviceIds.slice(0, REMOTE_FRAME_WS_AUTO_START_LIMIT)) {
|
|
2300
|
+
const device = lookup.get(String(deviceId || '').trim());
|
|
2301
|
+
if (!device?.connected) {
|
|
2302
|
+
skipped.push({ deviceId, reason: 'not-connected' });
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
if (device.liveStreamActive === true) {
|
|
2307
|
+
skipped.push({ deviceId, reason: 'already-live' });
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
const liveCapable = device.liveStreamEnabled === true
|
|
2312
|
+
|| device.liveStreamCapable === true
|
|
2313
|
+
|| device.capabilities?.liveStream === true
|
|
2314
|
+
|| device.capabilities?.stream === true
|
|
2315
|
+
|| device.capabilities?.screenStream === true;
|
|
2316
|
+
if (!liveCapable) {
|
|
2317
|
+
skipped.push({ deviceId, reason: 'live-unavailable' });
|
|
2318
|
+
continue;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
const result = remoteHub.startLiveStream(deviceId, {
|
|
2322
|
+
...options,
|
|
2323
|
+
streamId: `mdm-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
|
|
2324
|
+
});
|
|
2325
|
+
if (result?.ok === true) {
|
|
2326
|
+
started.push({
|
|
2327
|
+
deviceId,
|
|
2328
|
+
streamId: result.streamId || '',
|
|
2329
|
+
fps: result.fps || options.fps
|
|
2330
|
+
});
|
|
2331
|
+
} else {
|
|
2332
|
+
skipped.push({
|
|
2333
|
+
deviceId,
|
|
2334
|
+
reason: result?.error || 'start-failed'
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
|
|
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;
|
|
2351
|
+
sendRemoteFrameClientJson(ws, {
|
|
2352
|
+
type: 'RemoteFrameLiveAutoStart',
|
|
2353
|
+
timestamp: now,
|
|
2354
|
+
requested: Math.min(deviceIds.length, REMOTE_FRAME_WS_AUTO_START_LIMIT),
|
|
2355
|
+
started,
|
|
2356
|
+
skipped: skipped.slice(0, 24),
|
|
2357
|
+
options
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2226
2360
|
}
|
|
2227
2361
|
|
|
2228
2362
|
function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
@@ -2258,20 +2392,36 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
|
|
|
2258
2392
|
}
|
|
2259
2393
|
|
|
2260
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
|
+
|
|
2261
2405
|
if (remoteFrameClients.size === 0) {
|
|
2406
|
+
remoteFrameWsDiagnostics.framesNoClients += 1;
|
|
2262
2407
|
return;
|
|
2263
2408
|
}
|
|
2264
2409
|
|
|
2265
|
-
const deviceId =
|
|
2410
|
+
const deviceId = remoteFrameWsDiagnostics.lastFrameDeviceId;
|
|
2266
2411
|
if (!deviceId) {
|
|
2412
|
+
remoteFrameWsDiagnostics.framesInvalid += 1;
|
|
2267
2413
|
return;
|
|
2268
2414
|
}
|
|
2269
2415
|
|
|
2270
2416
|
const packet = buildRemoteFrameBinaryPacket(frameEvent);
|
|
2271
2417
|
if (!packet) {
|
|
2418
|
+
remoteFrameWsDiagnostics.framesInvalid += 1;
|
|
2272
2419
|
return;
|
|
2273
2420
|
}
|
|
2274
2421
|
|
|
2422
|
+
let matched = 0;
|
|
2423
|
+
let sent = 0;
|
|
2424
|
+
let dropped = 0;
|
|
2275
2425
|
for (const client of remoteFrameClients) {
|
|
2276
2426
|
if (client.readyState !== 1) {
|
|
2277
2427
|
continue;
|
|
@@ -2282,23 +2432,85 @@ function broadcastRemoteBinaryFrame(frameEvent) {
|
|
|
2282
2432
|
continue;
|
|
2283
2433
|
}
|
|
2284
2434
|
|
|
2435
|
+
matched += 1;
|
|
2285
2436
|
if (client.bufferedAmount > 8 * 1024 * 1024) {
|
|
2286
2437
|
client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
|
|
2438
|
+
client.remoteFrameLastDropAt = new Date().toISOString();
|
|
2439
|
+
remoteFrameWsDiagnostics.framesDropped += 1;
|
|
2440
|
+
dropped += 1;
|
|
2287
2441
|
continue;
|
|
2288
2442
|
}
|
|
2289
2443
|
|
|
2290
2444
|
try {
|
|
2291
2445
|
client.send(packet, { binary: true });
|
|
2292
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;
|
|
2293
2452
|
} catch {
|
|
2294
2453
|
client.remoteFrameDroppedCount = (client.remoteFrameDroppedCount || 0) + 1;
|
|
2454
|
+
client.remoteFrameLastDropAt = new Date().toISOString();
|
|
2455
|
+
remoteFrameWsDiagnostics.framesDropped += 1;
|
|
2456
|
+
dropped += 1;
|
|
2295
2457
|
}
|
|
2296
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
|
+
};
|
|
2297
2503
|
}
|
|
2298
2504
|
|
|
2299
2505
|
remoteFrameWss.on('connection', (ws, req) => {
|
|
2300
2506
|
remoteFrameClients.add(ws);
|
|
2301
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;
|
|
2302
2514
|
try {
|
|
2303
2515
|
const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
2304
2516
|
updateRemoteFrameClientSubscription(ws, {
|
|
@@ -2322,12 +2534,21 @@ remoteFrameWss.on('connection', (ws, req) => {
|
|
|
2322
2534
|
});
|
|
2323
2535
|
}
|
|
2324
2536
|
});
|
|
2325
|
-
ws.on('close', () =>
|
|
2326
|
-
|
|
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
|
+
});
|
|
2327
2547
|
sendRemoteFrameClientJson(ws, {
|
|
2328
2548
|
type: 'RemoteFrameSocketReady',
|
|
2329
2549
|
timestamp: new Date().toISOString(),
|
|
2330
|
-
protocol: 'mindexec.remote.frames.binary.v1'
|
|
2550
|
+
protocol: 'mindexec.remote.frames.binary.v1',
|
|
2551
|
+
clientId: ws.remoteFrameClientId
|
|
2331
2552
|
});
|
|
2332
2553
|
});
|
|
2333
2554
|
|
|
@@ -9192,6 +9413,7 @@ app.get('/api/status', async (req, res) => {
|
|
|
9192
9413
|
bridgeTokenHeader,
|
|
9193
9414
|
bridgeAuthRequired,
|
|
9194
9415
|
remoteHub: remoteHub.getStatus({ includeSecrets: false }),
|
|
9416
|
+
remoteFrameWs: serializeRemoteFrameWsDiagnostics(),
|
|
9195
9417
|
remoteAgent: serializeRemoteAgentState(),
|
|
9196
9418
|
remoteRegistryFollower: serializeRemoteRegistryFollowerState(),
|
|
9197
9419
|
shellJobsPath: '/api/shell/jobs',
|
|
@@ -13828,7 +13828,16 @@
|
|
|
13828
13828
|
}
|
|
13829
13829
|
|
|
13830
13830
|
const deviceIds = getRemoteFleetBinaryFrameDeviceIds(session);
|
|
13831
|
-
const
|
|
13831
|
+
const liveOptions = session.liveOptions || {};
|
|
13832
|
+
const optionKey = [
|
|
13833
|
+
liveOptions.autoStartLive === true ? 'auto-live' : 'passive',
|
|
13834
|
+
Number(liveOptions.fps || 0) || 0,
|
|
13835
|
+
Number(liveOptions.maxWidth || 0) || 0,
|
|
13836
|
+
Number(liveOptions.maxHeight || 0) || 0,
|
|
13837
|
+
Number(liveOptions.quality || 0) || 0,
|
|
13838
|
+
String(liveOptions.mode || '')
|
|
13839
|
+
].join('|');
|
|
13840
|
+
const key = `${deviceIds.join('\n')}::${optionKey}`;
|
|
13832
13841
|
if (!force && session.subscriptionKey === key) {
|
|
13833
13842
|
return true;
|
|
13834
13843
|
}
|
|
@@ -13837,7 +13846,13 @@
|
|
|
13837
13846
|
session.ws.send(JSON.stringify({
|
|
13838
13847
|
type: 'subscribe',
|
|
13839
13848
|
nodeId: session.nodeId,
|
|
13840
|
-
deviceIds
|
|
13849
|
+
deviceIds,
|
|
13850
|
+
autoStartLive: liveOptions.autoStartLive === true,
|
|
13851
|
+
fps: liveOptions.fps,
|
|
13852
|
+
maxWidth: liveOptions.maxWidth,
|
|
13853
|
+
maxHeight: liveOptions.maxHeight,
|
|
13854
|
+
quality: liveOptions.quality,
|
|
13855
|
+
mode: liveOptions.mode
|
|
13841
13856
|
}));
|
|
13842
13857
|
return true;
|
|
13843
13858
|
}
|
|
@@ -13984,7 +13999,7 @@
|
|
|
13984
13999
|
}
|
|
13985
14000
|
}
|
|
13986
14001
|
|
|
13987
|
-
function startRemoteFleetBinaryFrameSocket(bodyView, getDeviceIds) {
|
|
14002
|
+
function startRemoteFleetBinaryFrameSocket(bodyView, getDeviceIds, options = {}) {
|
|
13988
14003
|
const nodeId = String(bodyView?.dataset?.nodeId || '').trim();
|
|
13989
14004
|
if (!nodeId || typeof WebSocket !== 'function') {
|
|
13990
14005
|
return false;
|
|
@@ -14007,6 +14022,14 @@
|
|
|
14007
14022
|
|
|
14008
14023
|
session.bodies.add(bodyView);
|
|
14009
14024
|
session.getDeviceIds = getDeviceIds;
|
|
14025
|
+
session.liveOptions = {
|
|
14026
|
+
autoStartLive: options.autoStartLive === true,
|
|
14027
|
+
fps: Number(options.fps || REMOTE_FLEET_MONITOR_LIVE_FPS) || REMOTE_FLEET_MONITOR_LIVE_FPS,
|
|
14028
|
+
maxWidth: Number(options.maxWidth || 960) || 960,
|
|
14029
|
+
maxHeight: Number(options.maxHeight || 540) || 540,
|
|
14030
|
+
quality: Number(options.quality || 60) || 60,
|
|
14031
|
+
mode: String(options.mode || 'remote-fast')
|
|
14032
|
+
};
|
|
14010
14033
|
bodyView._remoteFleetBinaryFrameSession = session;
|
|
14011
14034
|
openRemoteFleetBinaryFrameSocket(session).catch(() => undefined);
|
|
14012
14035
|
return true;
|
|
@@ -15304,7 +15327,14 @@
|
|
|
15304
15327
|
|
|
15305
15328
|
let binarySession = bodyView._remoteFleetBinaryFrameSession || null;
|
|
15306
15329
|
if (!binarySession) {
|
|
15307
|
-
if (!startRemoteFleetBinaryFrameSocket(bodyView, () => []
|
|
15330
|
+
if (!startRemoteFleetBinaryFrameSocket(bodyView, () => [], {
|
|
15331
|
+
autoStartLive: true,
|
|
15332
|
+
fps: REMOTE_FLEET_CONTROL_LIVE_FPS,
|
|
15333
|
+
maxWidth: 1280,
|
|
15334
|
+
maxHeight: 720,
|
|
15335
|
+
quality: 65,
|
|
15336
|
+
mode: 'remote-fast-control'
|
|
15337
|
+
})) {
|
|
15308
15338
|
return false;
|
|
15309
15339
|
}
|
|
15310
15340
|
binarySession = bodyView._remoteFleetBinaryFrameSession || null;
|
|
@@ -15319,6 +15349,15 @@
|
|
|
15319
15349
|
}
|
|
15320
15350
|
|
|
15321
15351
|
binarySession.controlSessions.add(controlSession);
|
|
15352
|
+
binarySession.liveOptions = {
|
|
15353
|
+
...(binarySession.liveOptions || {}),
|
|
15354
|
+
autoStartLive: true,
|
|
15355
|
+
fps: REMOTE_FLEET_CONTROL_LIVE_FPS,
|
|
15356
|
+
maxWidth: 1280,
|
|
15357
|
+
maxHeight: 720,
|
|
15358
|
+
quality: 65,
|
|
15359
|
+
mode: 'remote-fast-control'
|
|
15360
|
+
};
|
|
15322
15361
|
controlSession.binaryFrameSession = binarySession;
|
|
15323
15362
|
controlSession.binaryFramePreferred = true;
|
|
15324
15363
|
refreshRemoteFleetBinaryFrameSubscription(binarySession, true);
|
|
@@ -17970,6 +18009,13 @@
|
|
|
17970
18009
|
const binaryFrameSocketStarted = startRemoteFleetBinaryFrameSocket(bodyView, () => {
|
|
17971
18010
|
const liveIds = getVisibleLiveFrameDeviceIds();
|
|
17972
18011
|
return liveIds.length > 0 ? liveIds : getVisibleFrameDeviceIds();
|
|
18012
|
+
}, {
|
|
18013
|
+
autoStartLive: true,
|
|
18014
|
+
fps: REMOTE_FLEET_MONITOR_LIVE_FPS,
|
|
18015
|
+
maxWidth: 960,
|
|
18016
|
+
maxHeight: 540,
|
|
18017
|
+
quality: 60,
|
|
18018
|
+
mode: 'remote-fast'
|
|
17973
18019
|
});
|
|
17974
18020
|
if (!binaryFrameSocketStarted) {
|
|
17975
18021
|
window.RuntimeTrace?.emit?.('remote.frame.wsUnavailable', {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
self.assetsManifest = {
|
|
2
|
-
"version": "
|
|
2
|
+
"version": "IfRQGIi2",
|
|
3
3
|
"assets": [
|
|
4
4
|
{
|
|
5
5
|
"hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
"hash": "sha256-
|
|
89
|
+
"hash": "sha256-WBIjGLmIxv1h8qPdh1KvY1XXHi1slyoTo2BVqXis+hQ=",
|
|
90
90
|
"url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
|
|
91
91
|
},
|
|
92
92
|
{
|