@mindexec/cli 0.2.40 → 0.2.41
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/remote-hub.js +210 -103
- package/scripts/remote-http-smoke.mjs +4 -1
- package/scripts/remote-hub-smoke.mjs +60 -0
package/package.json
CHANGED
package/remote-hub.js
CHANGED
|
@@ -9,6 +9,8 @@ const DEFAULT_AGENT_TASK_TIMEOUT_MS = 120000;
|
|
|
9
9
|
const MAX_LINE_CHARS = 4 * 1024 * 1024;
|
|
10
10
|
const MAX_THUMBNAIL_BASE64_CHARS = 3 * 1024 * 1024;
|
|
11
11
|
const MAX_STREAM_BASE64_CHARS = 3 * 1024 * 1024;
|
|
12
|
+
const MAX_THUMBNAIL_BINARY_BYTES = Math.floor(MAX_THUMBNAIL_BASE64_CHARS * 3 / 4);
|
|
13
|
+
const MAX_STREAM_BINARY_BYTES = Math.floor(MAX_STREAM_BASE64_CHARS * 3 / 4);
|
|
12
14
|
const MAX_AGENT_TASK_CHARS = 4000;
|
|
13
15
|
const MAX_AGENT_TASK_RESULT_CHARS = 3000;
|
|
14
16
|
const RECENT_TASK_LIMIT = 12;
|
|
@@ -1075,6 +1077,161 @@ export function createRemoteHub(options = {}) {
|
|
|
1075
1077
|
return task;
|
|
1076
1078
|
}
|
|
1077
1079
|
|
|
1080
|
+
function normalizeFrameByteLength(framePayload, frameData = '') {
|
|
1081
|
+
if (Buffer.isBuffer(framePayload)) {
|
|
1082
|
+
return framePayload.length;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return Math.floor(String(frameData || '').length * 3 / 4);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function buildFrameDataUrl(framePayload, frameData, mimeType) {
|
|
1089
|
+
if (Buffer.isBuffer(framePayload)) {
|
|
1090
|
+
return `data:${mimeType};base64,${framePayload.toString('base64')}`;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
return String(frameData || '').startsWith('data:')
|
|
1094
|
+
? String(frameData || '')
|
|
1095
|
+
: `data:${mimeType};base64,${frameData}`;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function applyThumbnailFrame(device, message, framePayload, transport = 'json-base64') {
|
|
1099
|
+
const frameData = Buffer.isBuffer(framePayload)
|
|
1100
|
+
? ''
|
|
1101
|
+
: safeString(framePayload, MAX_THUMBNAIL_BASE64_CHARS + 1);
|
|
1102
|
+
const frameSeq = Number(message.frameSeq);
|
|
1103
|
+
const byteLength = normalizeFrameByteLength(framePayload, frameData);
|
|
1104
|
+
if ((!Buffer.isBuffer(framePayload) && !frameData)
|
|
1105
|
+
|| byteLength > MAX_THUMBNAIL_BINARY_BYTES
|
|
1106
|
+
|| !Number.isFinite(frameSeq)) {
|
|
1107
|
+
device.counters.thumbnailFramesDropped += 1;
|
|
1108
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1109
|
+
reason: 'invalid-thumbnail-frame',
|
|
1110
|
+
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
1111
|
+
transport
|
|
1112
|
+
});
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
1117
|
+
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
1118
|
+
device.latestThumbnail = {
|
|
1119
|
+
streamId: safeString(message.streamId, 128) || 'thumbnail',
|
|
1120
|
+
frameSeq,
|
|
1121
|
+
commandId: safeString(message.commandId, 128),
|
|
1122
|
+
width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
|
|
1123
|
+
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
1124
|
+
mimeType,
|
|
1125
|
+
format: mimeType,
|
|
1126
|
+
capturedAt,
|
|
1127
|
+
receivedAt: device.lastSeenAt,
|
|
1128
|
+
byteLength,
|
|
1129
|
+
transport,
|
|
1130
|
+
dataUrl: buildFrameDataUrl(framePayload, frameData, mimeType)
|
|
1131
|
+
};
|
|
1132
|
+
device.counters.thumbnailFramesReceived += 1;
|
|
1133
|
+
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
1134
|
+
streamId: device.latestThumbnail.streamId,
|
|
1135
|
+
frameSeq,
|
|
1136
|
+
width: device.latestThumbnail.width,
|
|
1137
|
+
height: device.latestThumbnail.height,
|
|
1138
|
+
transport
|
|
1139
|
+
});
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function applyLiveFrame(device, message, framePayload, transport = 'json-base64') {
|
|
1144
|
+
const frameData = Buffer.isBuffer(framePayload)
|
|
1145
|
+
? ''
|
|
1146
|
+
: safeString(framePayload, MAX_STREAM_BASE64_CHARS + 1);
|
|
1147
|
+
const frameSeq = Number(message.frameSeq);
|
|
1148
|
+
const streamId = safeString(message.streamId, 128) || 'live';
|
|
1149
|
+
if (!device.activeLiveStream?.active || device.activeLiveStream.streamId !== streamId) {
|
|
1150
|
+
device.counters.liveFramesDropped += 1;
|
|
1151
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1152
|
+
reason: 'stale-live-stream-frame',
|
|
1153
|
+
streamId,
|
|
1154
|
+
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
1155
|
+
transport
|
|
1156
|
+
});
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const byteLength = normalizeFrameByteLength(framePayload, frameData);
|
|
1161
|
+
if ((!Buffer.isBuffer(framePayload) && !frameData)
|
|
1162
|
+
|| byteLength > MAX_STREAM_BINARY_BYTES
|
|
1163
|
+
|| !Number.isFinite(frameSeq)) {
|
|
1164
|
+
device.counters.liveFramesDropped += 1;
|
|
1165
|
+
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1166
|
+
reason: 'invalid-live-frame',
|
|
1167
|
+
streamId,
|
|
1168
|
+
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null,
|
|
1169
|
+
transport
|
|
1170
|
+
});
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
1175
|
+
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
1176
|
+
device.latestLiveFrame = {
|
|
1177
|
+
streamId,
|
|
1178
|
+
frameSeq,
|
|
1179
|
+
commandId: safeString(message.commandId, 128),
|
|
1180
|
+
width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
|
|
1181
|
+
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
1182
|
+
mimeType,
|
|
1183
|
+
format: mimeType,
|
|
1184
|
+
mode: safeString(message.mode || device.activeLiveStream.mode || 'remote-fast', 80),
|
|
1185
|
+
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) : device.activeLiveStream.fps,
|
|
1186
|
+
capturedAt,
|
|
1187
|
+
receivedAt: device.lastSeenAt,
|
|
1188
|
+
byteLength,
|
|
1189
|
+
transport,
|
|
1190
|
+
dataUrl: buildFrameDataUrl(framePayload, frameData, mimeType)
|
|
1191
|
+
};
|
|
1192
|
+
device.activeLiveStream.lastFrameAt = device.lastSeenAt;
|
|
1193
|
+
device.activeLiveStream.lastFrameSeq = frameSeq;
|
|
1194
|
+
device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
|
|
1195
|
+
device.counters.liveFramesReceived += 1;
|
|
1196
|
+
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
1197
|
+
streamId,
|
|
1198
|
+
frameSeq,
|
|
1199
|
+
width: device.latestLiveFrame.width,
|
|
1200
|
+
height: device.latestLiveFrame.height,
|
|
1201
|
+
mode: device.latestLiveFrame.mode,
|
|
1202
|
+
transport
|
|
1203
|
+
});
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function handleAgentBinaryFrame(socket, state, header, framePayload) {
|
|
1208
|
+
if (!state.authenticated || !state.device) {
|
|
1209
|
+
writeJsonLine(socket, { type: 'error', error: 'hello-required' });
|
|
1210
|
+
socket.destroy();
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const device = state.device;
|
|
1215
|
+
device.counters.messagesReceived += 1;
|
|
1216
|
+
device.lastSeenAt = new Date().toISOString();
|
|
1217
|
+
|
|
1218
|
+
const frameKind = safeString(header.frameKind || header.kind || header.frameType || '', 40).toLowerCase();
|
|
1219
|
+
if (frameKind === 'thumbnail' || header.type === 'thumbnail.binary') {
|
|
1220
|
+
applyThumbnailFrame(device, header, framePayload, 'binary');
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (frameKind === 'stream' || frameKind === 'live' || header.type === 'stream.binary') {
|
|
1225
|
+
applyLiveFrame(device, header, framePayload, 'binary');
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
emitRemoteEvent('RemoteAgentMessageIgnored', device, {
|
|
1230
|
+
messageType: safeString(header.type, 80),
|
|
1231
|
+
frameKind
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1078
1235
|
function handleAgentMessage(socket, state, message) {
|
|
1079
1236
|
if (!message || typeof message !== 'object') {
|
|
1080
1237
|
return;
|
|
@@ -1139,97 +1296,11 @@ export function createRemoteHub(options = {}) {
|
|
|
1139
1296
|
});
|
|
1140
1297
|
break;
|
|
1141
1298
|
case 'thumbnail.frame': {
|
|
1142
|
-
|
|
1143
|
-
const frameSeq = Number(message.frameSeq);
|
|
1144
|
-
if (!frameData || frameData.length > MAX_THUMBNAIL_BASE64_CHARS || !Number.isFinite(frameSeq)) {
|
|
1145
|
-
device.counters.thumbnailFramesDropped += 1;
|
|
1146
|
-
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1147
|
-
reason: 'invalid-thumbnail-frame',
|
|
1148
|
-
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null
|
|
1149
|
-
});
|
|
1150
|
-
break;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
1154
|
-
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
1155
|
-
device.latestThumbnail = {
|
|
1156
|
-
streamId: safeString(message.streamId, 128) || 'thumbnail',
|
|
1157
|
-
frameSeq,
|
|
1158
|
-
commandId: safeString(message.commandId, 128),
|
|
1159
|
-
width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
|
|
1160
|
-
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
1161
|
-
mimeType,
|
|
1162
|
-
format: mimeType,
|
|
1163
|
-
capturedAt,
|
|
1164
|
-
receivedAt: device.lastSeenAt,
|
|
1165
|
-
byteLength: Math.floor(frameData.length * 3 / 4),
|
|
1166
|
-
dataUrl: frameData.startsWith('data:')
|
|
1167
|
-
? frameData
|
|
1168
|
-
: `data:${mimeType};base64,${frameData}`
|
|
1169
|
-
};
|
|
1170
|
-
device.counters.thumbnailFramesReceived += 1;
|
|
1171
|
-
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
1172
|
-
streamId: device.latestThumbnail.streamId,
|
|
1173
|
-
frameSeq,
|
|
1174
|
-
width: device.latestThumbnail.width,
|
|
1175
|
-
height: device.latestThumbnail.height
|
|
1176
|
-
});
|
|
1299
|
+
applyThumbnailFrame(device, message, message.data, 'json-base64');
|
|
1177
1300
|
break;
|
|
1178
1301
|
}
|
|
1179
1302
|
case 'stream.frame': {
|
|
1180
|
-
|
|
1181
|
-
const frameSeq = Number(message.frameSeq);
|
|
1182
|
-
const streamId = safeString(message.streamId, 128) || 'live';
|
|
1183
|
-
if (!device.activeLiveStream?.active || device.activeLiveStream.streamId !== streamId) {
|
|
1184
|
-
device.counters.liveFramesDropped += 1;
|
|
1185
|
-
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1186
|
-
reason: 'stale-live-stream-frame',
|
|
1187
|
-
streamId,
|
|
1188
|
-
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null
|
|
1189
|
-
});
|
|
1190
|
-
break;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
if (!frameData || frameData.length > MAX_STREAM_BASE64_CHARS || !Number.isFinite(frameSeq)) {
|
|
1194
|
-
device.counters.liveFramesDropped += 1;
|
|
1195
|
-
emitRemoteEvent('RemoteFrameDropped', device, {
|
|
1196
|
-
reason: 'invalid-live-frame',
|
|
1197
|
-
streamId,
|
|
1198
|
-
frameSeq: Number.isFinite(frameSeq) ? frameSeq : null
|
|
1199
|
-
});
|
|
1200
|
-
break;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
const mimeType = safeString(message.mimeType || message.format || 'image/jpeg', 80) || 'image/jpeg';
|
|
1204
|
-
const capturedAt = safeString(message.capturedAt, 80) || device.lastSeenAt;
|
|
1205
|
-
device.latestLiveFrame = {
|
|
1206
|
-
streamId,
|
|
1207
|
-
frameSeq,
|
|
1208
|
-
commandId: safeString(message.commandId, 128),
|
|
1209
|
-
width: Number.isFinite(Number(message.width)) ? Number(message.width) : 0,
|
|
1210
|
-
height: Number.isFinite(Number(message.height)) ? Number(message.height) : 0,
|
|
1211
|
-
mimeType,
|
|
1212
|
-
format: mimeType,
|
|
1213
|
-
mode: safeString(message.mode || device.activeLiveStream.mode || 'remote-fast', 80),
|
|
1214
|
-
fps: Number.isFinite(Number(message.fps)) ? Number(message.fps) : device.activeLiveStream.fps,
|
|
1215
|
-
capturedAt,
|
|
1216
|
-
receivedAt: device.lastSeenAt,
|
|
1217
|
-
byteLength: Math.floor(frameData.length * 3 / 4),
|
|
1218
|
-
dataUrl: frameData.startsWith('data:')
|
|
1219
|
-
? frameData
|
|
1220
|
-
: `data:${mimeType};base64,${frameData}`
|
|
1221
|
-
};
|
|
1222
|
-
device.activeLiveStream.lastFrameAt = device.lastSeenAt;
|
|
1223
|
-
device.activeLiveStream.lastFrameSeq = frameSeq;
|
|
1224
|
-
device.activeLiveStream.framesReceived = (device.activeLiveStream.framesReceived || 0) + 1;
|
|
1225
|
-
device.counters.liveFramesReceived += 1;
|
|
1226
|
-
emitRemoteEvent('RemoteFrameReceived', device, {
|
|
1227
|
-
streamId,
|
|
1228
|
-
frameSeq,
|
|
1229
|
-
width: device.latestLiveFrame.width,
|
|
1230
|
-
height: device.latestLiveFrame.height,
|
|
1231
|
-
mode: device.latestLiveFrame.mode
|
|
1232
|
-
});
|
|
1303
|
+
applyLiveFrame(device, message, message.data, 'json-base64');
|
|
1233
1304
|
break;
|
|
1234
1305
|
}
|
|
1235
1306
|
default:
|
|
@@ -1242,14 +1313,14 @@ export function createRemoteHub(options = {}) {
|
|
|
1242
1313
|
|
|
1243
1314
|
function handleSocket(socket) {
|
|
1244
1315
|
allSockets.add(socket);
|
|
1245
|
-
socket.setEncoding('utf8');
|
|
1246
1316
|
socket.setNoDelay(true);
|
|
1247
1317
|
socket.setKeepAlive(true, heartbeatMs);
|
|
1248
1318
|
|
|
1249
1319
|
const state = {
|
|
1250
1320
|
authenticated: false,
|
|
1251
1321
|
device: null,
|
|
1252
|
-
buffer:
|
|
1322
|
+
buffer: Buffer.alloc(0),
|
|
1323
|
+
pendingBinaryFrame: null
|
|
1253
1324
|
};
|
|
1254
1325
|
|
|
1255
1326
|
const helloTimer = setTimeout(() => {
|
|
@@ -1260,27 +1331,63 @@ export function createRemoteHub(options = {}) {
|
|
|
1260
1331
|
}, 10000);
|
|
1261
1332
|
|
|
1262
1333
|
socket.on('data', chunk => {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1334
|
+
const incoming = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1335
|
+
state.buffer = state.buffer.length > 0
|
|
1336
|
+
? Buffer.concat([state.buffer, incoming])
|
|
1337
|
+
: incoming;
|
|
1338
|
+
|
|
1339
|
+
while (!socket.destroyed) {
|
|
1340
|
+
if (state.pendingBinaryFrame) {
|
|
1341
|
+
const { header, byteLength } = state.pendingBinaryFrame;
|
|
1342
|
+
if (state.buffer.length < byteLength) {
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
const framePayload = state.buffer.subarray(0, byteLength);
|
|
1347
|
+
state.buffer = state.buffer.subarray(byteLength);
|
|
1348
|
+
state.pendingBinaryFrame = null;
|
|
1349
|
+
handleAgentBinaryFrame(socket, state, header, framePayload);
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
const newlineIndex = state.buffer.indexOf(0x0a);
|
|
1354
|
+
if (newlineIndex < 0) {
|
|
1355
|
+
if (state.buffer.length > MAX_LINE_CHARS) {
|
|
1356
|
+
writeJsonLine(socket, { type: 'error', error: 'message-too-large' });
|
|
1357
|
+
socket.destroy();
|
|
1358
|
+
}
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1269
1361
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const line = state.buffer.slice(0, newlineIndex);
|
|
1273
|
-
state.buffer = state.buffer.slice(newlineIndex + 1);
|
|
1362
|
+
const lineBuffer = state.buffer.subarray(0, newlineIndex);
|
|
1363
|
+
state.buffer = state.buffer.subarray(newlineIndex + 1);
|
|
1274
1364
|
|
|
1275
1365
|
try {
|
|
1276
|
-
const message = parseJsonLine(
|
|
1366
|
+
const message = parseJsonLine(lineBuffer.toString('utf8'));
|
|
1367
|
+
if (message?.type === 'frame.binary') {
|
|
1368
|
+
const frameKind = safeString(message.frameKind || message.kind || message.frameType, 40).toLowerCase();
|
|
1369
|
+
const maxBytes = frameKind === 'thumbnail'
|
|
1370
|
+
? MAX_THUMBNAIL_BINARY_BYTES
|
|
1371
|
+
: MAX_STREAM_BINARY_BYTES;
|
|
1372
|
+
const byteLength = Number(message.byteLength ?? message.payloadBytes ?? message.dataLength);
|
|
1373
|
+
if (!Number.isFinite(byteLength) || byteLength < 1 || byteLength > maxBytes) {
|
|
1374
|
+
writeJsonLine(socket, { type: 'error', error: 'invalid-binary-frame' });
|
|
1375
|
+
logWarn('remote', 'invalid binary frame header from agent.');
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
state.pendingBinaryFrame = {
|
|
1380
|
+
header: message,
|
|
1381
|
+
byteLength: Math.floor(byteLength)
|
|
1382
|
+
};
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1277
1386
|
handleAgentMessage(socket, state, message);
|
|
1278
1387
|
} catch (err) {
|
|
1279
1388
|
writeJsonLine(socket, { type: 'error', error: 'invalid-json' });
|
|
1280
1389
|
logWarn('remote', `invalid agent message: ${err?.message || err}`);
|
|
1281
1390
|
}
|
|
1282
|
-
|
|
1283
|
-
newlineIndex = state.buffer.indexOf('\n');
|
|
1284
1391
|
}
|
|
1285
1392
|
});
|
|
1286
1393
|
|
|
@@ -5,10 +5,13 @@ import net from 'node:net';
|
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
8
9
|
|
|
9
10
|
const BRIDGE_TOKEN = 'remote-http-smoke-token';
|
|
10
11
|
const PAIR_TOKEN = 'remote-http-pair-token';
|
|
11
12
|
const SYNTHETIC_COUNT = Number(process.env.REMOTE_HTTP_SMOKE_COUNT || 500);
|
|
13
|
+
const LOCAL_BRIDGE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
|
+
const LOCAL_BRIDGE_PACKAGE = JSON.parse(readFileSync(path.join(LOCAL_BRIDGE_DIR, 'package.json'), 'utf8'));
|
|
12
15
|
|
|
13
16
|
function wait(ms) {
|
|
14
17
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
@@ -155,7 +158,7 @@ async function runSyntheticEnabledSmoke() {
|
|
|
155
158
|
assert.equal(status.started, true);
|
|
156
159
|
assert.equal(status.port, remoteHubPort);
|
|
157
160
|
assert.equal(status.managerPackage, '@mindexec/cli');
|
|
158
|
-
assert.equal(status.managerVersion,
|
|
161
|
+
assert.equal(status.managerVersion, LOCAL_BRIDGE_PACKAGE.version);
|
|
159
162
|
assert.equal(status.agentPackage, '@mindexec/remote');
|
|
160
163
|
assert.equal(status.canvasPagination, 'none');
|
|
161
164
|
assert.equal(status.canvasDeviceListMode, 'all-devices');
|
|
@@ -8,6 +8,16 @@ function writeJsonLine(socket, payload) {
|
|
|
8
8
|
socket.write(`${JSON.stringify(payload)}\n`);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function writeBinaryFrame(socket, header, payload) {
|
|
12
|
+
const framePayload = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
13
|
+
socket.write(`${JSON.stringify({
|
|
14
|
+
...header,
|
|
15
|
+
type: 'frame.binary',
|
|
16
|
+
byteLength: framePayload.length
|
|
17
|
+
})}\n`);
|
|
18
|
+
socket.write(framePayload);
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
function wait(ms) {
|
|
12
22
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
13
23
|
}
|
|
@@ -34,6 +44,7 @@ const hub = createRemoteHub({
|
|
|
34
44
|
REMOTE_HUB_TASK_TIMEOUT_MS: '120'
|
|
35
45
|
}
|
|
36
46
|
});
|
|
47
|
+
const smokePngFrame = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mP8z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC', 'base64');
|
|
37
48
|
|
|
38
49
|
try {
|
|
39
50
|
await hub.start();
|
|
@@ -115,6 +126,33 @@ try {
|
|
|
115
126
|
assert.equal(thumbnailDevice.latestThumbnail.streamId, 'smoke-thumb');
|
|
116
127
|
assert.equal(thumbnailDevice.counters.thumbnailFramesReceived, 1);
|
|
117
128
|
|
|
129
|
+
const binaryThumbnailCommand = hub.requestThumbnail('smoke-device', {
|
|
130
|
+
streamId: 'smoke-thumb-binary',
|
|
131
|
+
maxWidth: 320,
|
|
132
|
+
maxHeight: 180,
|
|
133
|
+
quality: 50
|
|
134
|
+
});
|
|
135
|
+
assert.equal(binaryThumbnailCommand.ok, true);
|
|
136
|
+
writeBinaryFrame(socket, {
|
|
137
|
+
frameKind: 'thumbnail',
|
|
138
|
+
commandId: binaryThumbnailCommand.commandId,
|
|
139
|
+
streamId: 'smoke-thumb-binary',
|
|
140
|
+
frameSeq: 4,
|
|
141
|
+
width: 2,
|
|
142
|
+
height: 1,
|
|
143
|
+
mimeType: 'image/png',
|
|
144
|
+
capturedAt: new Date().toISOString()
|
|
145
|
+
}, smokePngFrame);
|
|
146
|
+
|
|
147
|
+
const binaryThumbnailDevice = await waitFor(() => {
|
|
148
|
+
const current = hub.listDevices();
|
|
149
|
+
return current[0]?.latestThumbnail?.frameSeq === 4 ? current[0] : null;
|
|
150
|
+
});
|
|
151
|
+
assert.equal(binaryThumbnailDevice.latestThumbnail.streamId, 'smoke-thumb-binary');
|
|
152
|
+
assert.equal(binaryThumbnailDevice.latestThumbnail.transport, 'binary');
|
|
153
|
+
assert.equal(binaryThumbnailDevice.latestThumbnail.byteLength, smokePngFrame.length);
|
|
154
|
+
assert.equal(binaryThumbnailDevice.counters.thumbnailFramesReceived, 2);
|
|
155
|
+
|
|
118
156
|
const liveCommand = hub.startLiveStream('smoke-device', {
|
|
119
157
|
streamId: 'smoke-live',
|
|
120
158
|
fps: 5,
|
|
@@ -145,6 +183,28 @@ try {
|
|
|
145
183
|
assert.equal(liveDevice.latestLiveFrame.mode, 'remote-fast');
|
|
146
184
|
assert.equal(liveDevice.counters.liveFramesReceived, 1);
|
|
147
185
|
|
|
186
|
+
writeBinaryFrame(socket, {
|
|
187
|
+
frameKind: 'stream',
|
|
188
|
+
commandId: liveCommand.commandId,
|
|
189
|
+
streamId: 'smoke-live',
|
|
190
|
+
frameSeq: 5,
|
|
191
|
+
width: 2,
|
|
192
|
+
height: 1,
|
|
193
|
+
mimeType: 'image/png',
|
|
194
|
+
mode: 'remote-fast',
|
|
195
|
+
fps: 5,
|
|
196
|
+
capturedAt: new Date().toISOString()
|
|
197
|
+
}, smokePngFrame);
|
|
198
|
+
|
|
199
|
+
const binaryLiveDevice = await waitFor(() => {
|
|
200
|
+
const current = hub.listDevices();
|
|
201
|
+
return current[0]?.latestLiveFrame?.frameSeq === 5 ? current[0] : null;
|
|
202
|
+
});
|
|
203
|
+
assert.equal(binaryLiveDevice.latestLiveFrame.streamId, 'smoke-live');
|
|
204
|
+
assert.equal(binaryLiveDevice.latestLiveFrame.transport, 'binary');
|
|
205
|
+
assert.equal(binaryLiveDevice.latestLiveFrame.byteLength, smokePngFrame.length);
|
|
206
|
+
assert.equal(binaryLiveDevice.counters.liveFramesReceived, 2);
|
|
207
|
+
|
|
148
208
|
const staleFrameBefore = liveDevice.counters.liveFramesDropped;
|
|
149
209
|
writeJsonLine(socket, {
|
|
150
210
|
type: 'stream.frame',
|