Skip to content

Instantly share code, notes, and snippets.

@jlai
Last active July 13, 2020 11:24

Revisions

  1. jlai revised this gist May 14, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion webos-ss-client.js
    Original file line number Diff line number Diff line change
    @@ -144,7 +144,7 @@ function DeviceFinder () {

    util.inherits(DeviceFinder, events.EventEmitter);

    DeviceFinder.getDeviceList = function () {
    DeviceFinder.prototype.getDeviceList = function () {
    return this.devices;
    };

  2. jlai created this gist May 14, 2014.
    301 changes: 301 additions & 0 deletions webos-ss-client.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,301 @@
    /*
    * Copyright (c) 2014 LG Electronics.
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */

    var events = require('events');
    var WebSocket = require('ws');
    var util = require('util');

    if (process.version === 'v0.4.12') {
    // Hacks to make ws library run

    Buffer.prototype.readUInt32LE = function (offset, noAssert) {
    return this[offset] | (this[offset+1] << 8) | (this[offset+2] << 16) | (this[offset+3] << 24);
    };

    Buffer.prototype.writeUInt32LE = function (value, offset, noAssert) {
    this[offset] = value & 0xFF;
    this[offset+1] = (value >>> 8) & 0xFF;
    this[offset+2] = (value >>> 16) & 0xFF;
    this[offset+3] = (value >>> 24) & 0xFF;
    };

    function FakeBuffer () {
    throw new Error("This buffer type doesn't exist on this version of node");
    };

    global.ArrayBuffer = FakeBuffer;
    global.Uint8Array = FakeBuffer;
    global.Uint16Array = FakeBuffer;
    global.Uint32Array = FakeBuffer;
    global.Int8Array = FakeBuffer;
    global.Int16Array = FakeBuffer;
    global.Int32Array = FakeBuffer;
    global.Float32Array = FakeBuffer;
    global.Float64Array = FakeBuffer;
    }

    var Client = function () {
    events.EventEmitter.call(this);

    this.requestId = 1;
    this.requests = {};
    this.manifest = {
    permissions: ["LAUNCH", "CONTROL_AUDIO"]
    };
    };

    events.EventEmitter.call(Client);
    util.inherits(Client, events.EventEmitter);

    Client.prototype.connect = function (ip, cb) {
    if (cb) {
    var handler = function () {
    this.removeListener('connected', handler);
    this.removeListener('error', handler);
    this.removeListener('close', handler);

    cb();
    };

    this.on('connected', handler);
    this.on('error', handler);
    this.on('close', handler);
    }

    this.ws = new WebSocket("ws://" + ip + ":3000", {origin: "null"});

    this.ws.on('open', function () {
    console.log("opened");
    this.send({
    type: 'register',
    payload: {
    manifest: this.manifest
    }
    });
    }.bind(this));

    this.ws.on('message', function (data) {
    console.log("Received message: " + data);

    var message = JSON.parse(data);

    var request = message.id ? this.requests[message.id] : null;

    if (message.type === "response" || message.type === "error") {
    if (request) {
    if (request.callback) {
    request.callback(message.payload);
    }

    if (!request.isSubscription) {
    delete this.requests[request];
    }
    }
    } else if (message.type === "registered") {
    this.emit('connected');
    }
    }.bind(this));

    this.ws.on('error', function (err) {
    this.emit('error', err);
    }.bind(this));

    this.ws.on('close', function () {
    this.emit('close', 'connection closed');
    }.bind(this));
    };

    Client.prototype.send = function (obj) {
    console.log("Sending: " + JSON.stringify(obj));
    this.ws.send(JSON.stringify(obj));
    };

    Client.prototype.sendRequest = function (uri, payload, cb) {
    var requestId = this.requestId++;

    this.send({
    type: 'request',
    id: requestId,
    uri: uri,
    payload: payload || {}
    });

    this.requests[requestId] = {callback: cb};
    };

    function DeviceFinder () {
    events.EventEmitter.call(this);
    this.devices = {};
    this.startDiscovery();
    };

    util.inherits(DeviceFinder, events.EventEmitter);

    DeviceFinder.getDeviceList = function () {
    return this.devices;
    };

    DeviceFinder.prototype.startDiscovery = function () {
    if (this.socket) {
    return;
    }

    var dgram = require('dgram');
    var socket = this.socket = dgram.createSocket('udp4');
    var sendSocket = dgram.createSocket('udp4');

    var sendSearch = this.sendSearch.bind(this);
    var cleanOld = function () {
    Object.keys(this.devices).forEach(function (ip) {
    var record = this.devices[ip];

    if (record && --record.ttl <= 0) {
    delete this.devices[ip];
    this.emit("lost", rinfo.address);
    }
    }, this);
    }.bind(this);

    socket.on('listening', function () {
    socket.setBroadcast(true);
    socket.addMembership('239.255.255.250');

    for (var i = 0; i < 3; i++) {
    setTimeout(function () {
    sendSearch();
    }, Math.random() * 1000);
    }

    this.rescanId = setInterval(function () {
    cleanOld();
    sendSearch();
    }, 10 * 1000);
    });

    socket.on('message', function (data, rinfo) {
    if (/^HTTP\/1.1 200 OK\r\n/.test(data) || /^NOTIFY \* HTTP\/1.1\r\n/.test(data)) {
    console.log("got response\n" + data);

    if (/USN:.*service:webos-second-screen/.test(data)) {
    if (/ssdp:byebye/.test(data)) {
    delete this.devices[rinfo.address];
    this.emit("lost", rinfo.address);
    } else {
    var existing = this.devices[rinfo.address];
    this.devices[rinfo.address] = {ttl: 2};

    if (!existing) {
    this.emit("found", rinfo.address);
    }
    }
    this.emit("update");
    }
    }
    }.bind(this));

    socket.bind(0, '0.0.0.0');
    };

    DeviceFinder.prototype.sendSearch = function () {
    var data = "M-SEARCH * HTTP/1.1\r\n" +
    "Host: 239.255.255.250:1900\r\n" +
    "ST: urn:lge-com:service:webos-second-screen:1\r\n" +
    "MAN: \"ssdp:discover\"\r\n" +
    "MX: 5\r\n" +
    "\r\n";

    console.log("sending: " + data);
    var buffer = new Buffer(data, 'ascii');
    this.socket.send(buffer, 0, buffer.length, 1900, "239.255.255.250");
    };

    DeviceFinder.prototype.stopDiscovery = function () {
    if (this.socket) {
    this.socket.end();
    this.socket.destroy();
    this.socket = null;
    }

    if (this.rescanId) {
    clearInterval(this.rescanId);
    this.rescanId = null;
    }
    };

    function openBrowserOnTV(ip, url, cb) {
    var client = new Client();

    client.connect(ip, function (err) {
    if (err) {
    console.log("error connecting");
    cb(err);
    return;
    } else {
    console.log("connected");
    }

    client.sendRequest('ssap://system.launcher/open', {"target": url}, cb);
    });
    }

    function toggleMuteOnTV(ip, cb) {
    var client = new Client();

    client.connect(ip, function (err) {
    if (err) {
    console.log("error connecting");
    cb(err);
    return;
    } else {
    console.log("connected");
    }

    client.sendRequest('ssap://audio/getMute', {}, function (response) {
    client.sendRequest('ssap://audio/setMute', {"mute": !response.mute}, cb);
    });
    });
    }

    function openBrowserOnFirstWebOSTVFound() {
    var df = new DeviceFinder();

    df.startDiscovery();

    df.on('found', function (ip) {
    console.log("found TV ip: " + ip);
    openBrowserOnTV(ip, "http://www.connectsdk.com", function done (err) {
    if (err) {
    console.log("launch browser failed: " + JSON.stringify(err));
    } else {
    console.log("launch browser successful");
    }
    process.exit(0);
    });
    });
    }

    /*
    toggleMuteOnTV("192.168.0.145", function (err) {
    if (err) {
    console.log("err: " + JSON.stringify(err));
    } else {
    console.log("success");
    }
    process.exit(0);
    });
    */

    openBrowserOnFirstWebOSTVFound();