Nikolay Suslov 6 ani în urmă
comite
c75b9e83cb
9 a modificat fișierele cu 1431 adăugiri și 0 ștergeri
  1. 5 0
      .gitignore
  2. 36 0
      LICENSE.md
  3. 5 0
      README.md
  4. 106 0
      lib/helpers.js
  5. 493 0
      lib/reflector.js
  6. 6 0
      node-server.js
  7. 116 0
      node_vwf.js
  8. 629 0
      package-lock.json
  9. 35 0
      package.json

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+# Node.js
+node_modules
+/npm-debug.log
+cert
+.vscode

+ 36 - 0
LICENSE.md

@@ -0,0 +1,36 @@
+LiveCoding.space
+The MIT License (MIT)
+Copyright (c) 2018 Nikolai Suslov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+Virtual World Framework
+Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
+Secretary of Defense (Personnel & Readiness).
+
+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.

+ 5 - 0
README.md

@@ -0,0 +1,5 @@
+# LiveCoding.space reflector
+
+Standalone message reflector for LiveCoding.space application (base on VWF reflector)
+time.livecoding.space
+

+ 106 - 0
lib/helpers.js

@@ -0,0 +1,106 @@
+//   helpers.js
+//   This file contains some low level helper functions for the VWF nodeJS server.
+
+var libpath = require( 'path' ),
+    fs = require( 'fs' );
+
+    
+// List of valid ID characters for use in an instance.
+var ValidIDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+// IsInstanceID tests if the passed in potential Instance ID 
+// is a valid instance id.
+function IsInstanceID( potentialInstanceID ) {
+    if ( potentialInstanceID.match(/^[0-9A-Za-z]{16}$/) ) {
+        return true;
+    }
+    return false;
+}
+
+// GenerateInstanceID function creates a randomly generated instance ID.
+function GenerateInstanceID( ) {
+    var text = "";
+    
+    for( var i=0; i < 16; i++ )
+        text += ValidIDChars.charAt( Math.floor( Math.random( ) * ValidIDChars.length ) );
+
+    return text;
+}
+
+// JoinPath
+// Takes multiple arguments, joins them together into one path.
+function JoinPath( /* arguments */ ) {
+    var result = "";
+    if ( arguments.length > 0 ) {
+        if ( arguments[ 0 ] ) {
+            result = arguments[ 0 ];
+        }
+        for ( var index = 1; index < arguments.length; index++ ) {
+            var newSegment = arguments[ index ];
+            if ( newSegment == undefined ) {
+                newSegment = "";
+            }
+
+            if ( ( newSegment[ 0 ] == "/" ) && ( result[ result.length - 1 ] == "/" ) ) {
+                result = result + newSegment.slice( 1 );
+            } else if ( ( newSegment[ 0 ] == "/" ) || ( result[ result.length - 1 ] == "/" ) ) {
+                result = result + newSegment;
+            } else {
+                result = result + "/" + newSegment;
+            }
+            //result = libpath.join( result, newSegment );
+        }
+    }
+    return result;
+}
+
+// IsDirectory tests if the passed in path exists, and if it is a directory.
+function IsDirectory( path ) {
+    // var seperatorFixedPath = path.replace( /\//g, libpath.sep );
+    // if ( ! fs.existsSync( seperatorFixedPath ) ) {
+    //     return false;
+    // }
+    // return fs.statSync( seperatorFixedPath ).isDirectory();
+    return true
+}
+
+
+// IsFile tests if the passed in path exists, and if it is a file.
+function IsFile( path ) {
+    // var seperatorFixedPath = path.replace( /\//g, libpath.sep );
+    // if ( ! fs.existsSync( seperatorFixedPath ) ) {
+    //     return false;
+    // }
+    // return fs.statSync( seperatorFixedPath ).isFile();
+
+    return true
+
+}
+
+
+// GenerateSegments takes a string, breaks it into
+// '/' separated segments, and removes potential
+// blank first and last segments. 
+function GenerateSegments( argument ) {
+    var result = argument.split("/");
+    if ( result.length > 0 ) {
+        if ( result[ 0 ] == "" ) {
+            result.shift();
+        }
+    }
+    if ( result.length > 0 ) {
+        if ( result[ result.length - 1 ] == "" ) {
+            result.pop();
+        }
+    }
+    return result;
+}
+
+
+
+exports.JoinPath = JoinPath;
+exports.IsDirectory = IsDirectory;
+exports.IsFile = IsFile;
+exports.IsInstanceID = IsInstanceID;
+exports.GenerateSegments = GenerateSegments;
+exports.GenerateInstanceID = GenerateInstanceID;

+ 493 - 0
lib/reflector.js

@@ -0,0 +1,493 @@
+"use strict";
+// reflector.js
+// 
+//var parseurl = require( './parse-url' ),
+   // persistence = require( './persistence' ),
+
+   var helpers = require( './helpers' );
+   var fs = require( 'fs' );
+
+
+function parseSocketUrl( socket ) {
+
+    try
+    {
+        var query = require('url')
+            .parse(socket.handshake.url)
+            .query;
+       var referer = require('querystring')
+            .parse(query)
+            .pathname;
+        var resObj = require('querystring')
+        .parse(query)
+        .path;
+
+        var namespace = referer;
+        if(!namespace) return null;
+        if (namespace[namespace.length - 1] != "/")
+            namespace += "/";
+
+        let parsedPath = JSON.parse(resObj);
+            
+        if (parsedPath) {
+            return parsedPath
+        }
+        // else {
+        //     return parseurl.Process(namespace);
+        // } 
+       
+    }
+    catch (e)
+    {
+        return null;
+    }
+}
+
+//Get the instance ID from the handshake headers for a socket
+function GetNamespace( processedURL ) {
+    if ( ( processedURL[ 'instance' ] ) && ( processedURL[ 'public_path' ] ) ) {
+        return helpers.JoinPath( processedURL[ 'public_path' ], processedURL[ 'application' ], processedURL[ 'instance' ] );
+    }
+    return undefined;
+}
+
+function GetNow( ) {
+    return new Date( ).getTime( ) / 1000.0;
+}
+
+function OnConnection( socket ) {
+
+    let resObj = parseSocketUrl( socket );
+    
+    if (resObj == null) {
+
+        setInterval(function() {
+
+   var address = socket.conn.request.headers.host;
+             var obj = {};
+             for (var prop in global.instances) {
+                obj[prop] =  {
+                    "instance":address + prop,
+                    "clients": Object.keys(global.instances[prop].clients).length
+                };
+            }
+            var json = JSON.stringify(obj);
+            socket.emit('getWebAppUpdate', json);
+}, 3000);
+
+//          socket.on('getWebAppUpdate', function(msg){
+          
+//   });
+
+        return
+    }
+
+    let processedURL = resObj.path;
+    //get instance for new connection
+    var namespace = GetNamespace( processedURL );
+    if ( namespace == undefined ) {
+        return;
+    }
+
+    //prepare for persistence request in case that's what this is
+    
+    var loadInfo = resObj.loadInfo //GetLoadForSocket( processedURL );
+    var saveObject = resObj.saveObject //persistence.LoadSaveObject( loadInfo );
+	   
+    
+
+    //if it's a new instance, setup record 
+    if( !global.instances[ namespace ] ) {
+        global.instances[ namespace ] = { };
+        global.instances[ namespace ].clients = { };
+        global.instances[ namespace ].pendingList = [ ];
+        global.instances[ namespace ].start_time = undefined;
+        global.instances[ namespace ].pause_time = undefined;
+        global.instances[ namespace ].rate = 1.0;
+        global.instances[ namespace ].setTime = function( time ) {
+            this.start_time = GetNow( ) - time;
+            this.pause_time = undefined;
+            this.rate = 1.0;
+        };
+        global.instances[ namespace ].isPlaying = function( ) {
+            if ( ( this.start_time != undefined ) && ( this.pause_time == undefined ) ) {
+                return true;
+            }
+            return false
+        };
+        global.instances[ namespace ].isPaused = function( ) {
+            if ( ( this.start_time != undefined ) && ( this.pause_time != undefined ) ) {
+                return true;
+            }
+            return false
+        };
+        global.instances[ namespace ].isStopped = function( ) {
+            if ( this.start_time == undefined ) {
+                return true;
+            }
+            return false;
+        };
+        global.instances[ namespace ].getTime = function( ) {
+            if ( this.isPlaying( ) ) {
+                return ( GetNow( ) - this.start_time ) * this.rate;
+            } else if ( this.isPaused( ) ) {
+                return ( this.pause_time - this.start_time ) * this.rate;
+            }
+            else {
+                return 0.0;
+            }
+        };
+        global.instances[ namespace ].play = function( ) {
+            if ( this.isStopped( ) ) {
+                this.start_time = GetNow( );
+                this.pause_time = undefined;
+            } else if ( this.isPaused( ) ) {
+                this.start_time = this.start_time + ( GetNow( ) - this.pause_time );
+                this.pause_time = undefined;
+            }
+        };
+        global.instances[ namespace ].pause = function( ) {
+            if ( this.isPlaying( ) ) {
+                this.pause_time = GetNow( );
+            }
+        };
+        global.instances[ namespace ].stop = function( ) {
+            if ( ( this.isPlaying( ) ) || ( this.isPaused( ) ) ) {
+                this.start_time = undefined;
+                this.pause_time = undefined;
+            }
+        };
+        global.instances[ namespace ].setTime( 0.0 );
+
+        if ( saveObject ) {
+            if ( saveObject[ "queue" ] ) {
+                if ( saveObject[ "queue" ][ "time" ] ) {
+                    global.instances[ namespace ].setTime( saveObject[ "queue" ][ "time" ] );
+                }
+             }
+        }
+        
+
+        global.instances[ namespace ].state = { };
+        
+        var log;
+        function generateLogFile() {
+            try {
+                if ( !fs.existsSync( './/log/' ) ) {
+                    fs.mkdir( './/log/', function ( err ) {
+                        if ( err ) {
+                            console.log ( err );
+                        } 
+                    })
+                }
+                log = fs.createWriteStream( './/log/' + namespace.replace( /[\\\/]/g, '_' ), { 'flags': 'a' } );
+            } catch( err ) {
+                console.log( 'Error generating Node Server Log File\n');
+            }
+        }
+
+        global.instances[ namespace ].Log = function ( message, level ) {
+            if( global.logLevel >= level ) {
+                if ( !log ) {
+                    generateLogFile();
+                }
+                log.write( message + '\n' );
+                global.log( message + '\n' );
+            }
+        };
+        
+        global.instances[ namespace ].Error = function ( message, level ) {
+            var red, brown, reset;
+            red   = '\u001b[31m';
+            brown  = '\u001b[33m';
+            reset = '\u001b[0m';
+            if ( global.logLevel >= level ) {
+                if ( !log ) {
+                    generateLogFile();
+                }
+                log.write( message + '\n' );
+                global.log( red + message + reset + '\n' );
+            }
+        };
+
+
+        //keep track of the timer for this instance
+        global.instances[ namespace ].timerID = setInterval( function ( ) {
+            var message = { parameters: [ ], time: global.instances[ namespace ].getTime( ) };
+            for ( var i in global.instances[ namespace ].clients ) {
+                var client = global.instances[ namespace ].clients[ i ];
+                if ( ! client.pending ) {
+                    client.emit( 'message', message );
+                }
+            }
+                if(global.instances[ namespace ]){
+            if ( global.instances[ namespace ].pendingList.pending ) {
+                global.instances[ namespace ].pendingList.push( message );
+            }
+        }
+        }, 50 );
+
+    }
+
+    //add the new client to the instance data
+    global.instances[ namespace ].clients[ socket.id ] = socket;	 
+
+    socket.pending = true;
+
+    //Get the descriptor for the `clients.vwf` child.
+    var clientDescriptor = GetClientDescriptor( socket );
+    
+        // The time for the setState message should be the time the new client joins, so save that time
+        var setStateTime = global.instances[ namespace ].getTime( );
+    
+        // If this client is the first, it can just load the application, and mark it not pending
+        if ( Object.keys( global.instances[ namespace ].clients ).length === 1 ) {
+    
+            if ( saveObject ) {
+                socket.emit( 'message', {
+                    action: "setState",
+                    parameters: [ saveObject ],
+                    time: global.instances[ namespace ].getTime( )
+                } );
+            }
+            else {
+                var instance = namespace;
+                //Get the state and load it.
+                //Now the server has a rough idea of what the simulation is
+    
+                socket.emit( 'message', { 
+                    action: "createNode", 
+                    parameters: [ "http://vwf.example.com/clients.vwf" ], 
+                    time: global.instances[ namespace ].getTime( ) 
+                } );
+    
+                socket.emit( 'message', { 
+                    action: "createNode", 
+                    parameters: [
+                        ( processedURL.public_path === "/" ? "" : processedURL.public_path ) + "/" + processedURL.application,
+                        "application"
+                    ],
+                    time: global.instances[ namespace ].getTime( )
+                } );
+            }
+    
+            socket.pending = false;
+    
+            //xapi.logClient( saveObject, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, true, true );
+        }
+        else {  //this client is not the first, we need to get the state and mark it pending
+            if ( ! global.instances[ namespace ].pendingList.pending ) {
+                var firstclient = Object.keys( global.instances[ namespace ].clients )[ 0 ];
+                firstclient = global.instances[ namespace ].clients[ firstclient ];
+                firstclient.emit( 'message', {
+                    action: "getState",
+                    respond: true,
+                    time: global.instances[ namespace ].getTime( )
+                } );
+                global.instances[ namespace ].Log( 'GetState from Client', 2 );
+                global.instances[ namespace ].pendingList.pending = true;
+            }
+            socket.pending = true;
+    
+        }
+    
+        //Create a child in the application's 'clients.vwf' global to represent this client.
+        var clientNodeMessage = {
+            action: "createChild",
+            parameters: [ "http://vwf.example.com/clients.vwf", socket.id, clientDescriptor ],
+            time: global.instances[ namespace ].getTime( )
+        };
+    
+        // Send messages to all the existing clients (that are not pending),
+        // telling them to create a new node under the "clients" parent for the new client
+        for ( var i in global.instances[ namespace ].clients ) {
+            var client = global.instances[ namespace ].clients[ i ];
+            if ( !client.pending ) {
+                client.emit ( 'message',  clientNodeMessage );
+            }
+        }
+        if ( global.instances[ namespace ].pendingList.pending ) {
+            global.instances[ namespace ].pendingList.push( clientNodeMessage );
+        }
+
+
+        socket.on( 'message', function ( msg ) {
+            
+                    //need to add the client identifier to all outgoing messages
+                    try {
+                        var message = JSON.parse( msg );
+                    }
+                    catch ( e ) {
+                        console.error( "Error on socket message: ", e );
+                        return;
+                    }
+            
+                    message.client = socket.id;
+                    message.time = global.instances[ namespace ].getTime( );
+            
+                    if ( message.result === undefined ) {
+            
+                        //distribute message to all clients on given instance
+                        for ( var i in global.instances[ namespace ].clients ) {
+                            var client = global.instances[ namespace ].clients[ i ];
+            
+                            //just a regular message, so push if the client is pending a load, otherwise just send it.
+                            if ( ! client.pending ) {
+                                client.emit( 'message', message );
+                            }
+                        }
+            
+                        if (global.instances[ namespace ]) {
+                        if ( global.instances[ namespace ].pendingList.pending ) {
+                            global.instances[ namespace ].pendingList.push( message );
+                        }
+                    }
+
+                    } else if ( message.action == "getState" ) {
+            
+                        //distribute message to all clients on given instance
+                        for ( var i in global.instances[ namespace ].clients ) {
+                            var client = global.instances[ namespace ].clients[ i ];
+            
+                            //if the message was get state, then fire all the pending messages after firing the setState
+                            if ( client.pending ) {
+                                global.instances[ namespace ].Log( 'Got State', 2 );
+                                var state = message.result;
+                                global.instances[ namespace ].Log( state, 2 );
+                                client.emit( 'message', { action: "setState", parameters: [ state ], time: setStateTime } );
+                                client.pending = false;
+                                for ( var j = 0; j < global.instances[ namespace ].pendingList.length; j++ ) {
+                                    client.emit( 'message', global.instances[ namespace ].pendingList[ j ] );
+                                }
+                                //xapi.logClient( state, undefined, undefined, namespace, GetClientDescriptor( client ).properties || {}, true, false );
+                            }
+                        }
+            
+                        global.instances[ namespace ].pendingList = [ ];
+            
+                    } else if ( message.action === "execute" ) {
+            
+                        var evaluation = socket.pendingEvaluations && socket.pendingEvaluations.shift();
+            
+                        if ( evaluation ) {
+                            evaluation.resolve( message.result );
+                            clearTimeout( evaluation.timeout );
+                        }
+            
+                    }
+            
+                } );
+
+    // When a client disconnects, go ahead and remove the instance data
+    socket.on( 'disconnect', function ( ) {
+        
+        // Remove the disconnecting client
+        var leavingClient = global.instances[ namespace ].clients[ socket.id ];
+        global.instances[ namespace ].clients[ socket.id ] = null;  
+        delete global.instances[ namespace ].clients[ socket.id ];
+        if ( leavingClient.pendingEvaluations ) {
+            leavingClient.pendingEvaluations.forEach( function( evaluation ) {
+                evaluation.reject( new Error( "connection closed" ) );
+                clearTimeout( evaluation.timeout );
+            } );
+        }
+
+        // Notify others of the disconnecting client.  Delete the child representing this client in the application's `clients.vwf` global.
+        var clientMessage = { action: "deleteChild", parameters: [ "http://vwf.example.com/clients.vwf", socket.id ], time: global.instances[ namespace ].getTime( ) };
+        for ( var i in global.instances[ namespace ].clients ) {
+            var client = global.instances[ namespace ].clients[ i ];
+            if ( ! client.pending ) {
+                client.emit ( 'message', clientMessage );
+            }
+        }
+        if ( global.instances[ namespace ].pendingList.pending ) {
+            global.instances[ namespace ].pendingList.push( clientMessage );
+        }
+        
+        // If it's the last client, delete the data and the timer
+        if ( Object.keys( global.instances[ namespace ].clients ).length == 0 ) {
+            clearInterval( global.instances[ namespace ].timerID );
+            delete global.instances[ namespace ];
+           // xapi.logClient( undefined, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, false, true );
+        } else {
+           // xapi.logClient( undefined, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, false, false );
+        }
+    } );
+}
+
+function Evaluate( namespace, node, expression ) {
+
+    return new Promise( function( resolve, reject ) {
+
+        var firstClientID = Object.keys( global.instances[ namespace ].clients )[ 0 ];
+        var firstClient = global.instances[ namespace ].clients[ firstClientID ];
+
+        if ( firstClient ) {
+            firstClient.pendingEvaluations = firstClient.pendingEvaluations || [];
+            firstClient.pendingEvaluations.push( {
+                resolve: resolve,
+                reject: reject,
+                timeout: setTimeout( function() { reject( new Error( "timeout" ) ) }, 1000 ),
+            } );
+            firstClient.emit( "message", { node: node, action: "execute", parameters: [ expression ], respond: true, time: global.instances[ namespace ].getTime() } );
+        } else {
+            reject( new Error( "no clients are connected" ) );
+        }
+
+    } );
+
+}
+
+/// Get a descriptor for the `clients.vwf` child for a new client. An authenticator may set a
+/// descriptor in the session at `session.vwf.client`. If the authenticator doesn't provide a
+/// descriptor, use an empty node inheriting from `client.vwf`.
+
+function GetClientDescriptor( socket ) {
+
+    // socket.io doesn't provide access to the request and the session, but we do have the cookies.
+    // Create a mock request and run it through the session middleware to recreate the session. This
+    // creates a session object at `mockRequest.session`.
+
+    var mockRequest = {
+        headers: { cookie: socket.handshake.headers.cookie },
+        connection: {},
+        session: {},
+    };
+
+    var mockResponse = {
+        getHeader: function() {},
+        setHeader: function() {},
+    };
+
+    sessionStack.forEach( function( middleware ) {
+        middleware( mockRequest, mockResponse, function() {} );
+    } );
+
+    // Get the descriptor from `vwf.client` in the session.
+
+    var descriptor = ( mockRequest.session.vwf || {} ).client || {};
+
+    // Set the default prototype.
+
+    if ( ! descriptor.extends ) {
+        descriptor.extends = "http://vwf.example.com/client.vwf";
+    }
+
+    return descriptor;
+
+}
+
+/// Middleware stack to parse a cookie session from `req.headers.cookie` into `req.session`.
+
+var sessionStack = [
+    //cookieParser(),
+    //cookieSession( { secret: config.get( 'session.secret' ) } ),
+];
+
+function GetInstances() {
+    return global.instances;
+}
+
+exports.OnConnection = OnConnection;
+exports.Evaluate = Evaluate;
+exports.GetInstances = GetInstances;

