SocketHandler.class.ts 5.6 KB

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