luminary.js 15 KB

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