Source: org/terraswarm/accessor/accessors/web/hosts/cordova/modules/socket/socket.js

// Copyright (c) 2016-2017 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//
//

/**
 * Module supporting TCP sockets. FIXME: NOT ALL MODULE FUNCTIONALITY HAS BEEN IMPLEMENTED.
 * @module socket
 * @author Matt Weber
 * @version $$Id: socket.js 76043 2017-05-06 17:44:23Z eal $$
 */

// Stop extra messages from jslint.  Note that there should be no
// space between the / and the * and global.
/*globals actor, console, exports, Java, require, util */
/*jslint nomen: true */
/*jshint globalstrict: true */
"use strict";

//Uses the cordova-plugin-chrome-apps-sockets-tcp plugin.
//https://www.npmjs.com/package/cordova-plugin-chrome-apps-sockets-tcp
console.log("WARNING: socket.js module is incomplete.");

if(typeof chrome.sockets == "undefined"){
    console.log("WARNING: socket.js module does not have cordova-plugin-chrome-apps-sockets-tcp installed and will not work correctly.");
}
var tcpPlugin = chrome.sockets.tcp; 


var EventEmitter = require('events').EventEmitter;
var util = require('util');


///////////////////////////////////////////////////////////////////////////////
//// defaultClientOptions

/** FIXME Below are cordova-plugin-chrome-apps-sockets-tcp options. All of them 
 * are optional. Default values are shown here.
 * var defaultClientOptions = {
    'persistent' : false,
    'name' : '', //an application-defined string associated with the socket
    'bufferSize' : 4096 //the size of the buffer used to receive data
 } 
 
 */

/** FIXME: Below are cape code options that are not applicable here.
 * The default options for socket connections from the client side.
 */

/* var defaultClientOptions = {
    'connectTimeout': 6000, // in milliseconds.
    'idleTimeout': 0, // In seconds. 0 means don't timeout.
    'discardMessagesBeforeOpen': false,
    'emitBatchDataAsAvailable': false,
    'keepAlive': true,
    'maxUnsentMessages': 100,
    'noDelay': true,
    'pfxKeyCertPassword': '',
    'pfxKeyCertPath': '',
    'rawBytes': true,
    'receiveBufferSize': 65536,
    'receiveType': 'string',
    'reconnectAttempts': 10,
    'reconnectInterval': 1000,
    'sendBufferSize': 65536,
    'sendType': 'string',
    'sslTls': false,
    'trustAll': false,
    'trustedCACertPath': ''
};
*/

/** Construct an instance of a socket client that can send or receive messages
 *  to a server at the specified host and port.
 *  The returned object subclasses EventEmitter and emits the following events:
 *
 *  * open: Emitted with no arguments when the socket has been successfully opened.
 *  * data: Emitted with the data as an argument when data arrives on the socket.
 *  * close: Emitted with no arguments when the socket is closed.
 *  * error: Emitted with an error message when an error occurs.
 *
 *  You can invoke the this.send() function of this SocketClient object
 *  to send data to the server. If the socket is not opened yet,
 *  then data will be discarded or queued to be sent later,
 *  depending on the value of the discardMessagesBeforeOpen option
 *  (which defaults to false).
 *  
 *  FIXME: No options (including discardMessagesBeforeOpen) 
 *  have been implemented for this Cordova module!!!
 *
 *  The event 'close' will be emitted when the socket is closed, and 'error' if an
 *  an error occurs (with an error message as an argument).
 *
 *  A simple example that sends a message, and closes the socket on receiving a reply.
 *
 *      var socket = require('socket');
 *      var client = new socket.SocketClient();
 *      client.on('open', function() {
 *          client.send('hello world');
 *      });
 *      client.on('data', function onData(data) {
 *          print('Received from socket: ' + data);
 *          client.close();
 *      });
 *      socket.open();
 *
 *  @param port The remote port to connect to.
 *  @param host The remote host to connect to.
 *  @param options The options.
 */

