From eb21cf58fe4e661c31bc5d7f58988776d325c3d7 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:44:49 +0200 Subject: [PATCH] feat: Configure CozyClient to use CozyPouchLink We want the Flagship app to work when offline To make this possible we configure cozy-client with CozyPouchLink which role will be to synchronize necessary doctypes into a local PouchDB and serve them from it instead of from the cozy-stack when the device is offline For now the list of synchronized doctypes is hardcoded but in the future we expect to implement a dynamic list based on cozy-apps' manifests Related PR: cozy/cozy-client#1507 --- src/libs/client.js | 5 ++- src/libs/client.spec.js | 3 +- src/libs/clientHelpers/createClient.ts | 6 +++- src/pouchdb/getLinks.ts | 39 +++++++++++++++++++++ src/pouchdb/platformReactNative.appState.ts | 34 ++++++++++++++++++ src/pouchdb/platformReactNative.events.ts | 28 +++++++++++++++ src/pouchdb/platformReactNative.isOnline.ts | 5 +++ src/pouchdb/platformReactNative.netInfo.ts | 24 +++++++++++++ src/pouchdb/platformReactNative.storage.ts | 14 ++++++++ src/pouchdb/platformReactNative.ts | 12 +++++++ 10 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 src/pouchdb/getLinks.ts create mode 100644 src/pouchdb/platformReactNative.appState.ts create mode 100644 src/pouchdb/platformReactNative.events.ts create mode 100644 src/pouchdb/platformReactNative.isOnline.ts create mode 100644 src/pouchdb/platformReactNative.netInfo.ts create mode 100644 src/pouchdb/platformReactNative.storage.ts create mode 100644 src/pouchdb/platformReactNative.ts diff --git a/src/libs/client.js b/src/libs/client.js index 49206c162..f304bba49 100644 --- a/src/libs/client.js +++ b/src/libs/client.js @@ -30,6 +30,7 @@ export { } from '/libs/clientHelpers/initClient' export { call2FAInitClient } from '/libs/clientHelpers/twoFactorAuthentication' import { CozyPersistedStorageKeys, getData } from '/libs/localStore/storage' +import { getLinks } from '/pouchdb/getLinks' const log = Minilog('LoginScreen') @@ -44,6 +45,7 @@ export const getClient = async () => { return false } const { uri, oauthOptions, token } = oauthData + const links = getLinks() const client = new CozyClient({ uri, oauth: { token }, @@ -51,7 +53,8 @@ export const getClient = async () => { appMetadata: { slug: 'flagship', version: packageJSON.version - } + }, + links }) listenTokenRefresh(client) client.getStackClient().setOAuthOptions(oauthOptions) diff --git a/src/libs/client.spec.js b/src/libs/client.spec.js index 9de17df7a..6de3a03f8 100644 --- a/src/libs/client.spec.js +++ b/src/libs/client.spec.js @@ -62,7 +62,8 @@ describe('client', () => { appMetadata: { slug: 'flagship', version: packageJSON.version - } + }, + links: expect.anything() }) }) diff --git a/src/libs/clientHelpers/createClient.ts b/src/libs/clientHelpers/createClient.ts index 0a3a30bb6..8e16bc5bd 100644 --- a/src/libs/clientHelpers/createClient.ts +++ b/src/libs/clientHelpers/createClient.ts @@ -13,6 +13,7 @@ import googleServicesJson from '/../android/app/src/prod/google-services.json' import packageJSON from '../../../package.json' import { startListening } from '/app/domain/authentication/services/AuthService' +import { getLinks } from '/pouchdb/getLinks' /** * Create a CozyClient for the given Cozy instance and register it @@ -21,6 +22,8 @@ import { startListening } from '/app/domain/authentication/services/AuthService' * @returns {CozyClient} - The created and registered CozyClient */ export const createClient = async (instance: string): Promise => { + const links = getLinks() + const options = { scope: ['*'], oauth: { @@ -37,7 +40,8 @@ export const createClient = async (instance: string): Promise => { appMetadata: { slug: 'flagship', version: packageJSON.version - } + }, + links } const client = new CozyClient(options) diff --git a/src/pouchdb/getLinks.ts b/src/pouchdb/getLinks.ts new file mode 100644 index 000000000..5d3a91190 --- /dev/null +++ b/src/pouchdb/getLinks.ts @@ -0,0 +1,39 @@ +import { platformReactNative } from '/pouchdb/platformReactNative' + +import { default as PouchLink } from 'cozy-pouch-link' + +export const offlineDoctypes = [ + // cozy-home + 'io.cozy.accounts', + 'io.cozy.apps', + 'io.cozy.contacts', + 'io.cozy.files', + 'io.cozy.files.shortcuts', + 'io.cozy.home.settings', + 'io.cozy.jobs', + 'io.cozy.konnectors', + 'io.cozy.settings', + 'io.cozy.apps.suggestions', + 'io.cozy.triggers', + 'io.cozy.apps_registry', + + // mespapiers + 'io.cozy.bills', + 'io.cozy.sharings', + 'io.cozy.mespapiers.settings', + 'io.cozy.permissions' +] + +export const getLinks = () => { + const pouchLinkOptions = { + doctypes: offlineDoctypes, + initialSync: true, + platform: platformReactNative + } + + const pouchLink = new PouchLink({ + ...pouchLinkOptions + }) + + return [pouchLink] +} diff --git a/src/pouchdb/platformReactNative.appState.ts b/src/pouchdb/platformReactNative.appState.ts new file mode 100644 index 000000000..41d93b11c --- /dev/null +++ b/src/pouchdb/platformReactNative.appState.ts @@ -0,0 +1,34 @@ +import EventEmitter from 'events' + +import { AppState, AppStateStatus, NativeEventSubscription } from 'react-native' + +import Minilog from 'cozy-minilog' + +const log = Minilog('🛋️ PlatormReactNative.appState') + +let appState = AppState.currentState +let appStateHandler: NativeEventSubscription | undefined = undefined + +export const listenAppState = (eventEmitter: EventEmitter): void => { + appStateHandler = AppState.addEventListener('change', nextAppState => { + log.debug('🛋️ AppState event', nextAppState) + if (isGoingToSleep(nextAppState)) { + eventEmitter.emit('resume') + } + if (isGoingToWakeUp(nextAppState)) { + eventEmitter.emit('pause') + } + + appState = nextAppState + }) +} + +export const stopListeningAppState = (): void => { + appStateHandler?.remove() +} + +const isGoingToSleep = (nextAppState: AppStateStatus): boolean => + Boolean(appState.match(/active/) && nextAppState === 'background') + +const isGoingToWakeUp = (nextAppState: AppStateStatus): boolean => + Boolean(appState.match(/background/) && nextAppState === 'active') diff --git a/src/pouchdb/platformReactNative.events.ts b/src/pouchdb/platformReactNative.events.ts new file mode 100644 index 000000000..8382f7853 --- /dev/null +++ b/src/pouchdb/platformReactNative.events.ts @@ -0,0 +1,28 @@ +import { EventEmitter } from 'events' + +import { listenAppState } from '/pouchdb/platformReactNative.appState' +import { listenNetInfo } from '/pouchdb/platformReactNative.netInfo' + +export const pouchDbEmitter = new EventEmitter() + +const listenPouchEvents = (): void => { + listenAppState(pouchDbEmitter) + listenNetInfo(pouchDbEmitter) +} + +listenPouchEvents() + +export const events = { + addEventListener: ( + eventName: string, + handler: (...args: unknown[]) => void + ): void => { + pouchDbEmitter.addListener(eventName, handler) + }, + removeEventListener: ( + eventName: string, + handler: (...args: unknown[]) => void + ): void => { + pouchDbEmitter.removeListener(eventName, handler) + } +} diff --git a/src/pouchdb/platformReactNative.isOnline.ts b/src/pouchdb/platformReactNative.isOnline.ts new file mode 100644 index 000000000..8826fd11a --- /dev/null +++ b/src/pouchdb/platformReactNative.isOnline.ts @@ -0,0 +1,5 @@ +import { NetService } from '/libs/services/NetService' + +export const isOnline = (): Promise => { + return NetService.isConnected() +} diff --git a/src/pouchdb/platformReactNative.netInfo.ts b/src/pouchdb/platformReactNative.netInfo.ts new file mode 100644 index 000000000..2d52a3b8a --- /dev/null +++ b/src/pouchdb/platformReactNative.netInfo.ts @@ -0,0 +1,24 @@ +import EventEmitter from 'events' + +import NetInfo, { NetInfoSubscription } from '@react-native-community/netinfo' + +import Minilog from 'cozy-minilog' + +const log = Minilog('🛋️ PlatormReactNative.netInfo') + +let netInfoHandler: NetInfoSubscription | undefined = undefined + +export const listenNetInfo = (eventEmitter: EventEmitter): void => { + netInfoHandler = NetInfo.addEventListener(state => { + log.debug('🛋️ NetInfo event', state.isConnected) + if (state.isConnected) { + eventEmitter.emit('online') + } else { + eventEmitter.emit('offline') + } + }) +} + +export const stopListeningNetInfo = (): void => { + netInfoHandler?.() +} diff --git a/src/pouchdb/platformReactNative.storage.ts b/src/pouchdb/platformReactNative.storage.ts new file mode 100644 index 000000000..f294bc4ca --- /dev/null +++ b/src/pouchdb/platformReactNative.storage.ts @@ -0,0 +1,14 @@ +import AsyncStorage from '@react-native-async-storage/async-storage' + +export const storage = { + getItem: async (key: string): Promise => { + return AsyncStorage.getItem(key) + }, + setItem: async (key: string, value: string | undefined): Promise => { + if (value === undefined) return + return AsyncStorage.setItem(key, value) + }, + removeItem: async (key: string): Promise => { + return AsyncStorage.removeItem(key) + } +} diff --git a/src/pouchdb/platformReactNative.ts b/src/pouchdb/platformReactNative.ts new file mode 100644 index 000000000..9241e70d8 --- /dev/null +++ b/src/pouchdb/platformReactNative.ts @@ -0,0 +1,12 @@ +import { events } from '/pouchdb/platformReactNative.events' +import { isOnline } from '/pouchdb/platformReactNative.isOnline' +import { storage } from '/pouchdb/platformReactNative.storage' +import PouchDB from '/pouchdb/pouchdb' + +export const platformReactNative = { + storage, + events, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + pouchAdapter: PouchDB, + isOnline +}