@mindexec/cli 0.2.137 → 0.2.139

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.137",
3
+ "version": "0.2.139",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -13,6 +13,8 @@ const PAIR_TOKEN = 'remote-fast-mdm-browser-pair-token';
13
13
  const LEASE_ID = 'remote-fast-mdm-browser-lease';
14
14
  const LOCAL_BRIDGE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
15
15
  const REQUESTED_FPS = Number(process.env.MINDEXEC_REMOTE_MDM_BROWSER_REQUEST_FPS || 12);
16
+ const SAMPLE_MS = Number(process.env.MINDEXEC_REMOTE_MDM_BROWSER_SAMPLE_MS || 1500);
17
+ const MIN_PAINT_FPS = Number(process.env.MINDEXEC_REMOTE_MDM_BROWSER_MIN_PAINT_FPS || 8);
16
18
 
17
19
  function wait(ms) {
18
20
  return new Promise(resolve => setTimeout(resolve, ms));
@@ -161,14 +163,14 @@ async function launchBrowser(chromium) {
161
163
  throw new Error(`Unable to launch a Chromium browser: ${lastError?.message || 'unknown error'}`);
162
164
  }
163
165
 
164
- async function paintRemoteFrameInBrowser(page, wsUrl, deviceId, fps = REQUESTED_FPS) {
166
+ async function paintRemoteFramesInBrowser(page, wsUrl, deviceId, fps = REQUESTED_FPS) {
165
167
  await page.setContent(`
166
168
  <!doctype html>
167
169
  <meta charset="utf-8">
168
170
  <canvas id="screen" width="320" height="180"></canvas>
169
171
  `);
170
172
 
171
- return await page.evaluate(async ({ wsUrl, deviceId, fps }) => {
173
+ return await page.evaluate(async ({ wsUrl, deviceId, fps, sampleMs, minPaintFps }) => {
172
174
  const canvas = document.getElementById('screen');
173
175
  const context = canvas.getContext('2d', { alpha: false, desynchronized: true }) || canvas.getContext('2d');
174
176
  if (!context) {
@@ -198,9 +200,112 @@ async function paintRemoteFrameInBrowser(page, wsUrl, deviceId, fps = REQUESTED_
198
200
  } catch {
199
201
  // ignore close failure
200
202
  }
201
- reject(new Error('timed-out-waiting-for-mdm-frame'));
203
+ reject(new Error('timed-out-waiting-for-mdm-frames'));
202
204
  }, 20000);
203
205
  const ws = new WebSocket(wsUrl);
206
+ const painted = [];
207
+ const receivedSeqs = [];
208
+ let firstPaintAt = 0;
209
+ let lastPaintAt = 0;
210
+ let processing = false;
211
+ let pendingPacket = null;
212
+ let settled = false;
213
+
214
+ function finishIfReady() {
215
+ if (settled || painted.length < 2 || !firstPaintAt || !lastPaintAt) {
216
+ return;
217
+ }
218
+
219
+ const elapsedMs = lastPaintAt - firstPaintAt;
220
+ if (elapsedMs < sampleMs) {
221
+ return;
222
+ }
223
+
224
+ const uniqueSeqs = new Set(painted.map(item => item.frameSeq));
225
+ const paintFps = painted.length > 1
226
+ ? ((painted.length - 1) * 1000) / Math.max(1, elapsedMs)
227
+ : 0;
228
+ if (paintFps < minPaintFps) {
229
+ return;
230
+ }
231
+
232
+ settled = true;
233
+ clearTimeout(timeout);
234
+ ws.onclose = null;
235
+ try {
236
+ ws.close();
237
+ } catch {
238
+ // ignore close failure
239
+ }
240
+ resolve({
241
+ framesReceived: receivedSeqs.length,
242
+ framesPainted: painted.length,
243
+ uniqueSeqs: uniqueSeqs.size,
244
+ firstSeq: painted[0]?.frameSeq || 0,
245
+ lastSeq: painted[painted.length - 1]?.frameSeq || 0,
246
+ durationMs: elapsedMs,
247
+ paintFps,
248
+ fps: painted[painted.length - 1]?.fps || 0,
249
+ width: painted[painted.length - 1]?.width || 0,
250
+ height: painted[painted.length - 1]?.height || 0,
251
+ minBytes: Math.min(...painted.map(item => item.byteLength).filter(value => value > 0)),
252
+ maxBytes: Math.max(...painted.map(item => item.byteLength).filter(value => value > 0)),
253
+ mimeType: painted[painted.length - 1]?.mimeType || '',
254
+ transport: painted[painted.length - 1]?.transport || ''
255
+ });
256
+ }
257
+
258
+ async function paintPacket(packet) {
259
+ const { metadata, payload } = packet;
260
+ const blob = new Blob([payload], { type: metadata.mimeType || 'image/jpeg' });
261
+ const bitmap = await createImageBitmap(blob);
262
+ context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
263
+ bitmap.close?.();
264
+ const now = performance.now();
265
+ const frameSeq = Number(metadata.frameSeq || 0);
266
+ if (!firstPaintAt) {
267
+ firstPaintAt = now;
268
+ }
269
+ lastPaintAt = now;
270
+ painted.push({
271
+ frameSeq,
272
+ fps: Number(metadata.fps || 0),
273
+ width: Number(metadata.width || 0),
274
+ height: Number(metadata.height || 0),
275
+ byteLength: Number(metadata.byteLength || payload.byteLength || 0),
276
+ mimeType: String(metadata.mimeType || ''),
277
+ transport: String(metadata.transport || '')
278
+ });
279
+ finishIfReady();
280
+ }
281
+
282
+ async function processLatest(packet) {
283
+ pendingPacket = packet;
284
+ if (processing) {
285
+ return;
286
+ }
287
+
288
+ processing = true;
289
+ try {
290
+ while (pendingPacket && !settled) {
291
+ const next = pendingPacket;
292
+ pendingPacket = null;
293
+ await paintPacket(next);
294
+ }
295
+ } catch (error) {
296
+ settled = true;
297
+ clearTimeout(timeout);
298
+ try {
299
+ ws.close();
300
+ } catch {
301
+ // ignore close failure
302
+ }
303
+ reject(error);
304
+ } finally {
305
+ processing = false;
306
+ }
307
+ }
308
+
204
309
  ws.binaryType = 'arraybuffer';
205
310
  ws.onopen = () => {
206
311
  ws.send(JSON.stringify({
@@ -238,39 +343,23 @@ async function paintRemoteFrameInBrowser(page, wsUrl, deviceId, fps = REQUESTED_
238
343
  throw new Error('binary-ws-metadata-must-not-carry-frame-url-or-data-url');
239
344
  }
240
345
 
241
- const blob = new Blob([payload], { type: metadata.mimeType || 'image/jpeg' });
242
- const bitmap = await createImageBitmap(blob);
243
- context.fillStyle = '#000000';
244
- context.fillRect(0, 0, canvas.width, canvas.height);
245
- context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
246
- bitmap.close?.();
247
- const pixel = Array.from(context.getImageData(0, 0, 1, 1).data);
248
- clearTimeout(timeout);
249
- ws.onclose = null;
250
- ws.close();
251
- resolve({
252
- frameSeq: Number(metadata.frameSeq || 0),
253
- fps: Number(metadata.fps || 0),
254
- width: Number(metadata.width || 0),
255
- height: Number(metadata.height || 0),
256
- byteLength: Number(metadata.byteLength || payload.byteLength || 0),
257
- mimeType: String(metadata.mimeType || ''),
258
- transport: String(metadata.transport || ''),
259
- payloadBytes: payload.byteLength,
260
- pixel
261
- });
346
+ receivedSeqs.push(Number(metadata.frameSeq || 0));
347
+ await processLatest({ metadata, payload });
262
348
  } catch (error) {
263
- clearTimeout(timeout);
264
- try {
265
- ws.close();
266
- } catch {
267
- // ignore close failure
349
+ if (!settled) {
350
+ settled = true;
351
+ clearTimeout(timeout);
352
+ try {
353
+ ws.close();
354
+ } catch {
355
+ // ignore close failure
356
+ }
357
+ reject(error);
268
358
  }
269
- reject(error);
270
359
  }
271
360
  };
272
361
  });
273
- }, { wsUrl, deviceId, fps });
362
+ }, { wsUrl, deviceId, fps, sampleMs: SAMPLE_MS, minPaintFps: MIN_PAINT_FPS });
274
363
  }
275
364
 
276
365
  async function main() {
@@ -332,14 +421,18 @@ async function main() {
332
421
  browser = await launchBrowser(chromium);
333
422
  const page = await browser.newPage();
334
423
  const wsUrl = `${hostBridge.baseUrl.replace(/^http:/, 'ws:')}/api/remote/frames/ws?token=${encodeURIComponent(BRIDGE_TOKEN)}`;
335
- const painted = await paintRemoteFrameInBrowser(page, wsUrl, device.deviceId, REQUESTED_FPS);
336
- assert.ok(painted.frameSeq > 0, JSON.stringify(painted));
424
+ const painted = await paintRemoteFramesInBrowser(page, wsUrl, device.deviceId, REQUESTED_FPS);
425
+ assert.ok(painted.framesReceived >= painted.framesPainted, JSON.stringify(painted));
426
+ assert.ok(painted.framesPainted >= Math.max(4, Math.floor((SAMPLE_MS / 1000) * MIN_PAINT_FPS)), JSON.stringify(painted));
427
+ assert.ok(painted.uniqueSeqs >= painted.framesPainted - 1, JSON.stringify(painted));
428
+ assert.ok(painted.lastSeq > painted.firstSeq, JSON.stringify(painted));
429
+ assert.ok(painted.paintFps >= MIN_PAINT_FPS, JSON.stringify(painted));
337
430
  assert.equal(painted.fps, REQUESTED_FPS, JSON.stringify(painted));
338
431
  assert.ok(painted.width > 0 && painted.height > 0, JSON.stringify(painted));
339
- assert.ok(painted.byteLength > 0 && painted.payloadBytes > 0, JSON.stringify(painted));
432
+ assert.ok(painted.minBytes > 0 && painted.maxBytes >= painted.minBytes, JSON.stringify(painted));
340
433
  assert.match(painted.mimeType, /^image\/(jpeg|png|webp)$/i, JSON.stringify(painted));
341
434
 
342
- console.log(`RemoteFast MDM browser smoke OK (seq ${painted.frameSeq}, ${painted.width}x${painted.height}, ${painted.byteLength} bytes)`);
435
+ console.log(`RemoteFast MDM browser smoke OK (${painted.framesPainted} paints, ${painted.paintFps.toFixed(1)} fps, seq ${painted.firstSeq}-${painted.lastSeq}, ${painted.width}x${painted.height}, ${painted.minBytes}-${painted.maxBytes} bytes)`);
343
436
  } finally {
344
437
  if (browser) {
345
438
  await browser.close().catch(() => undefined);
@@ -866,9 +866,14 @@ try {
866
866
  assert.equal(binaryImage?.style.display, 'none');
867
867
  assert.equal(diagnostics.imageBitmapCalls.length, imageBitmapCountBeforeBinary + 1);
868
868
  assert.equal(diagnostics.objectUrlCalls.length, objectUrlCountBeforeBinary);
869
+ const binaryDrawCalls = document.canvasDrawCalls.slice(drawCountBeforeBinary);
869
870
  assert.ok(
870
- document.canvasDrawCalls.slice(drawCountBeforeBinary).some(call => call.op === 'drawImage'),
871
+ binaryDrawCalls.some(call => call.op === 'drawImage'),
871
872
  'expected binary Blob frame to draw directly to canvas');
873
+ assert.equal(
874
+ binaryDrawCalls.some(call => call.op === 'clearRect' || call.op === 'fillRect'),
875
+ false,
876
+ 'binary Blob frame paint must not clear/fill the canvas before drawing');
872
877
  const alternateDevice = devices.find(device =>
873
878
  device.Connected === true
874
879
  && device.DeviceId !== focusedDevice.DeviceId);
@@ -13461,7 +13461,7 @@
13461
13461
  const REMOTE_FLEET_FRAME_BLOB_CACHE_LIMIT = 96;
13462
13462
  const REMOTE_FLEET_LIVE_FRAME_DECODE_TIMEOUT_MS = 180;
13463
13463
  const REMOTE_FLEET_THUMBNAIL_FRAME_DECODE_TIMEOUT_MS = 1200;
13464
- const REMOTE_FLEET_BINARY_FRAME_MAX_DECODE_IN_FLIGHT = 2;
13464
+ const REMOTE_FLEET_BINARY_FRAME_MAX_DECODE_IN_FLIGHT = 1;
13465
13465
  const REMOTE_FLEET_BINARY_FRAME_WS_RECONNECT_MS = 1500;
13466
13466
  const REMOTE_FLEET_BINARY_FRAME_SUBSCRIBE_MS = 1000;
13467
13467
  const REMOTE_FLEET_BINARY_FRAME_STALE_FALLBACK_MS = 1400;
@@ -14269,7 +14269,7 @@
14269
14269
  width: 100%;
14270
14270
  height: 100%;
14271
14271
  display: none;
14272
- background: #0f172a;
14272
+ background: transparent;
14273
14273
  `;
14274
14274
  if (typeof preview?.insertBefore === 'function') {
14275
14275
  preview.insertBefore(canvas, preview.firstChild || null);
@@ -14361,9 +14361,6 @@
14361
14361
 
14362
14362
  context.imageSmoothingEnabled = true;
14363
14363
  context.imageSmoothingQuality = 'medium';
14364
- context.fillStyle = '#020617';
14365
- context.clearRect(0, 0, size.width, size.height);
14366
- context.fillRect(0, 0, size.width, size.height);
14367
14364
  context.drawImage(bitmap, sx, sy, sw, sh, dx, dy, dw, dh);
14368
14365
  canvas._remoteFleetContentRect = {
14369
14366
  left: dx / size.dpr,
@@ -18000,12 +17997,6 @@
18000
17997
  const hasVisibleLiveTarget = getVisibleLiveFrameDeviceIds().length > 0
18001
17998
  || devices.some(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetLiveCapable(device));
18002
17999
  if (hasVisibleLiveTarget || hasActiveLiveStream) {
18003
- ensureVisibleRemoteFleetLiveStreams().catch(error => {
18004
- window.RuntimeTrace?.emit?.('remote.live.autoStartFailed', {
18005
- nodeId,
18006
- error: error?.message || String(error || '')
18007
- });
18008
- });
18009
18000
  const binaryFrameSocketStarted = startRemoteFleetBinaryFrameSocket(bodyView, () => {
18010
18001
  const liveIds = getVisibleLiveFrameDeviceIds();
18011
18002
  return liveIds.length > 0 ? liveIds : getVisibleFrameDeviceIds();
@@ -18022,6 +18013,18 @@
18022
18013
  nodeId,
18023
18014
  reason: 'binary-frame-socket-not-started'
18024
18015
  });
18016
+ ensureVisibleRemoteFleetLiveStreams().catch(error => {
18017
+ window.RuntimeTrace?.emit?.('remote.live.autoStartFailed', {
18018
+ nodeId,
18019
+ error: error?.message || String(error || '')
18020
+ });
18021
+ });
18022
+ } else {
18023
+ window.RuntimeTrace?.emit?.('remote.live.wsAutoStartRequested', {
18024
+ nodeId,
18025
+ transport: 'ws-binary',
18026
+ fps: REMOTE_FLEET_MONITOR_LIVE_FPS
18027
+ });
18025
18028
  }
18026
18029
  bodyView._remoteFleetLiveRefreshTimer = setInterval(async () => {
18027
18030
  if (!document.body.contains(bodyView)) {
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "IfRQGIi2",
2
+ "version": "7WvF4J3m",
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-WBIjGLmIxv1h8qPdh1KvY1XXHi1slyoTo2BVqXis+hQ=",
89
+ "hash": "sha256-8Ndn0qAzhmRH7/OJtB0ve5LG2oN5e+PaAE7c0MyEzHM=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: IfRQGIi2 */
1
+ /* Manifest version: 7WvF4J3m */
2
2
  // Hosted deployments should prefer the network over stale offline caches.
3
3
  // This service worker immediately clears old Blazor offline caches and unregisters itself.
4
4