Skip to content
Snippets Groups Projects
Verified Commit 1a507966 authored by Nik | Klampfradler's avatar Nik | Klampfradler
Browse files

Properly handle all offline and invisible states

parent 3e55f92c
No related branches found
No related tags found
1 merge request!1123Resolve "Finalise Vuetify app as SPA"
Pipeline #108034 failed
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import { ApolloClient, HttpLink, from } from "@/apollo-boost"; import { ApolloClient, HttpLink, from } from "@/apollo-boost";
import { onError } from "@/apollo-link-error";
import { RetryLink } from "@/apollo-link-retry"; import { RetryLink } from "@/apollo-link-retry";
import { persistCache, LocalStorageWrapper } from "@/apollo3-cache-persist"; import { persistCache, LocalStorageWrapper } from "@/apollo3-cache-persist";
import { InMemoryCache } from "@/apollo-cache-inmemory"; import { InMemoryCache } from "@/apollo-cache-inmemory";
...@@ -97,15 +96,6 @@ function getGraphqlURL() { ...@@ -97,15 +96,6 @@ function getGraphqlURL() {
const links = [ const links = [
// Automatically retry failed queries // Automatically retry failed queries
new RetryLink(), 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) // Finally, the HTTP link to the real backend (Django)
new HttpLink({ new HttpLink({
uri: getGraphqlURL(), uri: getGraphqlURL(),
...@@ -122,6 +112,30 @@ const apolloClient = new ApolloClient({ ...@@ -122,6 +112,30 @@ const apolloClient = new ApolloClient({
const apolloOpts = { const apolloOpts = {
defaultClient: apolloClient, 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; export default apolloOpts;
{ query Pinf($payload: String) {
ping ping(payload: $payload)
} }
...@@ -46,6 +46,7 @@ const app = new Vue({ ...@@ -46,6 +46,7 @@ const app = new Vue({
showCacheAlert: false, showCacheAlert: false,
contentLoading: false, contentLoading: false,
offline: false, offline: false,
backgroundActive: true,
}), }),
router, router,
i18n, i18n,
......
...@@ -23,63 +23,48 @@ const offlineMixin = { ...@@ -23,63 +23,48 @@ const offlineMixin = {
}, },
mounted() { mounted() {
window.addEventListener("online", (e) => { window.addEventListener("online", (e) => {
console.debug("Navigator changed status to online."); console.info("Navigator changed status to online.");
if (!document.hidden) this.toggleGlobalQueries(false, true); this.checkOfflineState();
}); });
window.addEventListener("offline", (e) => { window.addEventListener("offline", (e) => {
console.debug("Navigator changed status to offline."); console.info("Navigator changed status to offline.");
this.toggleGlobalQueries(false, false); this.checkOfflineState();
}); });
document.addEventListener("visibilitychange", () => { document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") { console.info("Visibility changed status to", document.visibilityState);
console.debug("Visibility changed status to hidden."); this.checkOfflineState();
this.toggleGlobalQueries(false, false);
} else if (navigator.online) {
console.debug(
"Visibility changed status to visible. Navigator status is online."
);
this.toggleGlobalQueries(false, true);
}
}); });
}, },
methods: { methods: {
toggleGlobalQueries(enable, pingPolling) { checkOfflineState() {
console.debug("Toggling global queries: " + (enable ? "on" : "off")); if (navigator.onLine && document.visibilityState === "visible") {
if (enable) { console.info("Resuming background activity");
this.$apollo.queries.whoAmI.startPolling(10000); this.$root.backgroundActive = true;
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);
} else { } else {
this.$apollo.queries.ping.stopPolling(); console.info("Pausing background activity");
this.$root.backgroundActive = false;
} }
}, },
}, },
apollo: { apollo: {
ping: { ping: {
query: gqlPing, 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: { watch: {
ping: function (ping) { ping(payload) {
if (ping === "pong") { console.info("Pong received, clearing offline state");
console.debug("Pong was received. Resuming regular queries."); this.$root.offline = false;
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);
}
}, },
}, },
}; };
......
...@@ -27,7 +27,7 @@ from .user import UserType ...@@ -27,7 +27,7 @@ from .user import UserType
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
ping = graphene.String(default_value="pong") ping = graphene.String(payload=graphene.String())
notifications = graphene.List(NotificationType) notifications = graphene.List(NotificationType)
...@@ -53,6 +53,9 @@ class Query(graphene.ObjectType): ...@@ -53,6 +53,9 @@ class Query(graphene.ObjectType):
custom_menu_by_name = graphene.Field(CustomMenuType, name=graphene.String()) 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): def resolve_notifications(root, info, **kwargs):
return Notification.objects.filter( return Notification.objects.filter(
Q( Q(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment