Browse Source

fix(WS): edge case for some socket listeners being called multiple times

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 3 years ago
parent
commit
e2d79629cb

+ 1 - 23
backend/logic/actions/apis.js

@@ -123,7 +123,7 @@ export default {
 	 * @param {Function} cb - callback
 	 */
 	joinRoom(session, page, cb) {
-		if (page === "home" || page.startsWith("profile-")) {
+		if (page === "home" || page.startsWith("profile.") || page.startsWith("manage-station.")) {
 			WSModule.runJob("SOCKET_JOIN_ROOM", {
 				socketId: session.socketId,
 				room: page
@@ -137,28 +137,6 @@ export default {
 		cb({ status: "success", message: "Successfully joined room." });
 	},
 
-	/**
-	 * Joins a room
-	 *
-	 * @param {object} session - user session
-	 * @param {string} page - the room to join
-	 * @param {Function} cb - callback
-	 */
-	joinManageStationRoom: isAdminRequired((session, page, cb) => {
-		if (page.startsWith("manage-station.")) {
-			WSModule.runJob("SOCKET_JOIN_ROOM", {
-				socketId: session.socketId,
-				room: page
-			})
-				.then(() => {})
-				.catch(err => {
-					this.log("ERROR", `Joining room failed: ${err.message}`);
-				});
-		}
-
-		cb({ status: "success", message: "Successfully joined room." });
-	}),
-
 	/**
 	 * Joins an admin room
 	 *

+ 2 - 3
frontend/src/components/modals/ManageStation/Tabs/Playlists.vue

@@ -514,9 +514,8 @@ export default {
 			});
 		},
 		blacklistPlaylist(id) {
-			if (this.isSelected(id)) {
-				this.deselectPlaylist(id);
-			}
+			if (this.isSelected(id)) this.deselectPlaylist(id);
+
 			this.socket.dispatch(
 				"stations.excludePlaylist",
 				this.station._id,

+ 4 - 6
frontend/src/components/modals/ManageStation/index.vue

@@ -277,12 +277,10 @@ export default {
 					}
 				);
 
-				if (this.sector === "admin")
-					this.socket.dispatch(
-						"apis.joinManageStationRoom",
-						`manage-station.${this.stationId}`,
-						() => {}
-					);
+				this.socket.dispatch(
+					"apis.joinRoom",
+					`manage-station.${this.stationId}`
+				);
 
 				this.socket.on("event:station.updateName", res => {
 					this.station.name = res.data.name;

+ 4 - 3
frontend/src/main.js

@@ -238,9 +238,10 @@ lofig.folder = "../config/default.json";
 			window.stationInterval = 0;
 		}
 
-		if (window.socket) ws.removeAllListeners();
-
-		ws.clear();
+		if (ws.socket) {
+			ws.clear();
+			ws.removeAllListeners();
+		}
 
 		if (
 			to.meta.loginRequired ||

+ 80 - 53
frontend/src/mixins/SortablePlaylists.vue

@@ -34,67 +34,94 @@ export default {
 		}
 	},
 	mounted() {
-		this.socket.on("event:playlist.create", res => {
-			if (this.playlists.indexOf(res.data.playlist) === -1)
-				this.playlists.push(res.data.playlist);
-		});
+		this.socket.on(
+			"event:playlist.create",
+			res => this.playlists.push(res.data.playlist),
+			{ replaceable: true }
+		);
 
-		this.socket.on("event:playlist.delete", res => {
-			this.playlists.forEach((playlist, index) => {
-				if (playlist._id === res.data.playlistId) {
-					this.playlists.splice(index, 1);
-				}
-			});
-		});
+		this.socket.on(
+			"event:playlist.delete",
+			res => {
+				this.playlists.forEach((playlist, index) => {
+					if (playlist._id === res.data.playlistId) {
+						this.playlists.splice(index, 1);
+					}
+				});
+			},
+			{ replaceable: true }
+		);
 
-		this.socket.on("event:playlist.addSong", res => {
-			this.playlists.forEach((playlist, index) => {
-				if (playlist._id === res.data.playlistId) {
-					this.playlists[index].songs.push(res.data.song);
-				}
-			});
-		});
+		this.socket.on(
+			"event:playlist.addSong",
+			res => {
+				this.playlists.forEach((playlist, index) => {
+					if (playlist._id === res.data.playlistId) {
+						this.playlists[index].songs.push(res.data.song);
+					}
+				});
+			},
+			{ replaceable: true }
+		);
 
-		this.socket.on("event:playlist.removeSong", res => {
-			this.playlists.forEach((playlist, index) => {
-				if (playlist._id === res.data.playlistId) {
-					this.playlists[index].songs.forEach((song, index2) => {
-						if (song.youtubeId === res.data.youtubeId) {
-							this.playlists[index].songs.splice(index2, 1);
-						}
-					});
-				}
-			});
-		});
+		this.socket.on(
+			"event:playlist.removeSong",
+			res => {
+				this.playlists.forEach((playlist, index) => {
+					if (playlist._id === res.data.playlistId) {
+						this.playlists[index].songs.forEach((song, index2) => {
+							if (song.youtubeId === res.data.youtubeId) {
+								this.playlists[index].songs.splice(index2, 1);
+							}
+						});
+					}
+				});
+			},
+			{ replaceable: true }
+		);
 
-		this.socket.on("event:playlist.updateDisplayName", res => {
-			this.playlists.forEach((playlist, index) => {
-				if (playlist._id === res.data.playlistId) {
-					this.playlists[index].displayName = res.data.displayName;
-				}
-			});
-		});
+		this.socket.on(
+			"event:playlist.updateDisplayName",
+			res => {
+				this.playlists.forEach((playlist, index) => {
+					if (playlist._id === res.data.playlistId) {
+						this.playlists[index].displayName =
+							res.data.displayName;
+					}
+				});
+			},
+			{ replaceable: true }
+		);
 
-		this.socket.on("event:playlist.updatePrivacy", res => {
-			this.playlists.forEach((playlist, index) => {
-				if (playlist._id === res.data.playlist._id) {
-					this.playlists[index].privacy = res.data.playlist.privacy;
-				}
-			});
-		});
+		this.socket.on(
+			"event:playlist.updatePrivacy",
+			res => {
+				this.playlists.forEach((playlist, index) => {
+					if (playlist._id === res.data.playlist._id) {
+						this.playlists[index].privacy =
+							res.data.playlist.privacy;
+					}
+				});
+			},
+			{ replaceable: true }
+		);
 
-		this.socket.on("event:user.orderOfPlaylists.changed", res => {
-			const sortedPlaylists = [];
+		this.socket.on(
+			"event:user.orderOfPlaylists.changed",
+			res => {
+				const sortedPlaylists = [];
 
-			this.playlists.forEach(playlist => {
-				sortedPlaylists[
-					res.data.order.indexOf(playlist._id)
-				] = playlist;
-			});
+				this.playlists.forEach(playlist => {
+					sortedPlaylists[
+						res.data.order.indexOf(playlist._id)
+					] = playlist;
+				});
 
-			this.playlists = sortedPlaylists;
-			this.orderOfPlaylists = this.calculatePlaylistOrder();
-		});
+				this.playlists = sortedPlaylists;
+				this.orderOfPlaylists = this.calculatePlaylistOrder();
+			},
+			{ replaceable: true }
+		);
 	},
 	methods: {
 		calculatePlaylistOrder() {

+ 1 - 1
frontend/src/pages/Profile/tabs/Playlists.vue

@@ -131,7 +131,7 @@ export default {
 			ws.onConnect(() =>
 				this.socket.dispatch(
 					"apis.joinRoom",
-					`profile-${this.userId}-playlists`,
+					`profile.${this.userId}.playlists`,
 					() => {}
 				)
 			);

+ 1 - 1
frontend/src/pages/Profile/tabs/RecentActivity.vue

@@ -81,7 +81,7 @@ export default {
 			ws.onConnect(() =>
 				this.socket.dispatch(
 					"apis.joinRoom",
-					`profile-${this.userId}-activities`
+					`profile.${this.userId}.activities`
 				)
 			);
 

+ 28 - 8
frontend/src/ws.js

@@ -56,9 +56,27 @@ export default {
 				this.listeners = {};
 			}
 
-			addEventListener(type, cb) {
-				if (!(type in this.listeners)) this.listeners[type] = []; // add the listener type to listeners object
-				this.listeners[type].push(cb); // push the callback
+			addEventListener(type, cb, options) {
+				// add the listener type to listeners object
+				if (!(type in this.listeners)) this.listeners[type] = [];
+
+				const stack = this.listeners[type];
+
+				// push the callback
+				stack.push({ cb, ...options });
+
+				const replaceableIndexes = [];
+
+				// check for any replaceable callbacks
+				stack.forEach((element, index) => {
+					if (element.replaceable) replaceableIndexes.push(index);
+				});
+
+				// should always be 1 replaceable callback remaining
+				replaceableIndexes.pop();
+
+				// delete the other replaceable callbacks
+				replaceableIndexes.forEach(index => delete stack[index]);
 			}
 
 			// eslint-disable-next-line consistent-return
@@ -68,7 +86,7 @@ export default {
 				const stack = this.listeners[type];
 
 				stack.forEach((element, index) => {
-					if (element === cb) stack.splice(index, 1);
+					if (element.cb === cb) stack.splice(index, 1);
 				});
 			}
 
@@ -76,7 +94,7 @@ export default {
 				if (!(event.type in this.listeners)) return true; // event type doesn't exist
 
 				const stack = this.listeners[event.type].slice();
-				stack.forEach(element => element.call(this, event));
+				stack.forEach(element => element.cb.call(this, event));
 
 				return !event.defaultPrevented;
 			}
@@ -88,9 +106,11 @@ export default {
 				this.dispatcher = new ListenerHandler();
 			}
 
-			on(target, cb) {
-				this.dispatcher.addEventListener(target, event =>
-					cb(...event.detail)
+			on(target, cb, options) {
+				this.dispatcher.addEventListener(
+					target,
+					event => cb(...event.detail),
+					options
 				);
 			}