exports.SocketClient = function(port, host, options) {
    console.log('socket.js: SocketClient(' + port + ', ' + host + ', options');
    console.log('Warning: Cordova socket options have not yet been implemented in socket.js!');
    var socketId;

    // Set default values of arguments.
    // Careful: port == 0 means to find an available port, I think.
    this.port = port;
    if (port === null) {
        this.port = 4000;
    }
    this.host = host || 'localhost';


    //FIXME: Reconcile tcpPlugin's options with capeCode's options.
    //All of cordova-plugin-chrome-apps-sockets-tcp's options are optional, so it's ok to omit them.
    this.options = {};

    // Fill in default values.
    //this.options = options || {};
    //this.options = util._extend(defaultClientOptions, this.options);

    //FIXME: support pending sends
    //this.pendingSends = []
};
util.inherits(exports.SocketClient, EventEmitter);

/** Open the client. Call this after setting up listeners. */
exports.SocketClient.prototype.open = function () {
    console.log('socket.js: SocketClient.open(): ' + this.port + ", " + this.host);
    var thiz = this;
    tcpPlugin.create( function(createInfo){
        thiz.socketId = createInfo.socketId;
        tcpPlugin.connect(thiz.socketId, thiz.host, thiz.port, function(connectResult){
            if(connectResult < 0){
                console.log("Error connecting in socket.js. Error Code: " + connectResult);
                thiz.emit('error', "Error connecting in socket.js. Error Code: " + connectResult);
            }
            tcpPlugin.onReceive.addListener(function(info){
                var message;
                if(thiz.socketId == info.socketId){
                    message = arrayBufferToU8String(info.data);
                    thiz.emit('data', message);
                }
            });
            tcpPlugin.onReceiveError.addListener(function(info){
                var message;
                if(thiz.socketId == info.socketId){
                    thiz.emit('error', "Network error occured while waiting for data on socket: " + thiz.socketID + ", to " + thiz.host +" " + thiz.port + ". Error code: " + info.resultCode);
                }
            });

            thiz.emit('open');


        });
    });
};


/** cordova-plugin-chrome-apps-sockets-tcp uses an array buffer for sent and received messages.
 *  This helper function interprets the buffer as a UTF-8 string
 * 
 */

function arrayBufferToU8String(buf) {
  return String.fromCharCode.apply(null, new Uint8Array(buf));
}

/** cordova-plugin-chrome-apps-sockets-tcp uses an array buffer for sent and received messages.
 *  This helper function creates a buffer for a UTF-8 string. 
 * 
 */

function stringToArrayBuffer(str) {
  var buf = new ArrayBuffer(str.length); // 2 bytes for each char
  var bufView = new Uint8Array(buf);

  for (var i=0, strLen=str.length; i < strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return buf;
}


/** Send data over the socket.
 *  If the socket has not yet been successfully opened, then queue
 *  data to be sent later, when the socket is opened, unless
 *  discardMessagesBeforeOpen is true.
 *
 *  FIXME: discardMessageBeforeOpen is an option which has NOT been implemented yet.
 *  current behavior is as if it's set to true.
 *  @param data The data to send.
 */

exports.SocketClient.prototype.send = function (data) {
    //console.log('socket.js: SocketClient.send(' + data + ')');
    var thiz = this;
    tcpPlugin.send(this.socketId, stringToArrayBuffer(data), function(sendInfo){
        if(sendInfo.resultCode < 0){
            console.log("Error in sending. Error Code: " + sendInfo.resultCode);
            thiz.emit('error', "Error in sending. Error Code: " + sendInfo.resultCode);
            thiz.close(); //FIXME: is this right?
        }
        console.log("Send complete");
        if(sendInfo.resultCode == 0){
            console.log("Sent " + sendInfo.bytesSent + " bytes.");
        }
    });
};


/** Close the current connection with the server.
 *  This will indicate to the server that no more data
 *  will be sent, but data may still be received from the server.
 */
exports.SocketClient.prototype.close = function () {
    console.log('socket.js: SocketClient.close()');
    var thiz = this;
    tcpPlugin.close(this.socketId, function(){
        thiz.emit('close');
    });
};