@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
|
@@ -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
|
|
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-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
336
|
-
assert.ok(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.
|
|
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.
|
|
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);
|