appium-lg-webos-driver
Version:
LG WebOS support for Appium
373 lines • 18 kB
JavaScript
;
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _LGWSClient_instances, _LGWSClient_url, _LGWSClient_urlSecure, _LGWSClient_ws, _LGWSClient_saveClientKey, _LGWSClient_useSecureWebsocket, _LGWSClient_cmdNum, _LGWSClient_log, _LGWSClient_remoteClient, _LGWSClient_remoteKeyCooldown, _LGWSClient_keystore, _LGWSClient_valueBox, _LGWSClient_keystoreId, _LGWSClient_clientKey, _LGWSClient_receiveResponse, _LGWSClient_sendMessage, _LGWSClient_authPayload_get, _LGWSClient_getKeystore, _LGWSClient_updateAuthStatus;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LGWSClient = exports.WsEvent = void 0;
const ws_1 = __importDefault(require("ws"));
const bluebird_1 = __importDefault(require("bluebird"));
const constants_1 = require("./constants");
const support_1 = require("appium/support");
const path_1 = __importDefault(require("path"));
const lg_remote_client_1 = require("./lg-remote-client");
exports.WsEvent = ({
CONNECT: 'connect',
CLOSE: 'close',
ERROR: 'error',
OPEN: 'open',
MESSAGE: 'message',
});
class LGWSClient {
/**
*
* @param {import('../types').LGSocketClientOpts} opts
*/
constructor({ url, urlSecure, useSecureWebsocket, valueBox, deviceName, log = support_1.logger.getLogger('LGWsClient'), saveClientKey = true, remoteKeyCooldown, }) {
_LGWSClient_instances.add(this);
/** @type {string} */
_LGWSClient_url.set(this, void 0);
/** @type {string} */
_LGWSClient_urlSecure.set(this, void 0);
/** @type {WebSocket|undefined} */
_LGWSClient_ws.set(this, void 0);
/** @type {boolean} */
_LGWSClient_saveClientKey.set(this, void 0);
/** @type {boolean} */
_LGWSClient_useSecureWebsocket.set(this, void 0);
_LGWSClient_cmdNum.set(this, 0);
/** @type {import('@appium/types').AppiumLogger} */
_LGWSClient_log.set(this, void 0);
/** @type {LGRemoteClient|undefined} */
_LGWSClient_remoteClient.set(this, void 0);
/** @type {number|undefined} */
_LGWSClient_remoteKeyCooldown.set(this, void 0);
/**
* @type {import('./valuebox').ValueWrapper<string>}
*/
_LGWSClient_keystore.set(this, void 0);
/** @type {import('./valuebox').ValueBox} */
_LGWSClient_valueBox.set(this, void 0);
/**
* Unique identifier for key based on device name.
* Also doubles as a filename.
* @type {string}
*/
_LGWSClient_keystoreId.set(this, void 0);
/** @type {string|undefined} */
_LGWSClient_clientKey.set(this, void 0);
__classPrivateFieldSet(this, _LGWSClient_valueBox, valueBox, "f");
__classPrivateFieldSet(this, _LGWSClient_url, url, "f");
__classPrivateFieldSet(this, _LGWSClient_urlSecure, urlSecure, "f");
__classPrivateFieldSet(this, _LGWSClient_useSecureWebsocket, useSecureWebsocket, "f");
__classPrivateFieldSet(this, _LGWSClient_log, log, "f");
__classPrivateFieldSet(this, _LGWSClient_saveClientKey, saveClientKey, "f");
__classPrivateFieldSet(this, _LGWSClient_remoteKeyCooldown, remoteKeyCooldown, "f");
__classPrivateFieldSet(this, _LGWSClient_keystoreId, `clientKey-${deviceName}.txt`, "f");
}
/**
* @returns {Promise<void>}
*/
async connect() {
await new bluebird_1.default((res, rej) => {
/** @type {WebSocket} */
let ws;
const onOpen = () => {
ws.removeListener(exports.WsEvent.ERROR, onError);
res(ws);
};
/** @param {Error} err */
const onError = (err) => {
ws.removeListener(exports.WsEvent.OPEN, onOpen);
rej(err);
};
if (__classPrivateFieldGet(this, _LGWSClient_useSecureWebsocket, "f")) {
ws = new ws_1.default(__classPrivateFieldGet(this, _LGWSClient_urlSecure, "f"), { rejectUnauthorized: false }).once(exports.WsEvent.OPEN, onOpen).once(exports.WsEvent.ERROR, onError);
}
else {
ws = new ws_1.default(__classPrivateFieldGet(this, _LGWSClient_url, "f")).once(exports.WsEvent.OPEN, onOpen).once(exports.WsEvent.ERROR, onError);
}
__classPrivateFieldSet(this, _LGWSClient_ws, ws, "f");
});
}
/**
* @returns {Promise<void>}
*/
async disconnect() {
await new bluebird_1.default(async (res, rej) => {
const onClose = () => {
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.removeListener(exports.WsEvent.ERROR, onError);
res();
};
/** @param {Error} err */
const onError = (err) => {
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.removeListener(exports.WsEvent.CLOSE, onClose);
rej(err);
};
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.once(exports.WsEvent.CLOSE, onClose);
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.once(exports.WsEvent.ERROR, onError);
if (__classPrivateFieldGet(this, _LGWSClient_remoteClient, "f")) {
try {
await __classPrivateFieldGet(this, _LGWSClient_remoteClient, "f").disconnect();
}
catch (err) {
__classPrivateFieldGet(this, _LGWSClient_log, "f").warn(`Could not cleanly shut down remote client; err was: ${err}`);
}
finally {
__classPrivateFieldSet(this, _LGWSClient_remoteClient, undefined, "f");
}
}
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.close();
});
}
/**
*
* @returns {Promise<string>}
*/
async authenticate() {
if (__classPrivateFieldGet(this, _LGWSClient_saveClientKey, "f")) {
try {
const keystore = await __classPrivateFieldGet(this, _LGWSClient_instances, "m", _LGWSClient_getKeystore).call(this);
__classPrivateFieldGet(this, _LGWSClient_log, "f").info(`Trying to read key from file on disk at ${path_1.default.join(__classPrivateFieldGet(this, _LGWSClient_valueBox, "f").dir, keystore.id)}`);
__classPrivateFieldSet(this, _LGWSClient_clientKey, keystore.value, "f");
__classPrivateFieldGet(this, _LGWSClient_log, "f").info(`Success: key is '${__classPrivateFieldGet(this, _LGWSClient_clientKey, "f")}'`);
}
catch (ign) {
__classPrivateFieldGet(this, _LGWSClient_log, "f").info(`Could not read key from disk`);
}
}
const origClientKey = __classPrivateFieldGet(this, _LGWSClient_clientKey, "f");
if (!(await __classPrivateFieldGet(this, _LGWSClient_instances, "m", _LGWSClient_updateAuthStatus).call(this))) {
const { type, payload } = /** @type {InboundMsg<AuthPayload>} */ (await __classPrivateFieldGet(this, _LGWSClient_instances, "m", _LGWSClient_receiveResponse).call(this));
if (type === constants_1.MsgType.REGISTERED) {
__classPrivateFieldSet(this, _LGWSClient_clientKey, payload['client-key'], "f");
}
if (!(await __classPrivateFieldGet(this, _LGWSClient_instances, "m", _LGWSClient_updateAuthStatus).call(this))) {
throw new Error('Could not authenticate, please accept prompt');
}
}
if (__classPrivateFieldGet(this, _LGWSClient_saveClientKey, "f") && __classPrivateFieldGet(this, _LGWSClient_clientKey, "f") !== undefined && __classPrivateFieldGet(this, _LGWSClient_clientKey, "f") !== origClientKey) {
__classPrivateFieldGet(this, _LGWSClient_log, "f").info(`Client key changed, writing it to disk`);
const keystore = await __classPrivateFieldGet(this, _LGWSClient_instances, "m", _LGWSClient_getKeystore).call(this);
await keystore.put(__classPrivateFieldGet(this, _LGWSClient_clientKey, "f"));
}
return /** @type {string} */ (__classPrivateFieldGet(this, _LGWSClient_clientKey, "f"));
}
/**
*
* @param {any} msg
* @returns {msg is import('../types').RegisteredMsg}
*/
static isRegisteredMsg(msg) {
return msg.type === constants_1.MsgType.REGISTERED;
}
/**
*
* @param {any} msg
* @returns {msg is import('../types').PromptMsg}
*/
static isPromptMsg(msg) {
return msg.type === constants_1.MsgType.RESPONSE && msg.payload?.pairingType === 'PROMPT';
}
/**
*
* @returns {Promise<string>}
*/
async initialize() {
await this.connect();
return await this.authenticate();
}
/**
* @template {import('./constants').MessageType} T
* @template {SerializableObject|void} [P=void]
* @template {SerializableObject} [R=any]
* @param {T} type
* @param {string} [uri]
* @param {T extends 'register' ? AuthPayload : P} [payload]
* @returns {Promise<InboundMsg<R>>}
*/
async command(type, uri, payload = /** @type {T extends 'register' ? AuthPayload : P} */ ({})) {
var _a, _b;
/** @type {OutboundMsg<T extends 'register' ? AuthPayload : P>} */
const msg = {
id: `${type}_${__classPrivateFieldSet(this, _LGWSClient_cmdNum, (_b = __classPrivateFieldGet(this, _LGWSClient_cmdNum, "f"), _a = _b++, _b), "f"), _a}`,
type,
payload,
};
if (uri) {
msg.uri = uri;
}
const res = /** @type {InboundMsg<R>} */ (await __classPrivateFieldGet(this, _LGWSClient_instances, "m", _LGWSClient_sendMessage).call(this, msg));
if (res?.type === constants_1.MsgType.ERROR) {
throw new Error(res?.error);
}
return res;
}
/**
* @template {SerializableObject|void} [P=void]
* @template {SerializableObject} [R=any]
* @param {string} uri
* @param {P} [payload]
* @returns {Promise<R|undefined>}
*/
async request(uri, payload = /** @type {P} */ ({})) {
if (!__classPrivateFieldGet(this, _LGWSClient_clientKey, "f")) {
throw new Error(`Can't send request without client key set`);
}
const res = await this.command(constants_1.MsgType.REQUEST, uri, payload);
return res?.payload;
}
// API commands
async getListApps() {
const uri = 'ssap://com.webos.applicationManager/listApps';
return await this.request(uri);
}
async getForegroundAppInfo() {
const uri = 'ssap://com.webos.applicationManager/getForegroundAppInfo';
return await this.request(uri);
}
async getRemoteClient() {
if (!__classPrivateFieldGet(this, _LGWSClient_remoteClient, "f")) {
const uri = 'ssap://com.webos.service.networkinput/getPointerInputSocket';
const { socketPath } = await this.request(uri);
__classPrivateFieldSet(this, _LGWSClient_remoteClient, new lg_remote_client_1.LGRemoteClient({
url: socketPath,
log: __classPrivateFieldGet(this, _LGWSClient_log, "f"),
keyCooldown: __classPrivateFieldGet(this, _LGWSClient_remoteKeyCooldown, "f"),
}), "f");
await __classPrivateFieldGet(this, _LGWSClient_remoteClient, "f").connect();
}
return __classPrivateFieldGet(this, _LGWSClient_remoteClient, "f");
}
async getServiceList() {
const res = await this.request('ssap://api/getServiceList');
return res?.services;
}
async getVolume() {
const res = await this.request('ssap://audio/getVolume');
return res?.volumeStatus;
}
async volumeUp() {
return await this.request('ssap://audio/volumeUp');
}
async volumeDown() {
return await this.request('ssap://audio/volumeDown');
}
async mute() {
return await this.request('ssap://audio/setMute', { mute: true });
}
async unmute() {
return await this.request('ssap://audio/setMute', { mute: false });
}
async play() {
return await this.request('ssap://media.controls/play');
}
async stop() {
return await this.request('ssap://media.controls/stop');
}
async rewind() {
return await this.request('ssap://media.controls/rewind');
}
async fastForward() {
return await this.request('ssap://media.controls/fastForward');
}
async channelDown() {
return await this.request('ssap://tv/channelDown');
}
async channelUp() {
return await this.request('ssap://tv/channelUp');
}
}
exports.LGWSClient = LGWSClient;
_LGWSClient_url = new WeakMap(), _LGWSClient_urlSecure = new WeakMap(), _LGWSClient_ws = new WeakMap(), _LGWSClient_saveClientKey = new WeakMap(), _LGWSClient_useSecureWebsocket = new WeakMap(), _LGWSClient_cmdNum = new WeakMap(), _LGWSClient_log = new WeakMap(), _LGWSClient_remoteClient = new WeakMap(), _LGWSClient_remoteKeyCooldown = new WeakMap(), _LGWSClient_keystore = new WeakMap(), _LGWSClient_valueBox = new WeakMap(), _LGWSClient_keystoreId = new WeakMap(), _LGWSClient_clientKey = new WeakMap(), _LGWSClient_instances = new WeakSet(), _LGWSClient_receiveResponse =
/**
* @template {SerializableObject} [R=any]
* @returns {Promise<InboundMsg<R>>}
*/
async function _LGWSClient_receiveResponse() {
return await new bluebird_1.default((res, rej) => {
/** @param {string} msg */
const onMsg = (msg) => {
__classPrivateFieldGet(this, _LGWSClient_log, "f").debug(`Got response: ${msg}`);
try {
res(JSON.parse(msg));
}
catch (err) {
onError(/** @type {Error} */ (err));
}
};
/** @param {Error} err */
const onError = (err) => {
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.removeListener(exports.WsEvent.MESSAGE, onMsg);
rej(err);
};
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.once(exports.WsEvent.MESSAGE, onMsg);
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.once(exports.WsEvent.ERROR, onError);
});
}, _LGWSClient_sendMessage =
/**
* @template {SerializableObject|void} [P=void]
* @template {SerializableObject} [R=any]
* @param {OutboundMsg<P>} msgObj
* @returns {Promise<InboundMsg<R>>}
*/
async function _LGWSClient_sendMessage(msgObj) {
const msg = JSON.stringify(msgObj);
__classPrivateFieldGet(this, _LGWSClient_log, "f").debug(`Sending message: ${msg}`);
__classPrivateFieldGet(this, _LGWSClient_ws, "f")?.send(msg);
return await __classPrivateFieldGet(this, _LGWSClient_instances, "m", _LGWSClient_receiveResponse).call(this);
}, _LGWSClient_authPayload_get = function _LGWSClient_authPayload_get() {
/** @type {AuthPayload} */
const payload = { ...constants_1.AUTH_PAYLOAD };
if (__classPrivateFieldGet(this, _LGWSClient_clientKey, "f")) {
payload['client-key'] = __classPrivateFieldGet(this, _LGWSClient_clientKey, "f");
}
return payload;
}, _LGWSClient_getKeystore =
/**
* Gets / initializes the keystore based on the keystore ID
* @see {@linkcode #keystoreId}
* @returns {Promise<import('./valuebox').ValueWrapper<string>>}
*/
async function _LGWSClient_getKeystore() {
return (__classPrivateFieldGet(this, _LGWSClient_keystore, "f") ?? (__classPrivateFieldSet(this, _LGWSClient_keystore, await __classPrivateFieldGet(this, _LGWSClient_valueBox, "f").createWrapper(__classPrivateFieldGet(this, _LGWSClient_keystoreId, "f")), "f")));
}, _LGWSClient_updateAuthStatus =
/**
* Resolver `true` if registered, `false` if not.
* @returns {Promise<boolean>}
*/
async function _LGWSClient_updateAuthStatus() {
const msg = await this.command(constants_1.MsgType.REGISTER, undefined, __classPrivateFieldGet(this, _LGWSClient_instances, "a", _LGWSClient_authPayload_get));
if (LGWSClient.isRegisteredMsg(msg)) {
__classPrivateFieldSet(this, _LGWSClient_clientKey, msg.payload['client-key'], "f");
return true;
}
else if (LGWSClient.isPromptMsg(msg)) {
return false;
}
throw new Error(`Could not determine auth status; client is not registered, but no prompt is available`);
};
/**
* @typedef {import('./constants').AuthPayload} AuthPayload
* @typedef {import('../types').SerializableObject} SerializableObject
*/
/**
* @template {SerializableObject} [P=any]
* @typedef {import('../types').InboundMsg<P>} InboundMsg
*/
/**
* @template {SerializableObject|void} [P=void]
* @typedef {import('../types').OutboundMsg<P>} OutboundMsg
*/
//# sourceMappingURL=lg-socket-client.js.map