Browse Source

krestianstvo luminary init

Nikolay Suslov 4 years ago
parent
commit
fcb26365ab
39 changed files with 1581 additions and 512 deletions
  1. 2 0
      .gitignore
  2. 2 1
      config_example.json
  3. 2 1
      package.json
  4. 73 6
      public/app.js
  5. 2 2
      public/defaults/proxy/vwf.example.com/aframe/aentity.js
  6. 1 1
      public/defaults/proxy/vwf.example.com/aframe/ascene.js
  7. 2 2
      public/defaults/worlds/aframe/index.vwf.yaml
  8. 2 2
      public/defaults/worlds/aframe2/index.vwf.yaml
  9. 59 9
      public/helpers.js
  10. 4 2
      public/index.html
  11. 40 15
      public/lib/gundb/gun.js
  12. 0 0
      public/lib/gundb/gun.min.js
  13. 11 13
      public/lib/gundb/lib/bye.js
  14. 2 2
      public/lib/gundb/lib/evict.js
  15. 2 2
      public/lib/gundb/lib/promise.js
  16. 33 20
      public/lib/gundb/lib/radisk.js
  17. 1 1
      public/lib/gundb/lib/radisk2.js
  18. 0 1
      public/lib/gundb/lib/rindexed.js
  19. 3 3
      public/lib/gundb/lib/rs3.js
  20. 2 1
      public/lib/gundb/lib/stats.js
  21. 30 9
      public/lib/gundb/lib/store.js
  22. 2 3
      public/lib/gundb/sea/array.js
  23. 7 0
      public/lib/gundb/sea/base64.js
  24. 2 3
      public/lib/gundb/sea/buffer.js
  25. 2 2
      public/lib/gundb/sea/secret.js
  26. 1 1
      public/lib/gundb/sea/shim.js
  27. 137 6
      public/lib/widgets.js
  28. 506 0
      public/luminary.js
  29. 233 0
      public/reflector-client.js
  30. 105 279
      public/vwf.js
  31. 1 1
      public/vwf/model/aframe.js
  32. 5 5
      public/vwf/model/aframe/aframe-master.js
  33. 0 0
      public/vwf/model/aframe/aframe-master.js.map
  34. 0 0
      public/vwf/model/aframe/aframe-master.min.js
  35. 0 0
      public/vwf/model/aframe/aframe-master.min.js.map
  36. 92 82
      public/vwf/view/aframe.js
  37. 65 34
      public/vwf/view/document.js
  38. 112 3
      public/web/index-app.js
  39. 38 0
      webServer.js

+ 2 - 0
.gitignore

@@ -9,6 +9,8 @@ coverage
 # production
 build
 certs
+radata
+radata-*.*
 
 # misc
 .DS_Store

+ 2 - 1
config_example.json

@@ -5,5 +5,6 @@
     "sslCert":"./certs/server-crt.pem",
     "sslCA":"./certs/ca-crt.pem",
     "certPwd": "",
-    "reflector": false
+    "reflector": false,
+    "db": false
 }

+ 2 - 1
package.json

@@ -19,7 +19,8 @@
     "serve-static": "^1.13.2",
     "optimist": "0.6.1",
     "socket.io": "2.2.0",
-    "lcs-reflector": "0.2.1"
+    "lcs-reflector": "0.2.1",
+    "gun": "0.2019.930"
   },
   "scripts": {
     "start": "node index.js",

+ 73 - 6
public/app.js

@@ -11,13 +11,15 @@ import page from '/lib/page.mjs';
 import { Helpers } from '/helpers.js';
 import { WorldApp } from '/web/world-app.js';
 import { Widgets } from '/lib/widgets.js';
+import { ReflectorClient } from './reflector-client.js';
+import { Luminary } from '/luminary.js';
+
 
 
 class App {
   constructor() {
     console.log("app constructor");
     this.widgets = new Widgets;
-
     //globals
     window._app = this;
     window._cellWidgets = this.widgets;
@@ -25,6 +27,10 @@ class App {
     window._noty = new Noty;
     this.helpers = new Helpers;
 
+    this.luminary = new Luminary;
+    this.reflectorClient = new ReflectorClient;
+    this.config = {};
+
     this.initDB();
     this.initUser();
 
@@ -71,15 +77,36 @@ class App {
     var config = JSON.parse(localStorage.getItem('lcs_config'));
     if (!config) {
       config = {
-        'dbhost': 'https://' + window.location.hostname + ':8080/gun', //'http://localhost:8080/gun',
+        'luminary': false,
+        'luminaryPath': 'luminary',
+        'luminaryGlobalHBPath': 'server/heartbeat',
+        'luminaryGlobalHB': false,
+        'dbhost':  window.location.origin + '/gun', // 'https://' + window.location.hostname + ':8080/gun', //'http://localhost:8080/gun',
         'reflector': 'https://' + window.location.hostname + ':3002',
         'language': 'en'
       }
       localStorage.setItem('lcs_config', JSON.stringify(config));
     }
 
+    if(!config.luminaryPath){
+      config.luminaryPath = 'luminary';
+      localStorage.setItem('lcs_config', JSON.stringify(config));
+    }
+
+    if(!config.luminaryGlobalHBPath){
+      config.luminaryGlobalHBPath = 'server/heartbeat';
+      localStorage.setItem('lcs_config', JSON.stringify(config));
+    }
+
+    if(!config.luminaryGlobalHB){
+      config.luminaryGlobalHB = false;
+      localStorage.setItem('lcs_config', JSON.stringify(config));
+    }
 
-    const opt = { peers: this.dbHost, localStorage: false, axe: false }
+    this.config = config;
+
+    const opt = { peers: this.dbHost, localStorage: false, axe: false}
+   //const opt = { peers: this.dbHost, localStorage: false, until: 1000, chunk: 5, axe: false} //until: 5000, chunk: 5
     //opt.store = RindexedDB(opt);
     this.db = Gun(opt);
 
@@ -139,6 +166,46 @@ class App {
   }
 
 
+  async chooseConnection(data) {
+      if (this.isLuminary){
+        return await _app.luminary.connect(data) //use Luminary
+      } else {
+        return data //use Reflector
+      }
+  }
+
+  get isLuminary() {
+
+    return this.config.luminary;
+
+  }
+
+  get isLuminaryGlobalHB() {
+
+    return this.config.luminaryGlobalHB;
+
+  }
+
+  get luminaryGlobalHBPath() {
+
+    var res = "";
+    let config = localStorage.getItem('lcs_config');
+    if (config) {
+      res = JSON.parse(config).luminaryGlobalHBPath;
+    }
+    return res;
+  }
+
+  get luminaryPath() {
+
+    var res = "";
+    let config = localStorage.getItem('lcs_config');
+    if (config) {
+      res = JSON.parse(config).luminaryPath;
+    }
+    return res;
+  }
+
   get reflectorHost() {
 
     var res = "";
@@ -947,8 +1014,8 @@ class App {
 
         if ((parsedRequest['instance'] == undefined) && (parsedRequest['private_path'] == undefined) && (parsedRequest['public_path'] !== "/") && (parsedRequest['application'] !== undefined)) {
 
-          page.redirect(pathname + '/' + app.helpers.GenerateInstanceID());
-
+            //page.redirect(pathname + '/' + app.helpers.GenerateInstanceID());
+            window.location.pathname = pathname + '/' + app.helpers.GenerateInstanceID()
         }
       })
     })
@@ -985,7 +1052,7 @@ class App {
         var userLibraries = { model: {}, view: {} };
         var application;
 
-        vwf.loadConfiguration(application, userLibraries, compatibilityCheck);
+          vwf.loadConfiguration(application, userLibraries, compatibilityCheck);
       })
     })
 

+ 2 - 2
public/defaults/proxy/vwf.example.com/aframe/aentity.js

@@ -141,11 +141,11 @@ this.setOwner = function (param) {
 }
 
 this.updateMethod = function (methodName, methodBody, params) {
-    vwf_view.kernel.setMethod(this.id, methodName, { body: methodBody, type: "application/javascript", parameters: params});
+    vwf.setMethod(this.id, methodName, { body: methodBody, type: "application/javascript", parameters: params});
 }
 
 this.callMethod = function(methodName, params){
-    vwf_view.kernel.callMethod(this.id, methodName, params)
+    vwf.callMethod(this.id, methodName, params)
 }
 
 this.do = function() {

+ 1 - 1
public/defaults/proxy/vwf.example.com/aframe/ascene.js

@@ -1,5 +1,5 @@
 this.initialize = function () {
-    this.future(0).clientWatch();
+    this.future(3).clientWatch();
 };
 
 this.clientWatch = function () {

+ 2 - 2
public/defaults/worlds/aframe/index.vwf.yaml

@@ -127,6 +127,6 @@ children:
 methods:
   initialize:
     body: |
-      var runBox = vwf_view.kernel.find("", "/sphere/box2")[0];
+      var runBox = vwf.find("", "/sphere/box2")[0];
       console.log(runBox);
-      vwf_view.kernel.callMethod(runBox, "run");
+      vwf.callMethod(runBox, "run");

+ 2 - 2
public/defaults/worlds/aframe2/index.vwf.yaml

@@ -215,6 +215,6 @@ children:
 methods:
   initialize:
     body: |
-      var runBox = vwf_view.kernel.find("", "/sphere/box2")[0];
+      var runBox = vwf.find("", "/sphere/box2")[0];
       console.log(runBox);
-      vwf_view.kernel.callMethod(runBox, "run");
+      vwf.callMethod(runBox, "run");

+ 59 - 9
public/helpers.js

@@ -16,6 +16,25 @@ class Helpers {
         this.applicationRoot = "/"; //app
     }
 
+    reduceSaveObject(path) {
+        let obj = Object.assign({}, path);
+
+        if(path.saveObject){
+            if ( path.saveObject[ "queue" ] ) {
+                if ( path.saveObject[ "queue" ][ "time" ] ) {
+                    obj.saveObject = {
+                        "init": true,
+                        "queue":{
+                            "time": path.saveObject[ "queue" ][ "time" ]
+                        }
+                    }
+                }
+            }
+        }
+
+        return obj
+    }
+
 
     async Process(updatedURL) {
         var result =
@@ -91,6 +110,14 @@ class Helpers {
         return result;
     }
 
+    GetNamespace( processedURL ) {
+        if ( ( processedURL[ 'instance' ] ) && ( processedURL[ 'public_path' ] ) ) {
+            return this.JoinPath( processedURL[ 'public_path' ], processedURL[ 'application' ], processedURL[ 'instance' ] );
+        }
+        return undefined;
+    }
+
+
     async IsFileExist(path) {
 
         let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);
@@ -98,9 +125,12 @@ class Helpers {
         var seperatorFixedPath = path.slice(1);//path.replace(/\//g, '/');
         let worldName = seperatorFixedPath.split('/')[0];
         let fileName = seperatorFixedPath.replace(worldName + '/', "");
-        let doc = await userDB.get('worlds').get(worldName).get(fileName).then();
-        if (doc) {
-            return true
+        let world = await userDB.get('worlds').get(worldName).promOnce();
+        if (world) {
+            let doc = Object.keys(world.data).includes(fileName);//(await userDB.get('worlds').get(worldName).get(fileName).promOnce()).data;
+            if (doc) {
+                return true
+            }
         }
         return false
     }
@@ -108,11 +138,14 @@ class Helpers {
     async IsExist(path) {
 
         let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);
-        var seperatorFixedPath = path.slice(1);//path.replace(/\//g, '/');
-        let doc = await  userDB.get('worlds').get(seperatorFixedPath).then();
+        var seperatorFixedPath = (path.slice(1)).split('/');//path.replace(/\//g, '/');
+        if(seperatorFixedPath.length == 1){
+        let doc = (await  userDB.get('worlds').get(seperatorFixedPath[0]).promOnce()).data; //(await  userDB.get('worlds').promOnce()).data;
+       // let doc = Object.keys(worlds).includes('index_vwf_yaml');//(await  userDB.get('worlds').get(seperatorFixedPath).promOnce()).data;
         if (doc) {
             return true
         }
+    }
         return false
     }
 
@@ -139,10 +172,27 @@ class Helpers {
 
         if (path.match(/\.vwf$/)) {
 
-            for (const res of this.template_extensions) {
-                let check = await this.IsFileExist(this.JoinPath(path + res).split(".").join("_"));
-                if (check) return res
-            }
+            // ["", ".yaml", ".json"]
+
+            let check1 = await this.IsFileExist(this.JoinPath(path + "").split(".").join("_"));
+            if(check1)
+                return ""
+
+            let check2 = await this.IsFileExist(this.JoinPath(path + ".yaml").split(".").join("_"));
+            if(check2)
+                return ".yaml"
+
+            let check3 = await this.IsFileExist(this.JoinPath(path + ".json").split(".").join("_"));
+            if(check3)
+                return ".json"
+
+
+
+
+            // for (const res of this.template_extensions) {
+            //     let check = await this.IsFileExist(this.JoinPath(path + res).split(".").join("_"));
+            //     if (check) return res
+            // }
         }
 
         return undefined;

+ 4 - 2
public/index.html

@@ -30,11 +30,13 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
     <script src="/lib/gundb/lib/open.js"></script>
     <script src="/lib/gundb/lib/load.js"></script>
     <script src="/lib/gundb/lib/promise.js"></script>
+    <script src="/lib/gundb/lib/time.js"></script>
+    <script src="/lib/gundb/lib/bye.js"></script>
 
     <script src="/lib/gundb/lib/webrtc.js"></script>
-   
+    <script src="/lib/gundb/nts.js"></script>
 
-    <script src="/lib/gundb/lib/radix.js"></script>
+   <script src="/lib/gundb/lib/radix.js"></script>
     <script src="/lib/gundb/lib/radisk.js"></script>
     <script src="/lib/gundb/lib/store.js"></script>
     <script src="/lib/gundb/lib/rindexed.js"></script>

+ 40 - 15
public/lib/gundb/gun.js

@@ -811,16 +811,20 @@
 					// Maybe... in case the in-memory key we have is a local write
 					// we still need to trigger a pull/merge from peers.
 				} else {
+					//var S = +new Date;
 					node = Gun.obj.copy(node);
+					//console.log(+new Date - S, 'copy node');
 				}
 				node = Gun.graph.node(node);
 				tmp = (at||empty).ack;
+				//var S = +new Date;
 				root.on('in', {
 					'@': msg['#'],
 					how: 'mem',
 					put: node,
 					$: gun
 				});
+				//console.log(+new Date - S, 'root got send');
 				//if(0 < tmp){ return }
 				root.on('get', msg);
 			}
@@ -858,7 +862,7 @@
 		var state_lex = Gun.state.lex, _soul = Gun.val.link._, _has = '.', node_ = Gun.node._, rel_is = Gun.val.link.is;
 		var empty = {}, u;
 
-		console.debug = function(i, s){ return (console.debug.i && i === console.debug.i && console.debug.i++) && (console.log.apply(console, arguments) || s) };
+		console.only = function(i, s){ return (console.only.i && i === console.only.i && console.only.i++) && (console.log.apply(console, arguments) || s) };
 
 		Gun.log = function(){ return (!Gun.log.off && console.log.apply(console, arguments)), [].slice.call(arguments).join(' ') }
 		Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ || Gun.log(s) }
@@ -1295,19 +1299,18 @@
 			if(tmp = cat.soul || cat.link || cat.dub){ return cb(tmp, as, cat) }
 			if(cat.jam){ return cat.jam.push([cb, as]) }
 			cat.jam = [[cb,as]];
-			gun.get(function(msg, eve){
+			gun.get(function go(msg, eve){
 				if(u === msg.put && (tmp = Object.keys(cat.root.opt.peers).length) && ++acks < tmp){
 					return;
 				}
 				eve.rid(msg);
-				var at = ((at = msg.$) && at._) || {};
-				tmp = cat.jam; Gun.obj.del(cat, 'jam');
-				Gun.obj.map(tmp, function(as, cb){
-					cb = as[0]; as = as[1];
-					if(!cb){ return }
-					var id = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub;
-					cb(id, as, msg, eve);
-				});
+				var at = ((at = msg.$) && at._) || {}, i = 0, as;
+				tmp = cat.jam; delete cat.jam; // tmp = cat.jam.splice(0, 100);
+				//if(tmp.length){ process.nextTick(function(){ go(msg, eve) }) }
+				while(as = tmp[i++]){ //Gun.obj.map(tmp, function(as, cb){
+					var cb = as[0], id; as = as[1];
+					cb && cb(id = at.link || at.soul || rel.is(msg.put) || node_soul(msg.put) || at.dub, as, msg, eve);
+				} //);
 			}, {out: {get: {'.':true}}});
 			return gun;
 		}
@@ -1701,7 +1704,7 @@
 				return;
 			}
 			if(link && u === link.put && (tmp = rel.is(data))){ data = Gun.node.ify({}, tmp) }
-			eve.rid(msg);
+			eve.rid? eve.rid(msg) : eve.off();
 			opt.ok.call(gun || opt.$, data, msg.get);
 		}
 
@@ -1954,6 +1957,7 @@
 
 	;USE(function(module){
 		var Type = USE('../type');
+		var puff = (typeof setImmediate !== "undefined")? setImmediate : setTimeout;
 
 		function Mesh(root){
 			var mesh = function(){};
@@ -1972,10 +1976,18 @@
 				if('[' === tmp){
 					try{msg = JSON.parse(raw);}catch(e){opt.log('DAM JSON parse error', e)}
 					if(!msg){ return }
-					var i = 0, m;
-					while(m = msg[i++]){
-						mesh.hear(m, peer);
-					}
+					//console.log('hear batch length of', msg.length);
+					(function go(){
+						var S = +new Date; // STATS!
+						var m, c = 100; // hardcoded for now?
+						while(c-- && (m = msg.shift())){
+							mesh.hear(m, peer);
+						}
+						//console.log(+new Date - S, 'hear batch');
+						(mesh.hear.long || (mesh.hear.long = [])).push(+new Date - S);
+						if(!msg.length){ return }
+						puff(go, 0);
+					}());
 					return;
 				}
 				if('{' === tmp || (Type.obj.is(raw) && (msg = raw))){
@@ -1983,6 +1995,7 @@
 					}catch(e){return opt.log('DAM JSON parse error', e)}
 					if(!msg){ return }
 					if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) }
+					if(msg.DBG_s){ console.log(+new Date - msg.DBG_s, 'to hear', id) }
 					if(dup.check(id)){ return }
 					dup.track(id, true).it = msg; // GUN core also dedups, so `true` is needed. // Does GUN core need to dedup anymore?
 					if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) }
