ws.js 3.7 KB

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