From e6211871262447f746780fab0a0d26b449240a06 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 28 May 2024 19:25:59 +0200 Subject: [PATCH 1/6] refactor(pouch-link): Make localStorage a class an return async methods We want to reduce coupling between CozyPouchLink and the browser's local storage The first step is to groupe existing methods into a PouchLocalStorage class, this will allow (in the next commit) to inject a storage adapter into the class constructor Also in some platforms like ReactNative, the localstorage methods are asynchronous. So in order to be compatible with all platforms we want those methods to become async by default The big main of making those methods async is that we cannot call them from the `PouchManager` constructor. So we introduce a new `.init()` method in the `PouchManager` class. This async method now contains all the initialization logic and should be called after creating a new `PouchManager` As `PouchManager` is called internally by `CozyPouchLink` and is not meant to be a publicly available class, then we don't consider this as a breaking change --- .../examples/periodic-sync/index.js | 8 +- packages/cozy-pouch-link/src/CozyPouchLink.js | 36 +- packages/cozy-pouch-link/src/PouchManager.js | 64 ++-- .../cozy-pouch-link/src/PouchManager.spec.js | 126 ++++--- packages/cozy-pouch-link/src/localStorage.js | 354 ++++++++++-------- .../cozy-pouch-link/src/startReplication.js | 20 +- .../src/startReplication.spec.js | 23 +- packages/cozy-pouch-link/src/types.js | 4 + 8 files changed, 348 insertions(+), 287 deletions(-) diff --git a/packages/cozy-pouch-link/examples/periodic-sync/index.js b/packages/cozy-pouch-link/examples/periodic-sync/index.js index ecfa6b5990..9e84eb7257 100755 --- a/packages/cozy-pouch-link/examples/periodic-sync/index.js +++ b/packages/cozy-pouch-link/examples/periodic-sync/index.js @@ -62,8 +62,9 @@ class App extends React.Component { } componentDidMount() { - this.createManager() - this.displayDocs() + this.createManager().then(() => { + this.displayDocs() + }) } componentWillUnmount() { @@ -83,7 +84,7 @@ class App extends React.Component { }) } - createManager() { + async createManager() { this.manager = new PouchManager([DOCTYPE], { replicationDelay: 2 * 1000, getReplicationURL: this.getReplicationURL, @@ -101,6 +102,7 @@ class App extends React.Component { this.displayDocs() } }) + await this.manager.init() } async displayDocs() { diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index cc4dd3f181..a341975a35 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -16,15 +16,10 @@ import { default as helpers } from './helpers' import { getIndexNameFromFields, getIndexFields } from './mango' import * as jsonapi from './jsonapi' import PouchManager from './PouchManager' +import { PouchLocalStorage } from './localStorage' import logger from './logger' import { migratePouch } from './migrations/adapter' import { getDatabaseName, getPrefix } from './utils' -import { - getPersistedSyncedDoctypes, - persistAdapterName, - getAdapterName, - destroyWarmedUpQueries -} from './localStorage' PouchDB.plugin(PouchDBFind) @@ -94,6 +89,7 @@ class PouchLink extends CozyLink { this.doctypes = doctypes this.doctypesReplicationOptions = doctypesReplicationOptions this.indexes = {} + this.storage = new PouchLocalStorage() /** @type {Record} - Stores replication states per doctype */ this.replicationStatus = this.replicationStatus || {} @@ -103,10 +99,11 @@ class PouchLink extends CozyLink { * Return the PouchDB adapter name. * Should be IndexedDB for newest adapters. * - * @returns {string} The adapter name + * @returns {Promise} The adapter name */ static getPouchAdapterName = () => { - return getAdapterName() + const storage = new PouchLocalStorage() + return storage.getAdapterName() } getReplicationURL(doctype) { @@ -148,15 +145,15 @@ class PouchLink extends CozyLink { for (const plugin of plugins) { PouchDB.plugin(plugin) } - const doctypes = getPersistedSyncedDoctypes() + const doctypes = await this.storage.getPersistedSyncedDoctypes() for (const doctype of Object.keys(doctypes)) { const prefix = getPrefix(url) const dbName = getDatabaseName(prefix, doctype) await migratePouch({ dbName, fromAdapter, toAdapter }) - destroyWarmedUpQueries() // force recomputing indexes + await this.storage.destroyWarmedUpQueries() // force recomputing indexes } - persistAdapterName('indexeddb') + await this.storage.persistAdapterName('indexeddb') } catch (err) { console.error('PouchLink: PouchDB migration failed. ', err) } @@ -194,9 +191,9 @@ class PouchLink extends CozyLink { logger.log('Create pouches with ' + prefix + ' prefix') } - if (!getAdapterName()) { + if (!(await this.storage.getAdapterName())) { const adapter = get(this.options, 'pouch.options.adapter') - persistAdapterName(adapter) + await this.storage.persistAdapterName(adapter) } this.pouches = new PouchManager(this.doctypes, { @@ -210,6 +207,7 @@ class PouchLink extends CozyLink { prefix, executeQuery: this.executeQuery.bind(this) }) + await this.pouches.init() if (this.client && this.options.initialSync) { this.startReplication() @@ -333,7 +331,7 @@ class PouchLink extends CozyLink { return !!this.getPouch(impactedDoctype) } - request(operation, result = null, forward = doNothing) { + async request(operation, result = null, forward = doNothing) { const doctype = getDoctypeFromOperation(operation) if (!this.pouches) { @@ -355,7 +353,7 @@ class PouchLink extends CozyLink { return forward(operation) } - if (this.needsToWaitWarmup(doctype)) { + if (await this.needsToWaitWarmup(doctype)) { if (process.env.NODE_ENV !== 'production') { logger.info( `Tried to access local ${doctype} but not warmuped yet. Forwarding the operation to next link` @@ -386,18 +384,18 @@ class PouchLink extends CozyLink { * and return if those queries are already warmed up or not * * @param {string} doctype - Doctype to check - * @returns {boolean} the need to wait for the warmup + * @returns {Promise} the need to wait for the warmup */ - needsToWaitWarmup(doctype) { + async needsToWaitWarmup(doctype) { if ( this.doctypesReplicationOptions && this.doctypesReplicationOptions[doctype] && this.doctypesReplicationOptions[doctype].warmupQueries ) { - return !this.pouches.areQueriesWarmedUp( + return !(await this.pouches.areQueriesWarmedUp( doctype, this.doctypesReplicationOptions[doctype].warmupQueries - ) + )) } return false } diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 87eee2017d..a9420410c9 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -7,6 +7,7 @@ import zip from 'lodash/zip' import startsWith from 'lodash/startsWith' import { isMobileApp } from 'cozy-device-helper' +import { PouchLocalStorage } from './localStorage' import Loop from './loop' import logger from './logger' import { fetchRemoteLastSequence } from './remote' @@ -34,20 +35,26 @@ const getQueryAlias = query => { class PouchManager { constructor(doctypes, options) { this.options = options - const pouchPlugins = get(options, 'pouch.plugins', []) - const pouchOptions = get(options, 'pouch.options', {}) + this.doctypes = doctypes + + this.storage = new PouchLocalStorage() + } + + async init() { + const pouchPlugins = get(this.options, 'pouch.plugins', []) + const pouchOptions = get(this.options, 'pouch.options', {}) - forEach(pouchPlugins, plugin => PouchDB.plugin(plugin)) this.pouches = fromPairs( - doctypes.map(doctype => [ + this.doctypes.map(doctype => [ doctype, - new PouchDB(getDatabaseName(options.prefix, doctype), pouchOptions) + new PouchDB(getDatabaseName(this.options.prefix, doctype), pouchOptions) ]) ) - this.syncedDoctypes = localStorage.getPersistedSyncedDoctypes() - this.warmedUpQueries = localStorage.getPersistedWarmedUpQueries() - this.getReplicationURL = options.getReplicationURL - this.doctypesReplicationOptions = options.doctypesReplicationOptions || {} + this.syncedDoctypes = await this.storage.getPersistedSyncedDoctypes() + this.warmedUpQueries = await this.storage.getPersistedWarmedUpQueries() + this.getReplicationURL = this.options.getReplicationURL + this.doctypesReplicationOptions = + this.options.doctypesReplicationOptions || {} this.listenerLaunched = false // We must ensure databases exist on the remote before @@ -84,13 +91,13 @@ class PouchManager { } } - destroy() { + async destroy() { this.stopReplicationLoop() this.removeListeners() - this.clearSyncedDoctypes() - this.clearWarmedUpQueries() - localStorage.destroyAllDoctypeLastSequence() - localStorage.destroyAllLastReplicatedDocID() + await this.clearSyncedDoctypes() + await this.clearWarmedUpQueries() + await this.storage.destroyAllDoctypeLastSequence() + await this.storage.destroyAllLastReplicatedDocID() return Promise.all( Object.values(this.pouches).map(pouch => pouch.destroy()) @@ -189,9 +196,9 @@ class PouchManager { // Before the first replication, get the last remote sequence, // which will be used as a checkpoint for the next replication const lastSeq = await fetchRemoteLastSequence(getReplicationURL()) - localStorage.persistDoctypeLastSequence(doctype, lastSeq) + await this.storage.persistDoctypeLastSequence(doctype, lastSeq) } else { - seq = localStorage.getDoctypeLastSequence(doctype) + seq = await this.storage.getDoctypeLastSequence(doctype) } const replicationOptions = get( @@ -210,15 +217,16 @@ class PouchManager { const res = await startReplication( pouch, replicationOptions, - getReplicationURL + getReplicationURL, + this.storage ) if (seq) { // We only need the sequence for the second replication, as PouchDB // will use a local checkpoint for the next runs. - localStorage.destroyDoctypeLastSequence(doctype) + await this.storage.destroyDoctypeLastSequence(doctype) } - this.updateSyncInfo(doctype) + await this.updateSyncInfo(doctype) this.checkToWarmupDoctype(doctype, replicationOptions) if (this.options.onDoctypeSyncEnd) { this.options.onDoctypeSyncEnd(doctype) @@ -281,9 +289,9 @@ class PouchManager { return this.pouches[doctype] } - updateSyncInfo(doctype) { + async updateSyncInfo(doctype) { this.syncedDoctypes[doctype] = { date: new Date().toISOString() } - localStorage.persistSyncedDoctypes(this.syncedDoctypes) + await this.storage.persistSyncedDoctypes(this.syncedDoctypes) } getSyncInfo(doctype) { @@ -295,9 +303,9 @@ class PouchManager { return info ? !!info.date : false } - clearSyncedDoctypes() { + async clearSyncedDoctypes() { this.syncedDoctypes = {} - localStorage.destroySyncedDoctypes() + await this.storage.destroySyncedDoctypes() } async warmupQueries(doctype, queries) { @@ -312,7 +320,7 @@ class PouchManager { } }) ) - localStorage.persistWarmedUpQueries(this.warmedUpQueries) + await this.storage.persistWarmedUpQueries(this.warmedUpQueries) logger.log('PouchManager: warmupQueries for ' + doctype + ' are done') } catch (err) { logger.error( @@ -332,8 +340,8 @@ class PouchManager { } } - areQueriesWarmedUp(doctype, queries) { - const persistWarmedUpQueries = localStorage.getPersistedWarmedUpQueries() + async areQueriesWarmedUp(doctype, queries) { + const persistWarmedUpQueries = await this.storage.getPersistedWarmedUpQueries() return queries.every( query => persistWarmedUpQueries[doctype] && @@ -341,9 +349,9 @@ class PouchManager { ) } - clearWarmedUpQueries() { + async clearWarmedUpQueries() { this.warmedUpQueries = {} - localStorage.destroyWarmedUpQueries() + await this.storage.destroyWarmedUpQueries() } } diff --git a/packages/cozy-pouch-link/src/PouchManager.spec.js b/packages/cozy-pouch-link/src/PouchManager.spec.js index 0fa00a477d..8cb42cc934 100644 --- a/packages/cozy-pouch-link/src/PouchManager.spec.js +++ b/packages/cozy-pouch-link/src/PouchManager.spec.js @@ -19,10 +19,16 @@ jest.mock('./remote', () => ({ import * as rep from './startReplication' import PouchDB from 'pouchdb-browser' -import * as ls from './localStorage' +import { + LOCALSTORAGE_SYNCED_KEY, + LOCALSTORAGE_WARMUPEDQUERIES_KEY, + PouchLocalStorage +} from './localStorage' import { fetchRemoteLastSequence, fetchRemoteInstance } from './remote' +const ls = new PouchLocalStorage() + const sleep = delay => { return new Promise(resolve => { setTimeout(resolve, delay) @@ -63,7 +69,7 @@ describe('PouchManager', () => { getReplicationURL, onSync = jest.fn() - beforeEach(() => { + beforeEach(async () => { getReplicationURL = () => 'http://replicationURL.local' managerOptions = { replicationDelay: 16, @@ -72,6 +78,7 @@ describe('PouchManager', () => { prefix: 'cozy.tools' } manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() const pouch = manager.getPouch('io.cozy.todos') const replication = mocks.pouchReplication({ direction: 'pull', @@ -133,6 +140,7 @@ describe('PouchManager', () => { 'io.cozy.readonly': { strategy: 'fromRemote' } } }) + await manager.init() const normalPouch = manager.getPouch('io.cozy.todos') const readOnlyPouch = manager.getPouch('io.cozy.readonly') readOnlyPouch.replicate = {} @@ -155,6 +163,7 @@ describe('PouchManager', () => { } } ) + await manager.init() const normalPouch = manager.getPouch('io.cozy.todos') const readOnlyPouch = manager.getPouch('io.cozy.readonly') readOnlyPouch.replicate = {} @@ -231,14 +240,16 @@ describe('PouchManager', () => { it('should add pouch plugin', async () => { const options = { ...managerOptions, pouch: { plugins: ['myPlugin'] } } - new PouchManager(['io.cozy.todos'], options) + const manager = new PouchManager(['io.cozy.todos'], options) + await manager.init() expect(PouchDB.plugin).toHaveBeenCalledTimes(1) }) it('should instanciate pouch with options', async () => { const pouchOptions = { adapter: 'cordova-sqlite', location: 'default' } const options = { ...managerOptions, pouch: { options: pouchOptions } } - new PouchManager(['io.cozy.todos'], options) + const manager = new PouchManager(['io.cozy.todos'], options) + await manager.init() expect(PouchDB).toHaveBeenCalledWith( 'cozy.tools_io.cozy.todos', pouchOptions @@ -246,33 +257,36 @@ describe('PouchManager', () => { }) describe('getPersistedSyncedDoctypes', () => { - it('should return an empty array if local storage is empty', () => { - expect(ls.getPersistedSyncedDoctypes()).toEqual({}) + it('should return an empty array if local storage is empty', async () => { + expect(await ls.getPersistedSyncedDoctypes()).toEqual({}) }) - it('should return an empty array if local storage contains something that is not an array', () => { - localStorage.__STORE__[ls.LOCALSTORAGE_SYNCED_KEY] = 'true' - expect(ls.getPersistedSyncedDoctypes()).toEqual({}) + it('should return an empty array if local storage contains something that is not an array', async () => { + localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY] = 'true' + expect(await ls.getPersistedSyncedDoctypes()).toEqual({}) }) - it('should return the list of doctypes if local storage contains one', () => { + it('should return the list of doctypes if local storage contains one', async () => { const persistedSyncedDoctypes = { 'io.cozy.todos': { date: '2021-08-11T13:48:06.085Z' } } - localStorage.__STORE__[ls.LOCALSTORAGE_SYNCED_KEY] = JSON.stringify( + localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY] = JSON.stringify( + persistedSyncedDoctypes + ) + expect(await ls.getPersistedSyncedDoctypes()).toEqual( persistedSyncedDoctypes ) - expect(ls.getPersistedSyncedDoctypes()).toEqual(persistedSyncedDoctypes) }) }) describe('persistSyncedDoctypes', () => { - it('should put the list of synced doctypes in localStorage', () => { + it('should put the list of synced doctypes in localStorage', async () => { const manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() manager.syncedDoctypes = ['io.cozy.todos'] ls.persistSyncedDoctypes(manager.syncedDoctypes) - expect(localStorage.__STORE__[ls.LOCALSTORAGE_SYNCED_KEY]).toEqual( + expect(localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY]).toEqual( JSON.stringify(manager.syncedDoctypes) ) }) @@ -287,17 +301,19 @@ describe('PouchManager', () => { MockDate.reset() }) - it('should add the doctype to synced doctypes', () => { + it('should add the doctype to synced doctypes', async () => { const manager = new PouchManager(['io.cozy.todos'], managerOptions) - manager.updateSyncInfo('io.cozy.todos') + await manager.init() + await manager.updateSyncInfo('io.cozy.todos') expect(Object.keys(manager.syncedDoctypes)).toEqual(['io.cozy.todos']) }) - it('should persist the new synced doctypes list', () => { + it('should persist the new synced doctypes list', async () => { const manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() - manager.updateSyncInfo('io.cozy.todos') - expect(localStorage.__STORE__[ls.LOCALSTORAGE_SYNCED_KEY]).toEqual( + await manager.updateSyncInfo('io.cozy.todos') + expect(localStorage.__STORE__[LOCALSTORAGE_SYNCED_KEY]).toEqual( JSON.stringify({ 'io.cozy.todos': { date: '2021-08-01T00:00:00.000Z' } }) @@ -308,12 +324,13 @@ describe('PouchManager', () => { describe('isSynced', () => { let manager - beforeEach(() => { + beforeEach(async () => { manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() }) - it('should return true if the doctype is synced', () => { - manager.updateSyncInfo('io.cozy.todos') + it('should return true if the doctype is synced', async () => { + await manager.updateSyncInfo('io.cozy.todos') expect(manager.isSynced('io.cozy.todos')).toBe(true) }) @@ -323,90 +340,92 @@ describe('PouchManager', () => { }) describe('destroySyncedDoctypes', () => { - it('should destroy the local storage item', () => { - ls.destroySyncedDoctypes() + it('should destroy the local storage item', async () => { + await ls.destroySyncedDoctypes() expect(localStorage.removeItem).toHaveBeenLastCalledWith( - ls.LOCALSTORAGE_SYNCED_KEY + LOCALSTORAGE_SYNCED_KEY ) }) - it('should reset syncedDoctypes', () => { + it('should reset syncedDoctypes', async () => { manager.syncedDoctypes = { 'io.cozy.todos': { date: '2021-08-11T13:48:06.085Z' } } - manager.clearSyncedDoctypes() + await manager.clearSyncedDoctypes() expect(manager.syncedDoctypes).toEqual({}) }) }) describe('getPersistedWarmedUpQueriess', () => { - it('should return an empty object if local storage is empty', () => { - expect(ls.getPersistedWarmedUpQueries()).toEqual({}) + it('should return an empty object if local storage is empty', async () => { + expect(await ls.getPersistedWarmedUpQueries()).toEqual({}) }) - it('should return the list of queries if local storage contains ones', () => { + it('should return the list of queries if local storage contains ones', async () => { const persistedQueries = [query().options.as] - localStorage.__STORE__[ - ls.LOCALSTORAGE_WARMUPEDQUERIES_KEY - ] = JSON.stringify(persistedQueries) - expect(ls.getPersistedWarmedUpQueries()).toEqual(persistedQueries) + localStorage.__STORE__[LOCALSTORAGE_WARMUPEDQUERIES_KEY] = JSON.stringify( + persistedQueries + ) + expect(await ls.getPersistedWarmedUpQueries()).toEqual(persistedQueries) }) }) describe('persistWarmedUpQueries', () => { - it('should put the list of warmedUpQueries in localStorage', () => { + it('should put the list of warmedUpQueries in localStorage', async () => { const manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() manager.warmedUpQueries = { 'io.cozy.todos': ['query1', 'query2'] } - ls.persistWarmedUpQueries(manager.warmedUpQueries) + await ls.persistWarmedUpQueries(manager.warmedUpQueries) - expect( - localStorage.__STORE__[ls.LOCALSTORAGE_WARMUPEDQUERIES_KEY] - ).toEqual(JSON.stringify(manager.warmedUpQueries)) + expect(localStorage.__STORE__[LOCALSTORAGE_WARMUPEDQUERIES_KEY]).toEqual( + JSON.stringify(manager.warmedUpQueries) + ) }) }) describe('areQueriesWarmedUp', () => { let manager - beforeEach(() => { + beforeEach(async () => { manager = new PouchManager(['io.cozy.todos'], managerOptions) + await manager.init() }) - it('should return true if all the queries are warmuped', () => { + it('should return true if all the queries are warmuped', async () => { manager.warmedUpQueries = { 'io.cozy.todos': [query1().options.as, query2().options.as] } - ls.persistWarmedUpQueries(manager.warmedUpQueries) + await ls.persistWarmedUpQueries(manager.warmedUpQueries) expect( - manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) + await manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) ).toBe(true) }) - it('should return false if at least one query is not warmuped', () => { + it('should return false if at least one query is not warmuped', async () => { manager.warmedUpQueries = { 'io.cozy.todos': [query2().options.as] } - ls.persistWarmedUpQueries() + await ls.persistWarmedUpQueries() expect( - manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) + await manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) ).toBe(false) }) - it('should return false if the queries are not been done', () => { + it('should return false if the queries are not been done', async () => { expect( - manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) + await manager.areQueriesWarmedUp('io.cozy.todos', [query1(), query2()]) ).toBe(false) }) }) describe('clearWarmedupQueries', () => { - it('should clear the local storage item', () => { + it('should clear the local storage item', async () => { manager.clearWarmedUpQueries() expect(localStorage.removeItem).toHaveBeenLastCalledWith( - ls.LOCALSTORAGE_WARMUPEDQUERIES_KEY + LOCALSTORAGE_WARMUPEDQUERIES_KEY ) }) it('should reset warmedupQueries', () => { @@ -490,7 +509,7 @@ describe('PouchManager', () => { describe('warmupQueries', () => { let manager const executeMock = jest.fn() - beforeEach(() => { + beforeEach(async () => { let newManagerOptions = { ...managerOptions, executeQuery: executeMock, @@ -502,6 +521,7 @@ describe('PouchManager', () => { } } manager = new PouchManager(['io.cozy.todos'], newManagerOptions) + await manager.init() }) it('should executes warmeupQueries on the first replicationLoop only', async () => { @@ -524,7 +544,7 @@ describe('PouchManager', () => { .definition() .toDefinition() ) - expect(ls.getPersistedWarmedUpQueries()).toEqual({ + expect(await ls.getPersistedWarmedUpQueries()).toEqual({ 'io.cozy.todos': ['query1', 'query2'] }) //Simulation of a loop. Let's replicate again @@ -541,7 +561,7 @@ describe('PouchManager', () => { await manager.replicateOnce() await sleep(10) - expect(ls.getPersistedWarmedUpQueries()).toEqual({}) + expect(await ls.getPersistedWarmedUpQueries()).toEqual({}) expect(manager.warmedUpQueries['io.cozy.todos']).toBeUndefined() }) }) diff --git a/packages/cozy-pouch-link/src/localStorage.js b/packages/cozy-pouch-link/src/localStorage.js index ad05e48d6a..58df2d1e1d 100644 --- a/packages/cozy-pouch-link/src/localStorage.js +++ b/packages/cozy-pouch-link/src/localStorage.js @@ -7,182 +7,212 @@ export const LOCALSTORAGE_LASTREPLICATEDDOCID_KEY = 'cozy-client-pouch-link-lastreplicateddocid' export const LOCALSTORAGE_ADAPTERNAME = 'cozy-client-pouch-link-adaptername' -/** - * Persist the last replicated doc id for a doctype - * - * @param {string} doctype - The replicated doctype - * @param {string} id - The docid - */ -export const persistLastReplicatedDocID = (doctype, id) => { - const docids = getAllLastReplicatedDocID() - docids[doctype] = id - - window.localStorage.setItem( - LOCALSTORAGE_LASTREPLICATEDDOCID_KEY, - JSON.stringify(docids) - ) -} +export class PouchLocalStorage { + /** + * Persist the last replicated doc id for a doctype + * + * @param {string} doctype - The replicated doctype + * @param {string} id - The docid + * + * @returns {Promise} + */ + async persistLastReplicatedDocID(doctype, id) { + const docids = await this.getAllLastReplicatedDocID() + docids[doctype] = id + + await window.localStorage.setItem( + LOCALSTORAGE_LASTREPLICATEDDOCID_KEY, + JSON.stringify(docids) + ) + } -export const getAllLastReplicatedDocID = () => { - const item = window.localStorage.getItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) - return item ? JSON.parse(item) : {} -} + /** + * @returns {Promise>} + */ + async getAllLastReplicatedDocID() { + const item = await window.localStorage.getItem( + LOCALSTORAGE_LASTREPLICATEDDOCID_KEY + ) + return item ? JSON.parse(item) : {} + } -/** - * Get the last replicated doc id for a doctype - * - * @param {string} doctype - The doctype - * @returns {string} The last replicated docid - */ -export const getLastReplicatedDocID = doctype => { - const docids = getAllLastSequences() - return docids[doctype] -} + /** + * Get the last replicated doc id for a doctype + * + * @param {string} doctype - The doctype + * @returns {Promise} The last replicated docid + */ + async getLastReplicatedDocID(doctype) { + const docids = await this.getAllLastSequences() + return docids[doctype] + } -/** - * Destroy all the replicated doc id - */ -export const destroyAllLastReplicatedDocID = () => { - window.localStorage.removeItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) -} + /** + * Destroy all the replicated doc id + * + * @returns {Promise} + */ + async destroyAllLastReplicatedDocID() { + await window.localStorage.removeItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) + } -/** - * Persist the synchronized doctypes - * - * @typedef {object} SyncInfo - * @property {string} Date - * - * @param {Record} syncedDoctypes - The sync doctypes - */ -export const persistSyncedDoctypes = syncedDoctypes => { - window.localStorage.setItem( - LOCALSTORAGE_SYNCED_KEY, - JSON.stringify(syncedDoctypes) - ) -} + /** + * Persist the synchronized doctypes + * + * @param {Record} syncedDoctypes - The sync doctypes + * + * @returns {Promise} + */ + async persistSyncedDoctypes(syncedDoctypes) { + await window.localStorage.setItem( + LOCALSTORAGE_SYNCED_KEY, + JSON.stringify(syncedDoctypes) + ) + } -/** - * Get the persisted doctypes - * - * @returns {object} The synced doctypes - */ -export const getPersistedSyncedDoctypes = () => { - const item = window.localStorage.getItem(LOCALSTORAGE_SYNCED_KEY) - const parsed = item ? JSON.parse(item) : {} - if (typeof parsed !== 'object') { - return {} - } - return parsed -} + /** + * Get the persisted doctypes + * + * @returns {Promise} The synced doctypes + */ + async getPersistedSyncedDoctypes() { + const item = await window.localStorage.getItem(LOCALSTORAGE_SYNCED_KEY) + const parsed = item ? JSON.parse(item) : {} + if (typeof parsed !== 'object') { + return {} + } + return parsed + } -/** - * Destroy the synced doctypes - * - */ -export const destroySyncedDoctypes = () => { - window.localStorage.removeItem(LOCALSTORAGE_SYNCED_KEY) -} + /** + * Destroy the synced doctypes + * + * @returns {Promise} + */ + async destroySyncedDoctypes() { + await window.localStorage.removeItem(LOCALSTORAGE_SYNCED_KEY) + } -/** - * Persist the last CouchDB sequence for a synced doctype - * - * @param {string} doctype - The synced doctype - * @param {string} sequence - The sequence hash - */ -export const persistDoctypeLastSequence = (doctype, sequence) => { - const seqs = getAllLastSequences() - seqs[doctype] = sequence - - window.localStorage.setItem( - LOCALSTORAGE_LASTSEQUENCES_KEY, - JSON.stringify(seqs) - ) -} + /** + * Persist the last CouchDB sequence for a synced doctype + * + * @param {string} doctype - The synced doctype + * @param {string} sequence - The sequence hash + * + * @returns {Promise} + */ + async persistDoctypeLastSequence(doctype, sequence) { + const seqs = await this.getAllLastSequences() + seqs[doctype] = sequence + + await window.localStorage.setItem( + LOCALSTORAGE_LASTSEQUENCES_KEY, + JSON.stringify(seqs) + ) + } -export const getAllLastSequences = () => { - const item = window.localStorage.getItem(LOCALSTORAGE_LASTSEQUENCES_KEY) - return item ? JSON.parse(item) : {} -} + /** + * @returns {Promise} + */ + async getAllLastSequences() { + const item = await window.localStorage.getItem( + LOCALSTORAGE_LASTSEQUENCES_KEY + ) + return item ? JSON.parse(item) : {} + } -/** - * Get the last CouchDB sequence for a doctype - * - * @param {string} doctype - The doctype - * @returns {string} the last sequence - */ -export const getDoctypeLastSequence = doctype => { - const seqs = getAllLastSequences() - return seqs[doctype] -} + /** + * Get the last CouchDB sequence for a doctype + * + * @param {string} doctype - The doctype + * + * @returns {Promise} the last sequence + */ + async getDoctypeLastSequence(doctype) { + const seqs = await this.getAllLastSequences() + return seqs[doctype] + } -/** - * Destroy all the last sequence - */ -export const destroyAllDoctypeLastSequence = () => { - window.localStorage.removeItem(LOCALSTORAGE_LASTSEQUENCES_KEY) -} + /** + * Destroy all the last sequence + * + * @returns {Promise} + */ + async destroyAllDoctypeLastSequence() { + await window.localStorage.removeItem(LOCALSTORAGE_LASTSEQUENCES_KEY) + } -/** - * Destroy the last sequence for a doctype - * - * @param {string} doctype - The doctype - */ -export const destroyDoctypeLastSequence = doctype => { - const seqs = getAllLastSequences() - delete seqs[doctype] - window.localStorage.setItem( - LOCALSTORAGE_LASTSEQUENCES_KEY, - JSON.stringify(seqs) - ) -} + /** + * Destroy the last sequence for a doctype + * + * @param {string} doctype - The doctype + * + * @returns {Promise} + */ + async destroyDoctypeLastSequence(doctype) { + const seqs = await this.getAllLastSequences() + delete seqs[doctype] + await window.localStorage.setItem( + LOCALSTORAGE_LASTSEQUENCES_KEY, + JSON.stringify(seqs) + ) + } -/** - * Persist the warmed up queries - * - * @param {object} warmedUpQueries - The warmedup queries - */ -export const persistWarmedUpQueries = warmedUpQueries => { - window.localStorage.setItem( - LOCALSTORAGE_WARMUPEDQUERIES_KEY, - JSON.stringify(warmedUpQueries) - ) -} + /** + * Persist the warmed up queries + * + * @param {object} warmedUpQueries - The warmedup queries + * + * @returns {Promise} + */ + async persistWarmedUpQueries(warmedUpQueries) { + await window.localStorage.setItem( + LOCALSTORAGE_WARMUPEDQUERIES_KEY, + JSON.stringify(warmedUpQueries) + ) + } -/** - * Get the warmed up queries - * - * @returns {object} the warmed up queries - */ -export const getPersistedWarmedUpQueries = () => { - const item = window.localStorage.getItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) - if (!item) { - return {} - } - return JSON.parse(item) -} + /** + * Get the warmed up queries + * + * @returns {Promise} the warmed up queries + */ + async getPersistedWarmedUpQueries() { + const item = await window.localStorage.getItem( + LOCALSTORAGE_WARMUPEDQUERIES_KEY + ) + if (!item) { + return {} + } + return JSON.parse(item) + } -/** - * Destroy the warmed queries - * - */ -export const destroyWarmedUpQueries = () => { - window.localStorage.removeItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) -} + /** + * Destroy the warmed queries + * + * @returns {Promise} + */ + async destroyWarmedUpQueries() { + await window.localStorage.removeItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) + } -/** - * Get the adapter name - * - * @returns {string} The adapter name - */ -export const getAdapterName = () => { - return window.localStorage.getItem(LOCALSTORAGE_ADAPTERNAME) -} + /** + * Get the adapter name + * + * @returns {Promise} The adapter name + */ + async getAdapterName() { + return await window.localStorage.getItem(LOCALSTORAGE_ADAPTERNAME) + } -/** - * Persist the adapter name - * - * @param {string} adapter - The adapter name - */ -export const persistAdapterName = adapter => { - window.localStorage.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) + /** + * Persist the adapter name + * + * @param {string} adapter - The adapter name + * + * @returns {Promise} + */ + async persistAdapterName(adapter) { + await window.localStorage.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) + } } diff --git a/packages/cozy-pouch-link/src/startReplication.js b/packages/cozy-pouch-link/src/startReplication.js index 4ff5d581af..7774f8b13d 100644 --- a/packages/cozy-pouch-link/src/startReplication.js +++ b/packages/cozy-pouch-link/src/startReplication.js @@ -2,10 +2,7 @@ import { default as helpers } from './helpers' import startsWith from 'lodash/startsWith' import logger from './logger' import { fetchRemoteInstance } from './remote' -import { - getLastReplicatedDocID, - persistLastReplicatedDocID -} from './localStorage' + const { isDesignDocument, isDeletedDocument } = helpers const BATCH_SIZE = 1000 // we have mostly small documents @@ -40,13 +37,15 @@ const TIME_UNITS = [['ms', 1000], ['s', 60], ['m', 60], ['h', 24]] * @param {string} replicationOptions.doctype The doctype to replicate * @param {import('cozy-client/types/types').Query[]} replicationOptions.warmupQueries The queries to warmup * @param {Function} getReplicationURL A function that should return the remote replication URL + * @param {import('./localStorage').PouchLocalStorage} storage Methods to access local storage * * @returns {import('./types').CancelablePromise} A cancelable promise that resolves at the end of the replication */ export const startReplication = ( pouch, replicationOptions, - getReplicationURL + getReplicationURL, + storage ) => { let replication let docs = {} @@ -71,7 +70,7 @@ export const startReplication = ( // For the first remote->local replication, we manually replicate all docs // as it avoids to replicate all revs history, which can lead to // performances issues - docs = await replicateAllDocs(pouch, url, doctype) + docs = await replicateAllDocs(pouch, url, doctype, storage) const end = new Date() if (process.env.NODE_ENV !== 'production') { logger.info( @@ -144,13 +143,14 @@ const filterDocs = docs => { * @param {object} db - Pouch instance * @param {string} baseUrl - The remote instance * @param {string} doctype - The doctype to replicate + * @param {import('./localStorage').PouchLocalStorage} storage - Methods to access local storage * @returns {Promise} The retrieved documents */ -export const replicateAllDocs = async (db, baseUrl, doctype) => { +export const replicateAllDocs = async (db, baseUrl, doctype, storage) => { const remoteUrlAllDocs = new URL(`${baseUrl}/_all_docs`) const batchSize = BATCH_SIZE let hasMore = true - let startDocId = getLastReplicatedDocID(doctype) // Get last replicated _id in localStorage + let startDocId = await storage.getLastReplicatedDocID(doctype) // Get last replicated _id in localStorage let docs = [] while (hasMore) { @@ -169,7 +169,7 @@ export const replicateAllDocs = async (db, baseUrl, doctype) => { hasMore = false } await helpers.insertBulkDocs(db, docs) - persistLastReplicatedDocID(doctype, startDocId) + await storage.persistLastReplicatedDocID(doctype, startDocId) } } else { const res = await fetchRemoteInstance(remoteUrlAllDocs, { @@ -184,7 +184,7 @@ export const replicateAllDocs = async (db, baseUrl, doctype) => { filteredDocs.shift() // Remove first element, already included in previous request startDocId = filteredDocs[filteredDocs.length - 1]._id await helpers.insertBulkDocs(db, filteredDocs) - persistLastReplicatedDocID(doctype, startDocId) + await storage.persistLastReplicatedDocID(doctype, startDocId) docs = docs.concat(filteredDocs) if (res.rows.length < batchSize) { diff --git a/packages/cozy-pouch-link/src/startReplication.spec.js b/packages/cozy-pouch-link/src/startReplication.spec.js index 41acd9a503..874aea53af 100644 --- a/packages/cozy-pouch-link/src/startReplication.spec.js +++ b/packages/cozy-pouch-link/src/startReplication.spec.js @@ -1,13 +1,7 @@ import { fetchRemoteLastSequence, fetchRemoteInstance } from './remote' -import { getLastReplicatedDocID } from './localStorage' import { replicateAllDocs } from './startReplication' -jest.mock('./localStorage', () => ({ - getLastReplicatedDocID: jest.fn(), - persistLastReplicatedDocID: jest.fn() -})) - jest.mock('./remote', () => ({ fetchRemoteLastSequence: jest.fn(), fetchRemoteInstance: jest.fn() @@ -27,22 +21,27 @@ const generateDocs = nDocs => { return docs } +const storage = { + getLastReplicatedDocID: jest.fn(), + persistLastReplicatedDocID: jest.fn() +} + describe('replication through _all_docs', () => { beforeEach(() => { fetchRemoteLastSequence.mockResolvedValue('10-xyz') }) it('should replicate all docs', async () => { - getLastReplicatedDocID.mockReturnValue(null) + storage.getLastReplicatedDocID.mockReturnValue(null) const dummyDocs = generateDocs(2) fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs }) - const rep = await replicateAllDocs(null, url) + const rep = await replicateAllDocs(null, url, undefined, storage) const expectedDocs = dummyDocs.map(doc => doc.doc) expect(rep).toEqual(expectedDocs) }) it('should replicate all docs when it gets more docs than the batch limit', async () => { - getLastReplicatedDocID.mockReturnValue(null) + storage.getLastReplicatedDocID.mockReturnValue(null) const dummyDocs = generateDocs(1002) fetchRemoteInstance.mockResolvedValueOnce({ rows: dummyDocs.slice(0, 1001) @@ -51,17 +50,17 @@ describe('replication through _all_docs', () => { rows: dummyDocs.slice(1000, 1002) }) - const rep = await replicateAllDocs(null, url) + const rep = await replicateAllDocs(null, url, undefined, storage) const expectedDocs = dummyDocs.map(doc => doc.doc) expect(rep).toEqual(expectedDocs) }) it('should replicate from the last saved doc id', async () => { - getLastReplicatedDocID.mockReturnValue('5') + storage.getLastReplicatedDocID.mockReturnValue('5') const dummyDocs = generateDocs(10) fetchRemoteInstance.mockResolvedValue({ rows: dummyDocs.slice(5, 11) }) - const rep = await replicateAllDocs(null, url) + const rep = await replicateAllDocs(null, url, undefined, storage) const calledUrl = new URL(`${url}/_all_docs`) expect(fetchRemoteInstance).toHaveBeenCalledWith(calledUrl, { diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index 86fc108772..ddf4a7a4c2 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -11,4 +11,8 @@ * @typedef {CancelablePromise[] & Cancelable} CancelablePromises */ +/** @typedef {object} SyncInfo + * @property {string} Date + */ + export default {} From 1fbf1b22ec833b0a3e0a0fc781178ab1486c04ac Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:06:52 +0200 Subject: [PATCH 2/6] feat(pouch-link): Allow to inject a local storage in CozyPouchLink We want to reduce coupling between CozyPouchLink and the browser's local storage This will allow to use CozyPouchLink from a react-native project where `window.localStorage` API is not available If no custom storage is injected, then `platformWeb.js` will be used by default In order to inject a custom storage, create a new platform object containing the same API as `platformWeb.js` and add it into the PouchLink constructor's `platform` option ```js import { default as PouchLink } from 'cozy-pouch-link' // Class based on `platformWeb.js` import { CustomPlaftorm } from './CustomPlaftorm' const pouchLink = new PouchLink({ platform: new CustomPlaftorm() }) ``` --- packages/cozy-pouch-link/src/CozyPouchLink.js | 14 +++++--- packages/cozy-pouch-link/src/PouchManager.js | 6 ++-- .../cozy-pouch-link/src/PouchManager.spec.js | 3 +- packages/cozy-pouch-link/src/localStorage.js | 34 +++++++++++-------- packages/cozy-pouch-link/src/platformWeb.js | 15 ++++++++ packages/cozy-pouch-link/src/types.js | 12 +++++++ 6 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 packages/cozy-pouch-link/src/platformWeb.js diff --git a/packages/cozy-pouch-link/src/CozyPouchLink.js b/packages/cozy-pouch-link/src/CozyPouchLink.js index a341975a35..9b7f43da49 100644 --- a/packages/cozy-pouch-link/src/CozyPouchLink.js +++ b/packages/cozy-pouch-link/src/CozyPouchLink.js @@ -19,6 +19,7 @@ import PouchManager from './PouchManager' import { PouchLocalStorage } from './localStorage' import logger from './logger' import { migratePouch } from './migrations/adapter' +import { platformWeb } from './platformWeb' import { getDatabaseName, getPrefix } from './utils' PouchDB.plugin(PouchDBFind) @@ -74,6 +75,7 @@ class PouchLink extends CozyLink { * @param {number} [opts.replicationInterval] Milliseconds between replications * @param {string[]} opts.doctypes Doctypes to replicate * @param {object[]} opts.doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + * @param {import('./types').LinkPlatform} opts.platform Platform specific adapters and methods */ constructor(opts) { @@ -89,7 +91,9 @@ class PouchLink extends CozyLink { this.doctypes = doctypes this.doctypesReplicationOptions = doctypesReplicationOptions this.indexes = {} - this.storage = new PouchLocalStorage() + this.storage = new PouchLocalStorage( + options.platform?.storage || platformWeb.storage + ) /** @type {Record} - Stores replication states per doctype */ this.replicationStatus = this.replicationStatus || {} @@ -99,10 +103,11 @@ class PouchLink extends CozyLink { * Return the PouchDB adapter name. * Should be IndexedDB for newest adapters. * + * @param {import('./types').LocalStorage} localStorage Methods to access local storage * @returns {Promise} The adapter name */ - static getPouchAdapterName = () => { - const storage = new PouchLocalStorage() + static getPouchAdapterName = localStorage => { + const storage = new PouchLocalStorage(localStorage || platformWeb.storage) return storage.getAdapterName() } @@ -205,7 +210,8 @@ class PouchLink extends CozyLink { onDoctypeSyncStart: this.handleDoctypeSyncStart.bind(this), onDoctypeSyncEnd: this.handleDoctypeSyncEnd.bind(this), prefix, - executeQuery: this.executeQuery.bind(this) + executeQuery: this.executeQuery.bind(this), + platform: this.options.platform }) await this.pouches.init() diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index a9420410c9..4d2fb67038 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -10,9 +10,9 @@ import { isMobileApp } from 'cozy-device-helper' import { PouchLocalStorage } from './localStorage' import Loop from './loop' import logger from './logger' +import { platformWeb } from './platformWeb' import { fetchRemoteLastSequence } from './remote' import { startReplication } from './startReplication' -import * as localStorage from './localStorage' import { getDatabaseName } from './utils' const DEFAULT_DELAY = 30 * 1000 @@ -37,7 +37,9 @@ class PouchManager { this.options = options this.doctypes = doctypes - this.storage = new PouchLocalStorage() + this.storage = new PouchLocalStorage( + options.platform?.storage || platformWeb.storage + ) } async init() { diff --git a/packages/cozy-pouch-link/src/PouchManager.spec.js b/packages/cozy-pouch-link/src/PouchManager.spec.js index 8cb42cc934..6a5f7187b5 100644 --- a/packages/cozy-pouch-link/src/PouchManager.spec.js +++ b/packages/cozy-pouch-link/src/PouchManager.spec.js @@ -24,10 +24,11 @@ import { LOCALSTORAGE_WARMUPEDQUERIES_KEY, PouchLocalStorage } from './localStorage' +import { platformWeb } from './platformWeb' import { fetchRemoteLastSequence, fetchRemoteInstance } from './remote' -const ls = new PouchLocalStorage() +const ls = new PouchLocalStorage(platformWeb.storage) const sleep = delay => { return new Promise(resolve => { diff --git a/packages/cozy-pouch-link/src/localStorage.js b/packages/cozy-pouch-link/src/localStorage.js index 58df2d1e1d..923f45553c 100644 --- a/packages/cozy-pouch-link/src/localStorage.js +++ b/packages/cozy-pouch-link/src/localStorage.js @@ -8,6 +8,10 @@ export const LOCALSTORAGE_LASTREPLICATEDDOCID_KEY = export const LOCALSTORAGE_ADAPTERNAME = 'cozy-client-pouch-link-adaptername' export class PouchLocalStorage { + constructor(storageEngine) { + this.storageEngine = storageEngine + } + /** * Persist the last replicated doc id for a doctype * @@ -20,7 +24,7 @@ export class PouchLocalStorage { const docids = await this.getAllLastReplicatedDocID() docids[doctype] = id - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_LASTREPLICATEDDOCID_KEY, JSON.stringify(docids) ) @@ -30,7 +34,7 @@ export class PouchLocalStorage { * @returns {Promise>} */ async getAllLastReplicatedDocID() { - const item = await window.localStorage.getItem( + const item = await this.storageEngine.getItem( LOCALSTORAGE_LASTREPLICATEDDOCID_KEY ) return item ? JSON.parse(item) : {} @@ -53,7 +57,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async destroyAllLastReplicatedDocID() { - await window.localStorage.removeItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) + await this.storageEngine.removeItem(LOCALSTORAGE_LASTREPLICATEDDOCID_KEY) } /** @@ -64,7 +68,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async persistSyncedDoctypes(syncedDoctypes) { - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_SYNCED_KEY, JSON.stringify(syncedDoctypes) ) @@ -76,7 +80,7 @@ export class PouchLocalStorage { * @returns {Promise} The synced doctypes */ async getPersistedSyncedDoctypes() { - const item = await window.localStorage.getItem(LOCALSTORAGE_SYNCED_KEY) + const item = await this.storageEngine.getItem(LOCALSTORAGE_SYNCED_KEY) const parsed = item ? JSON.parse(item) : {} if (typeof parsed !== 'object') { return {} @@ -90,7 +94,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async destroySyncedDoctypes() { - await window.localStorage.removeItem(LOCALSTORAGE_SYNCED_KEY) + await this.storageEngine.removeItem(LOCALSTORAGE_SYNCED_KEY) } /** @@ -105,7 +109,7 @@ export class PouchLocalStorage { const seqs = await this.getAllLastSequences() seqs[doctype] = sequence - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_LASTSEQUENCES_KEY, JSON.stringify(seqs) ) @@ -115,7 +119,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async getAllLastSequences() { - const item = await window.localStorage.getItem( + const item = await this.storageEngine.getItem( LOCALSTORAGE_LASTSEQUENCES_KEY ) return item ? JSON.parse(item) : {} @@ -139,7 +143,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async destroyAllDoctypeLastSequence() { - await window.localStorage.removeItem(LOCALSTORAGE_LASTSEQUENCES_KEY) + await this.storageEngine.removeItem(LOCALSTORAGE_LASTSEQUENCES_KEY) } /** @@ -152,7 +156,7 @@ export class PouchLocalStorage { async destroyDoctypeLastSequence(doctype) { const seqs = await this.getAllLastSequences() delete seqs[doctype] - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_LASTSEQUENCES_KEY, JSON.stringify(seqs) ) @@ -166,7 +170,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async persistWarmedUpQueries(warmedUpQueries) { - await window.localStorage.setItem( + await this.storageEngine.setItem( LOCALSTORAGE_WARMUPEDQUERIES_KEY, JSON.stringify(warmedUpQueries) ) @@ -178,7 +182,7 @@ export class PouchLocalStorage { * @returns {Promise} the warmed up queries */ async getPersistedWarmedUpQueries() { - const item = await window.localStorage.getItem( + const item = await this.storageEngine.getItem( LOCALSTORAGE_WARMUPEDQUERIES_KEY ) if (!item) { @@ -193,7 +197,7 @@ export class PouchLocalStorage { * @returns {Promise} */ async destroyWarmedUpQueries() { - await window.localStorage.removeItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) + await this.storageEngine.removeItem(LOCALSTORAGE_WARMUPEDQUERIES_KEY) } /** @@ -202,7 +206,7 @@ export class PouchLocalStorage { * @returns {Promise} The adapter name */ async getAdapterName() { - return await window.localStorage.getItem(LOCALSTORAGE_ADAPTERNAME) + return await this.storageEngine.getItem(LOCALSTORAGE_ADAPTERNAME) } /** @@ -213,6 +217,6 @@ export class PouchLocalStorage { * @returns {Promise} */ async persistAdapterName(adapter) { - await window.localStorage.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) + await this.storageEngine.setItem(LOCALSTORAGE_ADAPTERNAME, adapter) } } diff --git a/packages/cozy-pouch-link/src/platformWeb.js b/packages/cozy-pouch-link/src/platformWeb.js new file mode 100644 index 0000000000..2eaa0eed3c --- /dev/null +++ b/packages/cozy-pouch-link/src/platformWeb.js @@ -0,0 +1,15 @@ +const storage = { + getItem: async key => { + return window.localStorage.getItem(key) + }, + setItem: async (key, value) => { + return window.localStorage.setItem(key, value) + }, + removeItem: async key => { + return window.localStorage.removeItem(key) + } +} + +export const platformWeb = { + storage +} diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index ddf4a7a4c2..26dd5b7d84 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -15,4 +15,16 @@ * @property {string} Date */ +/** + * @typedef {object} LocalStorage + * @property {function(string): Promise} getItem + * @property {function(string, string): Promise} setItem + * @property {function(string): Promise} removeItem + */ + +/** + * @typedef {object} LinkPlatform + * @property {LocalStorage} storage Methods to access local storage + */ + export default {} From 891219265dcf8497a1b946b8c601e3803c0b4adf Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 28 May 2024 19:20:41 +0200 Subject: [PATCH 3/6] feat(pouch-link): Allow to inject a Pouch adapter in CozyPouchLink In previous commit we added a `platform` option into the PouchLink constructor in order to allow injecting a custom local storage API from a react-native project We also want to inject a custom PouchDB adapter as a react-native project would use a different PouchDB implementation Like for the local storage API, if no injection is given, then `pouchdb-browser` adapter will be used by default --- packages/cozy-pouch-link/src/PouchManager.js | 8 ++++++-- packages/cozy-pouch-link/src/platformWeb.js | 5 ++++- packages/cozy-pouch-link/src/types.js | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 4d2fb67038..daff21599a 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -1,4 +1,3 @@ -import PouchDB from 'pouchdb-browser' import fromPairs from 'lodash/fromPairs' import forEach from 'lodash/forEach' import get from 'lodash/get' @@ -40,16 +39,21 @@ class PouchManager { this.storage = new PouchLocalStorage( options.platform?.storage || platformWeb.storage ) + this.PouchDB = options.platform?.pouchAdapter || platformWeb.pouchAdapter } async init() { const pouchPlugins = get(this.options, 'pouch.plugins', []) const pouchOptions = get(this.options, 'pouch.options', {}) + forEach(pouchPlugins, plugin => this.PouchDB.plugin(plugin)) this.pouches = fromPairs( this.doctypes.map(doctype => [ doctype, - new PouchDB(getDatabaseName(this.options.prefix, doctype), pouchOptions) + new this.PouchDB( + getDatabaseName(this.options.prefix, doctype), + pouchOptions + ) ]) ) this.syncedDoctypes = await this.storage.getPersistedSyncedDoctypes() diff --git a/packages/cozy-pouch-link/src/platformWeb.js b/packages/cozy-pouch-link/src/platformWeb.js index 2eaa0eed3c..e09b9de5ca 100644 --- a/packages/cozy-pouch-link/src/platformWeb.js +++ b/packages/cozy-pouch-link/src/platformWeb.js @@ -1,3 +1,5 @@ +import PouchDB from 'pouchdb-browser' + const storage = { getItem: async key => { return window.localStorage.getItem(key) @@ -11,5 +13,6 @@ const storage = { } export const platformWeb = { - storage + storage, + pouchAdapter: PouchDB } diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index 26dd5b7d84..c017f07132 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -25,6 +25,7 @@ /** * @typedef {object} LinkPlatform * @property {LocalStorage} storage Methods to access local storage + * @property {any} pouchAdapter PouchDB class (can be pouchdb-core or pouchdb-browser) */ export default {} From d0bf24142dfee1bfd7540dcca6568a44c76752dd Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 31 May 2024 16:07:06 +0200 Subject: [PATCH 4/6] feat(pouch-link): Allow to inject an `isOnline` method in CozyPouchLink In previous commit we added a `platform` option into the PouchLink constructor in order to allow injecting custom local storage API and PouchDB adapter from a react-native project We also want to inject a custom `isOnline` method as react-native does not provide the `window.navigator.onLine` API Like for the other APIs, if no injection is given, then `window.navigator.onLine` will be used by default --- packages/cozy-pouch-link/src/PouchManager.js | 3 ++- packages/cozy-pouch-link/src/platformWeb.js | 7 ++++++- packages/cozy-pouch-link/src/types.js | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index daff21599a..4a8ab21ee4 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -40,6 +40,7 @@ class PouchManager { options.platform?.storage || platformWeb.storage ) this.PouchDB = options.platform?.pouchAdapter || platformWeb.pouchAdapter + this.isOnline = options.platform?.isOnline || platformWeb.isOnline } async init() { @@ -174,7 +175,7 @@ class PouchManager { /** Starts replication */ async replicateOnce() { - if (!window.navigator.onLine) { + if (!(await this.isOnline())) { logger.info( 'PouchManager: The device is offline so the replication has been skipped' ) diff --git a/packages/cozy-pouch-link/src/platformWeb.js b/packages/cozy-pouch-link/src/platformWeb.js index e09b9de5ca..168b8a3391 100644 --- a/packages/cozy-pouch-link/src/platformWeb.js +++ b/packages/cozy-pouch-link/src/platformWeb.js @@ -12,7 +12,12 @@ const storage = { } } +const isOnline = async () => { + return window.navigator.onLine +} + export const platformWeb = { storage, - pouchAdapter: PouchDB + pouchAdapter: PouchDB, + isOnline } diff --git a/packages/cozy-pouch-link/src/types.js b/packages/cozy-pouch-link/src/types.js index c017f07132..87f8d17963 100644 --- a/packages/cozy-pouch-link/src/types.js +++ b/packages/cozy-pouch-link/src/types.js @@ -26,6 +26,7 @@ * @typedef {object} LinkPlatform * @property {LocalStorage} storage Methods to access local storage * @property {any} pouchAdapter PouchDB class (can be pouchdb-core or pouchdb-browser) + * @property {function(): Promise} isOnline Method that check if the app is connected to internet */ export default {} From d74cbf411d11609cf4587248e7eec5a20a6f7365 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 28 May 2024 19:23:00 +0200 Subject: [PATCH 5/6] feat(pouch-link): Allow to inject event methods in CozyPouchLink In previous commit we added a `platform` option into the PouchLink constructor in order to allow injecting custom local storage API, PouchDB adapter and isOnline method from a react-native project We also want to inject a custom evant emitter for online/offline and pause/resume events as react-native does not provide the `document.addEventListener` and `document.removeEventListener` APIs Like for the other APIs, if no injection is given, then `document` APIs will be used by default --- packages/cozy-pouch-link/src/PouchManager.js | 17 +++++++++-------- packages/cozy-pouch-link/src/platformWeb.js | 10 ++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/cozy-pouch-link/src/PouchManager.js b/packages/cozy-pouch-link/src/PouchManager.js index 4a8ab21ee4..b1eaf57ddd 100644 --- a/packages/cozy-pouch-link/src/PouchManager.js +++ b/packages/cozy-pouch-link/src/PouchManager.js @@ -41,6 +41,7 @@ class PouchManager { ) this.PouchDB = options.platform?.pouchAdapter || platformWeb.pouchAdapter this.isOnline = options.platform?.isOnline || platformWeb.isOnline + this.events = options.platform?.events || platformWeb.events } async init() { @@ -77,11 +78,11 @@ class PouchManager { addListeners() { if (!this.listenerLaunched) { if (isMobileApp()) { - document.addEventListener('pause', this.stopReplicationLoop) - document.addEventListener('resume', this.startReplicationLoop) + this.events.addEventListener('pause', this.stopReplicationLoop) + this.events.addEventListener('resume', this.startReplicationLoop) } - document.addEventListener('online', this.startReplicationLoop) - document.addEventListener('offline', this.stopReplicationLoop) + this.events.addEventListener('online', this.startReplicationLoop) + this.events.addEventListener('offline', this.stopReplicationLoop) this.listenerLaunched = true } } @@ -89,11 +90,11 @@ class PouchManager { removeListeners() { if (this.listenerLaunched) { if (isMobileApp()) { - document.removeEventListener('pause', this.stopReplicationLoop) - document.removeEventListener('resume', this.startReplicationLoop) + this.events.removeEventListener('pause', this.stopReplicationLoop) + this.events.removeEventListener('resume', this.startReplicationLoop) } - document.removeEventListener('online', this.startReplicationLoop) - document.removeEventListener('offline', this.stopReplicationLoop) + this.events.removeEventListener('online', this.startReplicationLoop) + this.events.removeEventListener('offline', this.stopReplicationLoop) this.listenerLaunched = false } } diff --git a/packages/cozy-pouch-link/src/platformWeb.js b/packages/cozy-pouch-link/src/platformWeb.js index 168b8a3391..9f96d78c38 100644 --- a/packages/cozy-pouch-link/src/platformWeb.js +++ b/packages/cozy-pouch-link/src/platformWeb.js @@ -1,5 +1,14 @@ import PouchDB from 'pouchdb-browser' +const events = { + addEventListener: (eventName, handler) => { + document.addEventListener(eventName, handler) + }, + removeEventListener: (eventName, handler) => { + document.removeEventListener(eventName, handler) + } +} + const storage = { getItem: async key => { return window.localStorage.getItem(key) @@ -18,6 +27,7 @@ const isOnline = async () => { export const platformWeb = { storage, + events, pouchAdapter: PouchDB, isOnline } From 0037e6f0d659b1b0b6d9163ba55077e554aa5a0b Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 16 Jul 2024 17:30:28 +0200 Subject: [PATCH 6/6] docs: Update types and documentation --- docs/api/cozy-pouch-link/classes/PouchLink.md | 103 +++++++------ .../cozy-pouch-link/types/CozyPouchLink.d.ts | 14 +- .../cozy-pouch-link/types/PouchManager.d.ts | 15 +- .../cozy-pouch-link/types/localStorage.d.ts | 142 +++++++++++++++--- .../cozy-pouch-link/types/platformWeb.d.ts | 17 +++ .../types/startReplication.d.ts | 4 +- packages/cozy-pouch-link/types/types.d.ts | 22 +++ 7 files changed, 241 insertions(+), 76 deletions(-) create mode 100644 packages/cozy-pouch-link/types/platformWeb.d.ts diff --git a/docs/api/cozy-pouch-link/classes/PouchLink.md b/docs/api/cozy-pouch-link/classes/PouchLink.md index 4a112f365f..a396a9c90e 100644 --- a/docs/api/cozy-pouch-link/classes/PouchLink.md +++ b/docs/api/cozy-pouch-link/classes/PouchLink.md @@ -27,6 +27,7 @@ constructor - Initializes a new PouchLink | `opts` | `Object` | - | | `opts.doctypes` | `string`\[] | Doctypes to replicate | | `opts.doctypesReplicationOptions` | `any`\[] | A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") | +| `opts.platform` | `LinkPlatform` | Platform specific adapters and methods | | `opts.replicationInterval` | `number` | - | *Overrides* @@ -35,7 +36,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:84](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L84) +[CozyPouchLink.js:81](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L81) ## Properties @@ -45,7 +46,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:132](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L132) +[CozyPouchLink.js:134](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L134) *** @@ -55,7 +56,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:94](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L94) +[CozyPouchLink.js:91](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L91) *** @@ -65,7 +66,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:95](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L95) +[CozyPouchLink.js:92](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L92) *** @@ -75,17 +76,17 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:96](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L96) +[CozyPouchLink.js:93](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L93) *** ### options -• **options**: { `replicationInterval`: `number` } & { `doctypes`: `string`\[] ; `doctypesReplicationOptions`: `any`\[] ; `replicationInterval`: `number` } +• **options**: { `replicationInterval`: `number` } & { `doctypes`: `string`\[] ; `doctypesReplicationOptions`: `any`\[] ; `platform`: `LinkPlatform` ; `replicationInterval`: `number` } *Defined in* -[CozyPouchLink.js:88](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L88) +[CozyPouchLink.js:85](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L85) *** @@ -95,7 +96,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:202](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L202) +[CozyPouchLink.js:204](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L204) *** @@ -107,6 +108,16 @@ CozyLink.constructor [CozyPouchLink.js:99](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L99) +*** + +### storage + +• **storage**: `PouchLocalStorage` + +*Defined in* + +[CozyPouchLink.js:94](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L94) + ## Methods ### addReferencesTo @@ -125,7 +136,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:563](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L563) +[CozyPouchLink.js:567](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L567) *** @@ -145,7 +156,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:524](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L524) +[CozyPouchLink.js:528](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L528) *** @@ -166,7 +177,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:567](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L567) +[CozyPouchLink.js:571](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L571) *** @@ -186,7 +197,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:552](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L552) +[CozyPouchLink.js:556](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L556) *** @@ -207,7 +218,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:423](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L423) +[CozyPouchLink.js:427](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L427) *** @@ -229,7 +240,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:495](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L495) +[CozyPouchLink.js:499](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L499) *** @@ -249,7 +260,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:441](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L441) +[CozyPouchLink.js:445](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L445) *** @@ -269,7 +280,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:317](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L317) +[CozyPouchLink.js:321](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L321) *** @@ -289,7 +300,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:112](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L112) +[CozyPouchLink.js:114](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L114) *** @@ -309,7 +320,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:313](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L313) +[CozyPouchLink.js:317](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L317) *** @@ -329,7 +340,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:254](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L254) +[CozyPouchLink.js:258](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L258) *** @@ -349,7 +360,7 @@ CozyLink.constructor *Defined in* -[CozyPouchLink.js:249](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L249) +[CozyPouchLink.js:253](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L253) *** @@ -375,7 +386,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:235](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L235) +[CozyPouchLink.js:239](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L239) *** @@ -395,7 +406,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:405](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L405) +[CozyPouchLink.js:409](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L409) *** @@ -416,7 +427,7 @@ Emits an event (pouchlink:sync:end) when the sync (all doctypes) is done *Defined in* -[CozyPouchLink.js:410](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L410) +[CozyPouchLink.js:414](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L414) *** @@ -446,13 +457,13 @@ Migrate the current adapter *Defined in* -[CozyPouchLink.js:146](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L146) +[CozyPouchLink.js:148](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L148) *** ### needsToWaitWarmup -▸ **needsToWaitWarmup**(`doctype`): `boolean` +▸ **needsToWaitWarmup**(`doctype`): `Promise`<`boolean`> Check if there is warmup queries for this doctype and return if those queries are already warmed up or not @@ -465,13 +476,13 @@ and return if those queries are already warmed up or not *Returns* -`boolean` +`Promise`<`boolean`> the need to wait for the warmup *Defined in* -[CozyPouchLink.js:391](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L391) +[CozyPouchLink.js:395](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L395) *** @@ -485,7 +496,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:165](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L165) +[CozyPouchLink.js:167](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L167) *** @@ -505,7 +516,7 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:293](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L293) +[CozyPouchLink.js:297](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L297) *** @@ -525,13 +536,13 @@ the need to wait for the warmup *Defined in* -[CozyPouchLink.js:131](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L131) +[CozyPouchLink.js:133](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L133) *** ### request -▸ **request**(`operation`, `result?`, `forward?`): `void` | `Promise`<`any`> +▸ **request**(`operation`, `result?`, `forward?`): `Promise`<`any`> *Parameters* @@ -543,7 +554,7 @@ the need to wait for the warmup *Returns* -`void` | `Promise`<`any`> +`Promise`<`any`> *Overrides* @@ -551,7 +562,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:336](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L336) +[CozyPouchLink.js:340](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L340) *** @@ -565,7 +576,7 @@ CozyLink.request *Defined in* -[CozyPouchLink.js:219](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L219) +[CozyPouchLink.js:223](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L223) *** @@ -584,7 +595,7 @@ Emits pouchlink:sync:start event when the replication begins *Defined in* -[CozyPouchLink.js:268](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L268) +[CozyPouchLink.js:272](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L272) *** @@ -603,7 +614,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:285](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L285) +[CozyPouchLink.js:289](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L289) *** @@ -623,7 +634,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:321](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L321) +[CozyPouchLink.js:325](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L325) *** @@ -637,7 +648,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:589](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L589) +[CozyPouchLink.js:593](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L593) *** @@ -657,7 +668,7 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:529](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L529) +[CozyPouchLink.js:533](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L533) *** @@ -677,23 +688,29 @@ Emits pouchlink:sync:stop event *Defined in* -[CozyPouchLink.js:534](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L534) +[CozyPouchLink.js:538](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L538) *** ### getPouchAdapterName -▸ `Static` **getPouchAdapterName**(): `string` +▸ `Static` **getPouchAdapterName**(`localStorage`): `Promise`<`string`> Return the PouchDB adapter name. Should be IndexedDB for newest adapters. +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `localStorage` | `LocalStorage` | Methods to access local storage | + *Returns* -`string` +`Promise`<`string`> The adapter name *Defined in* -[CozyPouchLink.js:108](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L108) +[CozyPouchLink.js:109](https://github.com/cozy/cozy-client/blob/master/packages/cozy-pouch-link/src/CozyPouchLink.js#L109) diff --git a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts index 2bbfe5e6cc..127c172b69 100644 --- a/packages/cozy-pouch-link/types/CozyPouchLink.d.ts +++ b/packages/cozy-pouch-link/types/CozyPouchLink.d.ts @@ -15,9 +15,10 @@ declare class PouchLink extends CozyLink { * Return the PouchDB adapter name. * Should be IndexedDB for newest adapters. * - * @returns {string} The adapter name + * @param {import('./types').LocalStorage} localStorage Methods to access local storage + * @returns {Promise} The adapter name */ - static getPouchAdapterName: () => string; + static getPouchAdapterName: (localStorage: import('./types').LocalStorage) => Promise; /** * constructor - Initializes a new PouchLink * @@ -25,11 +26,13 @@ declare class PouchLink extends CozyLink { * @param {number} [opts.replicationInterval] Milliseconds between replications * @param {string[]} opts.doctypes Doctypes to replicate * @param {object[]} opts.doctypesReplicationOptions A mapping from doctypes to replication options. All pouch replication options can be used, as well as the "strategy" option that determines which way the replication is done (can be "sync", "fromRemote" or "toRemote") + * @param {import('./types').LinkPlatform} opts.platform Platform specific adapters and methods */ constructor(opts?: { replicationInterval: number; doctypes: string[]; doctypesReplicationOptions: object[]; + platform: import('./types').LinkPlatform; }); options: { replicationInterval: number; @@ -37,10 +40,12 @@ declare class PouchLink extends CozyLink { replicationInterval?: number; doctypes: string[]; doctypesReplicationOptions: object[]; + platform: import('./types').LinkPlatform; }; doctypes: string[]; doctypesReplicationOptions: any[]; indexes: {}; + storage: PouchLocalStorage; /** @type {Record} - Stores replication states per doctype */ replicationStatus: Record; getReplicationURL(doctype: any): string; @@ -118,9 +123,9 @@ declare class PouchLink extends CozyLink { * and return if those queries are already warmed up or not * * @param {string} doctype - Doctype to check - * @returns {boolean} the need to wait for the warmup + * @returns {Promise} the need to wait for the warmup */ - needsToWaitWarmup(doctype: string): boolean; + needsToWaitWarmup(doctype: string): Promise; hasIndex(name: any): boolean; mergePartialIndexInSelector(selector: any, partialFilter: any): any; ensureIndex(doctype: any, query: any): Promise; @@ -158,4 +163,5 @@ declare class PouchLink extends CozyLink { syncImmediately(): Promise; } import { CozyLink } from "cozy-client"; +import { PouchLocalStorage } from "./localStorage"; import PouchManager from "./PouchManager"; diff --git a/packages/cozy-pouch-link/types/PouchManager.d.ts b/packages/cozy-pouch-link/types/PouchManager.d.ts index a3919c36a1..cbb07a7373 100644 --- a/packages/cozy-pouch-link/types/PouchManager.d.ts +++ b/packages/cozy-pouch-link/types/PouchManager.d.ts @@ -8,6 +8,12 @@ export default PouchManager; declare class PouchManager { constructor(doctypes: any, options: any); options: any; + doctypes: any; + storage: PouchLocalStorage; + PouchDB: any; + isOnline: any; + events: any; + init(): Promise; pouches: import("lodash").Dictionary; syncedDoctypes: any; warmedUpQueries: any; @@ -52,13 +58,14 @@ declare class PouchManager { cancelCurrentReplications(): void; waitForCurrentReplications(): Promise | Promise; getPouch(doctype: any): any; - updateSyncInfo(doctype: any): void; + updateSyncInfo(doctype: any): Promise; getSyncInfo(doctype: any): any; isSynced(doctype: any): boolean; - clearSyncedDoctypes(): void; + clearSyncedDoctypes(): Promise; warmupQueries(doctype: any, queries: any): Promise; checkToWarmupDoctype(doctype: any, replicationOptions: any): void; - areQueriesWarmedUp(doctype: any, queries: any): any; - clearWarmedUpQueries(): void; + areQueriesWarmedUp(doctype: any, queries: any): Promise; + clearWarmedUpQueries(): Promise; } +import { PouchLocalStorage } from "./localStorage"; import Loop from "./loop"; diff --git a/packages/cozy-pouch-link/types/localStorage.d.ts b/packages/cozy-pouch-link/types/localStorage.d.ts index b3295b9c4c..c60f0f36be 100644 --- a/packages/cozy-pouch-link/types/localStorage.d.ts +++ b/packages/cozy-pouch-link/types/localStorage.d.ts @@ -3,26 +3,122 @@ export const LOCALSTORAGE_WARMUPEDQUERIES_KEY: "cozy-client-pouch-link-warmupedq export const LOCALSTORAGE_LASTSEQUENCES_KEY: "cozy-client-pouch-link-lastreplicationsequence"; export const LOCALSTORAGE_LASTREPLICATEDDOCID_KEY: "cozy-client-pouch-link-lastreplicateddocid"; export const LOCALSTORAGE_ADAPTERNAME: "cozy-client-pouch-link-adaptername"; -export function persistLastReplicatedDocID(doctype: string, id: string): void; -export function getAllLastReplicatedDocID(): any; -export function getLastReplicatedDocID(doctype: string): string; -export function destroyAllLastReplicatedDocID(): void; -export function persistSyncedDoctypes(syncedDoctypes: Record): void; -export function getPersistedSyncedDoctypes(): object; -export function destroySyncedDoctypes(): void; -export function persistDoctypeLastSequence(doctype: string, sequence: string): void; -export function getAllLastSequences(): any; -export function getDoctypeLastSequence(doctype: string): string; -export function destroyAllDoctypeLastSequence(): void; -export function destroyDoctypeLastSequence(doctype: string): void; -export function persistWarmedUpQueries(warmedUpQueries: object): void; -export function getPersistedWarmedUpQueries(): object; -export function destroyWarmedUpQueries(): void; -export function getAdapterName(): string; -export function persistAdapterName(adapter: string): void; -/** - * Persist the synchronized doctypes - */ -export type SyncInfo = { - Date: string; -}; +export class PouchLocalStorage { + constructor(storageEngine: any); + storageEngine: any; + /** + * Persist the last replicated doc id for a doctype + * + * @param {string} doctype - The replicated doctype + * @param {string} id - The docid + * + * @returns {Promise} + */ + persistLastReplicatedDocID(doctype: string, id: string): Promise; + /** + * @returns {Promise>} + */ + getAllLastReplicatedDocID(): Promise>; + /** + * Get the last replicated doc id for a doctype + * + * @param {string} doctype - The doctype + * @returns {Promise} The last replicated docid + */ + getLastReplicatedDocID(doctype: string): Promise; + /** + * Destroy all the replicated doc id + * + * @returns {Promise} + */ + destroyAllLastReplicatedDocID(): Promise; + /** + * Persist the synchronized doctypes + * + * @param {Record} syncedDoctypes - The sync doctypes + * + * @returns {Promise} + */ + persistSyncedDoctypes(syncedDoctypes: Record): Promise; + /** + * Get the persisted doctypes + * + * @returns {Promise} The synced doctypes + */ + getPersistedSyncedDoctypes(): Promise; + /** + * Destroy the synced doctypes + * + * @returns {Promise} + */ + destroySyncedDoctypes(): Promise; + /** + * Persist the last CouchDB sequence for a synced doctype + * + * @param {string} doctype - The synced doctype + * @param {string} sequence - The sequence hash + * + * @returns {Promise} + */ + persistDoctypeLastSequence(doctype: string, sequence: string): Promise; + /** + * @returns {Promise} + */ + getAllLastSequences(): Promise; + /** + * Get the last CouchDB sequence for a doctype + * + * @param {string} doctype - The doctype + * + * @returns {Promise} the last sequence + */ + getDoctypeLastSequence(doctype: string): Promise; + /** + * Destroy all the last sequence + * + * @returns {Promise} + */ + destroyAllDoctypeLastSequence(): Promise; + /** + * Destroy the last sequence for a doctype + * + * @param {string} doctype - The doctype + * + * @returns {Promise} + */ + destroyDoctypeLastSequence(doctype: string): Promise; + /** + * Persist the warmed up queries + * + * @param {object} warmedUpQueries - The warmedup queries + * + * @returns {Promise} + */ + persistWarmedUpQueries(warmedUpQueries: object): Promise; + /** + * Get the warmed up queries + * + * @returns {Promise} the warmed up queries + */ + getPersistedWarmedUpQueries(): Promise; + /** + * Destroy the warmed queries + * + * @returns {Promise} + */ + destroyWarmedUpQueries(): Promise; + /** + * Get the adapter name + * + * @returns {Promise} The adapter name + */ + getAdapterName(): Promise; + /** + * Persist the adapter name + * + * @param {string} adapter - The adapter name + * + * @returns {Promise} + */ + persistAdapterName(adapter: string): Promise; +} diff --git a/packages/cozy-pouch-link/types/platformWeb.d.ts b/packages/cozy-pouch-link/types/platformWeb.d.ts new file mode 100644 index 0000000000..9b8b8fdf71 --- /dev/null +++ b/packages/cozy-pouch-link/types/platformWeb.d.ts @@ -0,0 +1,17 @@ +export namespace platformWeb { + export { storage }; + export { events }; + export { PouchDB as pouchAdapter }; + export { isOnline }; +} +declare namespace storage { + function getItem(key: any): Promise; + function setItem(key: any, value: any): Promise; + function removeItem(key: any): Promise; +} +declare namespace events { + function addEventListener(eventName: any, handler: any): void; + function removeEventListener(eventName: any, handler: any): void; +} +declare function isOnline(): Promise; +export {}; diff --git a/packages/cozy-pouch-link/types/startReplication.d.ts b/packages/cozy-pouch-link/types/startReplication.d.ts index c820925f87..44ff8eb07b 100644 --- a/packages/cozy-pouch-link/types/startReplication.d.ts +++ b/packages/cozy-pouch-link/types/startReplication.d.ts @@ -3,5 +3,5 @@ export function startReplication(pouch: object, replicationOptions: { initialReplication: boolean; doctype: string; warmupQueries: import('cozy-client/types/types').Query[]; -}, getReplicationURL: Function): import('./types').CancelablePromise; -export function replicateAllDocs(db: object, baseUrl: string, doctype: string): Promise; +}, getReplicationURL: Function, storage: import('./localStorage').PouchLocalStorage): import('./types').CancelablePromise; +export function replicateAllDocs(db: object, baseUrl: string, doctype: string, storage: import('./localStorage').PouchLocalStorage): Promise; diff --git a/packages/cozy-pouch-link/types/types.d.ts b/packages/cozy-pouch-link/types/types.d.ts index d38702d605..2cb24b393c 100644 --- a/packages/cozy-pouch-link/types/types.d.ts +++ b/packages/cozy-pouch-link/types/types.d.ts @@ -8,3 +8,25 @@ export type Cancelable = { }; export type CancelablePromise = Promise & Cancelable; export type CancelablePromises = CancelablePromise[] & Cancelable; +export type SyncInfo = { + Date: string; +}; +export type LocalStorage = { + getItem: (arg0: string) => Promise; + setItem: (arg0: string, arg1: string) => Promise; + removeItem: (arg0: string) => Promise; +}; +export type LinkPlatform = { + /** + * Methods to access local storage + */ + storage: LocalStorage; + /** + * PouchDB class (can be pouchdb-core or pouchdb-browser) + */ + pouchAdapter: any; + /** + * Method that check if the app is connected to internet + */ + isOnline: () => Promise; +};