ws.js 3.6 KB

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