luminary.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. /*
  2. The MIT License (MIT)
  3. Copyright (c) 2014-2020 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 = _app.helpers; //new Helpers;
  10. this.info = {};
  11. this.pendingList = [];
  12. this.status = {
  13. pending: true,
  14. initialized: false
  15. };
  16. this.clients = {};
  17. this.heartbeat = {}
  18. this.clientID = undefined;
  19. this.namespace = undefined;
  20. }
  21. createStream() {
  22. let self = this;
  23. this.streamScheduler = M.scheduler.newDefaultScheduler();
  24. const [induce, events] = M.createAdapter();
  25. this.streamAdapter = {
  26. induce: induce,
  27. events: events,
  28. };
  29. const eventsStream = M.multicast(events);
  30. const clientDelete = M.tap(el => {
  31. console.log("Check for deletion: ", el);
  32. self.deleteClient();
  33. }, M.constant('delete', M.periodic(5000)));
  34. const clientLive = M.tap(el => {
  35. //console.log("LIVE: ", el);
  36. _lum.get(this.namespace).get('clients').get(self.clientID).get('live').put('tick');
  37. }, M.constant('live', M.periodic(500)));
  38. const hb = M.tap(el => {
  39. if (self.hbInit) {
  40. self.checkForHB();
  41. }
  42. }, M.constant('heartbeat', M.periodic(50)));
  43. const allStreams = M.mergeArray([eventsStream, hb, clientLive, clientDelete]);
  44. const tapFunction = function (el) {
  45. //console.log('FINAL TAP: ', el)
  46. }
  47. const finalStream = M.tap((res) => {
  48. tapFunction(res);
  49. }, allStreams);
  50. M.runEffects(finalStream, this.streamScheduler);
  51. }
  52. unsubscribeFromHeartbeat() {
  53. //TODO
  54. }
  55. subscribeOnHeartbeat(heartbeat) {
  56. let self = this;
  57. heartbeat.put({
  58. 'tick': 0
  59. }).on(resp => {
  60. var res = Gun.obj.copy(resp);
  61. if (res.tick) {
  62. if (self.start_time) {
  63. let currentTick = Gun.state.is(res, 'tick');
  64. self.heartbeat.lastTick = currentTick;
  65. let msg = self.stamp(res);
  66. if (!self.status.pending) {
  67. self.onMessage(msg)
  68. } else {
  69. self.pendingList.push(msg);
  70. }
  71. }
  72. }
  73. })
  74. }
  75. subscribeOnMessages() {
  76. let self = this;
  77. let instance = _lum.get(this.namespace);
  78. instance.get('message').on(resp => {
  79. var res = Gun.obj.copy(resp);
  80. if (res.tick) {
  81. if (self.start_time) {
  82. let msg = self.stamp(res);
  83. if (msg.explicit) {
  84. if (msg.explicit == vwf.moniker_) {
  85. self.onMessage(msg);
  86. if (msg.action == 'setState') {
  87. if (self.status.pending) {
  88. self.distributePendingMessages();
  89. self.status.pending = false;
  90. }
  91. }
  92. console.log(res);
  93. }
  94. } else if (!self.status.pending) {
  95. self.onMessage(msg);
  96. } else {
  97. self.pendingList.push(msg);
  98. }
  99. }
  100. }
  101. })
  102. }
  103. stamp(source) {
  104. let self = this;
  105. var message = source.tick
  106. if (typeof message == "string") {
  107. message = JSON.parse(source.tick);
  108. }
  109. if (message.sender) {
  110. //console.log("HEARTBEAT FROM: " + message.sender);
  111. self.heartbeat.sender = message.sender;
  112. }
  113. message.state = Gun.state.is(source, 'tick');
  114. message.start_time = this.start_time; //Gun.state.is(source, 'start_time');
  115. message.rate = this.rate; //source.rate;
  116. var time = ((message.state - message.start_time) * message.rate) / 1000;
  117. if (message.action == 'getState') {
  118. console.log('GET STATE msg!!!');
  119. }
  120. if (message.action == 'setState') {
  121. time = ((this.setStateTime - message.start_time) * message.rate) / 1000;
  122. }
  123. message.time = Number(time);
  124. message.origin = "reflector";
  125. return message
  126. }
  127. async stampExternalMessage(msg) {
  128. let message = Object.assign({}, msg);
  129. message.client = this.clientID;
  130. let instance = _lum.get(this.namespace) //_LCSDB.get(meta.namespace);
  131. if (message.result === undefined) {
  132. instance.get('message').get('tick').put(JSON.stringify(message));
  133. } else if (message.action == "getState") {
  134. let state = message.result; //JSON.stringify(message.result);
  135. let toClient = message.parameters[2];
  136. let newMsg =
  137. JSON.stringify({
  138. action: "setState",
  139. parameters: [state],
  140. time: 'tick', //self.setStateTime,
  141. explicit: toClient
  142. })
  143. await (new Promise(res => {
  144. instance.get('message')
  145. .get('tick')
  146. .put(newMsg).once(res)
  147. })).then(res => {
  148. console.log("Set state")
  149. })
  150. } else if (message.action === "execute") {
  151. console.log("!!!! execute ", message)
  152. }
  153. }
  154. onMessage(message) {
  155. try {
  156. var fields = Object.assign({}, message);
  157. if (_app.config.streamMsg) {
  158. vwf.virtualTime.streamAdapter.induce(fields);
  159. } else {
  160. vwf.virtualTime.insert(fields, !fields.action);
  161. }
  162. } catch (e) {
  163. vwf.logger.warn(fields.action, fields.node, fields.member, fields.parameters,
  164. "exception performing action:", vwf.utility.exceptionMessage(e));
  165. }
  166. }
  167. async connect(path) {
  168. let self = this;
  169. let objForQuery = this.helpers.reduceSaveObject(path);
  170. this.clientID = Gun.text.random();
  171. this.namespace = this.helpers.GetNamespace(path.path);
  172. //vwf.moniker_ = self.clientID;
  173. this.info = {
  174. pathname: window.location.pathname.slice(1,
  175. window.location.pathname.lastIndexOf("/")),
  176. appRoot: "./public",
  177. path: JSON.stringify(objForQuery), //JSON.stringify(path)
  178. namespace: this.namespace,
  179. }
  180. //set an instance with namespace
  181. if (_app.config.multisocket) {
  182. let luminaryPath = _app.luminaryPath
  183. if (luminaryPath) {
  184. window._lum = Gun({
  185. peers: [luminaryPath + "/" + path.path.instance],
  186. musticast: false,
  187. localStorage: false,
  188. radisk: false,
  189. file: false
  190. });
  191. }
  192. } else {
  193. window._lum = _LCSDB;
  194. }
  195. self.createStream();
  196. let instance = _lum.get(this.namespace);
  197. //_lum.get('instances').set(instance);
  198. instance.not(function (res) {
  199. instance
  200. .put(self.info)
  201. .put({
  202. 'start_time': 'start_time',
  203. 'rate': 1
  204. //'message':{}
  205. });
  206. _lum.get('instances').set(instance);
  207. self.status.initialized = "first";
  208. });
  209. await (new Promise(res => {
  210. instance.once(res)
  211. })).then(res => {
  212. self.start_time = Gun.state.is(res, 'start_time');
  213. self.rate = res.rate;
  214. })
  215. let client = _lum.get(self.clientID).put({});
  216. await (new Promise(res => {
  217. instance.get('clients').set(client).once(res)
  218. })).then(r => {
  219. instance.get('clients').get(self.clientID).put({
  220. id: self.clientID,
  221. instance: self.namespace,
  222. user: path.user
  223. });
  224. });
  225. _lum.get('allclients').set(client);
  226. await (new Promise(res => {
  227. _lum.get(self.clientID).once(res)
  228. })).then(res => {
  229. self.setStateTime = Gun.state.is(res, 'id');
  230. })
  231. instance.get('clients').map().on(res => {
  232. if (res) {
  233. if (res.id && res.live) {
  234. let clientTime = Gun.state.is(res, 'live');
  235. //let now = Gun.time.is();
  236. //console.log("NEW CLIENT LIVE : " + res.id);
  237. if (!self.clients[res.id]) {
  238. self.clients[res.id] = {
  239. live: clientTime,
  240. old: clientTime
  241. }
  242. } else {
  243. self.clients[res.id].old = self.clients[res.id].live;
  244. self.clients[res.id].live = clientTime
  245. }
  246. if (self.status.initialized == "first" && self.setStateTime) {
  247. self.status.initialized = true;
  248. instance
  249. .put({
  250. 'start_time': 'start_time',
  251. 'rate': 1
  252. }).once(res => {
  253. self.start_time = Gun.state.is(res, 'start_time');
  254. self.rate = res.rate;
  255. if (!vwf.isLuminaryGlobalHB) {
  256. self.hbInit = true;
  257. }
  258. self.initFirst(res);
  259. });
  260. _app.helpers.notyOK("FIRST CLIENT");
  261. } else if (!self.status.initialized && self.setStateTime) {
  262. // if (self.clients.length == 1 && res.id == self.clientID){
  263. // console.log("THAT'S ME and ONLY ONE");
  264. // //request for the new instance
  265. // let path = JSON.parse(self.info.path);
  266. // window.location.pathname = path.user + path.path["public_path"];
  267. // }
  268. // else
  269. if (res.id == self.clientID) {
  270. console.log("THAT'S ME");
  271. } else if (self.clients[res.id].live - self.clients[res.id].old == 0 || self.clients[res.id].live - self.clients[res.id].old > 1000) {
  272. console.log("OLD CLIENT!");
  273. } else {
  274. console.log("REQUEST STATE FROM: " + res.id);
  275. self.status.initialized = true;
  276. if (!vwf.isLuminaryGlobalHB) {
  277. self.hbInit = true;
  278. }
  279. self.initOtherClient(res);
  280. _app.helpers.notyOK("CONNECTING TO EXISTED CLIENT...");
  281. }
  282. }
  283. }
  284. }
  285. })
  286. return path
  287. }
  288. distributePendingMessages() {
  289. let self = this;
  290. if (self.pendingList.length > 0) {
  291. console.log("!!!! getPendingMessages");
  292. let cloneList = [...self.pendingList];
  293. cloneList.forEach(el => {
  294. self.onMessage(el);
  295. })
  296. self.pendingList = [];
  297. //_app.status.pending = false;
  298. }
  299. }
  300. clientMessage() {
  301. let self = this;
  302. let clientDescriptor = {
  303. extends: "proxy/client.vwf"
  304. };
  305. let clientNodeMessage = {
  306. action: "createChild",
  307. parameters: ["proxy/clients.vwf", self.clientID, clientDescriptor],
  308. time: 'tick'
  309. }
  310. return clientNodeMessage
  311. }
  312. async initFirst(ack) {
  313. let self = this;
  314. let instance = _lum.get(self.namespace);
  315. let clientMsg =
  316. JSON.stringify({
  317. action: "createNode",
  318. parameters: ["proxy/clients.vwf"],
  319. time: 'tick',
  320. explicit: self.clientID
  321. })
  322. let processedURL = JSON.parse(self.info.path).path;
  323. let appMsg =
  324. JSON.stringify({
  325. action: "createNode",
  326. parameters: [
  327. (processedURL.public_path === "/" ? "" : processedURL.public_path) + "/" + processedURL.application,
  328. "application"
  329. ],
  330. time: 'tick',
  331. explicit: self.clientID
  332. })
  333. await (new Promise(res => {
  334. instance.get('message').put({})
  335. .get('tick')
  336. .put(clientMsg).once(res)
  337. })).then(res => {
  338. return new Promise(r => instance.get('message')
  339. .get('tick')
  340. .put(appMsg).once(r))
  341. }).then(r => {
  342. self.status.pending = false;
  343. let clientMessage = self.clientMessage();
  344. instance.get('message')
  345. .get('tick')
  346. .put(JSON.stringify(clientMessage), res => {
  347. console.log("CREATE CLIENT: - " + res);
  348. })
  349. })
  350. }
  351. async initOtherClient(ack) {
  352. console.log('new other client');
  353. let self = this;
  354. let instance = _lum.get(self.namespace);
  355. let masterID = ack.id;
  356. let msg =
  357. JSON.stringify({
  358. action: "getState",
  359. respond: true,
  360. time: 'tick',
  361. explicit: masterID,
  362. parameters: [null, null, self.clientID]
  363. })
  364. await (new Promise(res => {
  365. instance.get('message')
  366. .get('tick')
  367. .put(msg).once(res)
  368. })).then(res => {
  369. let clientMessage = JSON.stringify(self.clientMessage());
  370. return new Promise(r =>
  371. instance.get('message')
  372. .get('tick')
  373. .put(clientMessage).once(r))
  374. }).then(r => {
  375. console.log("CREATE CLIENT: - " + r);
  376. })
  377. }
  378. makeHB() {
  379. let self = this;
  380. //console.log("HeartBeat");
  381. let message = {
  382. parameters: "[]",
  383. time: 'tick', //hb
  384. sender: self.clientID
  385. };
  386. _lum.get(self.namespace).get('heartbeat').get('tick').put(JSON.stringify(message), function (ack) {
  387. if (ack.err) {
  388. //console.log('ERROR: ' + ack.err)
  389. }
  390. });
  391. }
  392. checkForHB() {
  393. let self = this;
  394. let sender = self.heartbeat.sender;
  395. let now = Gun.time.is();
  396. let diff = now - self.heartbeat.lastTick;
  397. if ((Object.keys(self.clients).length == 1) ||
  398. (sender == self.clientID && diff < 1000) ||
  399. (sender !== self.clientID && diff > 1000)) {
  400. self.makeHB()
  401. }
  402. }
  403. deleteClient() {
  404. let self = this;
  405. let instance = _lum.get(self.namespace);
  406. Object.keys(self.clients).forEach(el => {
  407. let current = Gun.time.is();
  408. if (el !== self.clientID) {
  409. if (current - self.clients[el].live > 10000) {
  410. console.log("CLIENT DISCONECTED : " + el);
  411. let clientDeleteMessage = {
  412. action: "deleteChild",
  413. parameters: ["proxy/clients.vwf", el],
  414. time: 'tick'
  415. };
  416. new Promise(res => {
  417. instance.get('message')
  418. .get('tick')
  419. .put(JSON.stringify(clientDeleteMessage)).once(res)
  420. }).then(res => {
  421. instance.get('clients').get(el).put(null);
  422. delete self.clients[el];
  423. })
  424. }
  425. }
  426. })
  427. }
  428. }
  429. export {
  430. Luminary
  431. }