luminary.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. /*
  2. The MIT License (MIT)
  3. Copyright (c) 2014-2019 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
  4. */
  5. import { Helpers } from '/helpers.js';
  6. class Luminary {
  7. constructor() {
  8. console.log("luminary constructor");
  9. this.helpers = new Helpers;
  10. this.info = {};
  11. this.pendingList = [];
  12. this.status = { pending: true, initialized: false, trials: 3 };
  13. this.clients = {};
  14. this.heartbeat = {}
  15. this.clientID = undefined;
  16. this.namespace = undefined;
  17. }
  18. unsubscribeFromHeartbeat() {
  19. //TODO
  20. }
  21. subscribeOnHeartbeat(heartbeat) {
  22. let self = this;
  23. heartbeat.on(resp => {
  24. var res = Gun.obj.copy(resp);
  25. if (res.tick) {
  26. if (self.start_time) {
  27. let currentTick = Gun.state.is(res, 'tick');
  28. self.heartbeat.lastTick = currentTick;
  29. let msg = self.stamp(res);
  30. if (!self.status.pending) {
  31. self.onMessage(msg)
  32. } else {
  33. self.pendingList.push(msg);
  34. }
  35. }
  36. }
  37. })
  38. }
  39. subscribeOnMessages() {
  40. let self = this;
  41. let instance = _LCSDB.get(this.namespace);
  42. instance.get('message').on(resp => {
  43. var res = Gun.obj.copy(resp);
  44. if (res.tick) {
  45. if (self.start_time) {
  46. let msg = self.stamp(res);
  47. if (msg.explicit) {
  48. if (msg.explicit == vwf.moniker_) {
  49. self.onMessage(msg);
  50. if (msg.action == 'setState') {
  51. if (self.status.pending) {
  52. self.distributePendingMessages();
  53. self.status.pending = false;
  54. }
  55. }
  56. console.log(res);
  57. }
  58. } else if (!self.status.pending) {
  59. self.onMessage(msg);
  60. } else {
  61. self.pendingList.push(msg);
  62. }
  63. }
  64. }
  65. })
  66. }
  67. stamp(source) {
  68. var message = source.tick
  69. if(typeof message == "string"){
  70. message = JSON.parse(source.tick);
  71. }
  72. // if(message.sender){
  73. // console.log("HEARTBEAT FROM: " + message.sender);
  74. // }
  75. message.state = Gun.state.is(source, 'tick');
  76. message.start_time = this.start_time; //Gun.state.is(source, 'start_time');
  77. message.rate = this.rate; //source.rate;
  78. var time = ((message.state - message.start_time) * message.rate) / 1000;
  79. if (message.action == 'getState') {
  80. console.log('GET STATE msg!!!');
  81. }
  82. if (message.action == 'setState') {
  83. time = ((this.setStateTime - message.start_time) * message.rate) / 1000;
  84. }
  85. message.time = Number(time);
  86. message.origin = "reflector";
  87. return message
  88. }
  89. stampExternalMessage(msg) {
  90. let message = Object.assign({}, msg);
  91. message.client = this.clientID;
  92. let instance = _LCSDB.get(this.namespace)//_LCSDB.get(meta.namespace);
  93. if (message.result === undefined) {
  94. instance.get('message').get('tick').put(JSON.stringify(message));
  95. } else if (message.action == "getState") {
  96. let state = message.result;//JSON.stringify(message.result);
  97. let toClient = message.parameters[0];
  98. let newMsg =
  99. JSON.stringify({
  100. action: "setState",
  101. parameters: [state],
  102. time: 'tick', //self.setStateTime,
  103. explicit: toClient
  104. })
  105. instance.get('message')
  106. .get('tick')
  107. .put(newMsg)
  108. } else if (message.action === "execute") {
  109. console.log("!!!! execute ", message)
  110. }
  111. }
  112. onMessage(message) {
  113. try {
  114. var fields = Object.assign({}, message);
  115. vwf.private.queue.insert(fields, !fields.action); // may invoke dispatch(), so call last before returning to the host
  116. } catch (e) {
  117. vwf.logger.warn(fields.action, fields.node, fields.member, fields.parameters,
  118. "exception performing action:", require("vwf/utility").exceptionMessage(e));
  119. }
  120. }
  121. async connect(path) {
  122. let self = this;
  123. let objForQuery = this.helpers.reduceSaveObject(path);
  124. this.clientID = Gun.text.random();
  125. this.namespace = this.helpers.GetNamespace(path.path);
  126. //vwf.moniker_ = clientID;
  127. this.info = {
  128. pathname: window.location.pathname.slice(1,
  129. window.location.pathname.lastIndexOf("/")),
  130. appRoot: "./public",
  131. path: JSON.stringify(objForQuery), //JSON.stringify(path)
  132. namespace: this.namespace,
  133. }
  134. //set an instance with namespace
  135. let luminaryPath = _app.luminaryPath;
  136. let lum = _LCSDB.get(luminaryPath);
  137. let instance = _LCSDB.get(this.namespace);
  138. instance.not(function (res) {
  139. instance
  140. .put(self.info)
  141. .put({
  142. 'start_time': 'start_time',
  143. 'rate': 1
  144. });
  145. lum.get('instances').set(instance);
  146. self.status.initialized = "first";
  147. });
  148. await instance.once(res => {
  149. self.start_time = Gun.state.is(res, 'start_time');
  150. self.rate = res.rate;
  151. }).promOnce();
  152. let client = _LCSDB.get(self.clientID).put({ id: self.clientID, instance: self.namespace, user: path.user }).once(res => {
  153. self.setStateTime = Gun.state.is(res, 'id');
  154. setInterval(function () {
  155. client.get('live').put('tick');
  156. }, 500);
  157. });
  158. instance.get('clients').set(client);
  159. lum.get('allclients').set(client);
  160. instance.get('clients').map().on(res => {
  161. if (res) {
  162. if (res.id && res.live) {
  163. let clientTime = Gun.state.is(res, 'live');
  164. //let now = Gun.time.is();
  165. //console.log("NEW CLIENT LIVE : " + res.id);
  166. if (!self.clients[res.id]) {
  167. self.clients[res.id] = {
  168. live: clientTime,
  169. old: clientTime
  170. }
  171. } else {
  172. self.clients[res.id].old = self.clients[res.id].live;
  173. self.clients[res.id].live = clientTime
  174. }
  175. if (self.status.initialized == "first" && self.setStateTime) {
  176. self.status.initialized = true;
  177. instance
  178. .put({
  179. 'start_time': 'start_time',
  180. 'rate': 1
  181. }).once(res => {
  182. self.start_time = Gun.state.is(res, 'start_time');
  183. self.rate = res.rate;
  184. if (!_app.isLuminaryGlobalHB) {
  185. let tickMsg = {
  186. parameters: "[]",
  187. time: 'tick', //hb
  188. sender: self.clientID
  189. };
  190. instance.get('heartbeat').get('tick').put(tickMsg);
  191. self.initHeartBeat();
  192. }
  193. self.initFirst(res);
  194. self.initDeleteClient();
  195. });
  196. let noty = new Noty({
  197. text: "FIRST CLIENT",
  198. timeout: 1000,
  199. theme: 'mint',
  200. layout: 'bottomRight',
  201. type: 'success'
  202. });
  203. noty.show();
  204. } else if (!self.status.initialized && self.setStateTime) {
  205. if (res.id == self.clientID && self.status.trials > 0) {
  206. self.status.trials = self.status.trials - 1;
  207. console.log("CONNECTION TRIALS FOR: " + res.id + ' - ' + self.status.trials);
  208. } else if (res.id !== self.clientID && self.clients[res.id].live - self.clients[res.id].old < 1000) {
  209. console.log("REQUEST STATE FROM: " + res.id);
  210. self.status.initialized = true;
  211. if (!_app.isLuminaryGlobalHB) {
  212. self.initHeartBeat();
  213. }
  214. self.initOtherClient(res);
  215. self.initDeleteClient();
  216. let noty = new Noty({
  217. text: "CONNECTING TO EXISTED CLIENT...",
  218. timeout: 1000,
  219. theme: 'mint',
  220. layout: 'bottomRight',
  221. type: 'success'
  222. });
  223. noty.show();
  224. } else if (res.id == self.clientID && self.status.trials == 0) {
  225. console.log("INITIALIZE WORLD FOR: " + res.id);
  226. //request for the new instance
  227. let path = JSON.parse(self.info.path);
  228. window.location.pathname = path.user + path.path["public_path"];
  229. }
  230. }
  231. }
  232. }
  233. })
  234. return path
  235. }
  236. distributePendingMessages() {
  237. let self = this;
  238. if (self.pendingList.length > 0) {
  239. console.log("!!!! getPendingMessages");
  240. let cloneList = [...self.pendingList];
  241. cloneList.forEach(el => {
  242. self.onMessage(el);
  243. })
  244. self.pendingList = [];
  245. //_app.status.pending = false;
  246. }
  247. }
  248. clientsMessage() {
  249. let self = this;
  250. let clientDescriptor = { extends: "http://vwf.example.com/client.vwf" };
  251. let clientNodeMessage =
  252. {
  253. action: "createChild",
  254. parameters: ["http://vwf.example.com/clients.vwf", self.clientID, clientDescriptor],
  255. time: 'tick'
  256. }
  257. return clientNodeMessage
  258. }
  259. initFirst(ack) {
  260. let self = this;
  261. let instance = _LCSDB.get(self.namespace);
  262. let clientMsg =
  263. JSON.stringify({
  264. action: "createNode",
  265. parameters: ["http://vwf.example.com/clients.vwf"],
  266. time: 'tick',
  267. explicit: self.clientID
  268. })
  269. let processedURL = JSON.parse(self.info.path).path;
  270. let appMsg =
  271. JSON.stringify({
  272. action: "createNode",
  273. parameters: [
  274. (processedURL.public_path === "/" ? "" : processedURL.public_path) + "/" + processedURL.application,
  275. "application"
  276. ],
  277. time: 'tick',
  278. explicit: self.clientID
  279. })
  280. instance.get('message')
  281. .get('tick')
  282. .put(clientMsg);
  283. instance.get('message')
  284. .get('tick')
  285. .put(appMsg, res => {
  286. self.status.pending = false;
  287. let clientsMessage = self.clientsMessage();
  288. instance.get('message')
  289. .get('tick')
  290. .put(JSON.stringify(clientsMessage), res => {
  291. console.log("CREATE CLIENT: - " + res);
  292. })
  293. });
  294. }
  295. initOtherClient(ack) {
  296. console.log('new other client');
  297. let self = this;
  298. let instance = _LCSDB.get(self.namespace);
  299. let masterID = ack.id;
  300. let msg =
  301. JSON.stringify({
  302. action: "getState",
  303. respond: true,
  304. time: 'tick',
  305. explicit: masterID,
  306. parameters: [self.clientID]
  307. })
  308. instance.get('message')
  309. .get('tick')
  310. .put(msg);
  311. let clientsMessage = self.clientsMessage();
  312. instance.get('message')
  313. .get('tick').put(JSON.stringify(clientsMessage), res=>{
  314. console.log("CREATE CLIENT: - " + res);
  315. });
  316. }
  317. initHeartBeat() {
  318. let self = this;
  319. let instance = _LCSDB.get(self.namespace);
  320. setInterval(function () {
  321. let message = {
  322. parameters: "[]",
  323. time: 'tick', //hb
  324. sender: self.clientID
  325. };
  326. instance.get('heartbeat').get('tick').once(data => {
  327. if (data) {
  328. //let res = JSON.parse(data);
  329. var res = data
  330. if(typeof res == "string"){
  331. res = JSON.parse(data);
  332. }
  333. if (res.sender) {
  334. let now = Gun.time.is();
  335. let diff = now - self.heartbeat.lastTick;
  336. if ((Object.keys(self.clients).length == 1)
  337. || (res.sender == self.clientID && diff < 1000)
  338. || (res.sender !== self.clientID && diff > 1000)) {
  339. //console.log("TICK FROM" + self.clientID);
  340. instance.get('heartbeat').get('tick').put(message, function (ack) {
  341. if (ack.err) {
  342. console.log('ERROR: ' + ack.err)
  343. }
  344. });
  345. }
  346. }
  347. }
  348. })
  349. }, 50);
  350. }
  351. initDeleteClient() {
  352. let self = this;
  353. let instance = _LCSDB.get(self.namespace);
  354. setInterval(function () {
  355. Object.keys(self.clients).forEach(el => {
  356. let current = Gun.time.is();
  357. if (el !== self.clientID) {
  358. if (current - self.clients[el].live > 10000) {
  359. console.log("CLIENT DISCONECTED : " + el);
  360. let clientDeleteMessage =
  361. {
  362. action: "deleteChild",
  363. parameters: ["http://vwf.example.com/clients.vwf", el],
  364. time: 'tick'
  365. };
  366. instance.get('message')
  367. .get('tick').once(res => {
  368. instance.get('message')
  369. .get('tick')
  370. .put(JSON.stringify(clientDeleteMessage), res => {
  371. instance.get('clients').get(el).put(null);
  372. delete self.clients[el];
  373. })
  374. })
  375. }
  376. }
  377. })
  378. }, 5000);
  379. }
  380. }
  381. export { Luminary }