From 1a507966c61dbcfabe5786e60a8657665386226c Mon Sep 17 00:00:00 2001 From: Dominik George <dominik.george@teckids.org> Date: Sun, 15 Jan 2023 01:00:56 +0100 Subject: [PATCH] Properly handle all offline and invisible states --- aleksis/core/assets/app/apollo.js | 34 +++++++--- .../core/assets/components/app/ping.graphql | 4 +- aleksis/core/assets/index.js | 1 + aleksis/core/assets/mixins/offline.js | 65 +++++++------------ aleksis/core/schema/__init__.py | 5 +- 5 files changed, 56 insertions(+), 53 deletions(-) diff --git a/aleksis/core/assets/app/apollo.js b/aleksis/core/assets/app/apollo.js index 35b1b231d..7236b9d3f 100644 --- a/aleksis/core/assets/app/apollo.js +++ b/aleksis/core/assets/app/apollo.js @@ -4,7 +4,6 @@ import { ApolloClient, HttpLink, from } from "@/apollo-boost"; -import { onError } from "@/apollo-link-error"; import { RetryLink } from "@/apollo-link-retry"; import { persistCache, LocalStorageWrapper } from "@/apollo3-cache-persist"; import { InMemoryCache } from "@/apollo-cache-inmemory"; @@ -97,15 +96,6 @@ function getGraphqlURL() { const links = [ // Automatically retry failed queries new RetryLink(), - // Add custom error handlers - onError(({ graphQLErrors, networkError }) => { - // Add a snackbar on all errors returned by the GraphQL endpoint - if (graphQLErrors) addErrorSnackbarItem("graphql.snackbar_error_message"); - // Set app offline globally on network errors - // This will cause the offline logic to kick in, starting a ping check or - // similar recovery strategies depending on the app/navigator state - if (networkError) app.offline = true; - }), // Finally, the HTTP link to the real backend (Django) new HttpLink({ uri: getGraphqlURL(), @@ -122,6 +112,30 @@ const apolloClient = new ApolloClient({ const apolloOpts = { defaultClient: apolloClient, + defaultOptions: { + $query: { + skip: (vm, queryKey) => { + // We only want to run this query when background activity is on and we are not reported offline + return !vm.$root.backgroundActive || vm.$root.offline; + }, + error: ({ graphQLErrors, networkError }, vm, key, type, options) => { + if (graphQLErrors) { + // Add a snackbar on all errors returned by the GraphQL endpoint + console.error("A GraphQL query failed on the server"); + addErrorSnackbarItem("graphql.snackbar_error_message"); + } + if (networkError) { + // Set app offline globally on network errors + // This will cause the offline logic to kick in, starting a ping check or + // similar recovery strategies depending on the app/navigator state + console.error( + "Network error during GraphQL query, setting offline state" + ); + vm.$root.offline = true; + } + }, + }, + }, }; export default apolloOpts; diff --git a/aleksis/core/assets/components/app/ping.graphql b/aleksis/core/assets/components/app/ping.graphql index 1a13edeb7..cc2f908e4 100644 --- a/aleksis/core/assets/components/app/ping.graphql +++ b/aleksis/core/assets/components/app/ping.graphql @@ -1,3 +1,3 @@ -{ - ping +query Pinf($payload: String) { + ping(payload: $payload) } diff --git a/aleksis/core/assets/index.js b/aleksis/core/assets/index.js index 459978ed1..ac9c36e45 100644 --- a/aleksis/core/assets/index.js +++ b/aleksis/core/assets/index.js @@ -46,6 +46,7 @@ const app = new Vue({ showCacheAlert: false, contentLoading: false, offline: false, + backgroundActive: true, }), router, i18n, diff --git a/aleksis/core/assets/mixins/offline.js b/aleksis/core/assets/mixins/offline.js index 7bdd551a3..02a34644e 100644 --- a/aleksis/core/assets/mixins/offline.js +++ b/aleksis/core/assets/mixins/offline.js @@ -23,63 +23,48 @@ const offlineMixin = { }, mounted() { window.addEventListener("online", (e) => { - console.debug("Navigator changed status to online."); - if (!document.hidden) this.toggleGlobalQueries(false, true); + console.info("Navigator changed status to online."); + this.checkOfflineState(); }); window.addEventListener("offline", (e) => { - console.debug("Navigator changed status to offline."); - this.toggleGlobalQueries(false, false); + console.info("Navigator changed status to offline."); + this.checkOfflineState(); }); document.addEventListener("visibilitychange", () => { - if (document.visibilityState === "hidden") { - console.debug("Visibility changed status to hidden."); - this.toggleGlobalQueries(false, false); - } else if (navigator.online) { - console.debug( - "Visibility changed status to visible. Navigator status is online." - ); - this.toggleGlobalQueries(false, true); - } + console.info("Visibility changed status to", document.visibilityState); + this.checkOfflineState(); }); }, methods: { - toggleGlobalQueries(enable, pingPolling) { - console.debug("Toggling global queries: " + (enable ? "on" : "off")); - if (enable) { - this.$apollo.queries.whoAmI.startPolling(10000); - this.$apollo.queries.messages.startPolling(1000); - } else { - this.$apollo.queries.whoAmI.stopPolling(); - this.$apollo.queries.messages.stopPolling(); - } - if (pingPolling) { - console.debug("Starting ping polling."); - this.$apollo.queries.ping.observer.resetLastResults(); - this.ping = null; - this.$apollo.queries.ping.startPolling(1000); + checkOfflineState() { + if (navigator.onLine && document.visibilityState === "visible") { + console.info("Resuming background activity"); + this.$root.backgroundActive = true; } else { - this.$apollo.queries.ping.stopPolling(); + console.info("Pausing background activity"); + this.$root.backgroundActive = false; } }, }, apollo: { ping: { query: gqlPing, + variables: () => { + return { + payload: Date.now().toString(), + }; + }, + pollInterval: 1000, + skip: (component, query) => { + // We only want to run this query when background activity is on and we are reported offline + return !(component.$root.backgroundActive && component.$root.offline); + }, }, }, watch: { - ping: function (ping) { - if (ping === "pong") { - console.debug("Pong was received. Resuming regular queries."); - this.toggleGlobalQueries(true, false); - this.$root.offline = false; - } - }, - "$root.offline": function (offline) { - if (navigator.onLine && !document.hidden) { - console.debug("Queries failed, but navigator is online."); - this.toggleGlobalQueries(false, true); - } + ping(payload) { + console.info("Pong received, clearing offline state"); + this.$root.offline = false; }, }, }; diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py index a4ed81580..a94f88d48 100644 --- a/aleksis/core/schema/__init__.py +++ b/aleksis/core/schema/__init__.py @@ -27,7 +27,7 @@ from .user import UserType class Query(graphene.ObjectType): - ping = graphene.String(default_value="pong") + ping = graphene.String(payload=graphene.String()) notifications = graphene.List(NotificationType) @@ -53,6 +53,9 @@ class Query(graphene.ObjectType): custom_menu_by_name = graphene.Field(CustomMenuType, name=graphene.String()) + def resolve_ping(root, info, payload) -> str: + return payload + def resolve_notifications(root, info, **kwargs): return Notification.objects.filter( Q( -- GitLab