ws.ts 4.6 KB

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