Browse Source

refactor: Renamed CustomWebSocket to SocketHandler and fixed reconnection issues

Owen Diffey 1 năm trước cách đây
mục cha
commit
d13729b1cd

+ 1 - 1
frontend/src/App.vue

@@ -183,7 +183,7 @@ onMounted(async () => {
 
 	disconnectedMessage.value.hide();
 
-	socket.onConnect(() => {
+	socket.onConnect(true, () => {
 		socketConnected.value = true;
 
 		socket.dispatch(

+ 2 - 15
frontend/src/aw.ts

@@ -1,4 +1,5 @@
 import Toast from "toasters";
+import utils from "@/utils";
 
 let gotPong = false;
 let pingTries = 0;
@@ -66,21 +67,7 @@ export default {
 
 	enable() {
 		if (!enabled) {
-			uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
-				/[xy]/g,
-				symbol => {
-					let array;
-
-					if (symbol === "y") {
-						array = ["8", "9", "a", "b"];
-						return array[Math.floor(Math.random() * array.length)];
-					}
-
-					array = new Uint8Array(1);
-					window.crypto.getRandomValues(array);
-					return (array[0] % 16).toString(16);
-				}
-			);
+			uuid = utils.guid();
 
 			document.addEventListener(
 				"ActivityWatchMusareEvent",

+ 46 - 30
frontend/src/classes/CustomWebSocket.class.ts → frontend/src/classes/SocketHandler.class.ts

@@ -1,10 +1,18 @@
 import ListenerHandler from "@/classes/ListenerHandler.class";
 import { useUserAuthStore } from "@/stores/userAuth";
+import utils from "@/utils";
+
+export default class SocketHandler {
+	socket: WebSocket;
+
+	url: string;
 
-export default class CustomWebSocket extends WebSocket {
 	dispatcher: ListenerHandler;
 
-	onConnectCbs: any[];
+	onConnectCbs: {
+		temp: any[];
+		persist: any[];
+	};
 
 	ready: boolean;
 
@@ -17,8 +25,6 @@ export default class CustomWebSocket extends WebSocket {
 		persist: any[];
 	};
 
-	CB_REF: number;
-
 	CB_REFS: object;
 
 	PROGRESS_CB_REFS: object;
@@ -39,12 +45,16 @@ export default class CustomWebSocket extends WebSocket {
 
 	trigger: (type: string, target: string, data?: any) => void; // Mock only
 
-	constructor(url) {
-		super(url);
-
+	constructor(url: string) {
 		this.dispatcher = new ListenerHandler();
 
-		this.onConnectCbs = [];
+		this.url = url;
+
+		this.onConnectCbs = {
+			temp: [],
+			persist: []
+		};
+
 		this.ready = false;
 		this.firstInit = true;
 
@@ -56,7 +66,6 @@ export default class CustomWebSocket extends WebSocket {
 		};
 
 		// 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 = {};
 
@@ -64,13 +73,15 @@ export default class CustomWebSocket extends WebSocket {
 	}
 
 	init() {
+		this.socket = new WebSocket(this.url);
+
 		const userAuthStore = useUserAuthStore();
 
-		this.onopen = () => {
+		this.socket.onopen = () => {
 			console.log("WS: SOCKET OPENED");
 		};
 
-		this.onmessage = message => {
+		this.socket.onmessage = message => {
 			const data = JSON.parse(message.data);
 			const name = data.shift(0);
 
@@ -93,10 +104,11 @@ export default class CustomWebSocket extends WebSocket {
 			);
 		};
 
-		this.onclose = () => {
+		this.socket.onclose = () => {
 			console.log("WS: SOCKET CLOSED");
 
 			this.ready = false;
+			this.firstInit = false;
 
 			this.onDisconnectCbs.temp.forEach(cb => cb());
 			this.onDisconnectCbs.persist.forEach(cb => cb());
@@ -105,7 +117,7 @@ export default class CustomWebSocket extends WebSocket {
 			if (!userAuthStore.banned) setTimeout(() => this.init(), 1000);
 		};
 
-		this.onerror = err => {
+		this.socket.onerror = err => {
 			console.log("WS: SOCKET ERROR", err);
 		};
 
@@ -114,7 +126,8 @@ export default class CustomWebSocket extends WebSocket {
 			this.on("ready", () => {
 				console.log("WS: SOCKET READY");
 
-				this.onConnectCbs.forEach(cb => cb());
+				this.onConnectCbs.temp.forEach(cb => cb());
+				this.onConnectCbs.persist.forEach(cb => cb());
 
 				this.ready = true;
 
@@ -138,47 +151,50 @@ export default class CustomWebSocket extends WebSocket {
 	}
 
 	dispatch(...args) {
-		if (this.readyState !== 1)
+		if (this.socket.readyState !== 1)
 			return this.pendingDispatches.push(() => this.dispatch(...args));
 
 		const lastArg = args[args.length - 1];
+		const CB_REF = utils.guid();
 
 		if (typeof lastArg === "function") {
-			this.CB_REF += 1;
-			this.CB_REFS[this.CB_REF] = lastArg;
+			this.CB_REFS[CB_REF] = lastArg;
 
-			return this.send(
-				JSON.stringify([...args.slice(0, -1), { CB_REF: this.CB_REF }])
+			return this.socket.send(
+				JSON.stringify([...args.slice(0, -1), { 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;
+			this.CB_REFS[CB_REF] = lastArg.cb;
+			this.PROGRESS_CB_REFS[CB_REF] = lastArg.onProgress;
 
-			return this.send(
+			return this.socket.send(
 				JSON.stringify([
 					...args.slice(0, -1),
-					{ CB_REF: this.CB_REF, onProgress: true }
+					{ CB_REF, onProgress: true }
 				])
 			);
 		}
 
-		return this.send(JSON.stringify([...args]));
+		return this.socket.send(JSON.stringify([...args]));
 	}
 
-	onConnect(cb) {
-		if (this.readyState === 1 && this.ready) cb();
+	onConnect(...args) {
+		const cb = args[1] || args[0];
+		if (this.socket.readyState === 1 && this.ready) cb();
 
-		return this.onConnectCbs.push(cb);
+		if (args[0] === true) this.onConnectCbs.persist.push(cb);
+		else this.onConnectCbs.temp.push(cb);
 	}
 
 	onDisconnect(...args) {
-		if (args[0] === true) this.onDisconnectCbs.persist.push(args[1]);
-		else this.onDisconnectCbs.temp.push(args[0]);
+		const cb = args[1] || args[0];
+		if (args[0] === true) this.onDisconnectCbs.persist.push(cb);
+		else this.onDisconnectCbs.temp.push(cb);
 	}
 
 	clearCallbacks() {
+		this.onConnectCbs.temp = [];
 		this.onDisconnectCbs.temp = [];
 	}
 

+ 1 - 1
frontend/src/classes/__mocks__/CustomWebSocket.class.ts → frontend/src/classes/__mocks__/SocketHandler.class.ts

@@ -1,6 +1,6 @@
 import ListenerHandler from "@/classes/ListenerHandler.class";
 
-export default class CustomWebSocketMock {
+export default class SocketHandlerMock {
 	dispatcher: ListenerHandler;
 
 	url: string;

+ 126 - 128
frontend/src/main.ts

@@ -53,8 +53,6 @@ app.use(VueTippy, {
 	defaultProps: { animation: "scale", touch: "hold" }
 });
 
-app.use(createPinia());
-
 app.component("Tippy", Tippy);
 
 app.component("PageMetadata", {
@@ -247,84 +245,87 @@ const router = createRouter({
 	]
 });
 
-const userAuthStore = useUserAuthStore();
-const modalsStore = useModalsStore();
-
-router.beforeEach((to, from, next) => {
-	if (window.stationInterval) {
-		clearInterval(window.stationInterval);
-		window.stationInterval = 0;
-	}
-
-	// if (to.name === "station") {
-	// 	modalsStore.closeModal("manageStation");
-	// }
+app.use(createPinia());
 
-	modalsStore.closeAllModals();
+const { createSocket } = useWebsocketsStore();
+createSocket().then(async socket => {
+	const userAuthStore = useUserAuthStore();
+	const modalsStore = useModalsStore();
 
-	const { socket } = useWebsocketsStore();
+	router.beforeEach((to, from, next) => {
+		if (window.stationInterval) {
+			clearInterval(window.stationInterval);
+			window.stationInterval = 0;
+		}
 
-	if (socket.ready && to.fullPath !== from.fullPath) {
-		socket.clearCallbacks();
-		socket.destroyListeners();
-	}
+		// if (to.name === "station") {
+		// 	modalsStore.closeModal("manageStation");
+		// }
 
-	if (to.query.toast) {
-		const toast =
-			typeof to.query.toast === "string"
-				? { content: to.query.toast, timeout: 20000 }
-				: to.query.toast;
-		new Toast(toast);
-		const { query } = to;
-		delete query.toast;
-		next({ ...to, query });
-	} else if (
-		to.meta.loginRequired ||
-		to.meta.permissionRequired ||
-		to.meta.guestsOnly
-	) {
-		const gotData = () => {
-			if (to.meta.loginRequired && !userAuthStore.loggedIn)
-				next({ path: "/login" });
-			else if (
-				to.meta.permissionRequired &&
-				!userAuthStore.hasPermission(to.meta.permissionRequired)
-			)
-				next({ path: "/" });
-			else if (to.meta.guestsOnly && userAuthStore.loggedIn)
-				next({ path: "/" });
-			else next();
-		};
+		modalsStore.closeAllModals();
 
-		if (userAuthStore.gotData && userAuthStore.gotPermissions) gotData();
-		else {
-			const unsubscribe = userAuthStore.$onAction(
-				({ name, after, onError }) => {
-					if (name === "authData" || name === "updatePermissions") {
-						after(() => {
-							if (
-								userAuthStore.gotData &&
-								userAuthStore.gotPermissions
-							)
-								gotData();
-							unsubscribe();
-						});
-
-						onError(() => {
-							unsubscribe();
-						});
-					}
-				}
-			);
+		if (socket.ready && to.fullPath !== from.fullPath) {
+			socket.clearCallbacks();
+			socket.destroyListeners();
 		}
-	} else next();
-});
 
-app.use(router);
+		if (to.query.toast) {
+			const toast =
+				typeof to.query.toast === "string"
+					? { content: to.query.toast, timeout: 20000 }
+					: to.query.toast;
+			new Toast(toast);
+			const { query } = to;
+			delete query.toast;
+			next({ ...to, query });
+		} else if (
+			to.meta.loginRequired ||
+			to.meta.permissionRequired ||
+			to.meta.guestsOnly
+		) {
+			const gotData = () => {
+				if (to.meta.loginRequired && !userAuthStore.loggedIn)
+					next({ path: "/login" });
+				else if (
+					to.meta.permissionRequired &&
+					!userAuthStore.hasPermission(to.meta.permissionRequired)
+				)
+					next({ path: "/" });
+				else if (to.meta.guestsOnly && userAuthStore.loggedIn)
+					next({ path: "/" });
+				else next();
+			};
+
+			if (userAuthStore.gotData && userAuthStore.gotPermissions)
+				gotData();
+			else {
+				const unsubscribe = userAuthStore.$onAction(
+					({ name, after, onError }) => {
+						if (
+							name === "authData" ||
+							name === "updatePermissions"
+						) {
+							after(() => {
+								if (
+									userAuthStore.gotData &&
+									userAuthStore.gotPermissions
+								)
+									gotData();
+								unsubscribe();
+							});
+
+							onError(() => {
+								unsubscribe();
+							});
+						}
+					}
+				);
+			}
+		} else next();
+	});
 
-lofig.folder = defaultConfigURL;
+	app.use(router);
 
-(async () => {
 	lofig.fetchConfig().then(config => {
 		const { configVersion, skipConfigVersionCheck } = config;
 		if (
@@ -339,77 +340,74 @@ lofig.folder = defaultConfigURL;
 		}
 	});
 
-	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
-			});
+	socket.on("ready", res => {
+		const { loggedIn, role, username, userId, email } = res.data;
+
+		userAuthStore.authData({
+			loggedIn,
+			role,
+			username,
+			email,
+			userId
 		});
+	});
 
-		socket.on("keep.event:user.banned", res =>
-			userAuthStore.banUser(res.data.ban)
-		);
+	socket.on("keep.event:user.banned", res =>
+		userAuthStore.banUser(res.data.ban)
+	);
 
-		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)
+	);
 
-		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);
+	});
 
-		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");
-})();
+});

+ 2 - 15
frontend/src/stores/modals.ts

@@ -1,5 +1,6 @@
 import { defineStore } from "pinia";
 import { defineAsyncComponent } from "vue";
+import utils from "@/utils";
 
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useEditUserStore } from "@/stores/editUser";
@@ -44,21 +45,7 @@ export const useModalsStore = defineStore("modals", {
 			});
 		},
 		openModal(dataOrModal) {
-			const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
-				/[xy]/g,
-				symbol => {
-					let array;
-
-					if (symbol === "y") {
-						array = ["8", "9", "a", "b"];
-						return array[Math.floor(Math.random() * array.length)];
-					}
-
-					array = new Uint8Array(1);
-					window.crypto.getRandomValues(array);
-					return (array[0] % 16).toString(16);
-				}
-			);
+			const uuid = utils.guid();
 
 			let modal;
 			let data;

+ 4 - 4
frontend/src/stores/websockets.ts

@@ -1,21 +1,21 @@
 import { defineStore } from "pinia";
-import CustomWebSocket from "@/classes/CustomWebSocket.class";
+import SocketHandler from "@/classes/SocketHandler.class";
 
 export const useWebsocketsStore = defineStore("websockets", {
 	state: () => ({
-		socket: <CustomWebSocket>{
+		socket: <SocketHandler>{
 			dispatcher: {}
 		}
 	}),
 	actions: {
-		createSocket(): Promise<CustomWebSocket> {
+		createSocket(): Promise<SocketHandler> {
 			return new Promise((resolve, reject) => {
 				lofig
 					.get("backend.websocketsDomain")
 					.then(websocketsDomain => {
 						const { listeners } = this.socket.dispatcher;
 
-						this.socket = new CustomWebSocket(websocketsDomain);
+						this.socket = new SocketHandler(websocketsDomain);
 
 						// only executes if the websocket object is being replaced
 						if (listeners) {

+ 4 - 4
frontend/src/tests/utils/setup.ts

@@ -1,8 +1,8 @@
 import "lofig";
-import CustomWebSocketMock from "@/classes/__mocks__/CustomWebSocket.class";
+import SocketHandlerMock from "@/classes/__mocks__/SocketHandler.class";
 
 vi.clearAllMocks();
 
-vi.spyOn(CustomWebSocketMock.prototype, "on");
-vi.spyOn(CustomWebSocketMock.prototype, "dispatch");
-vi.mock("@/classes/CustomWebSocket.class");
+vi.spyOn(SocketHandlerMock.prototype, "on");
+vi.spyOn(SocketHandlerMock.prototype, "dispatch");
+vi.mock("@/classes/SocketHandler.class");

+ 13 - 11
frontend/src/utils.ts

@@ -1,15 +1,17 @@
 export default {
-	guid: () => {
-		[1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1]
-			.map(b =>
-				b
-					? Math.floor((1 + Math.random()) * 0x10000)
-							.toString(16)
-							.substring(1)
-					: "-"
-			)
-			.join("");
-	},
+	guid: () =>
+		"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, symbol => {
+			let array;
+
+			if (symbol === "y") {
+				array = ["8", "9", "a", "b"];
+				return array[Math.floor(Math.random() * array.length)];
+			}
+
+			array = new Uint8Array(1);
+			window.crypto.getRandomValues(array);
+			return (array[0] % 16).toString(16);
+		}),
 	formatTime: originalDuration => {
 		if (typeof originalDuration === "number") {
 			if (originalDuration <= 0) return "0:00";