فهرست منبع

refactor: Consolidated and separated CustomWebSocket

Owen Diffey 2 سال پیش
والد
کامیت
95aa420502
35فایلهای تغییر یافته به همراه367 افزوده شده و 374 حذف شده
  1. 2 3
      frontend/src/App.vue
  2. 202 0
      frontend/src/classes/CustomWebSocket.class.ts
  3. 1 2
      frontend/src/components/AddToPlaylistDropdown.vue
  4. 1 2
      frontend/src/components/AdvancedTable.vue
  5. 1 2
      frontend/src/components/PlaylistTabBase.vue
  6. 1 2
      frontend/src/components/modals/BulkActions.vue
  7. 1 2
      frontend/src/components/modals/EditNews.vue
  8. 1 2
      frontend/src/components/modals/EditPlaylist/index.vue
  9. 1 2
      frontend/src/components/modals/EditSong/index.vue
  10. 1 2
      frontend/src/components/modals/EditUser.vue
  11. 1 2
      frontend/src/components/modals/ImportAlbum.vue
  12. 1 2
      frontend/src/components/modals/Report.vue
  13. 1 2
      frontend/src/components/modals/ViewApiRequest.vue
  14. 1 2
      frontend/src/components/modals/ViewPunishment.vue
  15. 1 2
      frontend/src/components/modals/ViewReport.vue
  16. 1 2
      frontend/src/components/modals/ViewYoutubeVideo.vue
  17. 5 3
      frontend/src/composables/useReports.ts
  18. 1 2
      frontend/src/composables/useSortablePlaylists.ts
  19. 64 62
      frontend/src/main.ts
  20. 1 2
      frontend/src/pages/Admin/Statistics.vue
  21. 1 2
      frontend/src/pages/Admin/YouTube/index.vue
  22. 1 2
      frontend/src/pages/Home.vue
  23. 1 3
      frontend/src/pages/News.vue
  24. 1 2
      frontend/src/pages/Profile/Tabs/RecentActivity.vue
  25. 1 2
      frontend/src/pages/Profile/index.vue
  26. 1 2
      frontend/src/pages/Settings/Tabs/Preferences.vue
  27. 1 2
      frontend/src/pages/Settings/index.vue
  28. 2 3
      frontend/src/pages/Station/index.vue
  29. 3 2
      frontend/src/stores/manageStation.ts
  30. 7 4
      frontend/src/stores/modals.ts
  31. 3 2
      frontend/src/stores/station.ts
  32. 29 27
      frontend/src/stores/userAuth.ts
  33. 27 17
      frontend/src/stores/websockets.ts
  34. 0 7
      frontend/src/types/customWebSocket.ts
  35. 0 197
      frontend/src/ws.ts

+ 2 - 3
frontend/src/App.vue

@@ -7,7 +7,6 @@ import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import ws from "@/ws";
 import aw from "@/aw";
 import aw from "@/aw";
 import keyboardShortcuts from "@/keyboardShortcuts";
 import keyboardShortcuts from "@/keyboardShortcuts";
 
 
