@mindexec/cli 0.2.137 → 0.2.138

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.138",
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);