diff --git a/package.json b/package.json index 370af3081..193268caa 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "cozy-logger": "^1.10.0", "cozy-minilog": "3.3.1", "cozy-pouch-link": "^52.0.0", + "cozy-realtime": "^5.6.2", "date-fns": "2.29.3", "events": "^3.3.0", "html-entities": "^2.3.3", diff --git a/src/@types/cozy-client.d.ts b/src/@types/cozy-client.d.ts index 347763e2e..757fba26e 100644 --- a/src/@types/cozy-client.d.ts +++ b/src/@types/cozy-client.d.ts @@ -185,6 +185,8 @@ declare module 'cozy-client' { options?: QueryOptions ) => Promise links: CozyLink[] + plugins: Record + registerPlugin: (plugin: unknown, options?: unknown) => void } export const createMockClient = (options?: ClientOptions): CozyClient => diff --git a/src/App.js b/src/App.js index 3efb859cb..238325336 100644 --- a/src/App.js +++ b/src/App.js @@ -71,6 +71,7 @@ import { } from '/screens/home/hooks/useLauncherContext' import LauncherView from '/screens/konnectors/LauncherView' import { makeImportantFilesAvailableOfflineInBackground } from '/app/domain/io.cozy.files/importantFiles' +import { useOfflineReplicationOnRealtime } from '/app/domain/offline/hooks/useOfflineReplicationOnRealtime' import { useShareFiles } from '/app/domain/osReceive/services/shareFilesService' import { ClouderyOffer } from '/app/view/IAP/ClouderyOffer' import { useDimensions } from '/libs/dimensions' @@ -130,6 +131,7 @@ const App = ({ setClient }) => { useNotifications() useGeolocationTracking() useCozyEnvironmentOverride() + useOfflineReplicationOnRealtime() useOfflineDebugUniversalLinks(client) usePerformancesUniversalLinks(client) diff --git a/src/app/domain/authentication/services/AuthService.ts b/src/app/domain/authentication/services/AuthService.ts index 58cd35ba1..0f070eca8 100644 --- a/src/app/domain/authentication/services/AuthService.ts +++ b/src/app/domain/authentication/services/AuthService.ts @@ -2,9 +2,9 @@ import { Linking } from 'react-native' import type CozyClient from 'cozy-client' import Minilog from 'cozy-minilog' -import PouchLink from 'cozy-pouch-link' import { asyncLogoutNoClient } from '/app/domain/authentication/utils/asyncLogoutNoClient' +import { triggerPouchReplication } from '/app/domain/offline/utils' export const authLogger = Minilog('AuthService') let clientInstance: CozyClient | null = null @@ -34,8 +34,7 @@ const handleLogin = (): void => { authLogger.info('Debounce replication') if (clientInstance === null) throw new Error('No client instance set') - const pouchLink = getPouchLink(clientInstance) - pouchLink?.startReplicationWithDebounce() + triggerPouchReplication(clientInstance) } catch (error) { authLogger.error('Error while handling login', error) } @@ -47,11 +46,3 @@ export const startListening = (client: CozyClient): void => { clientInstance.on('revoked', handleTokenError) clientInstance.on('login', handleLogin) } - -const getPouchLink = (client?: CozyClient): PouchLink | null => { - if (!client) { - return null - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return client.links.find(link => link instanceof PouchLink) || null -} diff --git a/src/app/domain/offline/hooks/useOfflineReplicationOnRealtime.ts b/src/app/domain/offline/hooks/useOfflineReplicationOnRealtime.ts new file mode 100644 index 000000000..3bb49ebde --- /dev/null +++ b/src/app/domain/offline/hooks/useOfflineReplicationOnRealtime.ts @@ -0,0 +1,54 @@ +import { useEffect } from 'react' + +import { useClient } from 'cozy-client' +import type { CozyClientDocument } from 'cozy-client/types/types' +import Minilog from 'cozy-minilog' + +import { triggerPouchReplication } from '/app/domain/offline/utils' +import { offlineDoctypes } from '/pouchdb/getLinks' + +const log = Minilog('📶 useOfflineReplicationOnRealtime') + +export const useOfflineReplicationOnRealtime = (): void => { + const client = useClient() + + useEffect(() => { + if (!client) return + + // @ts-expect-error client.plugins is not typed + const realtime = client.plugins.realtime as CozyRealtime + + const triggerReplication = + (verb: string) => + (doc: CozyClientDocument): void => { + const docInfo = `${verb} ${doc._type ?? ''} ${doc._id ?? ''}` + log.debug(`Trigger replication from realtime event (${docInfo})`) + triggerPouchReplication(client) + } + + const triggerReplicationCreated = triggerReplication('created') + const triggerReplicationUpdated = triggerReplication('updated') + const triggerReplicationDeleted = triggerReplication('deleted') + + offlineDoctypes.forEach(doctype => { + realtime.subscribe('created', doctype, triggerReplicationCreated) + realtime.subscribe('updated', doctype, triggerReplicationUpdated) + realtime.subscribe('deleted', doctype, triggerReplicationDeleted) + }) + + return () => { + offlineDoctypes.forEach(doctype => { + realtime.unsubscribe('created', doctype, triggerReplicationCreated) + realtime.unsubscribe('updated', doctype, triggerReplicationUpdated) + realtime.unsubscribe('deleted', doctype, triggerReplicationDeleted) + }) + } + }) +} + +interface CozyRealtime { + subscribe: (event: string, type: string, handler: Subscription) => void + unsubscribe: (event: string, type: string, handler: Subscription) => void +} + +type Subscription = (doc: CozyClientDocument) => void diff --git a/src/app/domain/offline/utils.ts b/src/app/domain/offline/utils.ts new file mode 100644 index 000000000..accf1754d --- /dev/null +++ b/src/app/domain/offline/utils.ts @@ -0,0 +1,19 @@ +import CozyClient from 'cozy-client' +import Minilog from 'cozy-minilog' +import PouchLink from 'cozy-pouch-link' + +const log = Minilog('📶 Offline utils') + +export const triggerPouchReplication = (client?: CozyClient): void => { + log.debug('Trigger PouchReplication (debounce)') + const pouchLink = getPouchLink(client) + pouchLink?.startReplicationWithDebounce() +} + +export const getPouchLink = (client?: CozyClient): PouchLink | null => { + if (!client) { + return null + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return client.links.find(link => link instanceof PouchLink) || null +} diff --git a/src/hooks/useGlobalAppState.ts b/src/hooks/useGlobalAppState.ts index f92c95bc3..e019ead6f 100644 --- a/src/hooks/useGlobalAppState.ts +++ b/src/hooks/useGlobalAppState.ts @@ -13,6 +13,7 @@ import { showSplashScreen, splashScreens } from '/app/theme/SplashScreenService' import { handleSecurityFlowWakeUp } from '/app/domain/authorization/services/SecurityService' import { devlog } from '/core/tools/env' import { synchronizeDevice } from '/app/domain/authentication/services/SynchronizeService' +import { triggerPouchReplication } from '/app/domain/offline/utils' const log = Minilog('useGlobalAppState') @@ -54,9 +55,11 @@ const onStateChange = ( if (isGoingToSleep(nextAppState)) handleSleep() if (isGoingToWakeUp(nextAppState)) { - Promise.all([handleWakeUp(client), synchronizeDevice(client)]).catch( - reason => log.error('Failed when waking up', reason) - ) + Promise.all([ + handleWakeUp(client), + synchronizeDevice(client), + triggerPouchReplication(client) + ]).catch(reason => log.error('Failed when waking up', reason)) } appState = nextAppState diff --git a/src/libs/client.js b/src/libs/client.js index 6427e703b..69c8ac7bb 100644 --- a/src/libs/client.js +++ b/src/libs/client.js @@ -4,6 +4,7 @@ import CozyClient from 'cozy-client' // @ts-ignore import flag from 'cozy-flags' import Minilog from 'cozy-minilog' +import { RealtimePlugin } from 'cozy-realtime' import { normalizeFqdn } from './functions/stringHelpers' @@ -77,6 +78,7 @@ export const getClient = async () => { token }) + await client.registerPlugin(RealtimePlugin) await client.registerPlugin(flag.plugin) await client.plugins.flags.initializing diff --git a/src/libs/clientHelpers/createClient.ts b/src/libs/clientHelpers/createClient.ts index 62c96ec98..82f65c7c7 100644 --- a/src/libs/clientHelpers/createClient.ts +++ b/src/libs/clientHelpers/createClient.ts @@ -1,5 +1,7 @@ import CozyClient from 'cozy-client' import flag from 'cozy-flags' +// @ts-expect-error cozy-realtime is not typed yet +import { RealtimePlugin } from 'cozy-realtime' import { CozyClientPerformanceApi } from '/app/domain/performances/measure' import strings from '/constants/strings.json' @@ -69,6 +71,7 @@ export const finalizeClientCreation = async ( const registerPlugins = async (client: CozyClient): Promise => { await client.registerPlugin(flag.plugin, null) + await client.registerPlugin(RealtimePlugin, null) } const initializePlugins = async (client: CozyClient): Promise => { diff --git a/yarn.lock b/yarn.lock index b4dac9697..39c8320da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8768,6 +8768,13 @@ cozy-pouch-link@^52.0.0: pouchdb-browser "^7.2.2" pouchdb-find "^7.2.2" +cozy-realtime@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/cozy-realtime/-/cozy-realtime-5.6.2.tgz#08f82f6d19a7e7a8f288c46d3cb0e8ddddb9f17f" + integrity sha512-ev5a7PthtswJAuxQeJnrwl1jTQ+vLnBhtwkMLIP+7tGd0m3G4nEPgXy0iTuNt4VPBtItrPrNpHdEbMKR9XGgFQ== + dependencies: + "@cozy/minilog" "^1.0.0" + cozy-stack-client@^52.0.0: version "52.0.0" resolved "https://registry.yarnpkg.com/cozy-stack-client/-/cozy-stack-client-52.0.0.tgz#b146997637facc8de55033a84fd7e4b30ea4533d"