UNPKG

holoplay-core

Version:

A library that works with Looking Glass HoloPlay Service

379 lines (375 loc) 10.4 kB
/** * This files defines the HoloPlayClient class and Message class. * * Copyright (c) [2019] [Looking Glass Factory] * * @link https://lookingglassfactory.com/ * @file This files defines the HoloPlayClient class and Message class. * @author Looking Glass Factory. * @version 0.0.7 * @license SEE LICENSE IN LICENSE.md */ /** Class representing a client to communicates with the HoloPlayService. */ class HoloPlayClient { /** * Establish a client to talk to HoloPlayService. * @constructor * @param {function} initCallback - optional; a function to trigger when response is received * @param {function} errCallback - optional; a function to trigger when there is a connection error * @param {function} closeCallback - optional; a function to trigger when the socket is closed * @param {boolean} debug - optional; default is false * @param {string} appId - optional * @param {boolean} isGreedy - optional * @param {string} oncloseBehavior - optional, can be 'wipe', 'hide', 'none' */ constructor (initCallback, errCallback, closeCallback, debug=false, appId, isGreedy, oncloseBehavior) { this.reqs = []; this.reps = []; this.requestId = this.getRequestId(); this.debug = debug; this.isGreedy = isGreedy; this.errCallback = errCallback; this.closeCallback = closeCallback; this.alwaysdebug = false; this.isConnected = false; let initCmd = null; if (appId || isGreedy || oncloseBehavior) { initCmd = new InitMessage(appId, isGreedy, oncloseBehavior, this.debug); } else { if (debug) this.alwaysdebug = true; if (typeof initCallback == 'function') initCmd = new InfoMessage(); } this.openWebsocket(initCmd, initCallback); } /** * Send a message over the websocket to HoloPlayService. * @public * @param {Message} msg - message object * @param {integer} timeoutSecs - optional, default is 60 seconds */ sendMessage(msg, timeoutSecs=60) { if (this.alwaysdebug) msg.cmd.debug = true; let cborData = msg.toCbor(); return this.sendRequestObj(cborData, timeoutSecs); } /** * Disconnects from the web socket. * @public */ disconnect() { this.ws.close(); } /** * Open a websocket and set handlers * @private */ openWebsocket (firstCmd=null, initCallback=null) { this.ws = new WebSocket('ws://localhost:11222/driver', ['rep.sp.nanomsg.org']); this.ws.parent = this; this.ws.binaryType = 'arraybuffer'; this.ws.onmessage = this.messageHandler; this.ws.onopen = (() => { this.isConnected = true; if (this.debug) { console.log("socket open"); } if (firstCmd != null) { this.sendMessage(firstCmd).then(initCallback); } }); this.ws.onerror = this.onSocketError; this.ws.onclose = this.onClose; } /** * Send a request object over websocket * @private */ sendRequestObj (data, timeoutSecs) { return new Promise((resolve, reject) => { let reqObj = { id: this.requestId++, parent: this, payload: data, success: resolve, error: reject, send: function () { if (this.debug) console.log("attemtping to send request with ID " + this.id); this.timeout = setTimeout(reqObj.send.bind(this), timeoutSecs * 1000); let tmp = new Uint8Array(data.byteLength + 4); let view = new DataView(tmp.buffer); view.setUint32(0, this.id); tmp.set(new Uint8Array(this.payload), 4); this.parent.ws.send(tmp.buffer); } }; this.reqs.push(reqObj); reqObj.send(); }); } /** * Handles a message when received * @private */ messageHandler (event) { console.log("message"); let data = event.data; if (data.byteLength < 4) return; let view = new DataView(data); let replyId = view.getUint32(0); if (replyId < 0x80000000) { this.parent.err("bad nng header"); return; } let i = this.parent.findReqIndex(replyId); if (i == -1) { this.parent.err("got reply that doesn't match known request!"); return; } let rep = { id: replyId, payload: CBOR.decode(data.slice(4)) }; if (rep.payload.error == 0) { this.parent.reqs[i].success(rep.payload); } else { this.parent.reqs[i].error(rep.payload); } clearTimeout(this.parent.reqs[i].timeout); this.parent.reqs.splice(i, 1); this.parent.reps.push(rep); if (this.debug) { console.log(rep.payload); } } getRequestId () { return Math.floor(this.prng() * (0x7fffffff)) + 0x80000000; } onClose(event) { this.parent.isConnected = false; if (this.parent.debug) { console.log('socket closed'); } if (typeof this.parent.closeCallback == 'function') this.parent.closeCallback(event); } onSocketError (error) { if (this.parent.debug) { console.log(error); } if (typeof this.parent.errCallback == 'function'){ this.parent.errCallback(error); } } err (errorMsg) { if (this.debug) { console.log("[DRIVER ERROR]" + errorMsg); } // TODO : make this return an event obj rather than a string // if (typeof this.errCallback == 'function') // this.errCallback(errorMsg); } findReqIndex (replyId) { let i = 0; for (; i<this.reqs.length; i++) { if (this.reqs[i].id == replyId) { return i; } } return -1; } prng () { if (this.rng == undefined) { this.rng = generateRng(); } return this.rng(); } } /** A class to represent messages being sent over to HoloPlay Service */ class Message { /** * Construct a barebone message. * @constructor */ constructor (cmd, bin) { this.cmd = cmd; this.bin = bin; } /** * Convert the class instance to the CBOR format * @public * @returns {CBOR} - cbor object of the message */ toCbor () { return CBOR.encode(this); } } /** Message to init. Extends the base Message class. */ class InitMessage extends Message { /** * @constructor * @param {string} appId - a unique id for app * @param {boolean} isGreedy - will it take over screen * @param {string} oncloseBehavior - can be 'wipe', 'hide', 'none' */ constructor (appId='', isGreedy=false, onclose='', debug=false) { let cmd = { "init": {} }; if (appId != '') cmd["init"].appid = appId; if (onclose != '') cmd["init"].onclose = onclose; if (isGreedy) cmd["init"].greedy = true; if (debug) cmd["init"].debug = true; super(cmd, null); } } /** Delete a quilt from HoloPlayService. Extends the base Message class. */ class DeleteMessage extends Message { /** * @constructor * @param {string} name - name of the quilt */ constructor (name='') { let cmd = { "delete": {"name": name} }; super(cmd, null); } } /** Check if a quilt exist in cache. Extends the base Message class. */ class CheckMessage extends Message { /** * @constructor * @param {string} name - name of the quilt */ constructor (name='') { let cmd = { "check": {"name": name} }; super(cmd, null); } } /** Wipes the image in Looking Glass and displays the background image */ class WipeMessage extends Message { /** * @constructor * @param {number} targetDisplay - optional, if not provided, default is 0 */ constructor (targetDisplay=null) { let cmd = { "wipe": {} }; if (targetDisplay != null) cmd["wipe"].targetDisplay = targetDisplay; super(cmd, null); } } /** Get info from the HoloPlayService */ class InfoMessage extends Message { /** * @constructor */ constructor () { let cmd = { "info": {} }; super(cmd, null); } } /** Show a quilt in the Looking Glass with the binary data of quilt provided */ class ShowMessage extends Message { /** * @constructor * @param {object} */ constructor (settings={vx: 5, vy: 9, aspect: 1.6}, bindata='', targetDisplay=null) { let cmd = { "show": { "source": "bindata", "quilt": { "type": "image", "settings": settings } } }; if (targetDisplay != null) cmd["show"]["targetDisplay"] = targetDisplay; super(cmd, bindata); } } /** extends the base Message class */ class CacheMessage extends Message { constructor (name, settings={vx: 5, vy: 9, aspect: 1.6}, bindata='', show=false) { let cmd = { "cache": { "show" : show, "quilt": { "name": name, "type": "image", "settings": settings, } } }; super(cmd, bindata); } } class ShowCachedMessage extends Message { constructor (name, targetDisplay=null, settings=null) { let cmd = { "show": { "source": "cache", "quilt": { "name": name } } }; if (targetDisplay != null) cmd["show"]["targetDisplay"] = targetDisplay; if (settings != null) cmd["show"]["quilt"].settings = settings; super(cmd, null); } } /* helper function */ function generateRng() { function xmur3(str) { for (var i = 0, h = 1779033703 ^ str.length; i < str.length; i++) h = Math.imul(h ^ str.charCodeAt(i), 3432918353), h = h << 13 | h >>> 19; return function () { h = Math.imul(h ^ h >>> 16, 2246822507); h = Math.imul(h ^ h >>> 13, 3266489909); return (h ^= h >>> 16) >>> 0; } } function xoshiro128ss(a, b, c, d) { return (() => { var t = b << 9, r = a * 5; r = (r << 7 | r >>> 25) * 9; c ^= a; d ^= b; b ^= c; a ^= d; c ^= t; d = d << 11 | d >>> 21; return (r >>> 0) / 4294967296; }) }; var state = Date.now(); var seed = xmur3(state.toString()); return xoshiro128ss(seed(), seed(), seed(), seed()); } if (typeof module === "object" && module && typeof module.exports === "object") { module.exports = { HoloPlayClient: HoloPlayClient, Message: Message, InitMessage: InitMessage, DeleteMessage: DeleteMessage, CheckMessage: CheckMessage, WipeMessage: WipeMessage, InfoMessage: InfoMessage, ShowMessage: ShowMessage, CacheMessage: CacheMessage, ShowCachedMessage: ShowCachedMessage } }