SocketHandler.class.ts 5.6 KB

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