ws.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // eslint-disable-next-line import/no-cycle
  2. import store from "./store";
  3. const onConnect = {
  4. temp: [],
  5. persist: []
  6. };
  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(...args) {
  19. if (args[0] === true) onConnect.persist.push(args[1]);
  20. else onConnect.temp.push(args[0]);
  21. },
  22. onDisconnect(...args) {
  23. if (args[0] === true) onDisconnect.persist.push(args[1]);
  24. else onDisconnect.temp.push(args[0]);
  25. },
  26. clear: () => {
  27. onConnect.temp = [];
  28. onDisconnect.temp = [];
  29. },
  30. removeAllListeners: () =>
  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. init(url) {
  39. // ensures correct context of socket object when dispatching (because socket object is recreated on reconnection)
  40. const waitForConnectionToDispatch = (...args) =>
  41. this.socket.dispatch(...args);
  42. class ListenerHandler extends EventTarget {
  43. constructor() {
  44. super();
  45. this.listeners = {};
  46. }
  47. addEventListener(type, cb) {
  48. if (!(type in this.listeners)) this.listeners[type] = []; // add the listener type to listeners object
  49. this.listeners[type].push(cb); // push the callback
  50. }
  51. // eslint-disable-next-line consistent-return
  52. removeEventListener(type, cb) {
  53. if (!(type in this.listeners)) return true; // event type doesn't exist
  54. const stack = this.listeners[type];
  55. stack.forEach((element, index) => {
  56. if (element === cb) stack.splice(index, 1);
  57. });
  58. }
  59. dispatchEvent(event) {
  60. if (!(event.type in this.listeners)) return true; // event type doesn't exist
  61. const stack = this.listeners[event.type].slice();
  62. stack.forEach(element => element.call(this, event));
  63. return !event.defaultPrevented;
  64. }
  65. }
  66. class CustomWebSocket extends WebSocket {
  67. constructor() {
  68. super(url);
  69. this.dispatcher = new ListenerHandler();
  70. }
  71. on(target, cb) {
  72. this.dispatcher.addEventListener(target, event =>
  73. cb(...event.detail)
  74. );
  75. }
  76. dispatch(...args) {
  77. CB_REF += 1;
  78. if (this.readyState !== 1)
  79. return pendingDispatches.push(() =>
  80. waitForConnectionToDispatch(...args)
  81. );
  82. const cb = args[args.length - 1];
  83. if (typeof cb === "function") {
  84. CB_REFS[CB_REF] = cb;
  85. return this.send(
  86. JSON.stringify([...args.slice(0, -1), { CB_REF }])
  87. );
  88. }
  89. return this.send(JSON.stringify([...args]));
  90. }
  91. }
  92. this.socket = new CustomWebSocket();
  93. store.dispatch("websockets/createSocket", this.socket);
  94. this.socket.onopen = () => {
  95. console.log("WS: SOCKET CONNECTED");
  96. setTimeout(() => {
  97. onConnect.temp.forEach(cb => cb());
  98. // dispatches that were attempted while the server was offline
  99. pendingDispatches.forEach(cb => cb());
  100. pendingDispatches = [];
  101. onConnect.persist.forEach(cb => cb());
  102. }, 50); // small delay between readyState being 1 and the server actually receiving dispatches
  103. };
  104. this.socket.onmessage = message => {
  105. const data = JSON.parse(message.data);
  106. const name = data.shift(0);
  107. if (name === "CB_REF") {
  108. const CB_REF = data.shift(0);
  109. CB_REFS[CB_REF](...data);
  110. return delete CB_REFS[CB_REF];
  111. }
  112. if (name === "ERROR") console.log("WS: SOCKET ERROR:", data[0]);
  113. return this.socket.dispatcher.dispatchEvent(
  114. new CustomEvent(name, {
  115. detail: data
  116. })
  117. );
  118. };
  119. this.socket.onclose = () => {
  120. console.log("WS: SOCKET DISCONNECTED");
  121. onDisconnect.temp.forEach(cb => cb());
  122. onDisconnect.persist.forEach(cb => cb());
  123. // try to reconnect every 1000ms
  124. setTimeout(() => this.init(url), 1000);
  125. };
  126. this.socket.onerror = err => {
  127. console.log("WS: SOCKET ERROR", err);
  128. // new Toast("Cannot perform this action at this time.");
  129. };
  130. }
  131. };