UNPKG

roku-ecp

Version:

A Node package designed to control Roku devices using TypeScript

202 lines (201 loc) 9.32 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Roku = void 0; const types_1 = require("./types"); const xml_js_1 = require("xml-js"); const util_1 = require("./util"); const app_1 = require("./app"); class Roku { /** * Initializes a new Roku given its location * @param {string} location The URL of the device */ constructor(location) { this.queue = []; this.processing = false; this.toString = () => `Roku (${this.location})`; // sequentially execute keypresses this.execute = (recursing, cmd) => __awaiter(this, void 0, void 0, function* () { if (cmd) this.queue.push(cmd); if (!recursing && this.processing) return; if (!this.queue.length) return (this.processing = false); this.processing = true; this.queue .shift()() .then(() => this.execute(true)); }); /** * Sends a GET request * @param {string} path Request URL * @param {{}} params Request parameters */ this.get = (path, params) => fetch(`${this.location}${path}${util_1.queryString(params)}`); /** * Sends a POST request with no body * @param {string} path Request URL * @param {{}} params Request parameters */ this.post = (path, params) => fetch(`${this.location}${path}${util_1.queryString(params)}`, { method: "post" }); /** * Enables an external client to drive the Roku Search UI to find and (optionally) launch content from an available provider. * @param {{}} options Search parameters (keyword required) */ this.search = (options) => this.post("search/browse", options); /** * Equivalent to pressing down and releasing the identified remote control key. Can accept keyboard alphanumeric characters when a keyboard screen is active (see type) * @param {string[]} keys A list of keys to press */ this.press = (...keys) => { keys.forEach((key) => { const literalKey = Object.keys(types_1.Keys).includes(key.toUpperCase()) ? key : `LIT_${key}`; this.execute(false, () => this.post(`keypress/${literalKey}`)); }); }; /** * Equivalent to pressing the identified remote control key * @param {string} key Key to be pressed (case insensitive) */ this.keyDown = (key) => this.post(`keydown/${key}`); /** * Equivalent to releasing the identified remote control key * @param {string} key Key to be released (case insensitive) */ this.keyUp = (key) => this.post(`keyup/${key}`); /** * Launches the identified app. Can accept launch parameters for deep linking. * @param {number} id The id of the app * @param {{}} options Launch parameters (for deep linking) */ this.launch = (id, options) => this.post(`launch/${id}`, options); /** * Sends custom events to the current application * @param {{}} options Input parameters */ this.input = (options) => this.post("input", options); /** * Retrieves information about the device * @returns {{}} Device details */ this.info = () => this.get("query/device-info") .then((res) => res.text()) .then((xml) => util_1.parse(xml_js_1.xml2js(xml, { compact: true })["device-info"])); /** * Retrieves information about the current application * @returns {{}} App details */ this.activeApp = () => this.get("query/active-app") .then((res) => res.text()) .then((xml) => util_1.parse(xml_js_1.xml2js(xml, { compact: true })["active-app"]["app"])); /** * Retrieves information about the device's applications * @returns {{}[]} An array of app details */ this.apps = () => this.get("query/apps") .then((res) => res.text()) .then((xml) => xml_js_1.xml2js(xml, { compact: true })["apps"]["app"].map(util_1.parse)); /** * Retrieves information about the currently tuned TV channel * @remarks Restricted to Roku TV devices that support live TV * @returns {{}} Channel details */ this.activeChannel = () => this.get("query/tv-active-channel") .then((res) => res.text()) .then((xml) => util_1.parse(xml_js_1.xml2js(xml, { compact: true })["tv-channel"]["channel"])); /** * Retrieves information about the TV channel / line-up available for viewing in the TV tuner UI * @remarks Restricted to Roku TV devices that support live TV * @returns {{}[]} An array of channel details */ this.channels = () => this.get("query/tv-channels") .then((res) => res.text()) .then((xml) => { const parsed = xml_js_1.xml2js(xml, { compact: true })["tv-channels"]; if (parsed["channel"]) return parsed["channel"].map(util_1.parse); return []; }); /** * Launches the identified channel * @remarks Restricted to Roku TV devices that support live TV * @param {number} id The channel number */ this.launchChannel = (id) => this.post("launch/tvinput.dtv", { ch: id }); /** * Wait between key presses * @param {number} ms Delay time in milliseconds */ this.wait = (ms) => this.execute(false, () => new Promise((resolve) => setTimeout(resolve, ms))); /** * Exits the current channel, and launches the Channel Store details screen of the identified app. * @param {number} id The id of the app */ this.install = (id) => this.post(`install/${id}`); /** * Types alphanumeric characters, provided a keyboard screen is active * @param {string} input Text to be typed */ this.type = (input) => input.split("").forEach((char) => this.press(char)); /** * Sends custom sensor events to the device * @param {"acceleration" | "orientation" | "rotation" | "magnetic"} input The sensor type * @param {{x: number, y: number, z: number}} values The sensor input values */ this.sensor = (input, values) => { const params = {}; Object.keys(values).forEach((key) => (params[`${input}.${key}`] = values[key])); return this.input(params); }; /** * Sends custom touch or multi-touch events to the device * @param {{x: number, y: number}} values The touch input values * @param {"up" | "down" | "press" | "move" | "cancel"} op The touch operation */ this.touch = (values, op) => { const params = op ? { "touch.0.op": op } : {}; Object.keys(values).forEach((key) => (params[`touch.0.${key}`] = values[key])); return this.input(params); }; /** * Creates a new instance of the `App` class * @param {{}} appInfo App information (id required) * @returns {App} The new App */ this.app = (appInfo) => new app_1.App(appInfo, this); /** * Returns an icon corresponding to the identified application * @param {number} id The id of the app */ this.icon = (id) => this.get(`query/icon/${id}`); /** * Retrieves information about the current stream segment and position of the content being played, the running time of the content, audio format, and buffering * @returns {{}} Media player details */ this.mediaPlayer = () => this.get("query/media-player") .then((res) => res.text()) .then((xml) => util_1.parse(xml_js_1.xml2js(xml, { compact: true })["player"])); // if (!location) throw "No location provided" // -> regex should match http://*:8060 | empty if (!location) throw new Error("No device location provided"); if (!/http:\/\/(localhost|\d+\.\d+\.\d+\.\d+):8060\//.test(location)) throw new Error("Invalid device location should match http://*:8060/"); this.location = location; } } exports.Roku = Roku; // static members Roku.discover = util_1.discover; Roku.keys = types_1.Keys;