SocketHandler.class.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import ListenerHandler from "@/classes/ListenerHandler.class";
  2. import { useUserAuthStore } from "@/stores/userAuth";
  3. import utils from "@/utils";
  4. export default class SocketHandler {
  5. socket?: WebSocket;
  6. url: string;
  7. dispatcher: ListenerHandler;
  8. onConnectCbs: {
  9. temp: (() => void)[];
  10. persist: (() => void)[];
  11. };
  12. ready: boolean;
  13. firstInit: boolean;
  14. pendingDispatches: (() => void)[];
  15. onDisconnectCbs: {
  16. temp: (() => void)[];
  17. persist: (() => void)[];
  18. };
  19. CB_REFS: Record<string, (...args: any[]) => void>;
  20. PROGRESS_CB_REFS: Record<string, (...args: any[]) => void>;
  21. data: {
  22. dispatch?: Record<string, (...args: any[]) => any>;
  23. progress?: Record<string, (...args: any[]) => any>;
  24. on?: Record<string, any>;
  25. }; // Mock only
  26. executeDispatch: boolean; // Mock only
  27. trigger: (type: string, target: string, data?: any) => void; // Mock only
  28. constructor(url: string) {
  29. this.dispatcher = new ListenerHandler();
  30. this.url = url;
  31. this.onConnectCbs = {
  32. temp: [],
  33. persist: []
  34. };
  35. this.ready = false;
  36. this.firstInit = true;
  37. this.pendingDispatches = [];
  38. this.onDisconnectCbs = {
  39. temp: [],
  40. persist: []
  41. };
  42. // references for when a dispatch event is ready to callback from server to client
  43. this.CB_REFS = {};
  44. this.PROGRESS_CB_REFS = {};
  45. this.init();
  46. // Mock only
  47. this.data = {};
  48. this.executeDispatch = true;
  49. this.trigger = () => {};
  50. }
  51. init() {
  52. this.socket = new WebSocket(this.url);
  53. const userAuthStore = useUserAuthStore();
  54. this.socket.onopen = () => {
  55. console.log("WS: SOCKET OPENED");
  56. };
  57. this.socket.onmessage = message => {
  58. const data = JSON.parse(message.data);
  59. const name = data.shift(0);
  60. if (name === "CB_REF") {
  61. const CB_REF = data.shift(0);
  62. this.CB_REFS[CB_REF](...data);
  63. return delete this.CB_REFS[CB_REF];
  64. }
  65. if (name === "PROGRESS_CB_REF") {
  66. const PROGRESS_CB_REF = data.shift(0);
  67. this.PROGRESS_CB_REFS[PROGRESS_CB_REF](...data);
  68. }
  69. if (name === "ERROR") console.log("WS: SOCKET ERROR:", data[0]);
  70. return this.dispatcher.dispatchEvent(
  71. new CustomEvent(name, {
  72. detail: data
  73. })
  74. );
  75. };
  76. this.socket.onclose = () => {
  77. console.log("WS: SOCKET CLOSED");
  78. this.ready = false;
  79. this.firstInit = false;
  80. this.onDisconnectCbs.temp.forEach(cb => cb());
  81. this.onDisconnectCbs.persist.forEach(cb => cb());
  82. // try to reconnect every 1000ms, if the user isn't banned
  83. if (!userAuthStore.banned) setTimeout(() => this.init(), 1000);
  84. };
  85. this.socket.onerror = err => {
  86. console.log("WS: SOCKET ERROR", err);
  87. };
  88. if (this.firstInit) {
  89. this.firstInit = false;
  90. this.on("ready", () => {
  91. console.log("WS: SOCKET READY");
  92. this.onConnectCbs.temp.forEach(cb => cb());
  93. this.onConnectCbs.persist.forEach(cb => cb());
  94. this.ready = true;
  95. setTimeout(() => {
  96. // dispatches that were attempted while the server was offline
  97. this.pendingDispatches.forEach(cb => cb());
  98. this.pendingDispatches = [];
  99. }, 150); // small delay between readyState being 1 and the server actually receiving dispatches
  100. userAuthStore.updatePermissions();
  101. });
  102. }
  103. }
  104. on(
  105. target: string,
  106. cb: (...args: any[]) => any,
  107. options?: EventListenerOptions & {
  108. replaceable?: boolean;
  109. modalUuid?: string;
  110. }
  111. ) {
  112. this.dispatcher.addEventListener(
  113. target,
  114. (event: CustomEvent) => cb(...event.detail),
  115. options
  116. );
  117. }
  118. dispatch(...args: [string, ...any[]]) {
  119. if (!this.socket || this.socket.readyState !== 1) {
  120. this.pendingDispatches.push(() => this.dispatch(...args));
  121. return undefined;
  122. }
  123. const lastArg = args[args.length - 1];
  124. const CB_REF = utils.guid();
  125. if (typeof lastArg === "function") {
  126. this.CB_REFS[CB_REF] = lastArg;
  127. return this.socket.send(
  128. JSON.stringify([...args.slice(0, -1), { CB_REF }])
  129. );
  130. }
  131. if (typeof lastArg === "object") {
  132. this.CB_REFS[CB_REF] = lastArg.cb;
  133. this.PROGRESS_CB_REFS[CB_REF] = lastArg.onProgress;
  134. return this.socket.send(
  135. JSON.stringify([
  136. ...args.slice(0, -1),
  137. { CB_REF, onProgress: true }
  138. ])
  139. );
  140. }
  141. return this.socket.send(JSON.stringify([...args]));
  142. }
  143. onConnect(cb: (...args: any[]) => any, persist = false) {
  144. if (this.socket && this.socket.readyState === 1 && this.ready) cb();
  145. if (persist) this.onConnectCbs.persist.push(cb);
  146. else this.onConnectCbs.temp.push(cb);
  147. }
  148. onDisconnect(cb: (...args: any[]) => any, persist = false) {
  149. if (persist) this.onDisconnectCbs.persist.push(cb);
  150. else this.onDisconnectCbs.temp.push(cb);
  151. }
  152. clearCallbacks() {
  153. this.onConnectCbs.temp = [];
  154. this.onDisconnectCbs.temp = [];
  155. }
  156. destroyListeners() {
  157. Object.keys(this.CB_REFS).forEach(id => {
  158. if (
  159. id.indexOf("$event:") !== -1 &&
  160. id.indexOf("$event:keep.") === -1
  161. )
  162. delete this.CB_REFS[id];
  163. });
  164. Object.keys(this.PROGRESS_CB_REFS).forEach(id => {
  165. if (
  166. id.indexOf("$event:") !== -1 &&
  167. id.indexOf("$event:keep.") === -1
  168. )
  169. delete this.PROGRESS_CB_REFS[id];
  170. });
  171. // destroy all listeners that aren't site-wide
  172. Object.keys(this.dispatcher.listeners).forEach(type => {
  173. if (type.indexOf("keep.") === -1 && type !== "ready")
  174. delete this.dispatcher.listeners[type];
  175. });
  176. }
  177. destroyModalListeners(modalUuid: string) {
  178. // destroy all listeners for a specific modal
  179. Object.keys(this.dispatcher.listeners).forEach(type =>
  180. this.dispatcher.listeners[type].forEach((element, index) => {
  181. if (element.options && element.options.modalUuid === modalUuid)
  182. this.dispatcher.listeners[type].splice(index, 1);
  183. })
  184. );
  185. }
  186. }