Home Reference Source

cables_dev/cables_ui/src/ui/socketcluster/sc_connection.js

  1. import { Logger, Events, TalkerAPI } from "cables-shared-client";
  2. import PacoConnector from "./sc_paconnector.js";
  3. import ScState from "./sc_state.js";
  4. import ScUiMultiplayer from "./sc_ui_multiplayer.js";
  5. import Gui, { gui } from "../gui.js";
  6. import { PatchConnectionSender } from "./patchconnection.js";
  7. import Chat from "../components/tabs/tab_chat.js";
  8. import { platform } from "../platform.js";
  9. import ScUi from "./sc_ui.js";
  10. import { notify } from "../elements/notification.js";
  11. import subPatchOpUtil from "../subpatchop_util.js";
  12. export default class ScConnection extends Events
  13. {
  14. constructor(cfg)
  15. {
  16. super();
  17. this.PING_INTERVAL = 5000;
  18. this.PINGS_TO_TIMEOUT = 5;
  19. this.OWN_PINGS_TO_TIMEOUT = 5;
  20. this._log = new Logger("scconnection");
  21. this._verboseLog = false;
  22. this._scConfig = cfg;
  23. this._active = cfg.hasOwnProperty("enabled") ? cfg.enabled : false;
  24. this._lastPingReceived = this.getTimestamp();
  25. this._socket = null;
  26. this._connected = false;
  27. this._connectedSince = null;
  28. this._inSessionSince = null;
  29. this._paco = null;
  30. this._pacoEnabled = false;
  31. this._patchConnection = new PatchConnectionSender(gui.corePatch());
  32. this._pacoSynced = false;
  33. this._pacoChannel = null;
  34. this._pacoLoopReady = false;
  35. this.patchChannelName = this._scConfig.patchChannel;
  36. this.userChannelName = this._scConfig.userChannel;
  37. this.userPatchChannelName = this._scConfig.userPatchChannel;
  38. this.broadcastChannelName = this._scConfig.broadcastChannel;
  39. this.multiplayerCapable = this._scConfig.multiplayerCapable;
  40. if (cfg)
  41. {
  42. this._init((isActive) =>
  43. {
  44. let showMultiplayerUi = (isActive && this.multiplayerCapable);
  45. if (this.showGuestUsers) showMultiplayerUi = true;
  46. if (gui.isRemoteClient) showMultiplayerUi = false;
  47. this._scUi = new ScUi(this);
  48. if (showMultiplayerUi)
  49. {
  50. this._multiplayerUi = new ScUiMultiplayer(this);
  51. this._chat = new Chat(gui.mainTabs, this);
  52. }
  53. });
  54. }
  55. }
  56. getTimestamp()
  57. {
  58. return (performance.timing.navigationStart + performance.now());
  59. }
  60. get showGuestUsers()
  61. {
  62. return gui && gui.project() && gui.project() && gui.project().settings && gui.project().visibility === "public";
  63. }
  64. get netMouseCursorDelay() { return 100; }
  65. get netTimelineScrollDelay() { return 100; }
  66. get chat() { return this._chat; }
  67. get state() { return this._state; }
  68. get connected() { return this._connected; }
  69. get client() { return this.state.clients[this.clientId]; }
  70. get clientId() { return this._socket.clientId; }
  71. get followers() { return this.state.followers; }
  72. get clients() { return this.state.clients; }
  73. get synced()
  74. {
  75. if (!this._pacoEnabled) { return true; }
  76. else { return this._pacoSynced; }
  77. }
  78. get inMultiplayerSession() { return this._pacoEnabled; }
  79. get hasOtherMultiplayerCapableClients()
  80. {
  81. if (!this.state) return false;
  82. let clientsInSession = false;
  83. const clients = Object.values(this.clients);
  84. for (let i = 0; i < clients.length; i++)
  85. {
  86. const client = clients[i];
  87. if (client.clientId === this.clientId) continue;
  88. if (client.multiplayerCapable)
  89. {
  90. clientsInSession = true;
  91. break;
  92. }
  93. }
  94. return clientsInSession;
  95. }
  96. get runningMultiplayerSession()
  97. {
  98. if (!this.state) return false;
  99. let clientsInSession = false;
  100. const clients = Object.values(this.clients);
  101. for (let i = 0; i < clients.length; i++)
  102. {
  103. const client = clients[i];
  104. if (client.inMultiplayerSession)
  105. {
  106. clientsInSession = true;
  107. break;
  108. }
  109. }
  110. return clientsInSession;
  111. }
  112. get onlyRemoteClientsConnected()
  113. {
  114. if (!this.state) return false;
  115. let onlyRemoteClients = true;
  116. const clients = Object.values(this.clients);
  117. for (let i = 0; i < clients.length; i++)
  118. {
  119. const client = clients[i];
  120. if (!client.inMultiplayerSession) continue;
  121. if (!client.isRemoteClient) onlyRemoteClients = false;
  122. }
  123. return onlyRemoteClients;
  124. }
  125. enableVerboseLogging()
  126. {
  127. this._verboseLog = true;
  128. }
  129. isConnected()
  130. {
  131. return this._connected;
  132. }
  133. getPilot()
  134. {
  135. return this.state.getPilot();
  136. }
  137. hasPilot()
  138. {
  139. return this.state.hasPilot();
  140. }
  141. canSaveInMultiplayer()
  142. {
  143. if (this._pacoEnabled)
  144. {
  145. return this.connected && this.client && this.client.isPilot;
  146. }
  147. else
  148. {
  149. return true;
  150. }
  151. }
  152. showChat()
  153. {
  154. this._chat.show();
  155. }
  156. setPacoPaused(paused)
  157. {
  158. if (this._paco) this._paco.paused = paused;
  159. }
  160. startMultiplayerSession()
  161. {
  162. if (this.runningMultiplayerSession)
  163. {
  164. this.joinMultiplayerSession();
  165. }
  166. else
  167. {
  168. if (this.multiplayerCapable)
  169. {
  170. if (!this.client.isRemoteClient)
  171. {
  172. this.client.isPilot = true;
  173. }
  174. this._inSessionSince = this.getTimestamp();
  175. this.client.inMultiplayerSession = true;
  176. this._sendPing(true);
  177. this._state.emitEvent("enableMultiplayer", { "username": this.client.username, "clientId": this.clientId, "started": true });
  178. }
  179. }
  180. }
  181. joinMultiplayerSession()
  182. {
  183. this.client.isPilot = false;
  184. this.client.following = null;
  185. this.client.inMultiplayerSession = true;
  186. this._inSessionSince = this.getTimestamp();
  187. this._state.emitEvent("enableMultiplayer", { "username": this.client.username, "clientId": this.clientId, "started": false });
  188. this._sendPing();
  189. }
  190. reconnectRemoteViewer()
  191. {
  192. let startSessionListener = null;
  193. if (!this.runningMultiplayerSession)
  194. {
  195. startSessionListener = this.on("multiplayerEnabled", () => { this._reconnectViewer(startSessionListener); });
  196. this.startMultiplayerSession();
  197. }
  198. else
  199. {
  200. this._reconnectViewer(startSessionListener);
  201. }
  202. }
  203. _reconnectViewer(startSessionListener)
  204. {
  205. if (startSessionListener) this._state.off(startSessionListener);
  206. gui.setRestriction(Gui.RESTRICT_MODE_FULL);
  207. this.client.isPilot = true;
  208. this.client.following = null;
  209. this.client.inMultiplayerSession = true;
  210. this._inSessionSince = this.getTimestamp();
  211. this._state.emitEvent("enableMultiplayer", { "username": this.client.username, "clientId": this.clientId, "started": true });
  212. this._sendPing(true);
  213. this._startPacoSend(this.clientId, true);
  214. }
  215. startRemoteViewer(doneCallback)
  216. {
  217. const listener = () => { this._state.off(listenerId); doneCallback(); };
  218. const listenerId = this._state.on("enableMultiplayer", listener);
  219. if (!this.inMultiplayerSession)
  220. {
  221. if (this.runningMultiplayerSession)
  222. {
  223. this.joinMultiplayerSession();
  224. }
  225. else
  226. {
  227. this.startMultiplayerSession();
  228. }
  229. }
  230. else
  231. {
  232. doneCallback();
  233. }
  234. }
  235. leaveMultiplayerSession()
  236. {
  237. this.client.isPilot = false;
  238. this._pacoChannel = this._socket.unsubscribe(this.userPatchChannelName + "/paco");
  239. this._pacoEnabled = false;
  240. this.client.inMultiplayerSession = false;
  241. this.client.following = null;
  242. this._inSessionSince = null;
  243. this.emitEvent("netLeaveSession");
  244. this._sendPing();
  245. }
  246. sendCurrentVersion()
  247. {
  248. if (this.client.isPilot)
  249. {
  250. this._startPacoSend(this.clientId, true);
  251. }
  252. }
  253. _startPacoSend(requestedBy, forceResync = false)
  254. {
  255. if (this.inMultiplayerSession)
  256. {
  257. if (!this._paco)
  258. {
  259. this._paco = new PacoConnector(this, this._patchConnection);
  260. this._patchConnection.connectors.push(this._paco);
  261. }
  262. const json = gui.corePatch().serialize({ "asObject": true });
  263. const payload = {
  264. "patch": JSON.stringify(json),
  265. "requestedBy": requestedBy,
  266. "forceResync": forceResync
  267. };
  268. this._paco.send(CABLES.PACO_LOAD, payload);
  269. this._pacoSynced = true;
  270. if (gui.scene().timer)
  271. {
  272. this.sendUi("timelineControl", { "command": "setPlay", "value": gui.scene().timer.isPlaying(), "time": gui.scene().timer.getTime() });
  273. }
  274. this.state.emitEvent("patchSynchronized");
  275. }
  276. }
  277. requestPilotPatch()
  278. {
  279. if (this.inMultiplayerSession && !this.client.isPilot)
  280. {
  281. this.sendPaco({ "requestedBy": this.client.clientId }, "resync");
  282. }
  283. }
  284. track(eventCategory, eventAction, eventLabel, meta = {})
  285. {
  286. if (!this._scConfig.enableTracking) return;
  287. const payload = {
  288. eventCategory,
  289. eventAction,
  290. eventLabel,
  291. meta
  292. };
  293. this.sendControl("track", payload);
  294. }
  295. sendNotification(title, text)
  296. {
  297. this._send(this.patchChannelName, "info", { "name": "notify", "title": title, "text": text });
  298. }
  299. sendControl(name, payload)
  300. {
  301. payload = payload || {};
  302. payload.name = name;
  303. this._send(this.patchChannelName, "control", payload);
  304. }
  305. sendUi(name, payload, sendOnEmptyClientList = false)
  306. {
  307. if (sendOnEmptyClientList || this.state.getNumClients() > 1)
  308. {
  309. payload = payload || {};
  310. payload.name = name;
  311. this._send(this.patchChannelName, "ui", payload);
  312. }
  313. }
  314. sendChat(text)
  315. {
  316. // remove html
  317. const el = document.createElement("div");
  318. el.innerHTML = text;
  319. text = el.textContent || el.innerText || "";
  320. this._send(this.patchChannelName, "chat", { "name": "chatmsg", text, "username": gui.user.username });
  321. }
  322. sendPaco(payload, name = "paco")
  323. {
  324. if (!this._pacoEnabled) return;
  325. if (this.client && (!this.client.isRemoteClient || name === "resync"))
  326. {
  327. payload.name = name || "paco";
  328. this._send(this.userPatchChannelName, "paco", payload);
  329. }
  330. }
  331. _init(doneCallback)
  332. {
  333. if (!this._active)
  334. {
  335. this._log.info("CABLES-SOCKETCLUSTER NOT ACTIVE, WON'T SEND MESSAGES (enable in config)");
  336. doneCallback(false);
  337. }
  338. this._token = this._scConfig.token;
  339. this._socket = socketClusterClient.create(this._scConfig);
  340. this._socket.patchChannelName = this.patchChannelName;
  341. this._socket.userChannelName = this.userChannelName;
  342. this._socket.userPatchChannelName = this.userPatchChannelName;
  343. this._state = new ScState(this);
  344. if (this.multiplayerCapable)
  345. {
  346. this._state.on("becamePilot", () =>
  347. {
  348. this._sendPing();
  349. this._startPacoSend(this.clientId);
  350. });
  351. this._state.on("enableMultiplayer", (payload) =>
  352. {
  353. this._pacoEnabled = true;
  354. (async () =>
  355. {
  356. if (!this._pacoEnabled) return;
  357. if (!this._pacoChannel)
  358. {
  359. this._pacoChannel = this._socket.subscribe(this.userPatchChannelName + "/paco");
  360. if (!this._pacoLoopReady)
  361. {
  362. this._pacoLoopReady = true;
  363. for await (const msg of this._pacoChannel)
  364. {
  365. this._handlePacoMessage(msg);
  366. this.emitEvent("netActivityIn");
  367. }
  368. }
  369. }
  370. })();
  371. if (!payload.started)
  372. {
  373. this.requestPilotPatch();
  374. }
  375. this.emitEvent("multiplayerEnabled");
  376. });
  377. }
  378. (async () =>
  379. {
  380. for await (const { error } of this._socket.listener("error"))
  381. {
  382. if (!this.isConnected()) return;
  383. if (this.inMultiplayerSession)
  384. {
  385. // notifyError("multiplayer server disconnected!", "wait for reconnection to rejoin session");
  386. this.leaveMultiplayerSession();
  387. }
  388. // socketcluster reports "hung up" errors during own reconnection/keepalive phase
  389. if (error.code !== 1006 && error.code !== 4001) this._log.info(error.code + " - " + error.message);
  390. this._connected = false;
  391. this.emitEvent("connectionError", error);
  392. this.emitEvent("connectionChanged");
  393. this.emitEvent("netActivityIn");
  394. }
  395. })();
  396. (async () =>
  397. {
  398. for await (const event of this._socket.listener("connect"))
  399. {
  400. this.emitEvent("netActivityIn");
  401. // this._log.verbose("sc connected!");
  402. this._connected = true;
  403. this._connectedSince = this.getTimestamp();
  404. this.emitEvent("connectionChanged");
  405. // send me patch
  406. this._updateMembers();
  407. if (this.client.isRemoteClient)
  408. {
  409. this.joinMultiplayerSession();
  410. }
  411. else
  412. {
  413. this._reconnectViewer();
  414. }
  415. }
  416. })();
  417. if (this.userChannelName)
  418. {
  419. (async () =>
  420. {
  421. const userChannel = this._socket.subscribe(this.userChannelName + "/activity");
  422. for await (const msg of userChannel)
  423. {
  424. if (msg && msg.data)
  425. {
  426. gui.updateActivityFeedIcon(msg.data);
  427. }
  428. }
  429. })();
  430. }
  431. (async () =>
  432. {
  433. const controlChannel = this._socket.subscribe(this.patchChannelName + "/control");
  434. for await (const msg of controlChannel)
  435. {
  436. this._handleControlChannelMessage(msg);
  437. this.emitEvent("netActivityIn");
  438. }
  439. })();
  440. (async () =>
  441. {
  442. const uiChannel = this._socket.subscribe(this.patchChannelName + "/ui");
  443. for await (const msg of uiChannel)
  444. {
  445. this._handleUiChannelMsg(msg);
  446. this.emitEvent("netActivityIn");
  447. }
  448. })();
  449. (async () =>
  450. {
  451. const infoChannel = this._socket.subscribe(this.patchChannelName + "/info");
  452. for await (const msg of infoChannel)
  453. {
  454. this._handleInfoChannelMsg(msg);
  455. this.emitEvent("netActivityIn");
  456. }
  457. })();
  458. (async () =>
  459. {
  460. const chatChannel = this._socket.subscribe(this.patchChannelName + "/chat");
  461. for await (const msg of chatChannel)
  462. {
  463. this._handleChatChannelMsg(msg);
  464. this.emitEvent("netActivityIn");
  465. }
  466. })();
  467. if (this.userPatchChannelName)
  468. {
  469. (async () =>
  470. {
  471. const userChannel = this._socket.subscribe(this.userPatchChannelName + "/info");
  472. for await (const msg of userChannel)
  473. {
  474. this._handleInfoChannelMsg(msg);
  475. this.emitEvent("netActivityIn");
  476. }
  477. })();
  478. }
  479. if (this.broadcastChannelName)
  480. {
  481. (async () =>
  482. {
  483. const userChannel = this._socket.subscribe(this.broadcastChannelName);
  484. for await (const msg of userChannel)
  485. {
  486. if (msg && msg.data && msg.data.build)
  487. {
  488. const opName = msg.data.opName;
  489. let text = "";
  490. switch (msg.data.build)
  491. {
  492. case "opchange":
  493. if (opName)
  494. {
  495. const usedOps = gui.corePatch().getOpsByObjName(opName);
  496. if (usedOps && usedOps.length > 0)
  497. {
  498. const usedOp = usedOps[0];
  499. gui.serverOps.execute(usedOp.opId, () =>
  500. {
  501. notify("reloaded op " + usedOp.objName);
  502. });
  503. }
  504. }
  505. break;
  506. case "attachmentchange":
  507. const attachmentName = msg.data.attachmentName;
  508. if (attachmentName !== subPatchOpUtil.blueprintSubpatchAttachmentFilename)
  509. {
  510. if (opName)
  511. {
  512. const usedOps = gui.corePatch().getOpsByObjName(opName);
  513. if (usedOps && usedOps.length > 0)
  514. {
  515. const usedOp = usedOps[0];
  516. gui.serverOps.execute(usedOp.opId, () =>
  517. {
  518. notify("reloaded op " + usedOp.objName);
  519. });
  520. }
  521. }
  522. }
  523. break;
  524. case "started":
  525. text = "Waiting while building";
  526. if (msg.data.module) text += " " + msg.data.module;
  527. text += "...";
  528. gui.restriction.setMessage("cablesbuild", text);
  529. break;
  530. case "ended":
  531. text = "done building";
  532. if (msg.data.module) text += " " + msg.data.module;
  533. text += "...";
  534. gui.restriction.setMessage("cablesbuild", null);
  535. gui.patchView.store.checkUpdated(null, false, true);
  536. gui.savedState.setSavedAll("force reload");
  537. if (!document.hidden) platform.talkerAPI.send(TalkerAPI.CMD_RELOAD_PATCH);
  538. break;
  539. }
  540. }
  541. }
  542. })();
  543. }
  544. window.addEventListener("beforeunload", (e) =>
  545. {
  546. if (!this.client) return;
  547. this.client.isDisconnected = true;
  548. if (this.inMultiplayerSession)
  549. {
  550. this.leaveMultiplayerSession();
  551. }
  552. else
  553. {
  554. this._sendPing();
  555. }
  556. // if (this._socket && this._socket.destroy)
  557. // {
  558. // this._socket.destroy();
  559. // }
  560. });
  561. doneCallback(true);
  562. }
  563. _updateMembers()
  564. {
  565. this.sendControl("pingMembers", {});
  566. setTimeout(() =>
  567. {
  568. this._updateMembers();
  569. }, this.PING_INTERVAL);
  570. }
  571. _sendPing(startedSession = false)
  572. {
  573. const x = gui.patchView.patchRenderer.viewBox ? gui.patchView.patchRenderer.viewBox.mousePatchX : null;
  574. const y = gui.patchView.patchRenderer.viewBox ? gui.patchView.patchRenderer.viewBox.mousePatchY : null;
  575. const subPatch = gui.patchView.getCurrentSubPatch();
  576. const zoom = gui.patchView.patchRenderer.viewBox ? gui.patchView.patchRenderer.viewBox.zoom : null;
  577. const scrollX = gui.patchView.patchRenderer.viewBox ? gui.patchView.patchRenderer.viewBox.scrollX : null;
  578. const scrollY = gui.patchView.patchRenderer.viewBox ? gui.patchView.patchRenderer.viewBox.scrollY : null;
  579. const payload = {
  580. "username": gui.user.usernameLowercase,
  581. "userid": gui.user.id,
  582. "connectedSince": this._connectedSince,
  583. "inSessionSince": this._inSessionSince,
  584. "isRemoteClient": gui.isRemoteClient,
  585. "inMultiplayerSession": this.client.inMultiplayerSession,
  586. "multiplayerCapable": this.multiplayerCapable,
  587. "startedSession": startedSession
  588. };
  589. if (this.client)
  590. {
  591. payload.isPilot = this.client.isPilot;
  592. payload.following = this.client.following;
  593. if (this.client.isDisconnected)
  594. {
  595. payload.isDisconnected = true;
  596. }
  597. if (this.inMultiplayerSession)
  598. {
  599. payload.x = x;
  600. payload.y = y;
  601. payload.subpatch = subPatch;
  602. payload.zoom = zoom;
  603. payload.scrollX = scrollX;
  604. payload.scrollY = scrollY;
  605. }
  606. }
  607. if (payload.isRemoteClient && platform.talkerAPI && !payload.isDisconnected)
  608. {
  609. payload.platform = platformLib;
  610. this.sendControl("pingAnswer", payload);
  611. }
  612. else
  613. {
  614. this.sendControl("pingAnswer", payload);
  615. }
  616. }
  617. _send(channel, topic, payload)
  618. {
  619. if (!this.client) return;
  620. if (this._active && this._connected)
  621. {
  622. try
  623. {
  624. // try to serialize payload to handle errors in scconnection early
  625. JSON.stringify(payload);
  626. const finalPayload = {
  627. "token": this._token,
  628. "clientId": this.client.clientId,
  629. "username": this.client.username,
  630. topic,
  631. ...payload,
  632. };
  633. this.emitEvent("netActivityOut");
  634. const perf = gui.uiProfiler.start("[sc] send");
  635. const scTopic = channel + "/" + topic;
  636. this._logVerbose("send:", scTopic, finalPayload);
  637. this._socket.transmitPublish(scTopic, finalPayload);
  638. perf.finish();
  639. }
  640. catch (e)
  641. {
  642. this._log.info("failed to serialize object before send, ignoring", payload);
  643. }
  644. }
  645. }
  646. _handleChatChannelMsg(msg)
  647. {
  648. if (!this.client) return;
  649. this._logVerbose("received:", this.patchChannelName + "/chat", msg);
  650. if (msg.data && msg.data.senderEditorId && (msg.data.senderEditorId === gui.editorSessionId)) msg.isOwn = true;
  651. if (msg.name === "chatmsg")
  652. {
  653. this.emitEvent("onChatMessage", msg);
  654. }
  655. }
  656. _handlePacoMessage(msg)
  657. {
  658. if (!this.client) return;
  659. if (msg.clientId === this._socket.clientId) return;
  660. if (msg.data && msg.data.senderEditorId && (msg.data.senderEditorId === gui.editorSessionId)) msg.isOwn = true;
  661. this._logVerbose("received:", this.patchChannelName + "/paco", msg);
  662. if (this.inMultiplayerSession && msg.name === "paco")
  663. {
  664. if (!this.client.isRemoteClient) return;
  665. const foreignRequest = (msg.data && msg.data.vars && msg.data.vars.requestedBy && this.client) && (msg.data.vars.requestedBy !== this.clientId);
  666. if (!this._paco)
  667. {
  668. if (msg.data.event !== CABLES.PACO_LOAD)
  669. {
  670. return;
  671. }
  672. this._log.info("first paco message !");
  673. this._paco = new PacoConnector(this, this._patchConnection);
  674. this._patchConnection.connectors.push(this._paco);
  675. this._synchronizePatch(msg.data);
  676. }
  677. else if (msg.data.event === CABLES.PACO_LOAD)
  678. {
  679. if (!foreignRequest || (msg.data.vars && msg.data.vars.forceResync))
  680. {
  681. this._synchronizePatch(msg.data, msg.data.vars.forceResync);
  682. }
  683. }
  684. else
  685. {
  686. const perf = gui.uiProfiler.start("[sc] paco receive");
  687. this._paco.receive(msg.data);
  688. perf.finish();
  689. this._pacoSynced = true;
  690. this.state.emitEvent("patchSynchronized");
  691. }
  692. }
  693. else if (msg.name === "resync")
  694. {
  695. if (msg.clientId === this._socket.clientId) return;
  696. let startSessionListener = null;
  697. const resyncPatch = () =>
  698. {
  699. if (startSessionListener) this.off(startSessionListener);
  700. if (this._pacoEnabled && this.client) // && this.client.isPilot)
  701. {
  702. this._log.info("RESYNC sending paco patch....");
  703. this._startPacoSend(msg.clientId);
  704. }
  705. };
  706. if (this.inMultiplayerSession)
  707. {
  708. resyncPatch();
  709. }
  710. }
  711. }
  712. _synchronizePatch(data)
  713. {
  714. if (!this._paco) return;
  715. this._pacoSynced = false;
  716. this.state.emitEvent("startPatchSync");
  717. const perf = gui.uiProfiler.start("[sc] paco sync");
  718. const cbId = gui.corePatch().on("patchLoadEnd", () =>
  719. {
  720. this._log.verbose("patchloadend in paco");
  721. gui.corePatch().off(cbId);
  722. this._pacoSynced = true;
  723. this.state.emitEvent("patchSynchronized");
  724. perf.finish();
  725. });
  726. gui.patchView.clearPatch();
  727. this._paco.receive(data);
  728. }
  729. _handleControlChannelMessage(msg)
  730. {
  731. if (!this.client) return;
  732. this._logVerbose("received:", this.patchChannelName + "/control", msg);
  733. if (msg.data && msg.data.senderEditorId && (msg.data.senderEditorId === gui.editorSessionId)) msg.isOwn = true;
  734. if (msg.name === "pingMembers")
  735. {
  736. const timeOutSeconds = this.PING_INTERVAL * this.OWN_PINGS_TO_TIMEOUT;
  737. const pingOutTime = this.getTimestamp() - timeOutSeconds;
  738. if (this._lastPingReceived < pingOutTime)
  739. {
  740. msg.seconds = timeOutSeconds / 1000;
  741. this.emitEvent("onPingTimeout", msg);
  742. this._log.info("didn't receive ping for more than", msg.seconds, "seconds");
  743. }
  744. if (msg.clientId !== this.clientId)
  745. {
  746. this._sendPing();
  747. }
  748. else
  749. {
  750. this._lastPingReceived = msg.lastSeen;
  751. }
  752. }
  753. if (msg.name === "pingAnswer")
  754. {
  755. msg.lastSeen = this.getTimestamp();
  756. this._lastPingReceived = msg.lastSeen;
  757. this.emitEvent("onPingAnswer", msg);
  758. }
  759. if (msg.name === "pilotRequest")
  760. {
  761. if (msg.clientId === this._socket.clientId) return;
  762. this.emitEvent("onPilotRequest", msg);
  763. }
  764. if (msg.name === "reloadOp")
  765. {
  766. if (!this.inMultiplayerSession) return;
  767. if (msg.clientId === this._socket.clientId) return;
  768. this.emitEvent("reloadOp", msg);
  769. }
  770. if (msg.name === "createdSubPatchOp")
  771. {
  772. this.emitEvent("createdSubPatchOp", msg);
  773. }
  774. }
  775. _handleUiChannelMsg(msg)
  776. {
  777. if (!this.client) return;
  778. this._logVerbose("received:", this.patchChannelName + "/ui", msg);
  779. if (msg.data && msg.data.senderEditorId && (msg.data.senderEditorId === gui.editorSessionId)) msg.isOwn = true;
  780. if (msg.clientId === this._socket.clientId) return;
  781. this.emitEvent(msg.name, msg);
  782. }
  783. _handleInfoChannelMsg(msg)
  784. {
  785. if (!this.client) return;
  786. this._logVerbose("received:", this.patchChannelName + "/info", msg);
  787. if (msg.data && msg.data.senderEditorId && (msg.data.senderEditorId === gui.editorSessionId)) msg.isOwn = true;
  788. if (msg.clientId === this._socket.clientId) return;
  789. this.emitEvent("onInfoMessage", msg);
  790. }
  791. _logVerbose(prefix, channel, msg)
  792. {
  793. if (this._verboseLog)
  794. {
  795. const { token, ...logMsg } = msg;
  796. this._logVerbose(prefix, channel, logMsg);
  797. }
  798. }
  799. }