Created
October 13, 2018 23:05
-
-
Save revskill10/19f596a8509d233a6aaf0d17d0e2c533 to your computer and use it in GitHub Desktop.
ApolloClient with offline support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {ApolloClient} from 'apollo-client' | |
import {InMemoryCache} from 'apollo-cache-inmemory' | |
import {HttpLink} from 'apollo-link-http' | |
import {ApolloLink} from 'apollo-link' | |
import {persistCache} from 'apollo-cache-persist' | |
import {onError} from 'apollo-link-error' | |
import {QueueMutationLink} from './QueueMutationLink' | |
import {SyncOfflineMutation} from './SyncOfflineMutation' | |
export const setupApolloClient = async () => { | |
const storage = window.localStorage | |
const uri = `https://api.graph.cool/simple/v1/cjicrt45i0svu01337s6tl944` | |
const httpLink = new HttpLink({uri}) | |
const onErrorLink = onError(({response, graphQLErrors, networkError}) => { | |
console.log(networkError) | |
console.log(graphQLErrors) | |
response = {errors: null} | |
}) | |
const queueLink = new QueueMutationLink({storage}) | |
const cache = new InMemoryCache() | |
let link = ApolloLink.from([queueLink, onErrorLink, httpLink]) | |
const apolloClient = new ApolloClient({link, cache,}) | |
await persistCache({ | |
cache, | |
storage: window.localStorage, | |
}) | |
window.addEventListener('online', () => queueLink.open({apolloClient})) | |
window.addEventListener('offline', () => queueLink.close()) | |
const syncOfflineMutation = new SyncOfflineMutation({apolloClient, storage}) | |
await syncOfflineMutation.init() | |
await syncOfflineMutation.sync() | |
return apolloClient | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const netInfo = () => { | |
const eventName = navigator && navigator.onLine ? 'online' : 'offline' | |
window.dispatchEvent(new Event(eventName)) | |
} | |
// window.addEventListener('online', updateIndicator); | |
// window.addEventListener('offline', updateIndicator); | |
// updateIndicator(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {ApolloLink, Observable} from 'apollo-link' | |
import {SyncOfflineMutation} from './SyncOfflineMutation' | |
export class QueueMutationLink extends ApolloLink { | |
constructor({storage} = {}) { | |
super() | |
if (!storage) throw new Error('Storage can be window.localStorage or AsyncStorage but was not set') | |
this.storage = storage | |
this.storeKey = '@offlineQueueKey' | |
this.queue = [] | |
this.isOpen = true | |
} | |
resync = async ({apolloClient, syncOfflineMutation}) => { | |
syncOfflineMutation = syncOfflineMutation || new SyncOfflineMutation({apolloClient, storage: this.storage}) | |
await syncOfflineMutation.init() | |
await syncOfflineMutation.sync() | |
this.clearQueue() | |
} | |
open = async ({apolloClient} = {}) => { | |
if (!apolloClient) return | |
this.isOpen = true | |
await this.resync({apolloClient}) | |
} | |
close = () => { | |
this.isOpen = false | |
} | |
request = (operation, forward) => { | |
if (this.isOpen) { | |
return forward(operation) | |
} | |
else { | |
//if it is close enqueue first before forwarding | |
this.enqueue({operation}) | |
//return {offline: true} | |
//return forward(operation) | |
return new Observable(() => { | |
return () => ({isOffline: true}) | |
}) | |
} | |
} | |
enqueue = (entry) => { | |
const item = {...entry} | |
const {operation} = item | |
const {query, variables} = operation || {} | |
let definitions = [] | |
if (query && query.definitions) | |
definitions = query.definitions.filter(e => e.operation === 'mutation') | |
//store only if there are values for query.definitions | |
if (definitions.length > 0) { | |
query.definitions = definitions | |
this.queue.push({mutation: query, variables}) | |
//update the value of local storage | |
this.storage.setItem(this.storeKey, JSON.stringify(this.queue)) | |
} | |
} | |
clearQueue = () => { | |
this.queue = [] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class SyncOfflineMutation { | |
constructor ({apolloClient, storage} = {}) { | |
if (!apolloClient) throw new Error('Apollo Client instance is required when syncing data, please assign value to it') | |
if (!storage) throw new Error('Storage can be window.localStorage or AsyncStorage but was not set') | |
this.apolloClient = apolloClient | |
this.storage = storage | |
this.storeKey = '@offlineQueueKey' | |
this.offlineData = [] | |
} | |
getOfflineData = async () => { | |
return this.storage.getItem(this.storeKey) | |
} | |
hasOfflineData = () => { | |
return !!(this.offlineData && this.offlineData.length > 0) | |
} | |
addOfflineData = (queue = []) => { | |
//add only if there is a value | |
if (queue && queue.length > 0) | |
this.storage.setItem(this.storeKey, JSON.stringify(queue)) | |
} | |
clearOfflineData = async () => { | |
this.offlineData = [] | |
return this.storage.removeItem(this.storeKey) | |
} | |
init = async () => { | |
let stored = await this.getOfflineData() | |
this.offlineData = JSON.parse(stored) || [] | |
} | |
sync = async () => { | |
//if there is no offline data then just exit | |
if (!this.hasOfflineData()) return | |
//return as promise, but in the end clear the storage | |
const uncommittedOfflineMutation = [] | |
await Promise.all(this.offlineData.map(async (item) => { | |
try { | |
await this.apolloClient.mutate(item) | |
} | |
catch (e) { | |
//set the errored mutation to the stash | |
uncommittedOfflineMutation.push(item) | |
} | |
})) | |
//wait before it was cleared | |
await this.clearOfflineData() | |
//then add again the uncommited storage | |
this.addOfflineData(uncommittedOfflineMutation) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
functionReplacer = (key, value) => { | |
if (typeof(value) === 'function') { | |
return value.toString() | |
} | |
return value | |
} | |
functionReviver = (key, value) => { | |
if (key === '') return value | |
if (typeof value === 'string') { | |
const rfunc = /function[^\(]*\(([^\)]*)\)[^\{]*{([^\}]*)\}/, | |
match = value.match(rfunc) | |
if (match) { | |
const args = match[1].split(',').map(function (arg) { | |
return arg.replace(/\s+/, '') | |
}) | |
return new Function(args, match[2]) | |
} | |
} | |
return value | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment