reflector.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. /*
  2. The MIT License (MIT)
  3. Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/lcs-reflector/blob/master/LICENSE.md)
  4. Virtual World Framework Apache 2.0 license (https://github.com/NikolaySuslov/lcs-reflector/blob/master/licenses/LICENSE_VWF.md)
  5. */
  6. "use strict";
  7. // JoinPath
  8. // Takes multiple arguments, joins them together into one path.
  9. function JoinPath( /* arguments */ ) {
  10. var result = "";
  11. if ( arguments.length > 0 ) {
  12. if ( arguments[ 0 ] ) {
  13. result = arguments[ 0 ];
  14. }
  15. for ( var index = 1; index < arguments.length; index++ ) {
  16. var newSegment = arguments[ index ];
  17. if ( newSegment == undefined ) {
  18. newSegment = "";
  19. }
  20. if ( ( newSegment[ 0 ] == "/" ) && ( result[ result.length - 1 ] == "/" ) ) {
  21. result = result + newSegment.slice( 1 );
  22. } else if ( ( newSegment[ 0 ] == "/" ) || ( result[ result.length - 1 ] == "/" ) ) {
  23. result = result + newSegment;
  24. } else {
  25. result = result + "/" + newSegment;
  26. }
  27. //result = libpath.join( result, newSegment );
  28. }
  29. }
  30. return result;
  31. }
  32. function parseSocketUrl( socket ) {
  33. try
  34. {
  35. var query = require('url')
  36. .parse(socket.handshake.url)
  37. .query;
  38. var referer = require('querystring')
  39. .parse(query)
  40. .pathname;
  41. var resObj = require('querystring')
  42. .parse(query)
  43. .path;
  44. var namespace = referer;
  45. if(!namespace) return null;
  46. if (namespace[namespace.length - 1] != "/")
  47. namespace += "/";
  48. let parsedPath = JSON.parse(resObj);
  49. if (parsedPath) {
  50. return parsedPath
  51. }
  52. // else {
  53. // return parseurl.Process(namespace);
  54. // }
  55. }
  56. catch (e)
  57. {
  58. return null;
  59. }
  60. }
  61. //Get the instance ID from the handshake headers for a socket
  62. function GetNamespace( processedURL ) {
  63. if ( ( processedURL[ 'instance' ] ) && ( processedURL[ 'public_path' ] ) ) {
  64. return JoinPath( processedURL[ 'public_path' ], processedURL[ 'application' ], processedURL[ 'instance' ] );
  65. }
  66. return undefined;
  67. }
  68. function GetNow( ) {
  69. //return new Date( ).getTime( ) / 1000.0;
  70. let hrt = process.hrtime();
  71. return (hrt[0] * 1e9 + hrt[1]) / 1e9
  72. }
  73. function OnConnection( socket ) {
  74. let resObj = parseSocketUrl( socket );
  75. if (resObj == null) {
  76. setInterval(function() {
  77. var address = socket.conn.request.headers.host;
  78. var obj = {};
  79. for (var prop in global.instances) {
  80. let user = global.instances[prop].user;
  81. let loadInfo = global.instances[prop].loadInfo;
  82. obj[prop] = {
  83. "instance":address + '/'+ user + prop,
  84. "clients": Object.keys(global.instances[prop].clients).length,
  85. "user": user,
  86. "loadInfo": loadInfo
  87. };
  88. }
  89. var json = JSON.stringify(obj);
  90. socket.emit('getWebAppUpdate', json);
  91. }, 3000);
  92. // socket.on('getWebAppUpdate', function(msg){
  93. // });
  94. return
  95. }
  96. let processedURL = resObj.path;
  97. //get instance for new connection
  98. var namespace = GetNamespace( processedURL );
  99. if ( namespace == undefined ) {
  100. return;
  101. }
  102. //prepare for persistence request in case that's what this is
  103. var loadInfo = resObj.loadInfo //GetLoadForSocket( processedURL );
  104. var saveObject = resObj.saveObject //persistence.LoadSaveObject( loadInfo );
  105. var user = resObj.user;
  106. //if it's a new instance, setup record
  107. if( !global.instances[ namespace ] ) {
  108. global.instances[ namespace ] = { };
  109. global.instances[ namespace ].loadInfo = loadInfo;
  110. global.instances[ namespace ].user = user;
  111. global.instances[ namespace ].clients = { };
  112. global.instances[ namespace ].pendingList = [ ];
  113. global.instances[ namespace ].start_time = undefined;
  114. global.instances[ namespace ].pause_time = undefined;
  115. global.instances[ namespace ].rate = 1.0;
  116. global.instances[ namespace ].setTime = function( time ) {
  117. this.start_time = GetNow( ) - time;
  118. this.pause_time = undefined;
  119. this.rate = 1.0;
  120. };
  121. global.instances[ namespace ].isPlaying = function( ) {
  122. if ( ( this.start_time != undefined ) && ( this.pause_time == undefined ) ) {
  123. return true;
  124. }
  125. return false
  126. };
  127. global.instances[ namespace ].isPaused = function( ) {
  128. if ( ( this.start_time != undefined ) && ( this.pause_time != undefined ) ) {
  129. return true;
  130. }
  131. return false
  132. };
  133. global.instances[ namespace ].isStopped = function( ) {
  134. if ( this.start_time == undefined ) {
  135. return true;
  136. }
  137. return false;
  138. };
  139. global.instances[ namespace ].getTime = function( ) {
  140. if ( this.isPlaying( ) ) {
  141. return ( GetNow( ) - this.start_time ) * this.rate;
  142. } else if ( this.isPaused( ) ) {
  143. return ( this.pause_time - this.start_time ) * this.rate;
  144. }
  145. else {
  146. return 0.0;
  147. }
  148. };
  149. global.instances[ namespace ].play = function( ) {
  150. if ( this.isStopped( ) ) {
  151. this.start_time = GetNow( );
  152. this.pause_time = undefined;
  153. } else if ( this.isPaused( ) ) {
  154. this.start_time = this.start_time + ( GetNow( ) - this.pause_time );
  155. this.pause_time = undefined;
  156. }
  157. };
  158. global.instances[ namespace ].pause = function( ) {
  159. if ( this.isPlaying( ) ) {
  160. this.pause_time = GetNow( );
  161. }
  162. };
  163. global.instances[ namespace ].stop = function( ) {
  164. if ( ( this.isPlaying( ) ) || ( this.isPaused( ) ) ) {
  165. this.start_time = undefined;
  166. this.pause_time = undefined;
  167. }
  168. };
  169. global.instances[ namespace ].setTime( 0.0 );
  170. if ( saveObject ) {
  171. if ( saveObject[ "queue" ] ) {
  172. if ( saveObject[ "queue" ][ "time" ] ) {
  173. global.instances[ namespace ].setTime( saveObject[ "queue" ][ "time" ] );
  174. }
  175. }
  176. }
  177. global.instances[ namespace ].state = { };
  178. var log;
  179. function generateLogFile() {
  180. try {
  181. if ( !fs.existsSync( './/log/' ) ) {
  182. fs.mkdir( './/log/', function ( err ) {
  183. if ( err ) {
  184. console.log ( err );
  185. }
  186. })
  187. }
  188. log = fs.createWriteStream( './/log/' + namespace.replace( /[\\\/]/g, '_' ), { 'flags': 'a' } );
  189. } catch( err ) {
  190. console.log( 'Error generating Node Server Log File\n');
  191. }
  192. }
  193. global.instances[ namespace ].Log = function ( message, level ) {
  194. if( global.logLevel >= level ) {
  195. if ( !log ) {
  196. generateLogFile();
  197. }
  198. log.write( message + '\n' );
  199. global.log( message + '\n' );
  200. }
  201. };
  202. global.instances[ namespace ].Error = function ( message, level ) {
  203. var red, brown, reset;
  204. red = '\u001b[31m';
  205. brown = '\u001b[33m';
  206. reset = '\u001b[0m';
  207. if ( global.logLevel >= level ) {
  208. if ( !log ) {
  209. generateLogFile();
  210. }
  211. log.write( message + '\n' );
  212. global.log( red + message + reset + '\n' );
  213. }
  214. };
  215. //keep track of the timer for this instance
  216. global.instances[ namespace ].timerID = setInterval( function ( ) {
  217. var message = { parameters: [ ], time: global.instances[ namespace ].getTime( ) };
  218. for ( var i in global.instances[ namespace ].clients ) {
  219. var client = global.instances[ namespace ].clients[ i ];
  220. if ( ! client.pending ) {
  221. client.emit( 'message', message );
  222. }
  223. }
  224. if(global.instances[ namespace ]){
  225. if ( global.instances[ namespace ].pendingList.pending ) {
  226. global.instances[ namespace ].pendingList.push( message );
  227. }
  228. }
  229. }, 50 );
  230. }
  231. //add the new client to the instance data
  232. global.instances[ namespace ].clients[ socket.id ] = socket;
  233. socket.pending = true;
  234. //Get the descriptor for the `clients.vwf` child.
  235. var clientDescriptor = GetClientDescriptor( socket );
  236. // The time for the setState message should be the time the new client joins, so save that time
  237. var setStateTime = global.instances[ namespace ].getTime( );
  238. // If this client is the first, it can just load the application, and mark it not pending
  239. if ( Object.keys( global.instances[ namespace ].clients ).length === 1 ) {
  240. if ( saveObject ) {
  241. socket.emit( 'message', {
  242. action: "setState",
  243. parameters: [ saveObject ],
  244. time: global.instances[ namespace ].getTime( )
  245. } );
  246. }
  247. else {
  248. var instance = namespace;
  249. //Get the state and load it.
  250. //Now the server has a rough idea of what the simulation is
  251. socket.emit( 'message', {
  252. action: "createNode",
  253. parameters: [ "proxy/clients.vwf" ],
  254. time: global.instances[ namespace ].getTime( )
  255. } );
  256. socket.emit( 'message', {
  257. action: "createNode",
  258. parameters: [
  259. ( processedURL.public_path === "/" ? "" : processedURL.public_path ) + "/" + processedURL.application,
  260. "application"
  261. ],
  262. time: global.instances[ namespace ].getTime( )
  263. } );
  264. }
  265. socket.pending = false;
  266. //xapi.logClient( saveObject, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, true, true );
  267. }
  268. else { //this client is not the first, we need to get the state and mark it pending
  269. if ( ! global.instances[ namespace ].pendingList.pending ) {
  270. var firstclient = Object.keys( global.instances[ namespace ].clients )[ 0 ];
  271. firstclient = global.instances[ namespace ].clients[ firstclient ];
  272. firstclient.emit( 'message', {
  273. action: "getState",
  274. respond: true,
  275. time: global.instances[ namespace ].getTime( )
  276. } );
  277. global.instances[ namespace ].Log( 'GetState from Client', 2 );
  278. global.instances[ namespace ].pendingList.pending = true;
  279. }
  280. socket.pending = true;
  281. }
  282. //Create a child in the application's 'clients.vwf' global to represent this client.
  283. var clientNodeMessage = {
  284. action: "createChild",
  285. parameters: [ "proxy/clients.vwf", socket.id, clientDescriptor ],
  286. time: global.instances[ namespace ].getTime( )
  287. };
  288. // Send messages to all the existing clients (that are not pending),
  289. // telling them to create a new node under the "clients" parent for the new client
  290. for ( var i in global.instances[ namespace ].clients ) {
  291. var client = global.instances[ namespace ].clients[ i ];
  292. if ( !client.pending ) {
  293. client.emit ( 'message', clientNodeMessage );
  294. }
  295. }
  296. if ( global.instances[ namespace ].pendingList.pending ) {
  297. global.instances[ namespace ].pendingList.push( clientNodeMessage );
  298. }
  299. socket.on( 'message', function ( msg ) {
  300. //need to add the client identifier to all outgoing messages
  301. try {
  302. var message = JSON.parse( msg );
  303. }
  304. catch ( e ) {
  305. console.error( "Error on socket message: ", e );
  306. return;
  307. }
  308. message.client = socket.id;
  309. message.time = global.instances[ namespace ].getTime( ); //message.time ? message.time : global.instances[ namespace ].getTime( );
  310. if ( message.result === undefined ) {
  311. //distribute message to all clients on given instance
  312. for ( var i in global.instances[ namespace ].clients ) {
  313. var client = global.instances[ namespace ].clients[ i ];
  314. //just a regular message, so push if the client is pending a load, otherwise just send it.
  315. if ( ! client.pending ) {
  316. client.emit( 'message', message );
  317. }
  318. }
  319. if (global.instances[ namespace ]) {
  320. if ( global.instances[ namespace ].pendingList.pending ) {
  321. global.instances[ namespace ].pendingList.push( message );
  322. }
  323. }
  324. } else if ( message.action == "getState" ) {
  325. //distribute message to all clients on given instance
  326. for ( var i in global.instances[ namespace ].clients ) {
  327. var client = global.instances[ namespace ].clients[ i ];
  328. //if the message was get state, then fire all the pending messages after firing the setState
  329. if ( client.pending ) {
  330. global.instances[ namespace ].Log( 'Got State', 2 );
  331. var state = message.result;
  332. global.instances[ namespace ].Log( state, 2 );
  333. client.emit( 'message', { action: "setState", parameters: [ state ], time: setStateTime } );
  334. client.pending = false;
  335. for ( var j = 0; j < global.instances[ namespace ].pendingList.length; j++ ) {
  336. client.emit( 'message', global.instances[ namespace ].pendingList[ j ] );
  337. }
  338. //xapi.logClient( state, undefined, undefined, namespace, GetClientDescriptor( client ).properties || {}, true, false );
  339. }
  340. }
  341. global.instances[ namespace ].pendingList = [ ];
  342. } else if ( message.action === "execute" ) {
  343. var evaluation = socket.pendingEvaluations && socket.pendingEvaluations.shift();
  344. if ( evaluation ) {
  345. evaluation.resolve( message.result );
  346. clearTimeout( evaluation.timeout );
  347. }
  348. }
  349. } );
  350. // When a client disconnects, go ahead and remove the instance data
  351. socket.on( 'disconnect', function ( ) {
  352. // Remove the disconnecting client
  353. var leavingClient = global.instances[ namespace ].clients[ socket.id ];
  354. global.instances[ namespace ].clients[ socket.id ] = null;
  355. delete global.instances[ namespace ].clients[ socket.id ];
  356. if ( leavingClient.pendingEvaluations ) {
  357. leavingClient.pendingEvaluations.forEach( function( evaluation ) {
  358. evaluation.reject( new Error( "connection closed" ) );
  359. clearTimeout( evaluation.timeout );
  360. } );
  361. }
  362. // Notify others of the disconnecting client. Delete the child representing this client in the application's `clients.vwf` global.
  363. var clientMessage = { action: "deleteChild", parameters: [ "proxy/clients.vwf", socket.id ], time: global.instances[ namespace ].getTime( ) };
  364. for ( var i in global.instances[ namespace ].clients ) {
  365. var client = global.instances[ namespace ].clients[ i ];
  366. if ( ! client.pending ) {
  367. client.emit ( 'message', clientMessage );
  368. }
  369. }
  370. if (global.instances[namespace]) {
  371. if (global.instances[namespace].pendingList.pending) {
  372. global.instances[namespace].pendingList.push(clientMessage);
  373. }
  374. // If it's the last client, delete the data and the timer
  375. if ( Object.keys( global.instances[ namespace ].clients ).length == 0 ) {
  376. clearInterval( global.instances[ namespace ].timerID );
  377. delete global.instances[ namespace ];
  378. // xapi.logClient( undefined, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, false, true );
  379. } else {
  380. // xapi.logClient( undefined, loadInfo[ 'application_path' ], loadInfo[ 'save_name' ], namespace, clientDescriptor.properties || {}, false, false );
  381. }
  382. }
  383. } );
  384. }
  385. function Evaluate( namespace, node, expression ) {
  386. return new Promise( function( resolve, reject ) {
  387. var firstClientID = Object.keys( global.instances[ namespace ].clients )[ 0 ];
  388. var firstClient = global.instances[ namespace ].clients[ firstClientID ];
  389. if ( firstClient ) {
  390. firstClient.pendingEvaluations = firstClient.pendingEvaluations || [];
  391. firstClient.pendingEvaluations.push( {
  392. resolve: resolve,
  393. reject: reject,
  394. timeout: setTimeout( function() { reject( new Error( "timeout" ) ) }, 1000 ),
  395. } );
  396. firstClient.emit( "message", { node: node, action: "execute", parameters: [ expression ], respond: true, time: global.instances[ namespace ].getTime() } );
  397. } else {
  398. reject( new Error( "no clients are connected" ) );
  399. }
  400. } );
  401. }
  402. /// Get a descriptor for the `clients.vwf` child for a new client. An authenticator may set a
  403. /// descriptor in the session at `session.vwf.client`. If the authenticator doesn't provide a
  404. /// descriptor, use an empty node inheriting from `client.vwf`.
  405. function GetClientDescriptor( socket ) {
  406. // socket.io doesn't provide access to the request and the session, but we do have the cookies.
  407. // Create a mock request and run it through the session middleware to recreate the session. This
  408. // creates a session object at `mockRequest.session`.
  409. var mockRequest = {
  410. headers: { cookie: socket.handshake.headers.cookie },
  411. connection: {},
  412. session: {},
  413. };
  414. var mockResponse = {
  415. getHeader: function() {},
  416. setHeader: function() {},
  417. };
  418. sessionStack.forEach( function( middleware ) {
  419. middleware( mockRequest, mockResponse, function() {} );
  420. } );
  421. // Get the descriptor from `vwf.client` in the session.
  422. var descriptor = ( mockRequest.session.vwf || {} ).client || {};
  423. // Set the default prototype.
  424. if ( ! descriptor.extends ) {
  425. descriptor.extends = "proxy/client.vwf";
  426. }
  427. return descriptor;
  428. }
  429. /// Middleware stack to parse a cookie session from `req.headers.cookie` into `req.session`.
  430. var sessionStack = [
  431. //cookieParser(),
  432. //cookieSession( { secret: config.get( 'session.secret' ) } ),
  433. ];
  434. function GetInstances() {
  435. return global.instances;
  436. }
  437. exports.OnConnection = OnConnection;
  438. exports.Evaluate = Evaluate;
  439. exports.GetInstances = GetInstances;