@@ -1998,7 +2011,9 @@
 						}
 						return;
 					}
+					//var S = +new Date;
 					root.on('in', msg);
+					//!msg.nts && console.log(+new Date - S, 'msg', msg['#']);
 					return;
 				}
 			}
@@ -2012,6 +2027,7 @@
 					if(this.to){ this.to.next(msg) } // compatible with middleware adapters.
 					if(!msg){ return false }
 					var id, hash, tmp, raw;
+					//var S = +new Date; //msg.DBG_s = msg.DBG_s || +new Date;
 					var meta = msg._||(msg._=function(){});
 					if(!(id = msg['#'])){ id = msg['#'] = Type.text.random(9) }
 					if(!(hash = msg['##']) && u !== msg.put){ hash = msg['##'] = Type.obj.hash(msg.put) }
@@ -2025,12 +2041,15 @@
 							}
 						}
 					}
+					//console.log(+new Date - S, 'mesh say prep');
 					dup.track(id).it = msg; // track for 9 seconds, default. Earth<->Mars would need more!
 					if(!peer){ peer = (tmp = dup.s[msg['@']]) && (tmp = tmp.it) && (tmp = tmp._) && (tmp = tmp.via) }
 					if(!peer && mesh.way){ return mesh.way(msg) }
 					if(!peer || !peer.id){ message = msg;
 						if(!Type.obj.is(peer || opt.peers)){ return false }
+						//var S = +new Date;
 						Type.obj.map(peer || opt.peers, each); // in case peer is a peer list.
+						//console.log(+new Date - S, 'mesh say loop');
 						return;
 					}
 					if(!peer.wire && mesh.wire){ mesh.wire(peer) }
@@ -2054,8 +2073,10 @@
 					peer.batch = peer.tail = null;
 					if(!tmp){ return }
 					if(!tmp.length){ return } // if(3 > tmp.length){ return } // TODO: ^
+					//var S = +new Date;
 					try{tmp = (1 === tmp.length? tmp[0] : JSON.stringify(tmp));
 					}catch(e){return opt.log('DAM JSON stringify error', e)}
+					//console.log(+new Date - S, 'mesh flush', tmp.length);
 					if(!tmp){ return }
 					send(tmp, peer);
 				}
@@ -2065,12 +2086,14 @@
 			// for now - find better place later.
 			function send(raw, peer){ try{
 				var wire = peer.wire;
+				//var S = +new Date;
 				if(peer.say){
 					peer.say(raw);
 				} else
 				if(wire.send){
 					wire.send(raw);
 				}
+				//console.log(+new Date - S, 'wire send', raw.length);
 				mesh.say.d += raw.length||0; ++mesh.say.c; // STATS!
 			}catch(e){
 				(peer.queue = peer.queue || []).push(raw);
@@ -2249,6 +2272,8 @@
 				return wire;
 			}catch(e){}}
 
+			setTimeout(function(){ root.on('out', {dam:'hi'}) },1); // it can take a while to open a socket, so maybe no longer lazy load for perf reasons?
+
 			var wait = 2 * 1000;
 			function reconnect(peer){
 				clearTimeout(peer.defer);

File diff suppressed because it is too large
+ 0 - 0
public/lib/gundb/gun.min.js


+ 11 - 13
public/lib/gundb/lib/bye.js

@@ -1,24 +1,22 @@
 var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
 
-Gun.on('opt', function(root){
+Gun.on('create', function(root){
 	this.to.next(root);
-	if(root.once){ return }
-	root.on('in', function(msg){
-		//Msg did not have a peer property saved before, so nothing ever went further
-		if(!msg._ || !msg.BYE){ return this.to.next(msg) }
-		var peer = msg._.via;
-		(peer.bye = peer.bye || []).push(msg.BYE);
-	})
+	var mesh = root.opt.mesh;
+	if(!mesh){ return }
+	mesh.hear['bye'] = function(msg, peer){
+		(peer.byes = peer.byes || []).push(msg.bye);
+	}
 	root.on('bye', function(peer){
 		this.to.next(peer);
-		if(!peer.bye){ return }
-		var gun = root.gun;
-		Gun.obj.map(peer.bye, function(data){
+		if(!peer.byes){ return }
+		var gun = root.$;
+		Gun.obj.map(peer.byes, function(data){
 			Gun.obj.map(data, function(put, soul){
 				gun.get(soul).put(put);
 			});
 		});
-		peer.bye = [];
+		peer.byes = [];
 	});
 });
 
@@ -30,7 +28,7 @@ Gun.chain.bye = function(){
 			var tmp = data;
 			(data = {})[at.get] = tmp;
 		});
-		root.on('out', {BYE: data});
+		root.on('out', {bye: data});
 		return gun;
 	}
 	return bye;

+ 2 - 2
public/lib/gundb/lib/evict.js

@@ -19,12 +19,12 @@
 		function GC(){
 			var souls = Object.keys(root.graph||empty);
 			var toss = Math.ceil(souls.length * 0.01);
-			//var start = Gun.state(), i = toss;
+			//var S = +new Date;
 			Gun.list.map(souls, function(soul){
 				if(--toss < 0){ return }
 				root.gun.get(soul).off();
 			});
-			//console.log("evicted", i, 'nodes in', ((Gun.state() - start)/1000).toFixed(2), 'sec.');
+			//console.log(+new Date - S, 'gc');
 		}
 		/*
 		root.on('in', function(msg){

+ 2 - 2
public/lib/gundb/lib/promise.js

@@ -1,4 +1,4 @@
-/* Promise Library v1.0 for GUN DB
+/* Promise Library v1.1 for GUN DB
 *  Turn any part of a gun chain into a promise, that you can then use
 *  .then().catch() pattern.
 *  In normal gun doing var item = gun.get('someKey'), gun returns a reference
@@ -67,7 +67,7 @@ Gun.chain.promPut = async function (item, opt) {
   var gun = this;
   return (new Promise((res, rej)=>{
     gun.put(item, function(ack) {
-        if(ack.err){rej(ack.err)}
+        if(ack.err){console.log(ack.err); ack.ok=-1; res({ref:gun, ack:ack})}
         res({ref:gun, ack:ack});
     }, opt);
   }))

+ 33 - 20
public/lib/gundb/lib/radisk.js

@@ -11,15 +11,15 @@
 		opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB.
 		opt.until = opt.until || opt.wait || 250;
 		opt.batch = opt.batch || (10 * 1000);
-		opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB
+		opt.chunk = opt.chunk || (1024 * 1024 * 1); // 1MB
 		opt.code = opt.code || {};
 		opt.code.from = opt.code.from || '!';
-		//opt.jsonify = true; if(opt.jsonify){ console.log("JSON RAD!!!") } // TODO: REMOVE!!!!
+		opt.jsonify = true;
 
 		function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') }
 		function atomic(v){ return u !== v && (!v || 'object' != typeof v) }
 		var map = Gun.obj.map;
-		var LOG = false;//true;
+		var LOG = false;
 
 		if(!opt.store){
 			return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!");
@@ -44,7 +44,9 @@
 			if(val instanceof Function){
 				var o = cb || {};
 				cb = val;
+				var S; LOG && (S = +new Date);
 				val = r.batch(key);
+				LOG && console.log(+new Date - S, 'rad mem');
 				if(u !== val){
 					cb(u, r.range(val, o), o);
 					if(atomic(val)){ return }
@@ -230,19 +232,22 @@
 			r.read = function(key, cb, o){
 				o = o || {};
 				if(RAD && !o.next){ // cache
+					var S; LOG && (S = +new Date);
 					var val = RAD(key);
+					LOG && console.log(+new Date - S, 'rad cached');
 					//if(u !== val){
 						//cb(u, val, o);
 						if(atomic(val)){ cb(u, val, o); return }
 						// if a node is requested and some of it is cached... the other parts might not be.
 					//}
 				}
-				o.span = (u !== o.start) || (u !== o.end);
+				o.span = (u !== o.start) || (u !== o.end); // is there a start or end?
 				var g = function Get(){};
 				g.lex = function(file){ var tmp;
 					file = (u === file)? u : decodeURIComponent(file);
 					tmp = o.next || key || (o.reverse? o.end || '\uffff' : o.start || '');
 					if(!file || (o.reverse? file < tmp : file > tmp)){
+						LOG && console.log(+new Date - S, 'rad read lex'); S = +new Date;
 						if(o.next || o.reverse){ g.file = file }
 						if(tmp = Q[g.file]){
 							tmp.push({key: key, ack: cb, file: g.file, opt: o});
@@ -263,25 +268,33 @@
 					g.info = info;
 					if(disk){ RAD = g.disk = disk }
 					disk = Q[g.file]; delete Q[g.file];
+					LOG && console.log(+new Date - S, 'rad read it in, now ack to:', disk.length); S = +new Date;
 					map(disk, g.ack);
+					LOG && console.log(+new Date - S, 'rad read acked');
 				}
 				g.ack = function(as){
 					if(!as.ack){ return }
-					var tmp = as.key, o = as.opt, info = g.info, rad = g.disk || noop, data = r.range(rad(tmp), o), last = rad.last || Radix.map(rad, rev, revo);
+					var key = as.key, o = as.opt, info = g.info, rad = g.disk || noop, data = r.range(rad(key), o), last = rad.last || Radix.map(rad, rev, revo);
 					o.parsed = (o.parsed || 0) + (info.parsed||0);
 					o.chunks = (o.chunks || 0) + 1;
-					if(!o.some){ o.some = (u !== data) }
-					if(u !== data){ as.ack(g.err, data, o) }
-					else if(!as.file){ !o.some && as.ack(g.err, u, o); return }
-					if(!o.span){
-						if(/*!last || */last === tmp){ !o.some && as.ack(g.err, u, o); return }
-						if(last && last > tmp && 0 != last.indexOf(tmp)){ !o.some && as.ack(g.err, u, o); return }
+					o.more = true;
+					if((!as.file) // if no more places to look
+					|| (!o.span && last === key) // if our key exactly matches the very last atomic record
+					|| (!o.span && last && last > key && 0 != last.indexOf(key)) // 'zach' may be lexically larger than 'za', but there still might be more, like 'zane' in the 'za' prefix bucket so do not end here.
+					){
+						o.more = u;
+						as.ack(g.err, data, o);
+						return 
 					}
-					if(o.some && o.parsed >= o.limit){ return }
+					if(u !== data){
+						as.ack(g.err, data, o); // more might be coming!
+						if(o.parsed >= o.limit){ return } // even if more, we've hit our limit, asking peer will need to make a new ask with a new starting point.
+					} 
 					o.next = as.file;
-					r.read(tmp, as.ack, o);
+					r.read(key, as.ack, o);
 				}
 				if(o.reverse){ g.lex.reverse = true }
+				LOG && (S = +new Date);
 				r.list(g.lex);
 			}
 			function rev(a,b){ return b }