@@ -179,7 +178,7 @@ onMounted(async () => {
 
 
 	disconnectedMessage.value.hide();
 	disconnectedMessage.value.hide();
 
 
-	ws.onConnect(() => {
+	socket.onConnect(() => {
 		socketConnected.value = true;
 		socketConnected.value = true;
 
 
 		socket.dispatch("users.getPreferences", res => {
 		socket.dispatch("users.getPreferences", res => {
@@ -239,7 +238,7 @@ onMounted(async () => {
 		});
 		});
 	});
 	});
 
 
-	ws.onDisconnect(true, () => {
+	socket.onDisconnect(true, () => {
 		socketConnected.value = false;
 		socketConnected.value = false;
 	});
 	});
 
 

+ 202 - 0
frontend/src/classes/CustomWebSocket.class.ts

@@ -0,0 +1,202 @@
+import ListenerHandler from "@/classes/ListenerHandler.class";
+import { useUserAuthStore } from "@/stores/userAuth";
+
+export default class CustomWebSocket extends WebSocket {
+	dispatcher: ListenerHandler;
+
+	onConnectCbs: any[];
+
+	ready: boolean;
+
+	firstInit: boolean;
+
+	pendingDispatches: any[];
+
+	onDisconnectCbs: {
+		temp: any[];
+		persist: any[];
+	};
+
+	CB_REF: number;
+
+	CB_REFS: object;
+
+	PROGRESS_CB_REFS: object;
+
+	constructor(url) {
+		super(url);
+
+		this.dispatcher = new ListenerHandler();
+
+		this.onConnectCbs = [];
+		this.ready = false;
+		this.firstInit = true;
+
+		this.pendingDispatches = [];
+
+		this.onDisconnectCbs = {
+			temp: [],
+			persist: []
+		};
+
+		// references for when a dispatch event is ready to callback from server to client
+		this.CB_REF = 0;
+		this.CB_REFS = {};
+		this.PROGRESS_CB_REFS = {};
+
+		this.init();
+	}
+
+	init() {
+		const userAuthStore = useUserAuthStore();
+
+		this.onopen = () => {
+			console.log("WS: SOCKET OPENED");
+		};
+
+		this.onmessage = message => {
+			const data = JSON.parse(message.data);
+			const name = data.shift(0);
+
+			if (name === "CB_REF") {
+				const CB_REF = data.shift(0);
+				this.CB_REFS[CB_REF](...data);
+				return delete this.CB_REFS[CB_REF];
+			}
+			if (name === "PROGRESS_CB_REF") {
+				const PROGRESS_CB_REF = data.shift(0);
+				this.PROGRESS_CB_REFS[PROGRESS_CB_REF](...data);
+			}
+
+			if (name === "ERROR") console.log("WS: SOCKET ERROR:", data[0]);
+
+			return this.dispatcher.dispatchEvent(
+				new CustomEvent(name, {
+					detail: data
+				})
+			);
+		};
+
+		this.onclose = () => {
+			console.log("WS: SOCKET CLOSED");
+
+			this.ready = false;
+
+			this.onDisconnectCbs.temp.forEach(cb => cb());
+			this.onDisconnectCbs.persist.forEach(cb => cb());
+
+			// try to reconnect every 1000ms, if the user isn't banned
+			if (!userAuthStore.banned) setTimeout(() => this.init(), 1000);
+		};
+
+		this.onerror = err => {
+			console.log("WS: SOCKET ERROR", err);
+		};
+
+		if (this.firstInit) {
+			this.firstInit = false;
+			this.on("ready", () => {
+				console.log("WS: SOCKET READY");
+
+				this.onConnectCbs.forEach(cb => cb());
+
+				this.ready = true;
+
+				setTimeout(() => {
+					// dispatches that were attempted while the server was offline
+					this.pendingDispatches.forEach(cb => cb());
+					this.pendingDispatches = [];
+				}, 150); // small delay between readyState being 1 and the server actually receiving dispatches
+
+				userAuthStore.updatePermissions();
+			});
+		}
+	}
+
+	on(target, cb, options?) {
+		this.dispatcher.addEventListener(
+			target,
+			event => cb(...event.detail),
+			options
+		);
+	}
+
+	dispatch(...args) {
+		if (this.readyState !== 1)
+			return this.pendingDispatches.push(() => this.dispatch(...args));
+
+		const lastArg = args[args.length - 1];
+
+		if (typeof lastArg === "function") {
+			this.CB_REF += 1;
+			this.CB_REFS[this.CB_REF] = lastArg;
+
+			return this.send(
+				JSON.stringify([...args.slice(0, -1), { CB_REF: this.CB_REF }])
+			);
+		}
+		if (typeof lastArg === "object") {
+			this.CB_REF += 1;
+			this.CB_REFS[this.CB_REF] = lastArg.cb;
+			this.PROGRESS_CB_REFS[this.CB_REF] = lastArg.onProgress;
+
+			return this.send(
+				JSON.stringify([
+					...args.slice(0, -1),
+					{ CB_REF: this.CB_REF, onProgress: true }
+				])
+			);
+		}
+
+		return this.send(JSON.stringify([...args]));
+	}
+
+	onConnect(cb) {
+		if (this.readyState === 1 && this.ready) cb();
+
+		return this.onConnectCbs.push(cb);
+	}
+
+	onDisconnect(...args) {
+		if (args[0] === true) this.onDisconnectCbs.persist.push(args[1]);
+		else this.onDisconnectCbs.temp.push(args[0]);
+	}
+
+	clearCallbacks() {
+		this.onDisconnectCbs.temp = [];
+	}
+
+	destroyListeners() {
+		Object.keys(this.CB_REFS).forEach(id => {
+			if (
+				id.indexOf("$event:") !== -1 &&
+				id.indexOf("$event:keep.") === -1
+			)
+				delete this.CB_REFS[id];
+		});
+
+		Object.keys(this.PROGRESS_CB_REFS).forEach(id => {
+			if (
+				id.indexOf("$event:") !== -1 &&
+				id.indexOf("$event:keep.") === -1
+			)
+				delete this.PROGRESS_CB_REFS[id];
+		});
+
+		// destroy all listeners that aren't site-wide
+		Object.keys(this.dispatcher.listeners).forEach(type => {
+			if (type.indexOf("keep.") === -1 && type !== "ready")
+				delete this.dispatcher.listeners[type];
+		});
+	}
+
+	destroyModalListeners(modalUuid) {
+		// destroy all listeners for a specific modal
+		Object.keys(this.dispatcher.listeners).forEach(type =>
+			this.dispatcher.listeners[type].forEach((element, index) => {
+				if (element.options && element.options.modalUuid === modalUuid)
+					this.dispatcher.listeners[type].splice(index, 1);
+			})
+		);
+	}
+}

+ 1 - 2
frontend/src/components/AddToPlaylistDropdown.vue

@@ -5,7 +5,6 @@ import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserPlaylistsStore } from "@/stores/userPlaylists";
 import { useUserPlaylistsStore } from "@/stores/userPlaylists";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import ws from "@/ws";
 
 
 const props = defineProps({
 const props = defineProps({
 	song: {
 	song: {
@@ -71,7 +70,7 @@ const createPlaylist = () => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 
 
 	socket.on("event:playlist.created", res => addPlaylist(res.data.playlist), {
 	socket.on("event:playlist.created", res => addPlaylist(res.data.playlist), {
 		replaceable: true
 		replaceable: true

+ 1 - 2
frontend/src/components/AdvancedTable.vue

@@ -17,7 +17,6 @@ import { DraggableList } from "vue-draggable-list";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import keyboardShortcuts from "@/keyboardShortcuts";
 import keyboardShortcuts from "@/keyboardShortcuts";
-import ws from "@/ws";
 import { useDragBox } from "@/composables/useDragBox";
 import { useDragBox } from "@/composables/useDragBox";
 import {
 import {
 	TableColumn,
 	TableColumn,
@@ -934,7 +933,7 @@ onMounted(async () => {
 		}
 		}
 	}
 	}
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 
 
 	// TODO, this doesn't address special properties
 	// TODO, this doesn't address special properties
 	if (props.events && props.events.updated)
 	if (props.events && props.events.updated)

+ 1 - 2
frontend/src/components/PlaylistTabBase.vue

@@ -2,7 +2,6 @@
 import { defineAsyncComponent, ref, reactive, computed, onMounted } from "vue";
 import { defineAsyncComponent, ref, reactive, computed, onMounted } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { storeToRefs } from "pinia";
-import ws from "@/ws";
 
 
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useStationStore } from "@/stores/station";
 import { useStationStore } from "@/stores/station";
@@ -296,7 +295,7 @@ const searchForPlaylists = page => {
 onMounted(() => {
 onMounted(() => {
 	showTab("search");
 	showTab("search");
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 </script>
 </script>
 
 

+ 1 - 2
frontend/src/components/modals/BulkActions.vue

@@ -6,7 +6,6 @@ import { useWebsocketsStore } from "@/stores/websockets";
 import { useLongJobsStore } from "@/stores/longJobs";
 import { useLongJobsStore } from "@/stores/longJobs";
 import { useBulkActionsStore } from "@/stores/bulkActions";
 import { useBulkActionsStore } from "@/stores/bulkActions";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import ws from "@/ws";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const AutoSuggest = defineAsyncComponent(
 const AutoSuggest = defineAsyncComponent(
@@ -96,7 +95,7 @@ onBeforeUnmount(() => {
 });
 });
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 </script>
 </script>
 
 

+ 1 - 2
frontend/src/components/modals/EditNews.vue

@@ -8,7 +8,6 @@ import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useEditNewsStore } from "@/stores/editNews";
 import { useEditNewsStore } from "@/stores/editNews";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import ws from "@/ws";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const SaveButton = defineAsyncComponent(
 const SaveButton = defineAsyncComponent(
@@ -142,7 +141,7 @@ onMounted(() => {
 		}
 		}
 	});
 	});
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 </script>
 </script>
 
 

+ 1 - 2
frontend/src/components/modals/EditPlaylist/index.vue

@@ -14,7 +14,6 @@ import { useEditPlaylistStore } from "@/stores/editPlaylist";
 import { useStationStore } from "@/stores/station";
 import { useStationStore } from "@/stores/station";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import ws from "@/ws";
 import utils from "@/utils";
 import utils from "@/utils";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
@@ -257,7 +256,7 @@ const clearAndRefillGenrePlaylist = () => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 
 
 	socket.on(
 	socket.on(
 		"event:playlist.song.added",
 		"event:playlist.song.added",

+ 1 - 2
frontend/src/components/modals/EditSong/index.vue

@@ -10,7 +10,6 @@ import {
 } from "vue";
 } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
 import aw from "@/aw";
 import aw from "@/aw";
-import ws from "@/ws";
 import validation from "@/validation";
 import validation from "@/validation";
 import keyboardShortcuts from "@/keyboardShortcuts";
 import keyboardShortcuts from "@/keyboardShortcuts";
 
 
@@ -1220,7 +1219,7 @@ onMounted(async () => {
 
 
 	useHTTPS.value = await lofig.get("cookie.secure");
 	useHTTPS.value = await lofig.get("cookie.secure");
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 
 
 	let volume = parseFloat(localStorage.getItem("volume"));
 	let volume = parseFloat(localStorage.getItem("volume"));
 	volume = typeof volume === "number" && !Number.isNaN(volume) ? volume : 20;
 	volume = typeof volume === "number" && !Number.isNaN(volume) ? volume : 20;

+ 1 - 2
frontend/src/components/modals/EditUser.vue

@@ -8,7 +8,6 @@ import {
 } from "vue";
 } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { storeToRefs } from "pinia";
-import ws from "@/ws";
 import validation from "@/validation";
 import validation from "@/validation";
 import { useEditUserStore } from "@/stores/editUser";
 import { useEditUserStore } from "@/stores/editUser";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
@@ -166,7 +165,7 @@ watch(
 );
 );
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {

+ 1 - 2
frontend/src/components/modals/ImportAlbum.vue

@@ -12,7 +12,6 @@ import { DraggableList } from "vue-draggable-list";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import { useImportAlbumStore } from "@/stores/importAlbum";
 import { useImportAlbumStore } from "@/stores/importAlbum";
-import ws from "@/ws";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const SongItem = defineAsyncComponent(
 const SongItem = defineAsyncComponent(
@@ -332,7 +331,7 @@ const updateTrackSong = updatedSong => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 
 
 	socket.on("event:admin.song.updated", res => {
 	socket.on("event:admin.song.updated", res => {
 		updateTrackSong(res.data.song);
 		updateTrackSong(res.data.song);

+ 1 - 2
frontend/src/components/modals/Report.vue

@@ -5,7 +5,6 @@ import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import { useReportStore } from "@/stores/report";
 import { useReportStore } from "@/stores/report";
-import ws from "@/ws";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const SongItem = defineAsyncComponent(
 const SongItem = defineAsyncComponent(
@@ -204,7 +203,7 @@ const create = () => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {

+ 1 - 2
frontend/src/components/modals/ViewApiRequest.vue

@@ -6,7 +6,6 @@ import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import { useViewApiRequestStore } from "@/stores/viewApiRequest";
 import { useViewApiRequestStore } from "@/stores/viewApiRequest";
-import ws from "@/ws";
 import "vue-json-pretty/lib/styles.css";
 import "vue-json-pretty/lib/styles.css";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
@@ -69,7 +68,7 @@ const remove = () => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {

+ 1 - 2
frontend/src/components/modals/ViewPunishment.vue

@@ -5,7 +5,6 @@ import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import { useViewPunishmentStore } from "@/stores/viewPunishment";
 import { useViewPunishmentStore } from "@/stores/viewPunishment";
-import ws from "@/ws";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const PunishmentItem = defineAsyncComponent(
 const PunishmentItem = defineAsyncComponent(
@@ -64,7 +63,7 @@ const deactivatePunishment = event => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {

+ 1 - 2
frontend/src/components/modals/ViewReport.vue

@@ -13,7 +13,6 @@ import { useModalsStore } from "@/stores/modals";
 import { useViewReportStore } from "@/stores/viewReport";
 import { useViewReportStore } from "@/stores/viewReport";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useReports } from "@/composables/useReports";
 import { useReports } from "@/composables/useReports";
-import ws from "@/ws";
 import { Report } from "@/types/report";
 import { Report } from "@/types/report";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
@@ -140,7 +139,7 @@ watch(
 );
 );
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {

+ 1 - 2
frontend/src/components/modals/ViewYoutubeVideo.vue

@@ -3,7 +3,6 @@ import { defineAsyncComponent, onMounted, onBeforeUnmount, ref } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { storeToRefs } from "pinia";
 import aw from "@/aw";
 import aw from "@/aw";
-import ws from "@/ws";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import { useViewYoutubeVideoStore } from "@/stores/viewYoutubeVideo";
 import { useViewYoutubeVideoStore } from "@/stores/viewYoutubeVideo";
@@ -457,7 +456,7 @@ const init = () => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {

+ 5 - 3
frontend/src/composables/useReports.ts

@@ -1,10 +1,12 @@
 import Toast from "toasters";
 import Toast from "toasters";
-import ws from "@/ws";
+import { useWebsocketsStore } from "@/stores/websockets";
 
 
 export const useReports = () => {
 export const useReports = () => {
+	const { socket } = useWebsocketsStore();
+
 	const resolveReport = ({ reportId, value }) =>
 	const resolveReport = ({ reportId, value }) =>
 		new Promise((resolve, reject) => {
 		new Promise((resolve, reject) => {
-			ws.socket.dispatch("reports.resolve", reportId, value, res => {
+			socket.dispatch("reports.resolve", reportId, value, res => {
 				new Toast(res.message);
 				new Toast(res.message);
 				if (res.status === "success")
 				if (res.status === "success")
 					return resolve({ status: "success" });
 					return resolve({ status: "success" });
@@ -14,7 +16,7 @@ export const useReports = () => {
 
 
 	const removeReport = reportId =>
 	const removeReport = reportId =>
 		new Promise((resolve, reject) => {
 		new Promise((resolve, reject) => {
-			ws.socket.dispatch("reports.remove", reportId, res => {
+			socket.dispatch("reports.remove", reportId, res => {
 				new Toast(res.message);
 				new Toast(res.message);
 				if (res.status === "success")
 				if (res.status === "success")
 					return resolve({ status: "success" });
 					return resolve({ status: "success" });

+ 1 - 2
frontend/src/composables/useSortablePlaylists.ts

@@ -5,7 +5,6 @@ import { DraggableList } from "vue-draggable-list";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserPlaylistsStore } from "@/stores/userPlaylists";
 import { useUserPlaylistsStore } from "@/stores/userPlaylists";
-import ws from "@/ws";
 
 
 export const useSortablePlaylists = () => {
 export const useSortablePlaylists = () => {
 	const orderOfPlaylists = ref([]);
 	const orderOfPlaylists = ref([]);
@@ -61,7 +60,7 @@ export const useSortablePlaylists = () => {
 
 
 		if (!userId.value) userId.value = myUserId.value;
 		if (!userId.value) userId.value = myUserId.value;
 
 
-		ws.onConnect(() => {
+		socket.onConnect(() => {
 			if (!isCurrentUser.value)
 			if (!isCurrentUser.value)
 				socket.dispatch(
 				socket.dispatch(
 					"apis.joinRoom",
 					"apis.joinRoom",

+ 64 - 62
frontend/src/main.ts

@@ -10,7 +10,7 @@ import Toast from "toasters";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import ws from "@/ws";
+import { useWebsocketsStore } from "@/stores/websockets";
 import ms from "@/ms";
 import ms from "@/ms";
 import i18n from "@/i18n";
 import i18n from "@/i18n";
 
 
@@ -262,9 +262,11 @@ router.beforeEach((to, from, next) => {
 
 
 	modalsStore.closeAllModals();
 	modalsStore.closeAllModals();
 
 
-	if (ws.socket && to.fullPath !== from.fullPath) {
-		ws.clearCallbacks();
-		ws.destroyListeners();
+	const { socket } = useWebsocketsStore();
+
+	if (socket.ready && to.fullPath !== from.fullPath) {
+		socket.clearCallbacks();
+		socket.destroyListeners();
 	}
 	}
 
 
 	if (to.query.toast) {
 	if (to.query.toast) {
@@ -337,77 +339,77 @@ lofig.folder = defaultConfigURL;
 		}
 		}
 	});
 	});
 
 
-	const websocketsDomain = await lofig.get("backend.websocketsDomain");
-	ws.init(websocketsDomain);
-
-	if (await lofig.get("siteSettings.mediasession")) ms.init();
-
-	ws.socket.on("ready", res => {
-		const { loggedIn, role, username, userId, email } = res.data;
-
-		userAuthStore.authData({
-			loggedIn,
-			role,
-			username,
-			email,
-			userId
+	const { createSocket } = useWebsocketsStore();
+	createSocket().then(socket => {
+		socket.on("ready", res => {
+			const { loggedIn, role, username, userId, email } = res.data;
+
+			userAuthStore.authData({
+				loggedIn,
+				role,
+				username,
+				email,
+				userId
+			});
 		});
 		});
-	});
 
 
-	ws.socket.on("keep.event:user.banned", res =>
-		userAuthStore.banUser(res.data.ban)
-	);
+		socket.on("keep.event:user.banned", res =>
+			userAuthStore.banUser(res.data.ban)
+		);
 
 
-	ws.socket.on("keep.event:user.username.updated", res =>
-		userAuthStore.updateUsername(res.data.username)
-	);
+		socket.on("keep.event:user.username.updated", res =>
+			userAuthStore.updateUsername(res.data.username)
+		);
 
 
-	ws.socket.on("keep.event:user.preferences.updated", res => {
-		const { preferences } = res.data;
+		socket.on("keep.event:user.preferences.updated", res => {
+			const { preferences } = res.data;
 
 
-		const {
-			changeAutoSkipDisliked,
-			changeNightmode,
-			changeActivityLogPublic,
-			changeAnonymousSongRequests,
-			changeActivityWatch
-		} = useUserPreferencesStore();
+			const {
+				changeAutoSkipDisliked,
+				changeNightmode,
+				changeActivityLogPublic,
+				changeAnonymousSongRequests,
+				changeActivityWatch
+			} = useUserPreferencesStore();
 
 
-		if (preferences.autoSkipDisliked !== undefined)
-			changeAutoSkipDisliked(preferences.autoSkipDisliked);
+			if (preferences.autoSkipDisliked !== undefined)
+				changeAutoSkipDisliked(preferences.autoSkipDisliked);
 
 
-		if (preferences.nightmode !== undefined) {
-			localStorage.setItem("nightmode", preferences.nightmode);
-			changeNightmode(preferences.nightmode);
-		}
+			if (preferences.nightmode !== undefined) {
+				localStorage.setItem("nightmode", preferences.nightmode);
+				changeNightmode(preferences.nightmode);
+			}
 
 
-		if (preferences.activityLogPublic !== undefined)
-			changeActivityLogPublic(preferences.activityLogPublic);
+			if (preferences.activityLogPublic !== undefined)
+				changeActivityLogPublic(preferences.activityLogPublic);
 
 
-		if (preferences.anonymousSongRequests !== undefined)
-			changeAnonymousSongRequests(preferences.anonymousSongRequests);
+			if (preferences.anonymousSongRequests !== undefined)
+				changeAnonymousSongRequests(preferences.anonymousSongRequests);
 
 
-		if (preferences.activityWatch !== undefined)
-			changeActivityWatch(preferences.activityWatch);
-	});
+			if (preferences.activityWatch !== undefined)
+				changeActivityWatch(preferences.activityWatch);
+		});
 
 
-	ws.socket.on("keep.event:user.role.updated", res => {
-		userAuthStore.updateRole(res.data.role);
-		userAuthStore.updatePermissions().then(() => {
-			const { meta } = router.currentRoute.value;
-			if (
-				meta &&
-				meta.permissionRequired &&
-				!userAuthStore.hasPermission(meta.permissionRequired)
-			)
-				router.push({
-					path: "/",
-					query: {
-						toast: "You no longer have access to the page you were viewing."
-					}
-				});
+		socket.on("keep.event:user.role.updated", res => {
+			userAuthStore.updateRole(res.data.role);
+			userAuthStore.updatePermissions().then(() => {
+				const { meta } = router.currentRoute.value;
+				if (
+					meta &&
+					meta.permissionRequired &&
+					!userAuthStore.hasPermission(meta.permissionRequired)
+				)
+					router.push({
+						path: "/",
+						query: {
+							toast: "You no longer have access to the page you were viewing."
+						}
+					});
+			});
 		});
 		});
 	});
 	});
 
 
+	if (await lofig.get("siteSettings.mediasession")) ms.init();
+
 	app.mount("#root");
 	app.mount("#root");
 })();
 })();

+ 1 - 2
frontend/src/pages/Admin/Statistics.vue

@@ -2,7 +2,6 @@
 import { ref, onMounted } from "vue";
 import { ref, onMounted } from "vue";
 import { useRoute } from "vue-router";
 import { useRoute } from "vue-router";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
-import ws from "@/ws";
 
 
 const route = useRoute();
 const route = useRoute();
 
 
@@ -28,7 +27,7 @@ const init = () => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 </script>
 </script>
 
 

+ 1 - 2
frontend/src/pages/Admin/YouTube/index.vue

@@ -4,7 +4,6 @@ import { useRoute } from "vue-router";
 import Toast from "toasters";
 import Toast from "toasters";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import ws from "@/ws";
 import { TableColumn, TableFilter, TableEvents } from "@/types/advancedTable";
 import { TableColumn, TableFilter, TableEvents } from "@/types/advancedTable";
 
 
 const AdvancedTable = defineAsyncComponent(
 const AdvancedTable = defineAsyncComponent(
@@ -193,7 +192,7 @@ const removeApiRequest = requestId => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 </script>
 </script>
 
 

+ 1 - 2
frontend/src/pages/Home.vue

@@ -16,7 +16,6 @@ import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import keyboardShortcuts from "@/keyboardShortcuts";
 import keyboardShortcuts from "@/keyboardShortcuts";
-import ws from "@/ws";
 
 
 const MainHeader = defineAsyncComponent(
 const MainHeader = defineAsyncComponent(
 	() => import("@/components/MainHeader.vue")
 	() => import("@/components/MainHeader.vue")
@@ -192,7 +191,7 @@ onMounted(async () => {
 		openModal(route.redirectedFrom.name);
 		openModal(route.redirectedFrom.name);
 	}
 	}
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 
 
 	socket.on("event:station.created", res => {
 	socket.on("event:station.created", res => {
 		const { station } = res.data;
 		const { station } = res.data;

+ 1 - 3
frontend/src/pages/News.vue

@@ -6,8 +6,6 @@ import { marked } from "marked";
 import DOMPurify from "dompurify";
 import DOMPurify from "dompurify";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 
 
-import ws from "@/ws";
-
 const MainHeader = defineAsyncComponent(
 const MainHeader = defineAsyncComponent(
 	() => import("@/components/MainHeader.vue")
 	() => import("@/components/MainHeader.vue")
 );
 );
@@ -67,7 +65,7 @@ onMounted(() => {
 		news.value = news.value.filter(item => item._id !== res.data.newsId);
 		news.value = news.value.filter(item => item._id !== res.data.newsId);
 	});
 	});
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 </script>
 </script>
 
 

+ 1 - 2
frontend/src/pages/Profile/Tabs/RecentActivity.vue

@@ -4,7 +4,6 @@ import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
-import ws from "@/ws";
 
 
 const ActivityItem = defineAsyncComponent(
 const ActivityItem = defineAsyncComponent(
 	() => import("@/components/ActivityItem.vue")
 	() => import("@/components/ActivityItem.vue")
@@ -87,7 +86,7 @@ const handleScroll = () => {
 onMounted(() => {
 onMounted(() => {
 	window.addEventListener("scroll", handleScroll);
 	window.addEventListener("scroll", handleScroll);
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 
 
 	socket.on("event:activity.updated", res => {
 	socket.on("event:activity.updated", res => {
 		activities.value.find(
 		activities.value.find(

+ 1 - 2
frontend/src/pages/Profile/index.vue

@@ -5,7 +5,6 @@ import { format, parseISO } from "date-fns";
 import { storeToRefs } from "pinia";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
-import ws from "@/ws";
 import { useTabQueryHandler } from "@/composables/useTabQueryHandler";
 import { useTabQueryHandler } from "@/composables/useTabQueryHandler";
 
 
 const MainHeader = defineAsyncComponent(
 const MainHeader = defineAsyncComponent(
@@ -60,7 +59,7 @@ onMounted(() => {
 	)
 	)
 		tab.value = route.query.tab;
 		tab.value = route.query.tab;
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 });
 });
 </script>
 </script>
 
 

+ 1 - 2
frontend/src/pages/Settings/Tabs/Preferences.vue

@@ -4,7 +4,6 @@ import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useUserPreferencesStore } from "@/stores/userPreferences";
-import ws from "@/ws";
 
 
 const SaveButton = defineAsyncComponent(
 const SaveButton = defineAsyncComponent(
 	() => import("@/components/SaveButton.vue")
 	() => import("@/components/SaveButton.vue")
@@ -66,7 +65,7 @@ const saveChanges = () => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
-	ws.onConnect(() =>
+	socket.onConnect(() =>
 		socket.dispatch("users.getPreferences", res => {
 		socket.dispatch("users.getPreferences", res => {
 			const { preferences } = res.data;
 			const { preferences } = res.data;
 
 

+ 1 - 2
frontend/src/pages/Settings/index.vue

@@ -4,7 +4,6 @@ import { onMounted, defineAsyncComponent } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
 import { useSettingsStore } from "@/stores/settings";
 import { useSettingsStore } from "@/stores/settings";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
-import ws from "@/ws";
 import { useTabQueryHandler } from "@/composables/useTabQueryHandler";
 import { useTabQueryHandler } from "@/composables/useTabQueryHandler";
 
 
 const MainHeader = defineAsyncComponent(
 const MainHeader = defineAsyncComponent(
@@ -53,7 +52,7 @@ onMounted(() => {
 
 
 	// this.localNightmode = this.nightmode;
 	// this.localNightmode = this.nightmode;
 
 
-	ws.onConnect(init);
+	socket.onConnect(init);
 
 
 	socket.on("event:user.password.linked", () =>
 	socket.on("event:user.password.linked", () =>
 		updateOriginalUser({
 		updateOriginalUser({

+ 2 - 3
frontend/src/pages/Station/index.vue

@@ -19,7 +19,6 @@ import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import aw from "@/aw";
 import aw from "@/aw";
 import ms from "@/ms";
 import ms from "@/ms";
-import ws from "@/ws";
 import keyboardShortcuts from "@/keyboardShortcuts";
 import keyboardShortcuts from "@/keyboardShortcuts";
 import utils from "@/utils";
 import utils from "@/utils";
 
 
@@ -1121,13 +1120,13 @@ onMounted(async () => {
 	}, 1000);
 	}, 1000);
 
 
 	if (socket.readyState === 1) join();
 	if (socket.readyState === 1) join();
-	ws.onConnect(() => {
+	socket.onConnect(() => {
 		socketConnected.value = true;
 		socketConnected.value = true;
 		clearTimeout(window.stationNextSongTimeout);
 		clearTimeout(window.stationNextSongTimeout);
 		join();
 		join();
 	});
 	});
 
 
-	ws.onDisconnect(true, () => {
+	socket.onDisconnect(true, () => {
 		socketConnected.value = false;
 		socketConnected.value = false;
 		const _currentSong = currentSong.value;
 		const _currentSong = currentSong.value;
 		if (nextSong.value)
 		if (nextSong.value)

+ 3 - 2
frontend/src/stores/manageStation.ts

@@ -2,7 +2,7 @@ import { defineStore } from "pinia";
 import { Station } from "@/types/station";
 import { Station } from "@/types/station";
 import { Playlist } from "@/types/playlist";
 import { Playlist } from "@/types/playlist";
 import { CurrentSong, Song } from "@/types/song";
 import { CurrentSong, Song } from "@/types/song";
-import ws from "@/ws";
+import { useWebsocketsStore } from "@/stores/websockets";
 
 
 export const useManageStationStore = props => {
 export const useManageStationStore = props => {
 	const { modalUuid } = props;
 	const { modalUuid } = props;
@@ -84,7 +84,8 @@ export const useManageStationStore = props => {
 			},
 			},
 			updatePermissions() {
 			updatePermissions() {
 				return new Promise(resolve => {
 				return new Promise(resolve => {
-					ws.socket.dispatch(
+					const { socket } = useWebsocketsStore();
+					socket.dispatch(
 						"utils.getPermissions",
 						"utils.getPermissions",
 						this.station._id,
 						this.station._id,
 						res => {
 						res => {

+ 7 - 4
frontend/src/stores/modals.ts

@@ -1,7 +1,7 @@
 import { defineStore } from "pinia";
 import { defineStore } from "pinia";
 import { defineAsyncComponent } from "vue";
 import { defineAsyncComponent } from "vue";
-import ws from "@/ws";
 
 
+import { useWebsocketsStore } from "@/stores/websockets";
 import { useEditUserStore } from "@/stores/editUser";
 import { useEditUserStore } from "@/stores/editUser";
 import { useEditSongStore } from "@/stores/editSong";
 import { useEditSongStore } from "@/stores/editSong";
 import { useBulkActionsStore } from "@/stores/bulkActions";
 import { useBulkActionsStore } from "@/stores/bulkActions";
@@ -33,7 +33,8 @@ export const useModalsStore = defineStore("modals", {
 
 
 			Object.entries(this.modals).forEach(([uuid, _modal]) => {
 			Object.entries(this.modals).forEach(([uuid, _modal]) => {
 				if (modal === _modal) {
 				if (modal === _modal) {
-					ws.destroyModalListeners(uuid);
+					const { socket } = useWebsocketsStore();
+					socket.destroyModalListeners(uuid);
 					this.activeModals.splice(
 					this.activeModals.splice(
 						this.activeModals.indexOf(uuid),
 						this.activeModals.indexOf(uuid),
 						1
 						1
@@ -133,15 +134,17 @@ export const useModalsStore = defineStore("modals", {
 				this.activeModals[this.activeModals.length - 1];
 				this.activeModals[this.activeModals.length - 1];
 			// TODO: make sure to only destroy/register modal listeners for a unique modal
 			// TODO: make sure to only destroy/register modal listeners for a unique modal
 			// remove any websocket listeners for the modal
 			// remove any websocket listeners for the modal
-			ws.destroyModalListeners(currentlyActiveModalUuid);
+			const { socket } = useWebsocketsStore();
+			socket.destroyModalListeners(currentlyActiveModalUuid);
 
 
 			this.activeModals.pop();
 			this.activeModals.pop();
 
 
 			delete this.modals[currentlyActiveModalUuid];
 			delete this.modals[currentlyActiveModalUuid];
 		},
 		},
 		closeAllModals() {
 		closeAllModals() {
+			const { socket } = useWebsocketsStore();
 			this.activeModals.forEach(modalUuid => {
 			this.activeModals.forEach(modalUuid => {
-				ws.destroyModalListeners(modalUuid);
+				socket.destroyModalListeners(modalUuid);
 			});
 			});
 
 
 			this.activeModals = [];
 			this.activeModals = [];

+ 3 - 2
frontend/src/stores/station.ts

@@ -3,7 +3,7 @@ import { Playlist } from "@/types/playlist";
 import { Song, CurrentSong } from "@/types/song";
 import { Song, CurrentSong } from "@/types/song";
 import { Station } from "@/types/station";
 import { Station } from "@/types/station";
 import { User } from "@/types/user";
 import { User } from "@/types/user";
-import ws from "@/ws";
+import { useWebsocketsStore } from "@/stores/websockets";
 
 
 export const useStationStore = defineStore("station", {
 export const useStationStore = defineStore("station", {
 	state: () => ({
 	state: () => ({
@@ -141,7 +141,8 @@ export const useStationStore = defineStore("station", {
 		},
 		},
 		updatePermissions() {
 		updatePermissions() {
 			return new Promise(resolve => {
 			return new Promise(resolve => {
-				ws.socket.dispatch(
+				const { socket } = useWebsocketsStore();
+				socket.dispatch(
 					"utils.getPermissions",
 					"utils.getPermissions",
 					this.station._id,
 					this.station._id,
 					res => {
 					res => {

+ 29 - 27
frontend/src/stores/userAuth.ts

@@ -1,7 +1,7 @@
 import { defineStore } from "pinia";
 import { defineStore } from "pinia";
 import Toast from "toasters";
 import Toast from "toasters";
 import validation from "@/validation";
 import validation from "@/validation";
-import ws from "@/ws";
+import { useWebsocketsStore } from "@/stores/websockets";
 
 
 export const useUserAuthStore = defineStore("userAuth", {
 export const useUserAuthStore = defineStore("userAuth", {
 	state: () => ({
 	state: () => ({
@@ -70,8 +70,9 @@ export const useUserAuthStore = defineStore("userAuth", {
 							"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character."
 							"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character."
 						)
 						)
 					);
 					);
-				else
-					ws.socket.dispatch(
+				else {
+					const { socket } = useWebsocketsStore();
+					socket.dispatch(
 						"users.register",
 						"users.register",
 						username,
 						username,
 						email,
 						email,
@@ -112,13 +113,15 @@ export const useUserAuthStore = defineStore("userAuth", {
 							return reject(new Error(res.message));
 							return reject(new Error(res.message));
 						}
 						}
 					);
 					);
+				}
 			});
 			});
 		},
 		},
 		login(user) {
 		login(user) {
 			return new Promise((resolve, reject) => {
 			return new Promise((resolve, reject) => {
 				const { email, password } = user;
 				const { email, password } = user;
 
 
-				ws.socket.dispatch("users.login", email, password, res => {
+				const { socket } = useWebsocketsStore();
+				socket.dispatch("users.login", email, password, res => {
 					if (res.status === "success") {
 					if (res.status === "success") {
 						return lofig.get("cookie").then(cookie => {
 						return lofig.get("cookie").then(cookie => {
 							const date = new Date();
 							const date = new Date();
@@ -156,7 +159,8 @@ export const useUserAuthStore = defineStore("userAuth", {
 		},
 		},
 		logout() {
 		logout() {
 			return new Promise((resolve, reject) => {
 			return new Promise((resolve, reject) => {
-				ws.socket.dispatch("users.logout", res => {
+				const { socket } = useWebsocketsStore();
+				socket.dispatch("users.logout", res => {
 					if (res.status === "success") {
 					if (res.status === "success") {
 						return resolve(
 						return resolve(
 							lofig.get("cookie").then(cookie => {
 							lofig.get("cookie").then(cookie => {
@@ -175,32 +179,29 @@ export const useUserAuthStore = defineStore("userAuth", {
 				if (typeof this.userIdMap[`Z${userId}`] !== "string") {
 				if (typeof this.userIdMap[`Z${userId}`] !== "string") {
 					if (this.userIdRequested[`Z${userId}`] !== true) {
 					if (this.userIdRequested[`Z${userId}`] !== true) {
 						this.requestingUserId(userId);
 						this.requestingUserId(userId);
-						ws.socket.dispatch(
-							"users.getBasicUser",
-							userId,
-							res => {
-								if (res.status === "success") {
-									const user = res.data;
+						const { socket } = useWebsocketsStore();
+						socket.dispatch("users.getBasicUser", userId, res => {
+							if (res.status === "success") {
+								const user = res.data;
 
 
-									this.mapUserId({
-										userId,
-										user: {
-											name: user.name,
-											username: user.username
-										}
-									});
+								this.mapUserId({
+									userId,
+									user: {
+										name: user.name,
+										username: user.username
+									}
+								});
 
 
-									this.pendingUserIdCallbacks[
-										`Z${userId}`
-									].forEach(cb => cb(user));
+								this.pendingUserIdCallbacks[
+									`Z${userId}`
+								].forEach(cb => cb(user));
 
 
-									this.clearPendingCallbacks(userId);
+								this.clearPendingCallbacks(userId);
 
 
-									return resolve(user);
-								}
-								return resolve(null);
+								return resolve(user);
 							}
 							}
-						);
+							return resolve(null);
+						});
 					} else {
 					} else {
 						this.pendingUser({
 						this.pendingUser({
 							userId,
 							userId,
@@ -250,7 +251,8 @@ export const useUserAuthStore = defineStore("userAuth", {
 		},
 		},
 		updatePermissions() {
 		updatePermissions() {
 			return new Promise(resolve => {
 			return new Promise(resolve => {
-				ws.socket.dispatch("utils.getPermissions", res => {
+				const { socket } = useWebsocketsStore();
+				socket.dispatch("utils.getPermissions", res => {
 					this.permissions = res.data.permissions;
 					this.permissions = res.data.permissions;
 					this.gotPermissions = true;
 					this.gotPermissions = true;
 					resolve(this.permissions);
 					resolve(this.permissions);

+ 27 - 17
frontend/src/stores/websockets.ts

@@ -1,5 +1,5 @@
 import { defineStore } from "pinia";
 import { defineStore } from "pinia";
-import { CustomWebSocket } from "@/types/customWebSocket";
+import CustomWebSocket from "@/classes/CustomWebSocket.class";
 
 
 export const useWebsocketsStore = defineStore("websockets", {
 export const useWebsocketsStore = defineStore("websockets", {
 	state: () => ({
 	state: () => ({
@@ -8,24 +8,34 @@ export const useWebsocketsStore = defineStore("websockets", {
 		}
 		}
 	}),
 	}),
 	actions: {
 	actions: {
-		createSocket(socket) {
-			const { listeners } = this.socket.dispatcher;
-			this.socket = socket;
+		createSocket(): Promise<CustomWebSocket> {
+			return new Promise((resolve, reject) => {
+				lofig
+					.get("backend.websocketsDomain")
+					.then(websocketsDomain => {
+						const { listeners } = this.socket.dispatcher;
 
 
-			// only executes if the websocket object is being replaced
-			if (listeners) {
-				// for each listener type
-				Object.keys(listeners).forEach(listenerType =>
-					// for each callback previously present for the listener type
-					listeners[listenerType].forEach(element => {
-						// add the listener back after the websocket object is reset
-						this.socket.dispatcher.addEventListener(
-							listenerType,
-							element.cb
-						);
+						this.socket = new CustomWebSocket(websocketsDomain);
+
+						// only executes if the websocket object is being replaced
+						if (listeners) {
+							// for each listener type
+							Object.keys(listeners).forEach(listenerType =>
+								// for each callback previously present for the listener type
+								listeners[listenerType].forEach(element => {
+									// add the listener back after the websocket object is reset
+									this.socket.dispatcher.addEventListener(
+										listenerType,
+										element.cb
+									);
+								})
+							);
+						}
+
+						resolve(this.socket);
 					})
 					})
-				);
-			}
+					.catch(err => reject(err));
+			});
 		}
 		}
 	},
 	},
 	getters: {
 	getters: {

+ 0 - 7
frontend/src/types/customWebSocket.ts

@@ -1,7 +0,0 @@
-import ListenerHandler from "@/classes/ListenerHandler.class";
-// TODO: Replace
-export interface CustomWebSocket extends WebSocket {
-	dispatcher: ListenerHandler;
-	on(target, cb, options?: any): void;
-	dispatch(...args): void;
-}

+ 0 - 197
frontend/src/ws.ts

@@ -1,197 +0,0 @@
-import { useWebsocketsStore } from "@/stores/websockets";
-import { useUserAuthStore } from "@/stores/userAuth";
-import ListenerHandler from "./classes/ListenerHandler.class";
-
-const onConnect = [];
-let ready = false;
-let firstInit = true;
-
-let pendingDispatches = [];
-
-const onDisconnect = {
-	temp: [],
-	persist: []
-};
-
-// references for when a dispatch event is ready to callback from server to client
-const CB_REFS = {};
-let CB_REF = 0;
-
-const PROGRESS_CB_REFS = {};
-
-export default {
-	socket: null,
-	dispatcher: null,
-
-	onConnect(cb) {
-		if (this.socket.readyState === 1 && ready) cb();
-
-		return onConnect.push(cb);
-	},
-
-	onDisconnect(...args) {
-		if (args[0] === true) onDisconnect.persist.push(args[1]);
-		else onDisconnect.temp.push(args[0]);
-	},
-
-	clearCallbacks: () => {
-		onDisconnect.temp = [];
-	},
-
-	destroyListeners() {
-		Object.keys(CB_REFS).forEach(id => {
-			if (
-				id.indexOf("$event:") !== -1 &&
-				id.indexOf("$event:keep.") === -1
-			)
-				delete CB_REFS[id];
-		});
-
-		Object.keys(PROGRESS_CB_REFS).forEach(id => {
-			if (
-				id.indexOf("$event:") !== -1 &&
-				id.indexOf("$event:keep.") === -1
-			)
-				delete PROGRESS_CB_REFS[id];
-		});
-
-		// destroy all listeners that aren't site-wide
-		Object.keys(this.socket.dispatcher.listeners).forEach(type => {
-			if (type.indexOf("keep.") === -1 && type !== "ready")
-				delete this.socket.dispatcher.listeners[type];
-		});
-	},
-
-	destroyModalListeners(modalUuid) {
-		// destroy all listeners for a specific modal
-		Object.keys(this.socket.dispatcher.listeners).forEach(type =>
-			this.socket.dispatcher.listeners[type].forEach((element, index) => {
-				if (element.options && element.options.modalUuid === modalUuid)
-					this.socket.dispatcher.listeners[type].splice(index, 1);
-			})
-		);
-	},
-
-	init(url) {
-		const userAuthStore = useUserAuthStore();
-
-		// ensures correct context of socket object when dispatching (because socket object is recreated on reconnection)
-		const waitForConnectionToDispatch = (...args) =>
-			this.socket.dispatch(...args);
-
-		class CustomWebSocket extends WebSocket {
-			dispatcher: ListenerHandler;
-
-			constructor() {
-				super(url);
-				this.dispatcher = new ListenerHandler();
-			}
-
-			on(target, cb, options) {
-				this.dispatcher.addEventListener(
-					target,
-					event => cb(...event.detail),
-					options
-				);
-			}
-
-			dispatch(...args) {
-				if (this.readyState !== 1)
-					return pendingDispatches.push(() =>
-						waitForConnectionToDispatch(...args)
-					);
-
-				const lastArg = args[args.length - 1];
-
-				if (typeof lastArg === "function") {
-					CB_REF += 1;
-					CB_REFS[CB_REF] = lastArg;
-
-					return this.send(
-						JSON.stringify([...args.slice(0, -1), { CB_REF }])
-					);
-				}
-				if (typeof lastArg === "object") {
-					CB_REF += 1;
-					CB_REFS[CB_REF] = lastArg.cb;
-					PROGRESS_CB_REFS[CB_REF] = lastArg.onProgress;
-
-					return this.send(
-						JSON.stringify([
-							...args.slice(0, -1),
-							{ CB_REF, onProgress: true }
-						])
-					);
-				}
-
-				return this.send(JSON.stringify([...args]));
-			}
-		}
-
-		this.socket = new CustomWebSocket();
-		const { createSocket } = useWebsocketsStore();
-		createSocket(this.socket);
-
-		this.socket.onopen = () => {
-			console.log("WS: SOCKET OPENED");
-		};
-
-		this.socket.onmessage = message => {
-			const data = JSON.parse(message.data);
-			const name = data.shift(0);
-
-			if (name === "CB_REF") {
-				const CB_REF = data.shift(0);
-				CB_REFS[CB_REF](...data);
-				return delete CB_REFS[CB_REF];
-			}
-			if (name === "PROGRESS_CB_REF") {
-				const PROGRESS_CB_REF = data.shift(0);
-				PROGRESS_CB_REFS[PROGRESS_CB_REF](...data);
-			}
-
-			if (name === "ERROR") console.log("WS: SOCKET ERROR:", data[0]);
-
-			return this.socket.dispatcher.dispatchEvent(
-				new CustomEvent(name, {
-					detail: data
-				})
-			);
-		};
-
-		this.socket.onclose = () => {
-			console.log("WS: SOCKET CLOSED");
-
-			ready = false;
-
-			onDisconnect.temp.forEach(cb => cb());
-			onDisconnect.persist.forEach(cb => cb());
-
-			// try to reconnect every 1000ms, if the user isn't banned
-			if (!userAuthStore.banned) setTimeout(() => this.init(url), 1000);
-		};
-
-		this.socket.onerror = err => {
-			console.log("WS: SOCKET ERROR", err);
-		};
-
-		if (firstInit) {
-			firstInit = false;
-			this.socket.on("ready", () => {
-				console.log("WS: SOCKET READY");
-
-				onConnect.forEach(cb => cb());
-
-				ready = true;
-
-				setTimeout(() => {
-					// dispatches that were attempted while the server was offline
-					pendingDispatches.forEach(cb => cb());
-					pendingDispatches = [];
-				}, 150); // small delay between readyState being 1 and the server actually receiving dispatches
-
-				userAuthStore.updatePermissions();
-			});
-		}
-	}
-};