SocketHandler.class.ts 5.4 KB

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