@@ -302,7 +315,7 @@
 				var p = function Parse(){}, info = {};
 				p.disk = Radix();
 				p.read = function(err, data){ var tmp;
-					LOG && console.log('read disk in', +new Date - start, ename(file)); // keep this commented out in 
+					LOG && console.log('read disk in', +new Date - S, ename(file)); // keep this commented out in 
 					delete Q[file];
 					if((p.err = err) || (p.not = !data)){
 						return map(q, p.ack);
@@ -319,12 +332,12 @@
 					}
 					info.parsed = data.length;
 
-					LOG && (start = +new Date); // keep this commented out in production!
-					if(opt.jsonify){ // temporary testing idea
+					LOG && (S = +new Date); // keep this commented out in production!
+					if(opt.jsonify || '{' === data[0]){ // temporary testing idea
 						try{
 							var json = JSON.parse(data);
 							p.disk.$ = json;
-							LOG && console.log('parsed JSON in', +new Date - start); // keep this commented out in production!
+							LOG && console.log('parsed JSON in', +new Date - S); // keep this commented out in production!
 							map(q, p.ack);
 							return;
 						}catch(e){ tmp = e }
@@ -333,7 +346,7 @@
 							return map(q, p.ack);
 						}
 					}
-					LOG && (start = +new Date); // keep this commented out in production!
+					LOG && (S = +new Date); // keep this commented out in production!
 					var tmp = p.split(data), pre = [], i, k, v;
 					if(!tmp || 0 !== tmp[1]){
 						p.err = "File '"+file+"' does not have root radix! ";
@@ -356,7 +369,7 @@
 						if(u !== k && u !== v){ p.disk(pre.join(''), v) }
 						tmp = p.split(tmp[2]);
 					}
-					LOG && console.log('parsed RAD in', +new Date - start); // keep this commented out in production!
+					LOG && console.log('parsed RAD in', +new Date - S); // keep this commented out in production!
 					//cb(err, p.disk);
 					map(q, p.ack);
 				};
@@ -376,7 +389,7 @@
 					if(p.err || p.not){ return cb(p.err, u, info) }
 					cb(u, p.disk, info);
 				}
-				var start; LOG && (start = +new Date); // keep this commented out in production!
+				var S; LOG && (S = +new Date); // keep this commented out in production!
 				if(raw){ return p.read(null, raw) }
 				opt.store.get(ename(file), p.read);
 			}

+ 1 - 1
public/lib/gundb/lib/radisk2.js

@@ -12,7 +12,7 @@
 		opt.pack = opt.pack || (opt.memory? (opt.memory * 1000 * 1000) : 1399000000) * 0.3; // max_old_space_size defaults to 1400 MB.
 		opt.until = opt.until || opt.wait || 250;
 		opt.batch = opt.batch || (10 * 1000);
-		opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB
+		opt.chunk = opt.chunk || (1024 * 1024 * 1); // 1MB
 		opt.code = opt.code || {};
 		opt.code.from = opt.code.from || '!';
 		//opt.jsonify = true; // TODO: REMOVE!!!!

+ 0 - 1
public/lib/gundb/lib/rindexed.js

