ws.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // eslint-disable-next-line import/no-cycle
  2. import store from "./store";
  3. import ListenerHandler from "./classes/ListenerHandler.class";
  4. const onConnect = [];
  5. let ready = false;
  6. let firstInit = true;
  7. let pendingDispatches = [];
  8. const onDisconnect = {
  9. temp: [],
  10. persist: []
  11. };
  12. // references for when a dispatch event is ready to callback from server to client
  13. const CB_REFS = {};
  14. let CB_REF = 0;
  15. const PROGRESS_CB_REFS = {};
  16. export default {
  17. socket: null,
  18. dispatcher: null,
  19. onConnect(cb) {
  20. if (this.socket.readyState === 1 && ready) cb();
  21. return onConnect.push(cb);
  22. },
  23. onDisconnect(...args) {
  24. if (args[0] === true) onDisconnect.persist.push(args[1]);
  25. else onDisconnect.temp.push(args[0]);
  26. },
  27. clearCallbacks: () => {
  28. onDisconnect.temp = [];
  29. },
  30. destroyListeners() {
  31. Object.keys(CB_REFS).forEach(id => {
  32. if (
  33. id.indexOf("$event:") !== -1 &&
  34. id.indexOf("$event:keep.") === -1
  35. )
  36. delete CB_REFS[id];
  37. });
  38. Object.keys(PROGRESS_CB_REFS).forEach(id => {
  39. if (
  40. id.indexOf("$event:") !== -1 &&
  41. id.indexOf("$event:keep.") === -1
  42. )
  43. delete PROGRESS_CB_REFS[id];
  44. });
  45. // destroy all listeners that aren't site-wide
  46. Object.keys(this.socket.dispatcher.listeners).forEach(type => {
  47. if (type.indexOf("keep.") === -1 && type !== "ready")
  48. delete this.socket.dispatcher.listeners[type];
  49. });
  50. },
  51. destroyModalListeners(modalUuid) {
  52. // destroy all listeners for a specific modal
  53. Object.keys(this.socket.dispatcher.listeners).forEach(type =>
  54. this.socket.dispatcher.listeners[type].forEach((element, index) => {
  55. if (element.options && element.options.modalUuid === modalUuid)
  56. this.socket.dispatcher.listeners[type].splice(index, 1);
  57. })
  58. );
  59. },
  60. init(url) {
  61. // ensures correct context of socket object when dispatching (because socket object is recreated on reconnection)
  62. const waitForConnectionToDispatch = (...args) =>
  63. this.socket.dispatch(...args);
  64. class CustomWebSocket extends WebSocket {
  65. constructor() {
  66. super(url);
  67. this.dispatcher = new ListenerHandler();
  68. }
  69. on(target, cb, options) {
  70. this.dispatcher.addEventListener(
  71. target,
  72. event => cb(...event.detail),
  73. options
  74. );
  75. }
  76. dispatch(...args) {
  77. if (this.readyState !== 1)
  78. return pendingDispatches.push(() =>
  79. waitForConnectionToDispatch(...args)
  80. );
  81. const lastArg = args[args.length - 1];
  82. if (typeof lastArg === "function") {
  83. CB_REF += 1;
  84. CB_REFS[CB_REF] = lastArg;
  85. return this.send(
  86. JSON.stringify([...args.slice(0, -1), { CB_REF }])
  87. );
  88. }
  89. if (typeof lastArg === "object") {
  90. CB_REF += 1;
  91. CB_REFS[CB_REF] = lastArg.cb;
  92. PROGRESS_CB_REFS[CB_REF] = lastArg.onProgress;
  93. return this.send(
  94. JSON.stringify([
  95. ...args.slice(0, -1),
  96. { CB_REF, onProgress: true }
  97. ])
  98. );
  99. }
  100. return this.send(JSON.stringify([...args]));
  101. }
  102. }
  103. this.socket = new CustomWebSocket();
  104. store.dispatch("websockets/createSocket", this.socket);
  105. this.socket.onopen = () => {
  106. console.log("WS: SOCKET OPENED");
  107. };
  108. this.socket.onmessage = message => {
  109. const data = JSON.parse(message.data);
  110. const name = data.shift(0);
  111. if (name === "CB_REF") {
  112. const CB_REF = data.shift(0);
  113. CB_REFS[CB_REF](...data);
  114. return delete CB_REFS[CB_REF];
  115. }
  116. if (name === "PROGRESS_CB_REF") {
  117. const PROGRESS_CB_REF = data.shift(0);
  118. PROGRESS_CB_REFS[PROGRESS_CB_REF](...data);
  119. }
  120. if (name === "ERROR") console.log("WS: SOCKET ERROR:", data[0]);
  121. return this.socket.dispatcher.dispatchEvent(
  122. new CustomEvent(name, {
  123. detail: data
  124. })
  125. );
  126. };
  127. this.socket.onclose = () => {
  128. console.log("WS: SOCKET CLOSED");
  129. ready = false;
  130. onDisconnect.temp.forEach(cb => cb());
  131. onDisconnect.persist.forEach(cb => cb());
  132. // try to reconnect every 1000ms
  133. setTimeout(() => this.init(url), 1000);
  134. };
  135. this.socket.onerror = err => {
  136. console.log("WS: SOCKET ERROR", err);
  137. };
  138. if (firstInit) {
  139. firstInit = false;
  140. this.socket.on("ready", () => {
  141. console.log("WS: SOCKET READY");
  142. onConnect.forEach(cb => cb());
  143. ready = true;
  144. setTimeout(() => {
  145. // dispatches that were attempted while the server was offline
  146. pendingDispatches.forEach(cb => cb());
  147. pendingDispatches = [];
  148. }, 150); // small delay between readyState being 1 and the server actually receiving dispatches
  149. });
  150. }
  151. }
  152. };