diff --git a/composables/useSubscriptionManager.ts b/composables/useSubscriptionManager.ts new file mode 100644 index 00000000..aa685268 --- /dev/null +++ b/composables/useSubscriptionManager.ts @@ -0,0 +1,28 @@ +const subscriptions = new Map void }>(); + +export function useSubscriptionManager() { + function subscribe(key: string, sub: { unsubscribe: () => void }) { + const existing = subscriptions.get(key); + if (existing) { + existing.unsubscribe(); + } + subscriptions.set(key, sub); + } + + function unsubscribe(key: string) { + const existing = subscriptions.get(key); + if (existing) { + existing.unsubscribe(); + subscriptions.delete(key); + } + } + + function unsubscribeAll() { + for (const [, sub] of subscriptions) { + sub.unsubscribe(); + } + subscriptions.clear(); + } + + return { subscribe, unsubscribe, unsubscribeAll }; +} diff --git a/stores/ApplicationSettings.ts b/stores/ApplicationSettings.ts index c7b099c4..3a2beb04 100644 --- a/stores/ApplicationSettings.ts +++ b/stores/ApplicationSettings.ts @@ -6,6 +6,7 @@ import { generateSubscription } from "~/graphql/graphqlGen"; import { useMatchmakingStore } from "./MatchmakingStore"; import { useAuthStore } from "./AuthStore"; import { order_by } from "@/generated/zeus"; +import { useSubscriptionManager } from "~/composables/useSubscriptionManager"; interface Region { value: string; @@ -36,6 +37,7 @@ export const useApplicationSettingsStore = defineStore( ref>(loadCachedSettings()); const subscribeToSettings = async () => { + const { subscribe } = useSubscriptionManager(); const subscription = getGraphqlClient().subscribe({ query: generateSubscription({ settings: [ @@ -48,17 +50,20 @@ export const useApplicationSettingsStore = defineStore( }), }); - subscription.subscribe({ - next: ({ data }) => { - settings.value = data.settings; - try { - localStorage.setItem( - SETTINGS_CACHE_KEY, - JSON.stringify(data.settings), - ); - } catch {} - }, - }); + subscribe( + "settings:settings", + subscription.subscribe({ + next: ({ data }) => { + settings.value = data.settings; + try { + localStorage.setItem( + SETTINGS_CACHE_KEY, + JSON.stringify(data.settings), + ); + } catch {} + }, + }), + ); }; subscribeToSettings(); @@ -66,6 +71,7 @@ export const useApplicationSettingsStore = defineStore( const currentPluginVersion = ref(null); const subscribeToPluginVersion = async () => { + const { subscribe } = useSubscriptionManager(); const authStore = useAuthStore(); if ( !authStore.me || @@ -92,11 +98,14 @@ export const useApplicationSettingsStore = defineStore( }), }); - subscription.subscribe({ - next: ({ data }) => { - currentPluginVersion.value = data.plugin_versions.at(0).version; - }, - }); + subscribe( + "settings:plugin_version", + subscription.subscribe({ + next: ({ data }) => { + currentPluginVersion.value = data.plugin_versions.at(0).version; + }, + }), + ); }; // Watch for user authentication before subscribing @@ -208,7 +217,10 @@ export const useApplicationSettingsStore = defineStore( const availableRegions = ref([]); + let latencyCheckInterval: ReturnType | null = null; + const subscribeToAvailableRegions = async () => { + const { subscribe } = useSubscriptionManager(); const subscription = getGraphqlClient().subscribe({ query: generateSubscription({ server_regions: [ @@ -230,19 +242,24 @@ export const useApplicationSettingsStore = defineStore( }), }); - subscription.subscribe({ - next: ({ data }) => { - availableRegions.value = data.server_regions; - useMatchmakingStore().checkLatenies(); - - setInterval( - () => { - useMatchmakingStore().checkLatenies(); - }, - 50 * 60 * 1000, - ); - }, - }); + subscribe( + "settings:available_regions", + subscription.subscribe({ + next: ({ data }) => { + availableRegions.value = data.server_regions; + useMatchmakingStore().checkLatenies(); + + if (!latencyCheckInterval) { + latencyCheckInterval = setInterval( + () => { + useMatchmakingStore().checkLatenies(); + }, + 50 * 60 * 1000, + ); + } + }, + }), + ); }; subscribeToAvailableRegions(); diff --git a/stores/MatchLobbyStore.ts b/stores/MatchLobbyStore.ts index 9bbbc862..394e946e 100644 --- a/stores/MatchLobbyStore.ts +++ b/stores/MatchLobbyStore.ts @@ -1,5 +1,6 @@ import { ref, computed } from "vue"; import { defineStore, acceptHMRUpdate } from "pinia"; +import { useSubscriptionManager } from "~/composables/useSubscriptionManager"; import { simpleMatchFields } from "~/graphql/simpleMatchFields"; import { $, @@ -51,14 +52,18 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { }), }); - subscription.subscribe({ - next: ({ data }) => { - liveMatchesCount.value = data?.matches_aggregate?.aggregate?.count || 0; - }, - error: (error) => { - console.error("Error in live matches subscription:", error); - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchLobby:liveMatches", + subscription.subscribe({ + next: ({ data }) => { + liveMatchesCount.value = data?.matches_aggregate?.aggregate?.count || 0; + }, + error: (error) => { + console.error("Error in live matches subscription:", error); + }, + }), + ); }; const subscribeToLiveTournaments = async () => { @@ -81,15 +86,19 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { }), }); - subscription.subscribe({ - next: ({ data }) => { - liveTournamentsCount.value = - data?.tournaments_aggregate?.aggregate?.count || 0; - }, - error: (error) => { - console.error("Error in live tournaments subscription:", error); - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchLobby:liveTournaments", + subscription.subscribe({ + next: ({ data }) => { + liveTournamentsCount.value = + data?.tournaments_aggregate?.aggregate?.count || 0; + }, + error: (error) => { + console.error("Error in live tournaments subscription:", error); + }, + }), + ); }; const subscribeToOpenRegistrationTournaments = async () => { @@ -112,18 +121,22 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { }), }); - subscription.subscribe({ - next: ({ data }) => { - openRegistrationTournamentsCount.value = - data?.tournaments_aggregate?.aggregate?.count || 0; - }, - error: (error) => { - console.error( - "Error in open registration tournaments subscription:", - error, - ); - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchLobby:openRegistrationTournaments", + subscription.subscribe({ + next: ({ data }) => { + openRegistrationTournamentsCount.value = + data?.tournaments_aggregate?.aggregate?.count || 0; + }, + error: (error) => { + console.error( + "Error in open registration tournaments subscription:", + error, + ); + }, + }), + ); }; const subscribeToOpenMatches = async () => { @@ -151,14 +164,18 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { }), }); - subscription.subscribe({ - next: ({ data }) => { - openMatchesCount.value = data?.matches_aggregate?.aggregate?.count || 0; - }, - error: (error) => { - console.error("Error in open matches subscription:", error); - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchLobby:openMatches", + subscription.subscribe({ + next: ({ data }) => { + openMatchesCount.value = data?.matches_aggregate?.aggregate?.count || 0; + }, + error: (error) => { + console.error("Error in open matches subscription:", error); + }, + }), + ); }; const subscribeToChatTournaments = async () => { @@ -198,14 +215,18 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { }), }); - subscription.subscribe({ - next: ({ data }) => { - chatTournaments.value = data?.tournaments || []; - }, - error: (error) => { - console.error("Error in chat tournaments subscription:", error); - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchLobby:chatTournaments", + subscription.subscribe({ + next: ({ data }) => { + chatTournaments.value = data?.tournaments || []; + }, + error: (error) => { + console.error("Error in chat tournaments subscription:", error); + }, + }), + ); }; const subscribeToManagingMatches = async () => { @@ -240,18 +261,22 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { }), }); - subscription.subscribe({ - next: ({ data }) => { - if (data?.matches_aggregate?.aggregate?.count !== undefined) { - managingMatchesCount.value = data.matches_aggregate.aggregate.count; - } else { - managingMatchesCount.value = 0; - } - }, - error: (error) => { - console.error("Error in managing matches subscription:", error); - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchLobby:managingMatches", + subscription.subscribe({ + next: ({ data }) => { + if (data?.matches_aggregate?.aggregate?.count !== undefined) { + managingMatchesCount.value = data.matches_aggregate.aggregate.count; + } else { + managingMatchesCount.value = 0; + } + }, + error: (error) => { + console.error("Error in managing matches subscription:", error); + }, + }), + ); }; const subscribeToManagingTournaments = async () => { @@ -284,19 +309,23 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { }), }); - subscription.subscribe({ - next: ({ data }) => { - if (data?.tournaments_aggregate?.aggregate?.count !== undefined) { - managingTournamentsCount.value = - data.tournaments_aggregate.aggregate.count; - } else { - managingTournamentsCount.value = 0; - } - }, - error: (error) => { - console.error("Error in managing tournaments subscription:", error); - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchLobby:managingTournaments", + subscription.subscribe({ + next: ({ data }) => { + if (data?.tournaments_aggregate?.aggregate?.count !== undefined) { + managingTournamentsCount.value = + data.tournaments_aggregate.aggregate.count; + } else { + managingTournamentsCount.value = 0; + } + }, + error: (error) => { + console.error("Error in managing tournaments subscription:", error); + }, + }), + ); }; const subscribeToMyMatches = async () => { @@ -373,11 +402,15 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { }, }); - subscription.subscribe({ - next: ({ data }) => { - myMatches.value = data?.matches; - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchLobby:myMatches", + subscription.subscribe({ + next: ({ data }) => { + myMatches.value = data?.matches; + }, + }), + ); }; const add = ( diff --git a/stores/MatchmakingStore.ts b/stores/MatchmakingStore.ts index 63906f9b..04379a4d 100644 --- a/stores/MatchmakingStore.ts +++ b/stores/MatchmakingStore.ts @@ -1,5 +1,6 @@ import { ref, watch, computed } from "vue"; import { defineStore, acceptHMRUpdate } from "pinia"; +import { useSubscriptionManager } from "~/composables/useSubscriptionManager"; import { e_match_types_enum, $, @@ -140,11 +141,15 @@ export const useMatchmakingStore = defineStore("matchmaking", () => { }, }); - subscription.subscribe({ - next: ({ data }) => { - friends.value = data.my_friends; - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchmaking:friends", + subscription.subscribe({ + next: ({ data }) => { + friends.value = data.my_friends; + }, + }), + ); }; const matchInvites = ref([]); @@ -180,11 +185,15 @@ export const useMatchmakingStore = defineStore("matchmaking", () => { }, }); - subscription.subscribe({ - next: ({ data }) => { - matchInvites.value = data.match_invites; - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchmaking:match_invites", + subscription.subscribe({ + next: ({ data }) => { + matchInvites.value = data.match_invites; + }, + }), + ); }; const onlineFriends = computed(() => { @@ -238,11 +247,15 @@ export const useMatchmakingStore = defineStore("matchmaking", () => { }, }); - subscription.subscribe({ - next: ({ data }) => { - lobbies.value = data.lobbies; - }, - }); + const { subscribe } = useSubscriptionManager(); + subscribe( + "matchmaking:lobbies", + subscription.subscribe({ + next: ({ data }) => { + lobbies.value = data.lobbies; + }, + }), + ); }; watch( @@ -252,6 +265,11 @@ export const useMatchmakingStore = defineStore("matchmaking", () => { subscribeToFriends(me.steam_id); subscribeToMatchInvites(me.steam_id); subscribeToLobbies(me.steam_id); + } else { + const { unsubscribe } = useSubscriptionManager(); + unsubscribe("matchmaking:friends"); + unsubscribe("matchmaking:match_invites"); + unsubscribe("matchmaking:lobbies"); } }, { immediate: true }, diff --git a/stores/NotificationStore.ts b/stores/NotificationStore.ts index 8caec26b..6714769f 100644 --- a/stores/NotificationStore.ts +++ b/stores/NotificationStore.ts @@ -4,6 +4,7 @@ import { typedGql } from "~/generated/zeus/typedDocumentNode"; import { $, order_by } from "~/generated/zeus"; import getGraphqlClient from "~/graphql/getGraphqlClient"; import { playerFields } from "~/graphql/playerFields"; +import { useSubscriptionManager } from "~/composables/useSubscriptionManager"; type Notification = { id: string; @@ -74,103 +75,114 @@ export const useNotificationStore = defineStore("notifaicationStore", () => { }; function subscribeToAll(steam_id: string) { - getGraphqlClient() - .subscribe({ - query: typedGql("subscription")({ - team_invites: [ - { - order_by: [{}, { created_at: order_by.desc }], - where: { steam_id: { _eq: $("steam_id", "bigint!") } }, - }, - { - id: true, - team: { id: true, name: true }, - invited_by: { ...playerFields }, - created_at: true, - }, - ], + const { subscribe } = useSubscriptionManager(); + + subscribe( + "notifications:team_invites", + getGraphqlClient() + .subscribe({ + query: typedGql("subscription")({ + team_invites: [ + { + order_by: [{}, { created_at: order_by.desc }], + where: { steam_id: { _eq: $("steam_id", "bigint!") } }, + }, + { + id: true, + team: { id: true, name: true }, + invited_by: { ...playerFields }, + created_at: true, + }, + ], + }), + variables: { steam_id }, + }) + .subscribe({ + next: ({ data }) => { + team_invites.value = data.team_invites; + }, }), - variables: { steam_id }, - }) - .subscribe({ - next: ({ data }) => { - team_invites.value = data.team_invites; - }, - }); + ); - getGraphqlClient() - .subscribe({ - query: typedGql("subscription")({ - tournament_team_invites: [ - { - order_by: [{}, { created_at: order_by.desc }], - where: { steam_id: { _eq: $("steam_id", "bigint!") } }, - }, - { - id: true, - team: { id: true, name: true, tournament: { name: true } }, - invited_by: { ...playerFields }, - created_at: true, - }, - ], + subscribe( + "notifications:tournament_team_invites", + getGraphqlClient() + .subscribe({ + query: typedGql("subscription")({ + tournament_team_invites: [ + { + order_by: [{}, { created_at: order_by.desc }], + where: { steam_id: { _eq: $("steam_id", "bigint!") } }, + }, + { + id: true, + team: { id: true, name: true, tournament: { name: true } }, + invited_by: { ...playerFields }, + created_at: true, + }, + ], + }), + variables: { steam_id }, + }) + .subscribe({ + next: ({ data }) => { + tournament_team_invites.value = data.tournament_team_invites; + }, }), - variables: { steam_id }, - }) - .subscribe({ - next: ({ data }) => { - tournament_team_invites.value = data.tournament_team_invites; - }, - }); + ); - getGraphqlClient() - .subscribe({ - query: typedGql("subscription")({ - notifications: [ - { - order_by: [{}, { created_at: order_by.desc }], - where: { - _and: [ - { deleted_at: { _is_null: true } }, - { - _or: [ - { is_read: { _eq: false } }, - { - _and: [ - { is_read: { _eq: true } }, - { - created_at: { - _gt: new Date( - Date.now() - 7 * 24 * 60 * 60 * 1000, - ), + subscribe( + "notifications:notifications", + getGraphqlClient() + .subscribe({ + query: typedGql("subscription")({ + notifications: [ + { + order_by: [{}, { created_at: order_by.desc }], + where: { + _and: [ + { deleted_at: { _is_null: true } }, + { + _or: [ + { is_read: { _eq: false } }, + { + _and: [ + { is_read: { _eq: true } }, + { + created_at: { + _gt: new Date( + Date.now() - 7 * 24 * 60 * 60 * 1000, + ), + }, }, - }, - ], - }, - ], - }, - ], + ], + }, + ], + }, + ], + }, }, - }, - { - id: true, - title: true, - message: true, - steam_id: true, - type: true, - entity_id: true, - is_read: true, - deletable: true, - created_at: true, - actions: true, - }, - ], + { + id: true, + title: true, + message: true, + steam_id: true, + type: true, + entity_id: true, + is_read: true, + deletable: true, + created_at: true, + actions: true, + }, + ], + }), + }) + .subscribe({ + next: ({ data }) => { + notifications.value = data.notifications; + }, }), - }) - .subscribe({ - next: ({ data }) => { - notifications.value = data.notifications; - }, - }); + ); } watch( @@ -178,6 +190,11 @@ export const useNotificationStore = defineStore("notifaicationStore", () => { (me) => { if (me) { subscribeToAll(me.steam_id); + } else { + const { unsubscribe } = useSubscriptionManager(); + unsubscribe("notifications:team_invites"); + unsubscribe("notifications:tournament_team_invites"); + unsubscribe("notifications:notifications"); } }, { immediate: true },