@@ -3,7 +3,6 @@
   function Store(opt){
     opt = opt || {};
     opt.file = String(opt.file || 'radata');
-    opt.chunk = opt.chunk || (1024 * 1024); // 1MB
     var db = null, u;
 
     try{opt.indexedDB = opt.indexedDB || indexedDB}catch(e){}

+ 3 - 3
public/lib/gundb/lib/rs3.js

@@ -8,9 +8,9 @@ Gun.on('create', function(root){
 	this.to.next(root);
 	var opt = root.opt;
 	if(!opt.s3 && !process.env.AWS_S3_BUCKET){ return }
-	opt.batch = opt.batch || (1000 * 10);
-	opt.until = opt.until || (1000 * 3);
-	opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB
+	//opt.batch = opt.batch || (1000 * 10);
+	//opt.until = opt.until || (1000 * 3); // ignoring these now, cause perf > cost
+	//opt.chunk = opt.chunk || (1024 * 1024 * 10); // 10MB // when cost only cents
 
 	try{AWS = require('aws-sdk');
 	}catch(e){

+ 2 - 1
public/lib/gundb/lib/stats.js

@@ -41,9 +41,10 @@ Gun.on('opt', function(root){
 		stats.node.count = Object.keys(root.graph||{}).length;
 		var dam = root.opt.mesh;
 		if(dam){
-			stats.dam = {'in': {count: dam.hear.c, done: dam.hear.d}, 'out': {count: dam.say.c, done: dam.say.d}};
+			stats.dam = {'in': {count: dam.hear.c, done: dam.hear.d, long: dam.hear.long}, 'out': {count: dam.say.c, done: dam.say.d}};
 			dam.hear.c = dam.hear.d = dam.say.c = dam.say.d = 0; // reset
 			stats.peers.time = dam.bye.time || 0;
+			dam.hear.long = [];
 		}
 		var rad = root.opt.store; rad = rad && rad.stats;
 		if(rad){

+ 30 - 9
public/lib/gundb/lib/store.js

@@ -3,7 +3,7 @@ var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
 Gun.on('create', function(root){
     if(Gun.TESTING){ root.opt.file = 'radatatest' }
     this.to.next(root);
-    var opt = root.opt, u;
+    var opt = root.opt, empty = {}, u;
     if(false === opt.radisk){ return }
     var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk');
     var Radix = Radisk.Radix;
@@ -14,14 +14,23 @@ Gun.on('create', function(root){
     root.on('put', function(msg){
         this.to.next(msg);
         var id = msg['#'] || Gun.text.random(3), track = !msg['@'], acks = track? 0 : u; // only ack non-acks.
-        if(msg.rad && !track){ return } // don't save our own acks
-        var start = (+new Date); // STATS!
+        var got = (msg._||empty).rad, now = Gun.state();
+        var S = (+new Date); // STATS!
         Gun.graph.is(msg.put, null, function(val, key, node, soul){
+            if(!track && got){
+                var at = (root.next||empty)[soul];
+                if(!at){ return }
+                if(u !== got['.']){ at = (at.next||empty)[key] }
+                if(!at){ return }
+                at.rad = now;
+                return;
+            }
             if(track){ ++acks }
             //console.log('put:', soul, key, val);
             val = Radisk.encode(val, null, esc)+'>'+Radisk.encode(Gun.state.is(node, key), null, esc);
             rad(soul+esc+key, val, (track? ack : u));
         });
+        //console.log(+new Date - S, 'put loop');
         function ack(err, ok){
             acks--;
             if(ack.err){ return }
@@ -31,11 +40,12 @@ Gun.on('create', function(root){
                 return;
             }
             if(acks){ return }
-            try{opt.store.stats.put.time[statp % 50] = (+new Date) - start; ++statp;
+            try{opt.store.stats.put.time[statp % 50] = (+new Date) - S; ++statp;
                 opt.store.stats.put.count++;
             }catch(e){} // STATS!
-            //console.log("PAT!", id);
+            //console.log(+new Date - S, 'put'); S = +new Date;
             root.on('in', {'@': id, ok: 1});
+            //console.log(+new Date - S, 'put sent');
         }
     });
  
@@ -67,12 +77,19 @@ Gun.on('create', function(root){
             o.limit = (tmp <= (o.pack || (1000 * 100)))? tmp : 1;
         }
         if(has['-'] || (soul||{})['-']){ o.reverse = true }
-        var start = (+new Date); // STATS! // console.log("GET!", id, JSON.stringify(key));
+        if(tmp = (root.next||empty)[soul]){
+            if(tmp && tmp.rad){ return }
+            if(o.atom){ tmp = (tmp.next||empty)[o.atom] }
+            if(tmp && tmp.rad){ return }
+        }
+        var S = (+new Date); // STATS!
         rad(key||'', function(err, data, o){
-            try{opt.store.stats.get.time[statg % 50] = (+new Date) - start; ++statg;
+            try{opt.store.stats.get.time[statg % 50] = (+new Date) - S; ++statg;
                 opt.store.stats.get.count++;
                 if(err){ opt.store.stats.get.err = err }
             }catch(e){} // STATS!
+            //if(u === data && o.chunks > 1){ return } // if we already sent a chunk, ignore ending empty responses. // this causes tests to fail.
+            //console.log(+new Date - S, 'got'); S = +new Date;
             if(data){
                 if(typeof data !== 'string'){
                     if(o.atom){
@@ -83,20 +100,24 @@ Gun.on('create', function(root){
                 }
                 if(!graph && data){ each(data, '') }
             }
-            root.on('in', {'@': id, put: graph, err: err? err : u, rad: Radix});
+            //console.log(+new Date - S, 'got prep'); S = +new Date;
+            root.on('in', {'@': id, put: graph, '%': o.more? 1 : u, err: err? err : u, _: each});
+            //console.log(+new Date - S, 'got sent');
         }, o);
+        //console.log(+new Date - S, 'get call');
         function each(val, has, a,b){
             if(!val){ return }
             has = (key+has).split(esc);
             var soul = has.slice(0,1)[0];
             has = has.slice(-1)[0];
             o.count = (o.count || 0) + val.length;
-            tmp = val.lastIndexOf('>');
+            var tmp = val.lastIndexOf('>');
             var state = Radisk.decode(val.slice(tmp+1), null, esc);
             val = Radisk.decode(val.slice(0,tmp), null, esc);
             (graph = graph || {})[soul] = Gun.state.ify(graph[soul], has, state, val, soul);
             if(o.limit && o.limit <= o.count){ return true }
         }
+        each.rad = get;
     });
     opt.store.stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // STATS!
     var statg = 0, statp = 0; // STATS!

+ 2 - 3
public/lib/gundb/sea/array.js

@@ -1,6 +1,5 @@
-    function btoa(b) {
-      return new Buffer(b).toString('base64');
-    };
+
+    require('./base64');
     // This is Array extended to have .toString(['utf8'|'hex'|'base64'])
     function SeaArray() {}
     Object.assign(SeaArray, { from: Array.from })

+ 7 - 0
public/lib/gundb/sea/base64.js

@@ -0,0 +1,7 @@
+
+    if(typeof global !== "undefined"){
+      var g = global;
+      g.btoa = function (data) { return Buffer.from(data, "binary").toString("base64"); };
+      g.atob = function (data) { return Buffer.from(data, "base64").toString("binary"); };
+    }
+  

+ 2 - 3
public/lib/gundb/sea/buffer.js

@@ -1,6 +1,5 @@
-    function atob(a) {
-      return new Buffer(a, 'base64').toString('binary');
-    };
+
+    require('./base64');
     // This is Buffer implementation used in SEA. Functionality is mostly
     // compatible with NodeJS 'safe-buffer' and is used for encoding conversions
     // between binary and 'hex' | 'utf8' | 'base64'

+ 2 - 2
public/lib/gundb/sea/secret.js

@@ -13,7 +13,7 @@
       var epriv = pair.epriv;
       var ecdhSubtle = shim.ossl || shim.subtle;
       var pubKeyData = keysToEcdhJwk(pub);
-      var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh);
+      var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },S.ecdh); // Thanks to @sirpy !
       var privKeyData = keysToEcdhJwk(epub, epriv);
       var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => {
         // privateKey scope doesn't leak out from here!
@@ -47,4 +47,4 @@
     }
 
     module.exports = SEA.secret;
-  
+  

+ 1 - 1
public/lib/gundb/sea/shim.js

@@ -22,7 +22,7 @@
         random: (len) => Buffer.from(crypto.randomBytes(len))
       });
       //try{
-        const { Crypto: WebCrypto } = USE('@peculiar/webcrypto', 1);
+        const { Crypto: WebCrypto } = require('@peculiar/webcrypto', 1);
         api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH
       //}catch(e){
         //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");

+ 137 - 6
public/lib/widgets.js

@@ -645,6 +645,56 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
 
         reflectorGUI() {
 
+            let luminaryGlobalHB = {
+                $cell: true,
+                _luminarySwitch: null,
+                $components: [
+                  {
+                    $type: "p",
+                    class: "mdc-typography--headline5",
+                    $text: "Use Global Heartbeat"
+                  },
+                  {
+                    $type: 'p'
+                  },
+                  _app.widgets.switch({
+                    'id': 'forceLuminary',
+                    'init': function () {
+                      this._switch = new mdc.switchControl.MDCSwitch(this);
+                      let config = localStorage.getItem('lcs_config');
+                      this._switch.checked = JSON.parse(config).luminaryGlobalHB;
+                      
+                     // this._replaceSwitch = this._switch;
+                      
+                    },
+                    'onchange': function (e) {
+    
+                        if (this._switch) {
+                            let chkAttr = this._switch.checked;//this.getAttribute('checked');
+                            if (chkAttr) {
+                                let config = JSON.parse(localStorage.getItem('lcs_config'));
+                                config.luminaryGlobalHB = true;
+                                localStorage.setItem('lcs_config', JSON.stringify(config));
+                                //this._switch.checked = false;
+                            } else {
+                                let config = JSON.parse(localStorage.getItem('lcs_config'));
+                                config.luminaryGlobalHB = false;
+                                localStorage.setItem('lcs_config', JSON.stringify(config));
+                            }
+                        }
+                    }
+                  }
+                  ),
+                  {
+                    $type: 'label',
+                    for: 'input-forceLuminary',
+                    $text: 'On / Off'
+                  }
+    
+                ]
+              }
+
+
             let reflectorGUI =
             {
                 $type: "div",
@@ -655,6 +705,10 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                 _dbHost: null,
                 _refHostField: null,
                 _dbHostField: null,
+                _lpath: null,
+                _lpathField: null,
+                _hbpath: null,
+                _hbpathField: null,
                 _initData: function () {
                     this._reflectorHost = '';
                     this._dbHost = '';
@@ -667,6 +721,12 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                     if (config.dbhost) {
                         this._dbHost =config.dbhost
                     }
+                    if (config.luminaryPath) {
+                        this._lpath = config.luminaryPath
+                    }
+                    if (config.luminaryGlobalHBPath) {
+                        this._hbpath = config.luminaryGlobalHBPath
+                    }
                 },
                 $init: function () {
                     this._initData();
@@ -685,7 +745,39 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                                     {
                                         $type: "h4",
                                         class: "mdc-typography--headline4",
-                                        $text: "Connection settings"
+                                        $text: "Gun DB settings"
+                                    }
+                                ]
+                                },
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                        {
+                                            $type: "span",
+                                            class: "mdc-typography--headline5",
+                                            $text: "DB Host: "
+                                        },
+                                        window._app.widgets.inputTextFieldOutlined({
+                                            "id": 'dbhostInput',
+                                            "label": "DB Host",
+                                            "value": this._dbHost,
+                                            "type": "text",
+                                            "init": function() {
+                                                this._dbHostField = new mdc.textField.MDCTextField(this);
+                                            },
+                                            "style": 'width: 400px;'
+                                        }),
+                                    ]
+                                },
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                    {
+                                        $type: "h4",
+                                        class: "mdc-typography--headline4",
+                                        $text: "Reflector settings"
                                     }
                                 ]
                                 },
@@ -710,6 +802,17 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                                         }),
                                     ]
                                 },
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                    {
+                                        $type: "h4",
+                                        class: "mdc-typography--headline4",
+                                        $text: "Krestianstvo Luminary settings (experimental)"
+                                    }
+                                ]
+                                },
                                 {
                                     $type: "div",
                                     class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
@@ -717,20 +820,46 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                                         {
                                             $type: "span",
                                             class: "mdc-typography--headline5",
-                                            $text: "DB Host: "
+                                            $text: "Luminary Path: "
                                         },
                                         window._app.widgets.inputTextFieldOutlined({
-                                            "id": 'dbhostInput',
-                                            "label": "DB Host",
-                                            "value": this._dbHost,
+                                            "id": 'lpathInput',
+                                            "label": "Luminary Path",
+                                            "value": this._lpath,
                                             "type": "text",
                                             "init": function() {
-                                                this._dbHostField = new mdc.textField.MDCTextField(this);
+                                                this._lpathField = new mdc.textField.MDCTextField(this);
                                             },
                                             "style": 'width: 400px;'
                                         }),
                                     ]
                                 },
+                                {
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                        {
+                                            $type: "span",
+                                            class: "mdc-typography--headline5",
+                                            $text: "Global Heartbeat Path: "
+                                        },
+                                        window._app.widgets.inputTextFieldOutlined({
+                                            "id": 'hbpathInput',
+                                            "label": "Global Heartbeat Path",
+                                            "value": this._hbpath,
+                                            "type": "text",
+                                            "init": function() {
+                                                this._hbpathField = new mdc.textField.MDCTextField(this);
+                                            },
+                                            "style": 'width: 400px;'
+                                        }),
+                                    ]
+                                },
+                               { 
+                                $type: "div",
+                                class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                $components: [luminaryGlobalHB ]
+                               },
                                 {
                                     $type: "div",
                                     class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
@@ -745,6 +874,8 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
         
                                                     config.reflector = this._refHostField.value;
                                                     config.dbhost = this._dbHostField.value;
+                                                    config.luminaryPath = this._lpathField.value;
+                                                    config.luminaryGlobalHBPath = this._hbpathField.value;
         
                                                     localStorage.setItem('lcs_config', JSON.stringify(config));
                                                     window.location.reload(true);

+ 506 - 0
public/luminary.js

@@ -0,0 +1,506 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2019 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+*/
+
+import { Helpers } from '/helpers.js';
+
+class Luminary {
+    constructor() {
+        console.log("luminary constructor");
+        this.helpers = new Helpers;
+        this.info = {};
+        this.pendingList = [];
+        this.status = { pending: true, initialized: false, trials: 3 };
+        this.clients = {};
+        this.heartbeat = {}
+        this.clientID = undefined;
+        this.namespace = undefined;
+    }
+
+    unsubscribeFromHeartbeat() {
+        //TODO
+    }
+
+    subscribeOnHeartbeat(heartbeat) {
+
+        let self = this;
+
+        heartbeat.on(resp => {
+
+            var res = Gun.obj.copy(resp);
+            if (res.tick) {
+
+                if (self.start_time) {
+
+                    let currentTick = Gun.state.is(res, 'tick');
+                    self.heartbeat.lastTick = currentTick;
+
+                    let msg = self.stamp(res);
+
+                    if (!self.status.pending) {
+                        self.onMessage(msg)
+                    } else {
+                        self.pendingList.push(msg);
+                    }
+                }
+            }
+        })
+
+    }
+
+    subscribeOnMessages() {
+
+        let self = this;
+        let instance = _LCSDB.get(this.namespace);
+
+        instance.get('message').on(resp => {
+            var res = Gun.obj.copy(resp);
+            if (res.tick) {
+                if (self.start_time) {
+                    let msg = self.stamp(res);
+
+                    if (msg.explicit) {
+                        if (msg.explicit == vwf.moniker_) {
+                            self.onMessage(msg);
+
+                            if (msg.action == 'setState') {
+                                if (self.status.pending) {
+                                    self.distributePendingMessages();
+                                    self.status.pending = false;
+                                }
+                            }
+                            console.log(res);
+                        }
+
+                    } else if (!self.status.pending) {
+                        self.onMessage(msg);
+                    } else {
+                        self.pendingList.push(msg);
+                    }
+                }
+            }
+        })
+    }
+
+    stamp(source) {
+
+        let message = JSON.parse(source.tick);
+
+        // if(message.sender){
+        //     console.log("HEARTBEAT FROM: " + message.sender);
+        // }
+
+        message.state = Gun.state.is(source, 'tick');
+        message.start_time = this.start_time; //Gun.state.is(source, 'start_time');
+        message.rate = this.rate; //source.rate;
+
+        var time = ((message.state - message.start_time) * message.rate) / 1000;
+
+        if (message.action == 'getState') {
+            console.log('GET STATE msg!!!');
+        }
+
+        if (message.action == 'setState') {
+            time = ((this.setStateTime - message.start_time) * message.rate) / 1000;
+        }
+
+        message.time = Number(time);
+        message.origin = "reflector";
+
+        return message
+    }
+
+    stampExternalMessage(msg) {
+
+        let message = Object.assign({}, msg);
+        message.client = this.clientID;
+
+        let instance = _LCSDB.get(this.namespace)//_LCSDB.get(meta.namespace);
+
+        if (message.result === undefined) {
+
+            instance.get('message').get('tick').put(JSON.stringify(message));
+
+        } else if (message.action == "getState") {
+
+            let state = message.result;//JSON.stringify(message.result);
+            let toClient = message.parameters[0];
+
+            let newMsg =
+                JSON.stringify({
+                    action: "setState",
+                    parameters: [state],
+                    time: 'tick', //self.setStateTime,
+                    explicit: toClient
+                })
+
+            instance.get('message')
+                .get('tick')
+                .put(newMsg)
+
+        } else if (message.action === "execute") {
+            console.log("!!!! execute ", message)
+        }
+
+    }
+
+    onMessage(message) {
+
+        try {
+
+            var fields = Object.assign({}, message);
+
+            vwf.private.queue.insert(fields, !fields.action); // may invoke dispatch(), so call last before returning to the host
+
+        } catch (e) {
+
+            vwf.logger.warn(fields.action, fields.node, fields.member, fields.parameters,
+                "exception performing action:", require("vwf/utility").exceptionMessage(e));
+
+        }
+    }
+
+
+    async connect(path) {
+
+        let self = this;
+
+        let objForQuery = this.helpers.reduceSaveObject(path);
+
+        this.clientID = Gun.text.random();
+        this.namespace = this.helpers.GetNamespace(path.path);
+
+        //vwf.moniker_ = clientID;  
+
+        this.info = {
+            pathname: window.location.pathname.slice(1,
+                window.location.pathname.lastIndexOf("/")),
+            appRoot: "./public",
+            path: JSON.stringify(objForQuery), //JSON.stringify(path)
+            namespace: this.namespace,
+        }
+
+        //set an instance with namespace
+        let luminaryPath = _app.luminaryPath;
+        let lum = _LCSDB.get(luminaryPath);
+
+        let instance = _LCSDB.get(this.namespace);
+
+        instance.not(function (res) {
+            instance
+                .put(self.info)
+                .put({
+                    'start_time': 'start_time',
+                    'rate': 1
+                });
+            lum.get('instances').set(instance);
+            self.status.initialized = "first";
+        });
+
+        await instance.once(res => {
+            self.start_time = Gun.state.is(res, 'start_time');
+            self.rate = res.rate;
+        }).promOnce();
+
+
+        let client = _LCSDB.get(self.clientID).put({ id: self.clientID, instance: self.namespace, user: path.user }).once(res => {
+            self.setStateTime = Gun.state.is(res, 'id');
+            setInterval(function () {
+                client.get('live').put('tick');
+            }, 500);
+        });
+        instance.get('clients').set(client);
+        lum.get('allclients').set(client);
+
+        instance.get('clients').map().on(res => {
+            if (res) {
+                if (res.id && res.live) {
+
+                    let clientTime = Gun.state.is(res, 'live');
+                    //let now = Gun.time.is();
+
+                    //console.log("NEW CLIENT LIVE : " + res.id);
+                    if (!self.clients[res.id]) {
+                        self.clients[res.id] = {
+                            live: clientTime,
+                            old: clientTime
+                        }
+                    } else {
+                        self.clients[res.id].old = self.clients[res.id].live;
+                        self.clients[res.id].live = clientTime
+                    }
+
+                    if (self.status.initialized == "first"  && self.setStateTime) {
+
+                        self.status.initialized = true;
+                        instance
+                            .put({
+                                'start_time': 'start_time',
+                                'rate': 1
+                            }).once(res => {
+                                self.start_time = Gun.state.is(res, 'start_time');
+                                self.rate = res.rate;
+
+                                if (!_app.isLuminaryGlobalHB) {
+                                    let tickMsg = {
+                                        parameters: [],
+                                        time: 'tick', //hb
+                                        sender: self.clientID
+                                    };
+                                    instance.get('heartbeat').get('tick').put(JSON.stringify(tickMsg));
+                                    self.initHeartBeat();
+                                }
+
+                                self.initFirst(res);
+                                self.initDeleteClient();
+
+
+
+                            });
+
+                        let noty = new Noty({
+                            text: "FIRST CLIENT",
+                            timeout: 1000,
+                            theme: 'mint',
+                            layout: 'bottomRight',
+                            type: 'success'
+                        });
+
+                        noty.show();
+                    } else if (!self.status.initialized && self.setStateTime) {
+
+                        if (res.id == self.clientID && self.status.trials > 0) {
+                            self.status.trials = self.status.trials - 1;
+                            console.log("CONNECTION TRIALS FOR: " + res.id + ' - ' + self.status.trials);
+                        } else if (res.id !== self.clientID && self.clients[res.id].live - self.clients[res.id].old < 1000) {
+                            console.log("REQUEST STATE FROM: " + res.id);
+
+                            self.status.initialized = true;
+
+                            if (!_app.isLuminaryGlobalHB) {
+                                self.initHeartBeat();
+                            }
+
+                            self.initOtherClient(res);
+                            self.initDeleteClient();
+
+
+                            let noty = new Noty({
+                                text: "CONNECTING TO EXISTED CLIENT...",
+                                timeout: 1000,
+                                theme: 'mint',
+                                layout: 'bottomRight',
+                                type: 'success'
+                            });
+
+                            noty.show();
+
+
+                        } else if (res.id == self.clientID && self.status.trials == 0) {
+                            console.log("INITIALIZE WORLD FOR: " + res.id);
+
+                            //request for the new instance 
+                            let path = JSON.parse(self.info.path);
+                            window.location.pathname = path.user + path.path["public_path"];
+                        }
+                    }
+                }
+            }
+        })
+
+        return path
+    }
+
+    distributePendingMessages() {
+
+        let self = this;
+
+        if (self.pendingList.length > 0) {
+            console.log("!!!! getPendingMessages");
+            let cloneList = [...self.pendingList];
+            cloneList.forEach(el => {
+                self.onMessage(el);
+            })
+            self.pendingList = [];
+            //_app.status.pending = false;
+        }
+    }
+
+
+    clientsMessage() {
+
+        let self = this;
+
+        let clientDescriptor = { extends: "http://vwf.example.com/client.vwf" };
+        let clientNodeMessage =
+        {
+            action: "createChild",
+            parameters: ["http://vwf.example.com/clients.vwf", self.clientID, clientDescriptor],
+            time: 'tick'
+        }
+
+        return clientNodeMessage
+
+    }
+
+    initFirst(ack) {
+
+        let self = this;
+        let instance = _LCSDB.get(self.namespace);
+
+        let clientMsg =
+            JSON.stringify({
+                action: "createNode",
+                parameters: ["http://vwf.example.com/clients.vwf"],
+                time: 'tick',
+                explicit: self.clientID
+            })
+
+        let processedURL = JSON.parse(self.info.path).path;
+
+        let appMsg =
+            JSON.stringify({
+                action: "createNode",
+                parameters: [
+                    (processedURL.public_path === "/" ? "" : processedURL.public_path) + "/" + processedURL.application,
+                    "application"
+                ],
+                time: 'tick',
+                explicit: self.clientID
+            })
+
+
+        instance.get('message')
+            .get('tick')
+            .put(clientMsg);
+
+        instance.get('message')
+            .get('tick')
+            .put(appMsg, res => {
+
+                self.status.pending = false;
+
+                let clientsMessage = self.clientsMessage();
+                instance.get('message')
+                    .get('tick')
+                    .put(JSON.stringify(clientsMessage), res => {
+                        console.log("CREATE CLIENT: - " + res);
+                    })
+            });
+
+    }
+
+
+    initOtherClient(ack) {
+
+        console.log('new other client');
+
+        let self = this;
+        let instance = _LCSDB.get(self.namespace);
+
+        let masterID = ack.id;
+
+        let msg =
+            JSON.stringify({
+                action: "getState",
+                respond: true,
+                time: 'tick',
+                explicit: masterID,
+                parameters: [self.clientID]
+            })
+
+        instance.get('message')
+            .get('tick')
+            .put(msg);
+
+        let clientsMessage = self.clientsMessage();
+
+        instance.get('message')
+            .get('tick').put(JSON.stringify(clientsMessage), res=>{
+              
+                    console.log("CREATE CLIENT: - " + res);
+            });
+
+    }
+
+
+    initHeartBeat() {
+
+        let self = this;
+        let instance = _LCSDB.get(self.namespace);
+
+        setInterval(function () {
+
+            let message = {
+                parameters: [],
+                time: 'tick', //hb
+                sender: self.clientID
+
+            };
+
+            instance.get('heartbeat').get('tick').once(data => {
+                if (data) {
+                    let res = JSON.parse(data);
+                    if (res.sender) {
+
+                        let now = Gun.time.is();
+                        let diff = now - self.heartbeat.lastTick;
+                        if ((Object.keys(self.clients).length == 1)
+                            || (res.sender == self.clientID && diff < 1000)
+                            || (res.sender !== self.clientID && diff > 1000)) {
+
+                            //console.log("TICK FROM" + self.clientID);    
+                            instance.get('heartbeat').get('tick').put(JSON.stringify(message), function (ack) {
+                                if (ack.err) {
+                                    console.log('ERROR: ' + ack.err)
+                                }
+                            });
+                        }
+                    }
+                }
+            })
+        }, 50);
+    }
+
+
+    initDeleteClient() {
+
+        let self = this;
+        let instance = _LCSDB.get(self.namespace);
+
+        setInterval(function () {
+            Object.keys(self.clients).forEach(el => {
+                let current = Gun.time.is();
+
+                if (el !== self.clientID) {
+                    if (current - self.clients[el].live > 10000) {
+                        console.log("CLIENT DISCONECTED : " + el);
+
+                        let clientDeleteMessage =
+                        {
+                            action: "deleteChild",
+                            parameters: ["http://vwf.example.com/clients.vwf", el],
+                            time: 'tick'
+                        };
+
+                        instance.get('message')
+                            .get('tick').once(res => {
+                                instance.get('message')
+                                    .get('tick')
+                                    .put(JSON.stringify(clientDeleteMessage), res => {
+
+                                        instance.get('clients').get(el).put(null);
+                                        delete self.clients[el];
+                                    })
+                            })
+                    }
+                }
+            })
+        }, 5000);
+    }
+
+
+}
+
+export { Luminary }

+ 233 - 0
public/reflector-client.js

@@ -0,0 +1,233 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2019 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
+*/
+
+import { Helpers } from '/helpers.js';
+
+class ReflectorClient {
+    constructor() {
+        console.log("reflector client constructor");
+        this.helpers = new Helpers;
+        this.socket = undefined;
+    }
+
+    connect(component_uri_or_json_or_object, path){
+
+        function isSocketIO07() {
+            //return ( parseFloat( io.version ) >= 0.7 );
+            return true
+        } 
+
+        try {
+
+            let objToRef = this.helpers.reduceSaveObject(path);
+
+            var options = {
+
+                // The socket is relative to the application path.
+
+                // resource: window.location.pathname.slice( 1,
+                //      window.location.pathname.lastIndexOf("/") ),
+
+                query: {
+                    pathname: window.location.pathname.slice( 1,
+                        window.location.pathname.lastIndexOf("/") ),
+                    appRoot: "./public",
+                    path: JSON.stringify(objToRef)//JSON.stringify(path)
+                  },
+                // query: 'pathname=' + window.location.pathname.slice( 1,
+                //     window.location.pathname.lastIndexOf("/") ),
+
+                // Use a secure connection when the application comes from https.
+                
+                secure: window.location.protocol === "https:",
+
+                // Don't attempt to reestablish lost connections. The client reloads after a
+                // disconnection to recreate the application from scratch.
+
+                //reconnect: false,
+                reconnection: false,
+                upgrade: false,
+                transports: ['websocket']
+
+            };
+
+            if ( isSocketIO07() ) {
+
+                //window.location.host
+        var host = window._app.reflectorHost; //localStorage.getItem('lcs_reflector'); 
+        //if(!host) host = 'http://localhost:3002'; //window.location.origin;       
+        this.socket = io.connect( host, options );
+                
+
+            } else {  // Ruby Server -- only supports socket.io 0.6
+
+                io.util.merge( options, {
+
+                    // For socket.io 0.6, specify the port since the default isn't correct when
+                    // using https.
+
+                    port: window.location.port ||
+                        ( window.location.protocol === "https:" ? 443 : 80 ),
+
+                    // The ruby socket.io server only supports WebSockets. Don't try the others.
+
+                    transports: [
+                        'websocket',
+                    ],
+
+                    // Increase the timeout because of starvation while loading the scene. The
+                    // server timeout must also be increased. (For socket.io 0.7+, the client
+                    // timeout is controlled by the server.)
+
+                    transportOptions: {
+                        "websocket": { timeout: 90000 },
+                    },
+
+                } );
+
+             this.socket = io.connect( undefined, options );
+            }
+
+        } catch ( e ) {
+
+            // If a connection to the reflector is not available, then run in single-user mode.
+            // Messages intended for the reflector will loop directly back to us in this case.
+            // Start a timer to monitor the incoming queue and dispatch the messages as though
+            // they were received from the server.
+
+            vwf.dispatch();
+
+            setInterval( function() {
+
+                var fields = {
+                    time: vwf.now + 0.010, // TODO: there will be a slight skew here since the callback intervals won't be exactly 10 ms; increment using the actual delta time; also, support play/pause/stop and different playback rates as with connected mode.
+                    origin: "reflector",
+                };
+
+                vwf.private.queue.insert( fields, true ); // may invoke dispatch(), so call last before returning to the host
+
+            }, 10 );
+
+        }
+
+        if ( this.socket ) {
+
+            this.socket.on('connect_error', function(err) {
+                console.log(err);
+                var errDiv = document.createElement("div");
+                errDiv.innerHTML = "<div class='vwf-err' style='z-index: 10; position: absolute; top: 80px; right: 50px'>Connection error!" + err + "</div>";
+                document.querySelector('body').appendChild(errDiv);
+                
+            });
+
+            this.socket.on( "connect", function() {
+
+                vwf.logger.infox( "-socket", "connected" );
+
+                if ( isSocketIO07() ) {
+                    vwf.moniker_ = this.id;                        
+                } else {  //Ruby Server
+                    vwf.moniker_ = this.transport.sessionid;
+                }
+
+            } );
+
+            // Configure a handler to receive messages from the server.
+            
+            // Note that this example code doesn't implement a robust parser capable of handling
+            // arbitrary text and that the messages should be placed in a dedicated priority
+            // queue for best performance rather than resorting the queue as each message
+            // arrives. Additionally, overlapping messages may cause actions to be performed out
+            // of order in some cases if messages are not processed on a single thread.
+
+            this.socket.on( "message", function( message ) {
+
+                // vwf.logger.debugx( "-socket", "message", message );
+
+                try {
+
+                    if ( isSocketIO07() ) {
+                        var fields = message;
+                    } else { // Ruby Server - Unpack the arguements
+                        var fields = JSON.parse( message );
+                    }
+
+                    fields.time = Number( fields.time );
+                    // TODO: other message validation (check node id, others?)
+
+                    fields.origin = "reflector";
+
+                    // Update the queue.  Messages in the queue are ordered by time, then by order of arrival.
+                    // Time is only advanced if the message has no action, meaning it is a tick.
+
+                    vwf.private.queue.insert( fields, !fields.action ); // may invoke dispatch(), so call last before returning to the host
+
+                    // Each message from the server allows us to move time forward. Parse the
+                    // timestamp from the message and call dispatch() to execute all queued
+                    // actions through that time, including the message just received.
+                
+                    // The simulation may perform immediate actions at the current time or it
+                    // may post actions to the queue to be performed in the future. But we only
+                    // move time forward for items arriving in the queue from the reflector.
+
+                } catch ( e ) {
+
+                    vwf.logger.warn( fields.action, fields.node, fields.member, fields.parameters,
+                        "exception performing action:", require( "vwf/utility" ).exceptionMessage( e ) );
+
+                }
+
+            } );
+
+            this.socket.on( "disconnect", function() {
+
+                vwf.logger.infox( "-socket", "disconnected" );
+
+                // Reload to rejoin the application.
+
+                window.location = window.location.href;
+
+            } );
+
+            this.socket.on( "error", function() { 
+
+                //Overcome by compatibility.js websockets check
+                document.querySelector('body').innerHTML = "<div class='vwf-err'>WebSockets connections are currently being blocked. Please check your proxy server settings.</div>";
+                // jQuery('body').html("<div class='vwf-err'>WebSockets connections are currently being blocked. Please check your proxy server settings.</div>"); 
+
+            } );
+
+            if ( !isSocketIO07() ) {
+                // Start communication with the reflector. 
+
+                this.socket.connect();  // TODO: errors can occur here too, particularly if a local client contains the socket.io files but there is no server; do the loopback here instead of earlier in response to new io.Socket.
+            }
+
+        } else if ( component_uri_or_json_or_object ) {
+
+            // Load the application. The application is rooted in a single node constructed here
+            // as an instance of the component passed to initialize(). That component, its
+            // prototype(s), and its children, and their prototypes and children, flesh out the
+            // entire application.
+
+            // TODO: add note that this is only for a self-determined application; with socket, wait for reflection server to tell us.
+            // TODO: maybe depends on component_uri_or_json_or_object too; when to override and not connect to reflection server?
+
+            this.createNode( component_uri_or_json_or_object, "application" );
+
+        } else {  // TODO: also do this if component_uri_or_json_or_object was invalid and createNode() failed
+
+            // TODO: show a selection dialog
+
+        }
+
+    }
+
+
+}
+
+export { ReflectorClient }

+ 105 - 279
public/vwf.js

@@ -378,12 +378,9 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                     active: false 
                 },
 