+ 6 - 0
node-server.js

@@ -0,0 +1,6 @@
+#!/usr/bin/env node
+
+var server = require( './node_vwf' );
+
+server.startVWF();
+

+ 116 - 0
node_vwf.js

@@ -0,0 +1,116 @@
+var path = require('path'),
+    http = require('http'),
+    https = require('https'),
+    fs = require('fs'),
+    url = require('url'),
+    sio = require('socket.io'),
+    reflector = require('./lib/reflector'),
+    argv = require('optimist').argv;
+
+
+function printGeneralHelp() {
+    console.log("Options:");
+    console.log("  -p, --port               Port to start server on. Default: 3000");
+    console.log("  -l, --log                Log level for server. Default: 1");
+    console.log("  -h, --help               Output usage information");
+    console.log("  -s, --ssl                Enables SSL");
+    console.log("  -k, --key                Path to private key");
+    console.log("  -c, --cert               Path to certificate");
+}
+
+
+// Basic logging function.
+global.log = function () {
+    var args = Array.prototype.slice.call(arguments);
+    var level = args.splice(args.length - 1)[0];
+
+    if (!isNaN(parseInt(level))) {
+        level = parseInt(level);
+    } else {
+        args.push(level)
+        level = 1;
+    };
+
+    if (level <= global.logLevel) {
+        console.log.apply(this, args);
+    }
+};
+
+function consoleNotice(string) {
+    var brown = '\u001b[33m';
+    var reset = '\u001b[0m';
+    global.log(brown + string + reset);
+}
+
+function consoleError(string) {
+    var red = '\u001b[31m';
+    var reset = '\u001b[0m';
+    global.log(red + string + reset);
+}
+
+//Start the VWF server
+function startVWF() {
+
+    global.logLevel = ((argv.l || argv.log) ? (argv.l || argv.log) : 1);
+    global.instances = {};
+
+    function serve(request, response){
+
+        response.writeHead( 200, {
+            "Content-Type": "application/json"
+        } );
+        var inst = Object.keys(global.instances);
+        var jsonobject = {
+            "reflector": "v0.0.1"
+            //"instances": inst
+        }
+        response.write( JSON.stringify( jsonobject ), "utf8" );
+        response.end();	
+        //console.log("Serve here")
+
+    }
+
+    function OnRequest(request, response) {
+        try {
+            serve(request, response);
+            // vwf.Serve( request, response );
+           
+        } catch (e) {
+            response.writeHead(500, {
+                "Content-Type": "text/plain"
+            });
+            response.write(e.toString(), "utf8");
+            response.end();
+        }
+    } // close onRequest
+
+    consoleNotice('LogLevel = ' + global.logLevel);
+
+    //consoleNotice( 'Serving VWF support files from ' + global.vwfRoot );
+
+    if (argv.nocache) {
+        FileCache.enabled = false;
+        consoleNotice('server cache disabled');
+    }
+
+    var ssl = (argv.s || argv.ssl);
+    var pass = ((argv.w) ? (argv.w) : undefined);
+    var sslOptions = {
+        key: ((argv.k || argv.key) ? fs.readFileSync(argv.k || argv.key) : undefined),
+        cert: ((argv.c || argv.cert) ? fs.readFileSync(argv.c || argv.cert) : undefined),
+        passphrase: JSON.stringify(pass)
+    };
+
+    //create the server
+    var port = ((argv.p || argv.port) ? (argv.p || argv.port) : 3001);
+
+    var srv = ssl ? https.createServer(sslOptions, OnRequest).listen(port) : http.createServer(OnRequest).listen(port);
+    consoleNotice('Serving on port ' + port);
+
+    var socketManager = sio.listen(srv, { log: false });
+
+    socketManager.set('transports', ['websocket']);
+    socketManager.sockets.on('connection', reflector.OnConnection);
+}
+
+exports.startVWF = startVWF;

