@appium/support
Version:
Support libs used across Appium packages
162 lines • 6.19 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MJpegStream = void 0;
const lodash_1 = __importDefault(require("lodash"));
const logger_1 = __importDefault(require("./logger"));
const bluebird_1 = __importDefault(require("bluebird"));
const image_util_1 = require("./image-util");
const node_stream_1 = require("node:stream");
const node_1 = require("./node");
const axios_1 = __importDefault(require("axios"));
let MJpegConsumer = null;
/**
* @throws {Error} If `mjpeg-consumer` module is not installed or cannot be loaded
*/
async function initMJpegConsumer() {
if (!MJpegConsumer) {
try {
MJpegConsumer = (await (0, node_1.requirePackage)('mjpeg-consumer'));
}
catch {
throw new Error('mjpeg-consumer module is required to use MJPEG-over-HTTP features. ' +
'Please install it first (npm i -g mjpeg-consumer) and restart Appium.');
}
}
return MJpegConsumer;
}
const MJPEG_SERVER_TIMEOUT_MS = 10000;
/** Class which stores the last bit of data streamed into it */
class MJpegStream extends node_stream_1.Writable {
errorHandler;
url;
/** Number of JPEG frames received so far (reset on {@linkcode clear}). Use stream['updateCount'] in tests if needed. */
updateCount = 0;
/** Last received JPEG chunk (base64 via {@linkcode lastChunkBase64}); `null` when no data yet or after {@linkcode clear}/stop. Use stream['lastChunk'] in tests if needed. */
lastChunk = null;
registerStartSuccess = null;
registerStartFailure = null;
responseStream = null;
consumer = null;
/**
* @param mJpegUrl - URL of MJPEG-over-HTTP stream
* @param errorHandler - additional function that will be called in the case of any errors
* @param options - Options to pass to the Writable constructor
*/
constructor(mJpegUrl, errorHandler = lodash_1.default.noop, options = {}) {
super(options);
this.errorHandler = errorHandler;
this.url = mJpegUrl;
this.clear();
}
get lastChunkBase64() {
const lastChunk = this.lastChunk;
if (lastChunk && !lodash_1.default.isEmpty(lastChunk) && lodash_1.default.isBuffer(lastChunk)) {
return lastChunk.toString('base64');
}
return null;
}
async lastChunkPNG() {
const chunk = this.lastChunk;
if (!chunk || lodash_1.default.isEmpty(chunk) || !lodash_1.default.isBuffer(chunk)) {
return null;
}
try {
return await (0, image_util_1.requireSharp)()(chunk).png().toBuffer();
}
catch (err) {
logger_1.default.warn(`Cannot convert MJPEG chunk to PNG: ${err.message}`);
return null;
}
}
async lastChunkPNGBase64() {
const png = await this.lastChunkPNG();
return png ? png.toString('base64') : null;
}
clear() {
this.registerStartSuccess = null;
this.registerStartFailure = null;
this.responseStream = null;
this.consumer = null;
this.lastChunk = null;
this.updateCount = 0;
}
async start(serverTimeout = MJPEG_SERVER_TIMEOUT_MS) {
this.stop();
const Consumer = await initMJpegConsumer();
this.consumer = new Consumer();
const url = this.url;
try {
this.responseStream = (await (0, axios_1.default)({
url,
responseType: 'stream',
timeout: serverTimeout,
})).data;
}
catch (e) {
let message;
if (e && typeof e === 'object' && 'response' in e) {
message = JSON.stringify(e.response);
}
else if (e instanceof Error) {
message = e.message;
}
else {
message = String(e);
}
throw new Error(`Cannot connect to the MJPEG stream at ${url}. Original error: ${message}`);
}
const onErr = (err) => {
this.lastChunk = null;
logger_1.default.error(`Error getting MJpeg screenshot chunk: ${err.message}`);
this.errorHandler(err);
if (this.registerStartFailure) {
this.registerStartFailure(err);
}
};
const onClose = () => {
logger_1.default.debug(`The connection to MJPEG server at ${url} has been closed`);
this.lastChunk = null;
};
// TODO: replace with native Promises in Appium 4
const startPromise = new bluebird_1.default((res, rej) => {
this.registerStartSuccess = res;
this.registerStartFailure = rej;
}).timeout(serverTimeout, `Waited ${serverTimeout}ms but the MJPEG server never sent any images`);
this.responseStream
.once('close', onClose)
.on('error', onErr)
.pipe(this.consumer)
.pipe(this);
await startPromise;
}
stop() {
if (this.consumer) {
this.consumer.unpipe(this);
}
if (this.responseStream) {
if (this.consumer) {
this.responseStream.unpipe(this.consumer);
}
this.responseStream.destroy();
}
this.clear();
}
/* eslint-disable @typescript-eslint/no-unused-vars -- Writable.write signature requires encoding and callback */
/* eslint-disable promise/prefer-await-to-callbacks -- Node Writable.write is callback-based */
write(data, encoding, callback) {
/* eslint-enable @typescript-eslint/no-unused-vars */
/* eslint-enable promise/prefer-await-to-callbacks */
this.lastChunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
this.updateCount++;
if (this.registerStartSuccess) {
this.registerStartSuccess();
this.registerStartSuccess = null;
}
return true;
}
}
exports.MJpegStream = MJpegStream;
//# sourceMappingURL=mjpeg.js.map