-
+                { library: "vwf/view/document", active: true },
                 { library: "vwf/model/aframeComponent", active: true },
-
                 { library: "vwf/kernel/view", active: true },
-                { library: "vwf/view/document", active: true },
-
                 { library: "vwf/view/editor-new", active: false },
 
                 { library: "vwf/view/webrtc", 
@@ -443,10 +440,9 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                     { library: "vwf/model/object", active: true }
                 ],
                 view: [
+                    { library: "vwf/view/document", active: true },
                     { library: "vwf/view/aframe", active: true },
                     { library: "vwf/view/aframeComponent", active: true },
-
-                    { library: "vwf/view/document", active: true },
                     { library: "vwf/view/editor-new", active: false },
 
                      { library: "vwf/view/ohm", active: true },
@@ -491,7 +487,7 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
            
             let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);
 
-            userDB.get('worlds').get(path.slice(1)).get(dbPath).get('file').load(function(res) {
+            userDB.get('worlds').get(path.slice(1)).get(dbPath).get('file').once(function(res) {
                 
                 var conf = "";
 
@@ -599,7 +595,7 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
     
                     require( requireConfig, getActiveLibraries(requireArray, false), function( ready ) {
     
-                        ready( async function() {
+                        ready( function() {
     
                             // Merge any application configuration settings into the configuration
                             // object.
@@ -611,7 +607,7 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                             // accepts three parameters: a world specification, model configuration parameters,
                             // and view configuration parameters.
     
-                            await vwf.initialize(application, getActiveLibraries(initializers["model"], true), getActiveLibraries(initializers["view"], true), callback);
+                            vwf.initialize(application, getActiveLibraries(initializers["model"], true), getActiveLibraries(initializers["view"], true), callback);
     
                         } );
     
@@ -860,6 +856,7 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
 
             //await _app.getApplicationState();
             await _app.getApplicationState()
+                .then(res => {return _app.chooseConnection(res)})
                 .then(res => {self.ready( application, res)})
 
         };
@@ -874,225 +871,37 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
             // Connect to the reflector. This implementation uses the socket.io library, which
             // communicates using a channel back to the server that provided the client documents.
 
-            try {
-
-                let objToRef = Object.assign({}, path);
-
-                if(path.saveObject){
-                    if ( path.saveObject[ "queue" ] ) {
-                        if ( path.saveObject[ "queue" ][ "time" ] ) {
-                            objToRef.saveObject = {
-                                "init": true,
-                                "queue":{
-                                    "time": path.saveObject[ "queue" ][ "time" ]
-                                }
-                            }
-                        }
-                    }
-                }
-
-            
-              
-
-                var options = {
-
-                    // The socket is relative to the application path.
-
-                    // resource: window.location.pathname.slice( 1,
-                    //      window.location.pathname.lastIndexOf("/") ),
-
-                    query: {
-                        pathname: window.location.pathname.slice( 1,
-                            window.location.pathname.lastIndexOf("/") ),
-                        appRoot: "./public",
-                        path: JSON.stringify(objToRef)//JSON.stringify(path)
-                      },
-                    // query: 'pathname=' + window.location.pathname.slice( 1,
-                    //     window.location.pathname.lastIndexOf("/") ),
-
-                    // Use a secure connection when the application comes from https.
-                    
-                    secure: window.location.protocol === "https:",
-
-                    // Don't attempt to reestablish lost connections. The client reloads after a
-                    // disconnection to recreate the application from scratch.
-
-                    //reconnect: false,
-                    reconnection: false,
-                    upgrade: false,
-                    transports: ['websocket']
-
-                };
-
-                if ( isSocketIO07() ) {
-
-                    //window.location.host
-            var host = window._app.reflectorHost; //localStorage.getItem('lcs_reflector'); 
-            //if(!host) host = 'http://localhost:3002'; //window.location.origin;       
-            socket = io.connect( host, options );
-                    
-
-                } else {  // Ruby Server -- only supports socket.io 0.6
-
-                    io.util.merge( options, {
-
-                        // For socket.io 0.6, specify the port since the default isn't correct when
-                        // using https.
-
-                        port: window.location.port ||
-                            ( window.location.protocol === "https:" ? 443 : 80 ),
-
-                        // The ruby socket.io server only supports WebSockets. Don't try the others.
-
-                        transports: [
-                            'websocket',
-                        ],
-
-                        // Increase the timeout because of starvation while loading the scene. The
-                        // server timeout must also be increased. (For socket.io 0.7+, the client
-                        // timeout is controlled by the server.)
-
-                        transportOptions: {
-                            "websocket": { timeout: 90000 },
-                        },
-
-                    } );
-
-                 socket = io.connect( undefined, options );
-                }
-
-            } catch ( e ) {
-
-                // If a connection to the reflector is not available, then run in single-user mode.
-                // Messages intended for the reflector will loop directly back to us in this case.
-                // Start a timer to monitor the incoming queue and dispatch the messages as though
-                // they were received from the server.
-
-                this.dispatch();
-
-                setInterval( function() {
-
-                    var fields = {
-                        time: vwf.now + 0.010, // TODO: there will be a slight skew here since the callback intervals won't be exactly 10 ms; increment using the actual delta time; also, support play/pause/stop and different playback rates as with connected mode.
-                        origin: "reflector",
-                    };
-
-                    queue.insert( fields, true ); // may invoke dispatch(), so call last before returning to the host
-
-                }, 10 );
-
-            }
-
-            if ( socket ) {
-
-                socket.on('connect_error', function(err) {
-                    console.log(err);
-                    var errDiv = document.createElement("div");
-                    errDiv.innerHTML = "<div class='vwf-err' style='z-index: 10; position: absolute; top: 80px; right: 50px'>Connection error!" + err + "</div>";
-                    document.querySelector('body').appendChild(errDiv);
-                    
-                });
-
-                socket.on( "connect", function() {
-
-                    vwf.logger.infox( "-socket", "connected" );
-
-                    if ( isSocketIO07() ) {
-                        vwf.moniker_ = this.id;                        
-                    } else {  //Ruby Server
-                        vwf.moniker_ = this.transport.sessionid;
-                    }
+            if (_app.isLuminary){
+                //Use Luminary for connection
 
-                } );
+                vwf.namespace_ = _app.luminary.namespace;//_app.helpers.GetNamespace(path.path); //.split(".").join("_");
+                vwf.moniker_ = _app.luminary.clientID;
+                console.log('namespace: ' + vwf.namespace_, ' for client: ' + vwf.moniker_);
+    
+                //let heartbeat = _LCSDB.get('server').get('heartbeat'); 
+               var heartbeat = _LCSDB.get(vwf.namespace_).get('heartbeat');
+
+                if(_app.isLuminaryGlobalHB && _app.luminaryGlobalHBPath ) {
+                    let hbPath = _app.luminaryGlobalHBPath.split('/');
+                    var heartbeat = _LCSDB;
+                    hbPath.forEach(el=>{
+                        heartbeat = heartbeat.get(el);
+                    })
+                } 
 
-                // Configure a handler to receive messages from the server.
+                    _app.luminary.subscribeOnHeartbeat(heartbeat);
+                    _app.luminary.subscribeOnMessages();
                 
-                // Note that this example code doesn't implement a robust parser capable of handling
-                // arbitrary text and that the messages should be placed in a dedicated priority
-                // queue for best performance rather than resorting the queue as each message
-                // arrives. Additionally, overlapping messages may cause actions to be performed out
-                // of order in some cases if messages are not processed on a single thread.
-
-                socket.on( "message", function( message ) {
-
-                    // vwf.logger.debugx( "-socket", "message", message );
-
-                    try {
-
-                        if ( isSocketIO07() ) {
-                            var fields = message;
-                        } else { // Ruby Server - Unpack the arguements
-                            var fields = JSON.parse( message );
-                        }
-
-                        fields.time = Number( fields.time );
-                        // TODO: other message validation (check node id, others?)
-
-                        fields.origin = "reflector";
-
-                        // Update the queue.  Messages in the queue are ordered by time, then by order of arrival.
-                        // Time is only advanced if the message has no action, meaning it is a tick.
-
-                        queue.insert( fields, !fields.action ); // may invoke dispatch(), so call last before returning to the host
-
-                        // Each message from the server allows us to move time forward. Parse the
-                        // timestamp from the message and call dispatch() to execute all queued
-                        // actions through that time, including the message just received.
-                    
-                        // The simulation may perform immediate actions at the current time or it
-                        // may post actions to the queue to be performed in the future. But we only
-                        // move time forward for items arriving in the queue from the reflector.
-
-                    } catch ( e ) {
-
-                        vwf.logger.warn( fields.action, fields.node, fields.member, fields.parameters,
-                            "exception performing action:", require( "vwf/utility" ).exceptionMessage( e ) );
-
-                    }
-
-                } );
-
-                socket.on( "disconnect", function() {
-
-                    vwf.logger.infox( "-socket", "disconnected" );
-
-                    // Reload to rejoin the application.
-
-                    window.location = window.location.href;
-
-                } );
-
-                socket.on( "error", function() { 
-
-                    //Overcome by compatibility.js websockets check
-                    document.querySelector('body').innerHTML = "<div class='vwf-err'>WebSockets connections are currently being blocked. Please check your proxy server settings.</div>";
-                    // jQuery('body').html("<div class='vwf-err'>WebSockets connections are currently being blocked. Please check your proxy server settings.</div>"); 
 
-                } );
-
-                if ( !isSocketIO07() ) {
-                    // Start communication with the reflector. 
-
-                    socket.connect();  // TODO: errors can occur here too, particularly if a local client contains the socket.io files but there is no server; do the loopback here instead of earlier in response to new io.Socket.
-                }
 
-            } else if ( component_uri_or_json_or_object ) {
-
-                // Load the application. The application is rooted in a single node constructed here
-                // as an instance of the component passed to initialize(). That component, its
-                // prototype(s), and its children, and their prototypes and children, flesh out the
-                // entire application.
-
-                // TODO: add note that this is only for a self-determined application; with socket, wait for reflection server to tell us.
-                // TODO: maybe depends on component_uri_or_json_or_object too; when to override and not connect to reflection server?
-
-                this.createNode( component_uri_or_json_or_object, "application" );
+                
 
-            } else {  // TODO: also do this if component_uri_or_json_or_object was invalid and createNode() failed
+            } else {
+                //Use Reflector for connection
 
-                // TODO: show a selection dialog
+                _app.reflectorClient.connect(component_uri_or_json_or_object, path);
 
-            }
+        }
 
         };
 
@@ -1152,11 +961,15 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                 // callback: callback_async,  // TODO: provisionally add fields to queue (or a holding queue) then execute callback when received back from reflector
             };
 
-            if ( socket ) {
+            if(_app.isLuminary){
+
+                _app.luminary.stampExternalMessage(fields);
+
+            } else if ( _app.reflectorClient.socket ) {
     
                 // Send the message.
                 var message = JSON.stringify( fields );
-                socket.send( message );
+                _app.reflectorClient.socket.send( message );
  
             } else {
                 
@@ -1192,15 +1005,19 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                 action: actionName,
                 member: memberName,
                 parameters: require( "vwf/utility" ).transform( parameters, require( "vwf/utility" ).transforms.transit ),
-                result: require( "vwf/utility" ).transform( result, require( "vwf/utility" ).transforms.transit ),
+                result: require( "vwf/utility" ).transform(result, require( "vwf/utility").transforms.transit)
             };
 
-            if ( socket ) {
+            if(_app.isLuminary){
+
+                _app.luminary.stampExternalMessage(fields);
+
+            } else if ( _app.reflectorClient.socket ) {
 
                 // Send the message.
 
                 var message = JSON.stringify( fields );
-                socket.send( message );
+                _app.reflectorClient.socket.send( message );
 
             } else {
 
@@ -1671,7 +1488,7 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
                 // If `nodeComponent` is a URI, load the descriptor. `nodeComponent` may be a URI, a
                 // descriptor or an ID here.
 
-                function( series_callback_async /* ( err, results ) */ ) {
+               function( series_callback_async /* ( err, results ) */ ) {
 
                     if ( componentIsURI( nodeComponent ) ) { // URI  // TODO: allow non-vwf URIs (models, images, etc.) to pass through to stage 2 and pass directly to createChild()
 
@@ -1688,7 +1505,7 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
 
                             components[nodeURI] = []; // [] => array of callbacks while loading => true
 
-                            loadComponent( nodeURI, undefined, function( nodeDescriptor ) /* async */ {
+                           loadComponent( nodeURI, undefined, function( nodeDescriptor ) /* async */ {
                                 nodeComponent = nodeDescriptor;
                                 series_callback_async(undefined, undefined);
                             }, function( errorMessage ) {
@@ -1744,7 +1561,7 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
 
                         var prototypeURI = require( "vwf/utility" ).resolveURI( nodeComponent.includes, nodeURI || baseURI );
 
-                        loadComponent( prototypeURI, undefined, function( prototypeDescriptor ) /* async */ {
+                      loadComponent( prototypeURI, undefined, function( prototypeDescriptor ) /* async */ {
                             prototypeDescriptor = resolvedDescriptor( prototypeDescriptor, prototypeURI );
                             nodeComponent = mergeDescriptors( nodeComponent, prototypeDescriptor ); // modifies prototypeDescriptor
                             series_callback_async( undefined, undefined );
@@ -2414,7 +2231,7 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
 
             async.series( [
 
-                function( series_callback_async /* ( err, results ) */ ) {
+              function( series_callback_async /* ( err, results ) */ ) {
 
                     // Rudimentary support for `{ includes: prototype }`, which absorbs a prototype
                     // descriptor into the child descriptor before creating the child. See the notes
@@ -2445,8 +2262,8 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
 
                                 queue.suspend( "before beginning " + childID ); // suspend the queue
 
-                                async.nextTick( async function() {
-                                   await series_callback_async( undefined, undefined );
+                                async.nextTick( function() {
+                                   series_callback_async( undefined, undefined );
                                     queue.resume( "after beginning " + childID ); // resume the queue; may invoke dispatch(), so call last before returning to the host
                                 } );
 
@@ -2465,8 +2282,8 @@ Copyright (c) 2014-2018 Nikolai Suslov and the Krestianstvo.org project contribu
 
                         queue.suspend( "before beginning " + childID ); // suspend the queue
 
-                        async.nextTick( async function() {
-                            await series_callback_async( undefined, undefined );
+                        async.nextTick( function() {
+                            series_callback_async( undefined, undefined );
                             queue.resume( "after beginning " + childID ); // resume the queue; may invoke dispatch(), so call last before returning to the host
                         } );
 
@@ -2598,11 +2415,11 @@ if ( ! childComponent.source ) {
                                 driver_ready = false;
                             }
 
-                            async function resume( err ) {
+                            function resume( err ) {
                                 window.clearTimeout( timeoutID );
                                 driver_ready = true;
                                 err && vwf.logger.warnx( "createChild", nodeID, childName + ":", err );
-                                await each_callback_async( err ); // resume createChild()
+                                each_callback_async( err ); // resume createChild()
                                 queue.resume( "after loading " + childComponent.source + " for " + childID + " in creatingNode" ); // resume the queue; may invoke dispatch(), so call last before returning to the host
                             }
 
@@ -2623,7 +2440,7 @@ if ( ! childComponent.source ) {
                     // Call createdNode() on each view. The view is being notified of a node that has
                     // been constructed.
 
-                    async.forEachSeries( vwf.views, function( view, each_callback_async /* ( err ) */ ) {
+                    async.forEach( vwf.views, function( view, each_callback_async /* ( err ) */ ) {
 
                         var driver_ready = true;
                         var timeoutID;
@@ -2643,11 +2460,11 @@ if ( ! childComponent.source ) {
                                 driver_ready = false;
                             }
 
-                            async function resume( err ) {
+                            function resume( err ) {
                                 window.clearTimeout( timeoutID );
                                 driver_ready = true;
                                 err && vwf.logger.warnx( "createChild", nodeID, childName + ":", err );
-                                await each_callback_async( err ); // resume createChild()
+                                each_callback_async( err ); // resume createChild()
                                 queue.resume( "after loading " + childComponent.source + " for " + childID + " in createdNode" ); // resume the queue; may invoke dispatch(), so call last before returning to the host
                             }
 
@@ -2935,8 +2752,8 @@ if ( ! childComponent.source ) {
 
                     queue.suspend( "before completing " + childID ); // suspend the queue
 
-                    async.nextTick( async function() {
-                        await callback_async( childID );
+                    async.nextTick( function() {
+                        callback_async( childID );
                         queue.resume( "after completing " + childID ); // resume the queue; may invoke dispatch(), so call last before returning to the host
                     } );
 
@@ -3240,8 +3057,8 @@ if ( ! childComponent.source ) {
         this.setProperty = function( nodeID, propertyName, propertyValue ) {
 
             this.logger.debuggx( "setProperty", function() {
-                return [ nodeID, propertyName, JSON.stringify( loggableValue( propertyValue ) ) ];
-            } );
+                 return [ nodeID, propertyName, JSON.stringify( loggableValue( propertyValue ) ) ];
+             } );
 
             var node = nodes.existing[nodeID];
 
@@ -3433,7 +3250,7 @@ if ( ! childComponent.source ) {
 
         this.getProperty = function( nodeID, propertyName, ignorePrototype ) {
 
-            this.logger.debuggx( "getProperty", nodeID, propertyName );
+             this.logger.debuggx( "getProperty", nodeID, propertyName );
 
             var propertyValue = undefined;
 
@@ -3748,9 +3565,9 @@ if ( ! childComponent.source ) {
 
         this.callMethod = function( nodeID, methodName, methodParameters ) {
 
-            this.logger.debuggx( "callMethod", function() {
-                return [ nodeID, methodName, JSON.stringify( loggableValues( methodParameters ) ) ];
-            } );
+             this.logger.debuggx( "callMethod", function() {
+                 return [ nodeID, methodName, JSON.stringify( loggableValues( methodParameters ) ) ];
+             } );
 
             // Call callingMethod() on each model. The first model to return a non-undefined value
             // dictates the return value.
@@ -4205,9 +4022,9 @@ if ( ! childComponent.source ) {
 
         this.fireEvent = function( nodeID, eventName, eventParameters ) {
 
-            this.logger.debuggx( "fireEvent", function() {
-                return [ nodeID, eventName, JSON.stringify( loggableValues( eventParameters ) ) ];
-            } );
+             this.logger.debuggx( "fireEvent", function() {
+                 return [ nodeID, eventName, JSON.stringify( loggableValues( eventParameters ) ) ];
+             } );
 
             // Encode any namespacing into the name. (Namespaced names were added in 0.6.21.)
 
@@ -4843,20 +4660,15 @@ if ( ! childComponent.source ) {
 
         // == Private functions ====================================================================
 
-        var isSocketIO07 = function() {
-            //return ( parseFloat( io.version ) >= 0.7 );
-            return true
-        }
-
         // -- loadComponent ------------------------------------------------------------------------
 
         /// @name module:vwf~loadComponent
 
-        var loadComponent = async function( nodeURI, baseURI, callback_async /* nodeDescriptor */, errback_async /* errorMessage */ ) {  // TODO: turn this into a generic xhr loader exposed as a kernel function?
+        var loadComponent = function( nodeURI, baseURI, callback_async /* nodeDescriptor */, errback_async /* errorMessage */ ) {  // TODO: turn this into a generic xhr loader exposed as a kernel function?
 
             if ( nodeURI == vwf.kutility.protoNodeURI ) {
 
-                await callback_async( vwf.kutility.protoNodeDescriptor );
+                callback_async( vwf.kutility.protoNodeDescriptor );
 
             } else if ( nodeURI.match( RegExp( "^data:application/json;base64," ) ) ) {
 
@@ -4864,7 +4676,7 @@ if ( ! childComponent.source ) {
                 // these ourselves since Chrome can't load data URIs due to cross origin
                 // restrictions.
 
-                await callback_async( JSON.parse( atob( nodeURI.substring( 29 ) ) ) );  // TODO: support all data URIs
+               callback_async( JSON.parse( atob( nodeURI.substring( 29 ) ) ) );  // TODO: support all data URIs
 
             } else {
 
@@ -4873,7 +4685,7 @@ if ( ! childComponent.source ) {
                 let fetchUrl =  remappedURI( require( "vwf/utility" ).resolveURI( nodeURI, baseURI ) );
                 let dbName = fetchUrl.replace(window.location.origin + '/', "").split(".").join("_") + '_yaml';
 
-                const parseComp = async function(f) {
+                const parseComp = function(f) {
 
                     let result = YAML.parse(f);
 
@@ -4881,7 +4693,7 @@ if ( ! childComponent.source ) {
                     // console.log(nativeObject);
  
                      if(nativeObject) {
-                         await callback_async( nativeObject );
+                         callback_async( nativeObject );
                          queue.resume( "after loading " + nodeURI ); // resume the queue; may invoke dispatch(), so call last before returning to the host
      
                      } else {
@@ -4903,21 +4715,29 @@ if ( ! childComponent.source ) {
                 if(dbName.includes("vwf_example_com")){
                     //userDB = await window._LCS_SYS_USER.get('proxy').then();
                    fileName = dbName;
-                   window._LCS_SYS_USER.get('proxy').get(fileName).get('file').load(function(r){
-                       //console.log(r);
-                       parseComp(r);
+                   window._LCS_SYS_USER.get('proxy').get(fileName).get('file').once(comp=>{
+                    parseComp (comp);
+                   })
+                   
+                //    (function(r){
+                //        //console.log(r);
+                //        parseComp(r);
 
-                   });
+                //    });
 
                 } else {
                     let worldName = dbName.split('/')[0];
                     //userDB = await window._LCS_WORLD_USER.get('worlds').path(worldName).then();
                    fileName = dbName.replace(worldName + '/', "");
-                   userDB.get('worlds').path(worldName).get(fileName).get('file').load(function(r){
-                    //console.log(r);
-                    parseComp(r);
+                   userDB.get('worlds').path(worldName).get(fileName).get('file').once(comp=>{
+                    parseComp (comp);
+                   })
+                   
+                //    (function(r){
+                //     //console.log(r);
+                //     parseComp(r);
 
-                });
+                // });
                 }
 
                 //console.log(source);
@@ -4936,7 +4756,7 @@ if ( ! childComponent.source ) {
 
         /// @name module:vwf~loadScript
 
-        var loadScript = async function( scriptURI, baseURI, callback_async /* scriptText */, errback_async /* errorMessage */ ) {
+        var loadScript = function( scriptURI, baseURI, callback_async /* scriptText */, errback_async /* errorMessage */ ) {
 
             if ( scriptURI.match( RegExp( "^data:application/javascript;base64," ) ) ) {
 
@@ -4944,7 +4764,7 @@ if ( ! childComponent.source ) {
                 // these ourselves since Chrome can't load data URIs due to cross origin
                 // restrictions.
 
-                await callback_async( atob( scriptURI.substring( 35 ) ) );  // TODO: support all data URIs
+               callback_async( atob( scriptURI.substring( 35 ) ) );  // TODO: support all data URIs
 
             } else {
 
@@ -4953,12 +4773,12 @@ if ( ! childComponent.source ) {
                 let fetchUrl = remappedURI( require( "vwf/utility" ).resolveURI( scriptURI, baseURI ) );
                 let dbName = fetchUrl.replace(window.location.origin + '/', "").split(".").join("_");
 
-                const parseComp = async function(res) {
+                const parseComp = function(res) {
 
                     let scriptText = res;
 
                     try {
-                        await callback_async( scriptText );
+                        callback_async( scriptText );
                         queue.resume( "after loading " + scriptURI ); // resume the queue; may invoke dispatch(), so call last before returning to the host
 
                     } catch (e) {
@@ -4980,18 +4800,24 @@ if ( ! childComponent.source ) {
                 if(dbName.includes("vwf_example_com")){
                     //userDB = window._LCS_SYS_USER.get('proxy');
                     fileName = dbName;
-                    window._LCS_SYS_USER.get('proxy').get(fileName).get('file').load(function(r){
-                        //console.log(r);
-                        parseComp(r);
-                    });
+                    window._LCS_SYS_USER.get('proxy').get(fileName).get('file').once(comp=>{
+                        parseComp (comp);
+                       })
+                    // window._LCS_SYS_USER.get('proxy').get(fileName).get('file').once(function(r){
+                    //     //console.log(r);
+                    //     parseComp(r);
+                    // });
  
                 } else {
                     fileName = dbName.replace(worldName + '/', "");
-                    userDB.get('worlds').path(worldName).get(fileName).get('file').load(function(r){
-                        //console.log(r);
-                        parseComp(r);
+                    userDB.get('worlds').path(worldName).get(fileName).get('file').once(comp=>{
+                        parseComp (comp);
+                       })
+                    // userDB.get('worlds').path(worldName).get(fileName).get('file').once(function(r){
+                    //     //console.log(r);
+                    //     parseComp(r);
     
-                    });
+                    // });
                 }
 
                // userDB.get(fileName).once(, {wait: 1000})

+ 1 - 1
public/vwf/model/aframe.js

@@ -616,7 +616,7 @@ define(["module", "vwf/model", "vwf/utility"], function (module, model, utility)
                                 let dbPath = propertyValue.split(".").join("_");
 
                                 let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);
-                                userDB.get('worlds').get(worldName).get(dbPath).get('file').load(function(response) {
+                                userDB.get('worlds').get(worldName).get(dbPath).get('file').once(function(response) {
                                     if (response) {
 
                                         if (Object.keys(response).length > 0) {

+ 5 - 5
public/vwf/model/aframe/aframe-master.js

@@ -79164,7 +79164,7 @@ _dereq_('./core/a-mixin');
 _dereq_('./extras/components/');
 _dereq_('./extras/primitives/');
 
-console.log('A-Frame Version: 0.9.2 (Date 2019-09-17, Commit #6c533498)');
+console.log('A-Frame Version: 0.9.2 (Date 2019-09-29, Commit #41547f59)');
 console.log('three Version (https://github.com/supermedium/three.js):',
             pkg.dependencies['super-three']);
 console.log('WebVR Polyfill Version:', pkg.dependencies['webvr-polyfill']);
@@ -80221,16 +80221,16 @@ module.exports.System = registerSystem('gltf-model', {
 
   init: function () {
     var path = this.data.dracoDecoderPath;
-    THREE.DRACOLoader.setDecoderPath(path);
-    this.dracoLoader = path ? new THREE.DRACOLoader() : null;
+    this.dracoLoader = new THREE.DRACOLoader();
+    this.dracoLoader.setDecoderPath(path);
   },
 
   update: function () {
     var path;
     if (this.dracoLoader) { return; }
     path = this.data.dracoDecoderPath;
-    THREE.DRACOLoader.setDecoderPath(path);
-    this.dracoLoader = path ? new THREE.DRACOLoader() : null;
+    this.dracoLoader = new THREE.DRACOLoader();
+    this.dracoLoader.setDecoderPath(path);
   },
 
   getDRACOLoader: function () {

File diff suppressed because it is too large
+ 0 - 0
public/vwf/model/aframe/aframe-master.js.map


File diff suppressed because it is too large
+ 0 - 0
public/vwf/model/aframe/aframe-master.min.js


File diff suppressed because it is too large
+ 0 - 0
public/vwf/model/aframe/aframe-master.min.js.map


+ 92 - 82
public/vwf/view/aframe.js

@@ -81,7 +81,7 @@ define(["module", "vwf/view"], function (module, view) {
 
                 prepairAvatar.then(res => {
                     // console.log(res);
-                    createAvatar.call(self, childID);
+                    createAvatar.call(self, childID);                  
                     postLoadAction.call(self, childID);
 
                     if (this.gearvr == true) {
@@ -415,87 +415,9 @@ define(["module", "vwf/view"], function (module, view) {
             //     });
             // }
 
-            var avatarName = 'avatar-' + self.kernel.moniker();
-
-            if (eventName == "createAvatar") {
-                console.log("creating avatar...");
-
-                // let avatarID = self.kernel.moniker();
-                // var nodeName = 'avatar-' + avatarID;
-
-                var newNode = {
-                    "id": avatarName,
-                    "uri": avatarName,
-                    "extends": "http://vwf.example.com/aframe/avatar.vwf",
-                    "properties": {
-                        "localUrl": '',
-                        "remoteUrl": '',
-                        "displayName": 'Avatar ' + randId(),
-                        "sharing": { audio: true, video: true },
-                        "selectMode": false,
-                        "position": [0, 1.6, 0]
-                    }
-                }
-
-                if (!self.state.nodes[avatarName]) {
-                   
-
-                    if (_LCSUSER.is) {
-
-                        _LCSUSER.get('profile').get('alias').once(alias => {
-                                if (alias){
-
-                                    newNode.properties.displayName = alias;
-                                    //vwf_view.kernel.callMethod(avatarName, "setMyName", [alias]);
-                                }
-                                vwf_view.kernel.createChild(nodeID, avatarName, newNode);
-                                });
-
-                                    _LCSUSER.get('profile').get('avatarNode').once(res => {
-                                        var myNode = null;
-                                        if (res) {
-                                            //myNode = JSON.parse(res.avatarNode);
-                                            myNode = res;
-                                            vwf_view.kernel.callMethod(avatarName, "createAvatarBody", [myNode, null]);
-                                        } else {
-                                            vwf_view.kernel.callMethod(avatarName, "createAvatarBody", []);
-                                        }
-                                       // newNode.properties.displayName = res.alias;
-            
-                                        
-                                        //"/../assets/avatars/male/avatar1.gltf"
-            
-            
-                                        //vwf_view.kernel.callMethod(avatarName, 'setUserAvatar', [res] );
-                                    });
-
-
-                                
-                      
-                    } else {
-
-                        vwf_view.kernel.createChild(nodeID, avatarName, newNode);
-                        vwf_view.kernel.callMethod(avatarName, "createAvatarBody", []);
-                        //"/../assets/avatars/male/avatar1.gltf"
-                    }
-
-                    //
-                    
-
-                }
-
-
-                // if(_LCSUSER.is){
-                //     _LCSUSER.get('profile').get('alias').once(res => {
-                //         vwf_view.kernel.callMethod(avatarName, 'setUserAvatar', [res] );
-                //     })
-                // }
-
+             var avatarName = 'avatar-' + self.kernel.moniker();
 
 
-
-            }
-
             let intersectEvents = ['hitstart', 'hitend', 'intersect', 'clearIntersect']; //'intersect', 
 
             let hitEvent = intersectEvents.filter(el=> el == eventName.slice(0,-5))[0]; //slice Event word
@@ -746,7 +668,7 @@ define(["module", "vwf/view"], function (module, view) {
             if (position && rotation && lastPosition && lastRotation) {
                 if (compareCoordinates(position, lastPosition, delta) || Math.abs(rotation.y - lastRotation.y) > delta) {
                     console.log("not equal!!")
-                    vwf_view.kernel.callMethod(avatarName, "followAvatarControl", [position, rotation]);
+                    self.kernel.callMethod(avatarName, "followAvatarControl", [position, rotation]);
                 }
             }
             self.nodes[avatarName].selfTickRotation = Object.assign({}, rotation);
@@ -987,7 +909,95 @@ define(["module", "vwf/view"], function (module, view) {
 
     function createAvatar(nodeID) {
 
-        vwf_view.kernel.fireEvent(nodeID, "createAvatar")
+
+       // vwf_view.kernel.fireEvent(nodeID, "createAvatar");
+
+       var avatarName = 'avatar-' + self.kernel.moniker();
+
+           console.log("creating avatar...");
+
+           // let avatarID = self.kernel.moniker();
+           // var nodeName = 'avatar-' + avatarID;
+
+           var newNode = {
+               "id": avatarName,
+               "uri": avatarName,
+               "extends": "http://vwf.example.com/aframe/avatar.vwf",
+               "properties": {
+                   "localUrl": '',
+                   "remoteUrl": '',
+                   "displayName": 'Avatar ' + randId(),
+                   "sharing": { audio: true, video: true },
+                   "selectMode": false,
+                   "position": [0, 1.6, 0]
+               }
+           }
+
+           if (!self.state.nodes[avatarName]) {
+              
+
+               if (_LCSUSER.is) {
+
+                   _LCSUSER.get('profile').get('alias').once(alias => {
+                           if (alias){
+
+                               newNode.properties.displayName = alias;
+                               //vwf_view.kernel.callMethod(avatarName, "setMyName", [alias]);
+                           }
+                           vwf_view.kernel.createChild(nodeID, avatarName, newNode);
+                           });
+
+                           _LCSUSER.get('profile').get('avatarNode').not(res=>{
+                               //vwf_view.kernel.callMethod(avatarName, "createAvatarBody", []);
+                           })
+
+                               _LCSUSER.get('profile').get('avatarNode').once(res => {
+                                   var myNode = null;
+                                   if (res) {
+                                       //myNode = JSON.parse(res.avatarNode);
+                                       myNode = res;
+                                       vwf_view.kernel.callMethod(avatarName, "createAvatarBody", [myNode, null]);
+                                   } else {
+                                       vwf_view.kernel.callMethod(avatarName, "createAvatarBody", []);
+                                   }
+                                  // newNode.properties.displayName = res.alias;
+       
+                                   
+                                   //"/../assets/avatars/male/avatar1.gltf"
+       
+       
+                                   //vwf_view.kernel.callMethod(avatarName, 'setUserAvatar', [res] );
+                               });
+
+
+                           
+                 
+               } else {
+
+                   vwf_view.kernel.createChild(nodeID, avatarName, newNode);
+                   vwf_view.kernel.callMethod(avatarName, "createAvatarBody", []);
+
+                   //"/../assets/avatars/male/avatar1.gltf"
+               }
+
+               //
+               
+
+           }
+
+
+           // if(_LCSUSER.is){
+           //     _LCSUSER.get('profile').get('alias').once(res => {
+           //         vwf_view.kernel.callMethod(avatarName, 'setUserAvatar', [res] );
+           //     })
+           // }
+
+
+
+
+       
+
+
 
         // let avatarID = self.kernel.moniker();
         // var nodeName = 'avatar-' + avatarID;

+ 65 - 34
public/vwf/view/document.js

@@ -24,18 +24,10 @@ define( [ "module", "vwf/view", "vwf/utility"], function( module, view, utility)
             var self = this;
 
             // At the root node of the application, load the UI chrome if available.
+        
+           
 
-            let setInnerHtml = function(elm, html) {
-                elm.innerHTML = html;
-                Array.from(elm.querySelectorAll("script")).forEach(function(el) {
-                  let newEl = document.createElement("script");
-                  Array.from(el.attributes).forEach(function(el) { 
-                    newEl.setAttribute(el.name, el.value)
-                  });
-                  newEl.appendChild(document.createTextNode(el.innerHTML));
-                  el.parentNode.replaceChild(newEl, el);
-                })
-              }
+           
 
             if ( childID == this.kernel.application() &&
                     ( window.location.protocol == "http:" || window.location.protocol == "https:" ) ) {
@@ -58,39 +50,63 @@ define( [ "module", "vwf/view", "vwf/utility"], function( module, view, utility)
               let worldName = path.slice(1);
               let userDB = _LCSDB.user(_LCS_WORLD_USER.pub);
 
-              userDB.get('worlds').get(worldName).get(dbPath).get('file').once(function(res) {
-                   
-                   var responseText = "";
-                   
-                   if (res) { 
-                    responseText = res//.file;
-                   }
 
-                     // If the overlay attached a `createdNode` handler, forward this first call
-                    // since the overlay will have missed it.
+              function loadDoc(doc){
 
-                   setInnerHtml(container, responseText);
+                var responseText = doc;
 
-                   if ( self.createdNode !== Object.getPrototypeOf( self ).createdNode ) {
-                    self.createdNode( nodeID, childID, childExtendsID, childImplementsIDs,
-                        childSource, childType, childURI, childName );
-                }
+                  // If the overlay attached a `createdNode` handler, forward this first call
+                 // since the overlay will have missed it.
 
-                // Remove the container div if an error occurred or if we received an empty
-                // result. The server sends an empty document when the application doesn't
-                // provide a chrome file.
+                setInnerHtml(container, responseText);
 
-                if (  responseText == "" ) {
-                    container.remove();
-                }
+                if ( self.createdNode !== Object.getPrototypeOf( self ).createdNode ) {
+                 self.createdNode( nodeID, childID, childExtendsID, childImplementsIDs,
+                     childSource, childType, childURI, childName );
+             }
 
-                // Resume the queue.
+             // Remove the container div if an error occurred or if we received an empty
+             // result. The server sends an empty document when the application doesn't
+             // provide a chrome file.
 
-                callback( true );
+             if (  responseText == "" ) {
+                 container.remove();
+             }
 
+             // Resume the queue.
 
+             
+             //callback( true );
 
-                })
+              }
+
+              userDB.get('worlds').get(worldName).once(res=>{
+
+                if(res){
+                    if(Object.keys(res).includes(dbPath)){
+                        userDB.get('worlds').get(worldName).get(dbPath).get('file').once(function(res) { 
+                        loadDoc(res);
+                        callback( true );
+                        })
+                    } else {
+                        //NEED TO FIXED!!! Error: Callback was already called.
+                        userDB.get('worlds').get('empty').get(dbPath).get('file').once(function(res) { 
+                            loadDoc(res);
+                            callback( true );
+                            })
+                        //   var emptyDoc = '<!DOCTYPE html><html><head><script type=\"text\/javascript\">\r\n\r\n        vwf_view.satProperty = function (nodeID, propertyName, propertyValue) {\r\n            if (propertyValue === undefined || propertyValue == null) {\r\n                return;\r\n            }\r\n        }\r\n\r\n\r\n    <\/script>\r\n<\/head>\r\n\r\n<body>\r\n<\/body>\r\n\r\n<\/html>';
+                       
+                        //   loadDoc(emptyDoc);
+                        //   callback( true );
+                        
+
+                    }
+                }
+                
+              })
+
+
+              
    
 
             // fetch("admin/chrome", {
@@ -162,4 +178,19 @@ define( [ "module", "vwf/view", "vwf/utility"], function( module, view, utility)
 
     } );
 
+
+    function setInnerHtml (elm, html) {
+        elm.innerHTML = html;
+        Array.from(elm.querySelectorAll("script")).forEach(function(el) {
+          let newEl = document.createElement("script");
+          Array.from(el.attributes).forEach(function(el) { 
+            newEl.setAttribute(el.name, el.value)
+          });
+          newEl.appendChild(document.createTextNode(el.innerHTML));
+          el.parentNode.replaceChild(newEl, el);
+        })
+      }
+
+
+
 } );

+ 112 - 3
public/web/index-app.js

@@ -13,8 +13,19 @@ class IndexApp {
 
 
         this.worlds = {};
+        this.instances = {};
         //this.language = _LangManager.language;
 
+        if(!_app.isLuminary){
+            this.initReflectorConnection();
+        }
+
+
+    }
+
+    
+    initReflectorConnection(){
+
         this.options = {
 
             query: 'pathname=' + window.location.pathname.slice(1,
@@ -62,8 +73,6 @@ class IndexApp {
 
     }
 
-    
-
     initHTML() {
 
         let self = this;
@@ -381,6 +390,59 @@ class IndexApp {
             }
         }
 
+        let luminaryFeature = {
+            $cell: true,
+            _luminarySwitch: null,
+            $components: [
+              {
+                $type: "p",
+                class: "mdc-typography--headline5",
+                $text: "Use Krestianstvo Luminary (experimental)"
+              },
+              {
+                $type: 'p'
+              },
+              _app.widgets.switch({
+                'id': 'forceLuminary',
+                'init': function () {
+                  this._switch = new mdc.switchControl.MDCSwitch(this);
+                  let config = localStorage.getItem('lcs_config');
+                  this._switch.checked = JSON.parse(config).luminary;
+                  
+                 // this._replaceSwitch = this._switch;
+                  
+                },
+                'onchange': function (e) {
+
+                    if (this._switch) {
+                        let chkAttr = this._switch.checked;//this.getAttribute('checked');
+                        if (chkAttr) {
+                            let config = JSON.parse(localStorage.getItem('lcs_config'));
+                            config.luminary = true;
+                            localStorage.setItem('lcs_config', JSON.stringify(config));
+                            window.location.reload(true);
+                            //this._switch.checked = false;
+                        } else {
+                            let config = JSON.parse(localStorage.getItem('lcs_config'));
+                            config.luminary = false;
+                            localStorage.setItem('lcs_config', JSON.stringify(config));
+                            window.location.reload(true);
+                        }
+                    }
+                }
+              }
+              ),
+              {
+                $type: 'label',
+                for: 'input-forceLuminary',
+                $text: 'On / Off'
+              }
+
+            ]
+          }
+
+          
+
         let userGUI =
         {
             $type: "div",
@@ -459,6 +521,7 @@ class IndexApp {
                                 window.location.pathname = '/settings';
                             }
                         }),
+                        luminaryFeature,
                     {
                         $type: "h1",
                         class: "mdc-typography--headline3",
@@ -689,7 +752,7 @@ class IndexApp {
                                 {
                                     $type: "span",
                                     class: "mdc-list-item__secondary-text",
-                                    $text: _LangManager.language.t('users') + m[1].clients
+                                    $text: _app.isLuminary ? _LangManager.language.t('users') + Object.keys(m[1].clients).length : _LangManager.language.t('users') + m[1].clients
                                 }
                             ]
                         }
@@ -713,6 +776,52 @@ class IndexApp {
 
             },
             $init: function () {
+
+                if(_app.isLuminary){
+                    let luminaryPath = _app.luminaryPath;
+                    let ref = _LCSDB.get(luminaryPath);
+                    setInterval(function () {
+    
+                    ref.get('allclients').once().map().once(res => {
+    
+                        if (res) {
+                            if (res.id) {
+                                let clientTime = Gun.state.is(res, 'live');
+                                let now = Gun.time.is();
+    
+                                if (now - clientTime < 10000) {
+                                    let instance = res.user + res.instance;
+                                    //let data = JSON.stringify({[res.instance]: {instance: instance, clients: {}, user: res.user, loadInfo: {}}});
+                                    //console.log(data);
+                                    if(!self.instances[res.instance]) {
+                                    self.instances[res.instance] = {id: res.instance, instance: instance, clients: {[res.id]: res}, user: res.user, loadInfo: {} }
+                                    } else {
+                                        self.instances[res.instance].clients[res.id] = res
+                                    }
+                                    let data = JSON.stringify(self.instances);
+                                    self.parseOnlineData(data);
+                                    
+                                } else {
+                                    if(self.instances[res.instance]){
+                                    delete self.instances[res.instance].clients[res.id];
+                                    if(Object.keys(self.instances[res.instance].clients).length == 0){
+                                        delete self.instances[res.instance];
+                                        self.parseOnlineData(JSON.stringify({}));
+                                    }
+                                    }
+                                        
+                                    //ref.get('instances').get(res.instance).get(res.id).put(null);
+                                }
+    
+                            }
+                        }
+                    }
+                    )
+                }, 5000);
+    
+
+                }
+
                 this._refresh();
             },
             $update: function () {

+ 38 - 0
webServer.js

@@ -22,6 +22,43 @@ var app = express();
 // var certificate = fs.readFileSync('sslcert/server.crt', 'utf8');
 
 
+function registerGunDB(srv){
+
+    if (global.configuration.db === undefined)
+    global.configuration.db = false;
+
+    if (global.configuration.db){
+
+        console.log('register gun db node...\n');
+        var Gun = require('gun')
+        require('gun/sea')
+        require('gun/lib/path')
+        require('gun/lib/not')
+        require('gun/nts')
+        require('gun/lib/bye')
+
+        app.use(Gun.serve);
+        global.gun = Gun({ web: srv, axe: false}); 
+
+        //GLOBAL HEARTBEAT SAMPLE
+        setInterval(function () {
+
+            let message = {
+                parameters: [],
+                time: 'tick'//hb
+            };
+        
+            global.gun.get('server').get('heartbeat').get('tick').put(JSON.stringify(message),function(ack){
+                if(ack.err){ 
+                    console.log('ERROR: ' + ack.err)
+                }});
+        
+        }, 50);
+
+    }
+
+}
+
 function registerReflector(srv) {
 
     if (global.configuration.reflector === undefined)
@@ -109,6 +146,7 @@ function startServer() {
     console.log('Serving on port ' + conf.port);
 
     registerReflector(srv);
+    registerGunDB(srv);
 
 }
 

Some files were not shown because too many files changed in this diff