ws.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. dispatcher: ListenerHandler;
  66. constructor() {
  67. super(url);
  68. this.dispatcher = new ListenerHandler();
  69. }
  70. on(target, cb, options) {
  71. this.dispatcher.addEventListener(
  72. target,
  73. event => cb(...event.detail),
  74. options
  75. );
  76. }
  77. dispatch(...args) {
  78. if (this.readyState !== 1)
  79. return pendingDispatches.push(() =>
  80. waitForConnectionToDispatch(...args)
  81. );
  82. const lastArg = args[args.length - 1];
  83. if (typeof lastArg === "function") {
  84. CB_REF += 1;
  85. CB_REFS[CB_REF] = lastArg;
  86. return this.send(
  87. JSON.stringify([...args.slice(0, -1), { CB_REF }])
  88. );
  89. }
  90. if (typeof lastArg === "object") {
  91. CB_REF += 1;
  92. CB_REFS[CB_REF] = lastArg.cb;
  93. PROGRESS_CB_REFS[CB_REF] = lastArg.onProgress;
  94. return this.send(
  95. JSON.stringify([
  96. ...args.slice(0, -1),
  97. { CB_REF, onProgress: true }
  98. ])
  99. );
  100. }
  101. return this.send(JSON.stringify([...args]));
  102. }
  103. }
  104. this.socket = new CustomWebSocket();
  105. store.dispatch("websockets/createSocket", this.socket);
  106. this.socket.onopen = () => {
  107. console.log("WS: SOCKET OPENED");
  108. };
  109. this.socket.onmessage = message => {
  110. const data = JSON.parse(message.data);
  111. const name = data.shift(0);
  112. if (name === "CB_REF") {
  113. const CB_REF = data.shift(0);
  114. CB_REFS[CB_REF](...data);
  115. return delete CB_REFS[CB_REF];
  116. }
  117. if (name === "PROGRESS_CB_REF") {
  118. const PROGRESS_CB_REF = data.shift(0);
  119. PROGRESS_CB_REFS[PROGRESS_CB_REF](...data);
  120. }
  121. if (name === "ERROR") console.log("WS: SOCKET ERROR:", data[0]);
  122. return this.socket.dispatcher.dispatchEvent(
  123. new CustomEvent(name, {
  124. detail: data
  125. })
  126. );
  127. };
  128. this.socket.onclose = () => {
  129. console.log("WS: SOCKET CLOSED");
  130. ready = false;
  131. onDisconnect.temp.forEach(cb => cb());
  132. onDisconnect.persist.forEach(cb => cb());
  133. // try to reconnect every 1000ms
  134. setTimeout(() => this.init(url), 1000);
  135. };
  136. this.socket.onerror = err => {
  137. console.log("WS: SOCKET ERROR", err);
  138. };
  139. if (firstInit) {
  140. firstInit = false;
  141. this.socket.on("ready", () => {
  142. console.log("WS: SOCKET READY");
  143. onConnect.forEach(cb => cb());
  144. ready = true;
  145. setTimeout(() => {
  146. // dispatches that were attempted while the server was offline
  147. pendingDispatches.forEach(cb => cb());
  148. pendingDispatches = [];
  149. }, 150); // small delay between readyState being 1 and the server actually receiving dispatches
  150. });
  151. }
  152. }
  153. };