+ 629 - 0
package-lock.json

@@ -0,0 +1,629 @@
+{
+  "name": "lcs-reflector",
+  "version": "0.0.1",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "accepts": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
+      "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
+      "requires": {
+        "mime-types": "2.1.17",
+        "negotiator": "0.6.1"
+      }
+    },
+    "after": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
+      "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
+    },
+    "argparse": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
+      "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
+      "requires": {
+        "sprintf-js": "1.0.3"
+      }
+    },
+    "arraybuffer.slice": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
+      "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
+    },
+    "async": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
+      "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
+      "requires": {
+        "lodash": "4.17.4"
+      }
+    },
+    "async-limiter": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
+      "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
+    },
+    "backo2": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
+      "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "base64-arraybuffer": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
+      "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
+    },
+    "base64id": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
+      "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY="
+    },
+    "better-assert": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
+      "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
+      "requires": {
+        "callsite": "1.0.0"
+      }
+    },
+    "blob": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
+      "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE="
+    },
+    "brace-expansion": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
+      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
+      "dev": true,
+      "requires": {
+        "balanced-match": "1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "browser-stdout": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
+      "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
+      "dev": true
+    },
+    "callsite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+      "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
+    },
+    "commander": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+      "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
+      "dev": true
+    },
+    "component-bind": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
+      "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
+    },
+    "component-emitter": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+      "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
+    },
+    "component-inherit": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
+      "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+    },
+    "crypto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+      "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "diff": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz",
+      "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==",
+      "dev": true
+    },
+    "engine.io": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.4.tgz",
+      "integrity": "sha1-PQIRtwpVLOhB/8fahiezAamkFi4=",
+      "requires": {
+        "accepts": "1.3.3",
+        "base64id": "1.0.0",
+        "cookie": "0.3.1",
+        "debug": "2.6.9",
+        "engine.io-parser": "2.1.2",
+        "uws": "0.14.5",
+        "ws": "3.3.3"
+      }
+    },
+    "engine.io-client": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.4.tgz",
+      "integrity": "sha1-T88TcLRxY70s6b4nM5ckMDUNTqE=",
+      "requires": {
+        "component-emitter": "1.2.1",
+        "component-inherit": "0.0.3",
+        "debug": "2.6.9",
+        "engine.io-parser": "2.1.2",
+        "has-cors": "1.1.0",
+        "indexof": "0.0.1",
+        "parseqs": "0.0.5",
+        "parseuri": "0.0.5",
+        "ws": "3.3.3",
+        "xmlhttprequest-ssl": "1.5.5",
+        "yeast": "0.1.2"
+      }
+    },
+    "engine.io-parser": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz",
+      "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==",
+      "requires": {
+        "after": "0.8.2",
+        "arraybuffer.slice": "0.0.7",
+        "base64-arraybuffer": "0.1.5",
+        "blob": "0.0.4",
+        "has-binary2": "1.0.2"
+      }
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "esprima": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
+      "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
+    },
+    "fs-extra": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
+      "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
+      "requires": {
+        "graceful-fs": "4.1.11",
+        "jsonfile": "4.0.0",
+        "universalify": "0.1.1"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "1.0.0",
+        "inflight": "1.0.6",
+        "inherits": "2.0.3",
+        "minimatch": "3.0.4",
+        "once": "1.4.0",
+        "path-is-absolute": "1.0.1"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.1.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+      "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+    },
+    "growl": {
+      "version": "1.10.3",
+      "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz",
+      "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==",
+      "dev": true
+    },
+    "has-binary2": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz",
+      "integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=",
+      "requires": {
+        "isarray": "2.0.1"
+      }
+    },
+    "has-cors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
+      "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
+    },
+    "has-flag": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+      "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+      "dev": true
+    },
+    "he": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+      "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+      "dev": true
+    },
+    "indexof": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+      "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
+      "requires": {
+        "once": "1.4.0",
+        "wrappy": "1.0.2"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+      "dev": true
+    },
+    "isarray": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+      "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+    },
+    "js-yaml": {
+      "version": "3.10.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
+      "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
+      "requires": {
+        "argparse": "1.0.9",
+        "esprima": "4.0.0"
+      }
+    },
+    "jsonfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+      "requires": {
+        "graceful-fs": "4.1.11"
+      }
+    },
+    "lodash": {
+      "version": "4.17.4",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
+      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
+    },
+    "mime": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz",
+      "integrity": "sha512-0Qz9uF1ATtl8RKJG4VRfOymh7PyEor6NbrI/61lRfuRe4vx9SNATrvAeTj2EWVRKjEQGskrzWkJBBY5NbaVHIA=="
+    },
+    "mime-db": {
+      "version": "1.30.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
+      "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
+    },
+    "mime-types": {
+      "version": "2.1.17",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
+      "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
+      "requires": {
+        "mime-db": "1.30.0"
+      }
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "1.1.8"
+      }
+    },
+    "minimist": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+      "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "dev": true,
+      "requires": {
+        "minimist": "0.0.8"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+          "dev": true
+        }
+      }
+    },
+    "mocha": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz",
+      "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==",
+      "dev": true,
+      "requires": {
+        "browser-stdout": "1.3.0",
+        "commander": "2.11.0",
+        "debug": "3.1.0",
+        "diff": "3.3.1",
+        "escape-string-regexp": "1.0.5",
+        "glob": "7.1.2",
+        "growl": "1.10.3",
+        "he": "1.1.1",
+        "mkdirp": "0.5.1",
+        "supports-color": "4.4.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        }
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "negotiator": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+      "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
+    },
+    "object-component": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
+      "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
+      "requires": {
+        "wrappy": "1.0.2"
+      }
+    },
+    "optimist": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+      "requires": {
+        "minimist": "0.0.10",
+        "wordwrap": "0.0.3"
+      }
+    },
+    "parseqs": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
+      "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+      "requires": {
+        "better-assert": "1.0.2"
+      }
+    },
+    "parseuri": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
+      "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
+      "requires": {
+        "better-assert": "1.0.2"
+      }
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "safe-buffer": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+      "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+    },
+    "should": {
+      "version": "13.2.1",
+      "resolved": "https://registry.npmjs.org/should/-/should-13.2.1.tgz",
+      "integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==",
+      "dev": true,
+      "requires": {
+        "should-equal": "2.0.0",
+        "should-format": "3.0.3",
+        "should-type": "1.4.0",
+        "should-type-adaptors": "1.1.0",
+        "should-util": "1.0.0"
+      }
+    },
+    "should-equal": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
+      "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
+      "dev": true,
+      "requires": {
+        "should-type": "1.4.0"
+      }
+    },
+    "should-format": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
+      "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
+      "dev": true,
+      "requires": {
+        "should-type": "1.4.0",
+        "should-type-adaptors": "1.1.0"
+      }
+    },
+    "should-type": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
+      "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
+      "dev": true
+    },
+    "should-type-adaptors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
+      "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
+      "dev": true,
+      "requires": {
+        "should-type": "1.4.0",
+        "should-util": "1.0.0"
+      }
+    },
+    "should-util": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz",
+      "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=",
+      "dev": true
+    },
+    "socket.io": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz",
+      "integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=",
+      "requires": {
+        "debug": "2.6.9",
+        "engine.io": "3.1.4",
+        "socket.io-adapter": "1.1.1",
+        "socket.io-client": "2.0.4",
+        "socket.io-parser": "3.1.2"
+      }
+    },
+    "socket.io-adapter": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz",
+      "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs="
+    },
+    "socket.io-client": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz",
+      "integrity": "sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44=",
+      "requires": {
+        "backo2": "1.0.2",
+        "base64-arraybuffer": "0.1.5",
+        "component-bind": "1.0.0",
+        "component-emitter": "1.2.1",
+        "debug": "2.6.9",
+        "engine.io-client": "3.1.4",
+        "has-cors": "1.1.0",
+        "indexof": "0.0.1",
+        "object-component": "0.0.3",
+        "parseqs": "0.0.5",
+        "parseuri": "0.0.5",
+        "socket.io-parser": "3.1.2",
+        "to-array": "0.1.4"
+      }
+    },
+    "socket.io-parser": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz",
+      "integrity": "sha1-28IoIVH8T6675Aru3Ady66YZ9/I=",
+      "requires": {
+        "component-emitter": "1.2.1",
+        "debug": "2.6.9",
+        "has-binary2": "1.0.2",
+        "isarray": "2.0.1"
+      }
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+    },
+    "supports-color": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
+      "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
+      "dev": true,
+      "requires": {
+        "has-flag": "2.0.0"
+      }
+    },
+    "to-array": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
+      "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
+    },
+    "ultron": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
+      "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
+    },
+    "universalify": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
+      "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc="
+    },
+    "uws": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/uws/-/uws-0.14.5.tgz",
+      "integrity": "sha1-Z6rzPEaypYel9mZtAPdpEyjxSdw=",
+      "optional": true
+    },
+    "wordwrap": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+      "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    },
+    "ws": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
+      "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
+      "requires": {
+        "async-limiter": "1.0.0",
+        "safe-buffer": "5.1.1",
+        "ultron": "1.1.1"
+      }
+    },
+    "xmlhttprequest-ssl": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
+      "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
+    },
+    "yeast": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
+      "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
+    }
+  }
+}

+ 35 - 0
package.json

@@ -0,0 +1,35 @@
+{
+  "name": "lcs-reflector",
+  "description": "LiveCoding.Space reflector",
+  "version": "0.0.1",
+  "author": "Nikolai Suslov",
+  "scripts": {
+    "start": "node ./node-server.js -p 3001",
+    "startSSL": "node ./node-server.js -p 3001 -s -k ./cert/key.pem -c ./cert/cert.pem",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "directories": {
+    "lib": "lib"
+  },
+  "main": "node-server.js",
+  "homepage": "http://livecoding.space",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/NikolaySuslov/lcs-reflector.git"
+  },
+  "dependencies": {
+    "crypto": "1.0.1",
+    "socket.io": "2.0.4",
+    "socket.io-client": "^2.0.4",
+    "async": "2.6.0",
+    "mime": "2.2.0",
+    "js-yaml": "3.10.0",
+    "optimist": "0.6.1",
+    "fs-extra": "5.0.0"
+  },
+  "devDependencies": {
+    "mocha": "x.x.x",
+    "should": "x.x.x"
+  },
+  "license": "Apache"
+}