From 36fea1b4e7957ed51ed1e86a6397ec75ab7ec10d Mon Sep 17 00:00:00 2001 From: doubleface Date: Tue, 12 Nov 2024 11:24:38 +0100 Subject: [PATCH 1/3] feat: Allow clisk konnectors to save data in their own account --- src/libs/Launcher.js | 26 ++++++++++++++++++++++++++ src/libs/ReactNativeLauncher.js | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/libs/Launcher.js b/src/libs/Launcher.js index 4b4c37a7d..c475022fc 100644 --- a/src/libs/Launcher.js +++ b/src/libs/Launcher.js @@ -471,6 +471,32 @@ export default class Launcher { } } + /** + * Save data in the current account's data attribute + * + * @param {Object} data - any object serializable with JSON.stringify + * @returns > + */ + async saveAccountData(data) { + const { launcherClient: client, account } = this.getStartContext() || {} + + if (!account._id) { + throw new Error('Launcher: No associated account. Cannot save account data yet') + } + + const { data: currentAccount } = await client.query( + Q('io.cozy.accounts').getById(account._id) + ) + currentAccount.data = data + const { data: newAccount } = await client.save(currentAccount) + + this.setStartContext({ + ...this.getStartContext(), + account: newAccount + }) + return newAccount + } + /** * Calls cozy-konnector-libs' saveFiles function * diff --git a/src/libs/ReactNativeLauncher.js b/src/libs/ReactNativeLauncher.js index 0e265b1a1..b508b4d7e 100644 --- a/src/libs/ReactNativeLauncher.js +++ b/src/libs/ReactNativeLauncher.js @@ -156,7 +156,8 @@ class ReactNativeLauncher extends Launcher { 'getCookiesByDomain', 'saveCookieToKeychain', 'getCookieByDomainAndName', - 'getCookieFromKeychainByName' + 'getCookieFromKeychainByName', + 'saveAccountData' ], listenedEventsNames: ['log'] }), From 9674402f6ad69438ce8358ce19c661ead4816852 Mon Sep 17 00:00:00 2001 From: doubleface Date: Tue, 12 Nov 2024 11:25:51 +0100 Subject: [PATCH 2/3] feat: Allow clisk konnectors to run server job Clisk konnectors can now run themselves as server jobs. The result of the execution is given back to the clisk konnector. This is just a first step. Later, it will be possible to let hybrid konnector be run only in server mode when the context is relevant. --- src/libs/Launcher.js | 28 ++++++++++++++++- src/libs/ReactNativeLauncher.js | 3 +- src/libs/ReactNativeLauncher.spec.js | 45 ++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/libs/Launcher.js b/src/libs/Launcher.js index c475022fc..59b3bf4e7 100644 --- a/src/libs/Launcher.js +++ b/src/libs/Launcher.js @@ -471,6 +471,30 @@ export default class Launcher { } } + /** + * Allow client konnector to run a server version of itself. Wait for the end of the execution + * and the returns the result of the execution + * + * @param {Object} args - any arguments we want to pass to server job. Must be serializable as string + * @param {Object} [options] - options object + * @param {Number} [options.timeout] - timeout in ms + * @param {Number} [options.max_exec_count] - maximum errored execution count + * @returns {Promise} + */ + async runServerJob(args = {}, options = {}) { + const { client, konnector, trigger, account } = this.getStartContext() || {} + const jobArgs = { + ...args, + konnector: konnector.slug, + account: account._id, + folder_to_save: trigger.message?.folder_to_save + } + const { data: job } = await client + .collection('io.cozy.jobs') + .create('konnector', jobArgs, options, true) + return await client.collection('io.cozy.jobs').waitFor(job.id) + } + /** * Save data in the current account's data attribute * @@ -481,7 +505,9 @@ export default class Launcher { const { launcherClient: client, account } = this.getStartContext() || {} if (!account._id) { - throw new Error('Launcher: No associated account. Cannot save account data yet') + throw new Error( + 'Launcher: No associated account. Cannot save account data yet' + ) } const { data: currentAccount } = await client.query( diff --git a/src/libs/ReactNativeLauncher.js b/src/libs/ReactNativeLauncher.js index b508b4d7e..9880bd668 100644 --- a/src/libs/ReactNativeLauncher.js +++ b/src/libs/ReactNativeLauncher.js @@ -157,7 +157,8 @@ class ReactNativeLauncher extends Launcher { 'saveCookieToKeychain', 'getCookieByDomainAndName', 'getCookieFromKeychainByName', - 'saveAccountData' + 'saveAccountData', + 'runServerJob' ], listenedEventsNames: ['log'] }), diff --git a/src/libs/ReactNativeLauncher.spec.js b/src/libs/ReactNativeLauncher.spec.js index 3d913cb7d..b464f5ead 100644 --- a/src/libs/ReactNativeLauncher.spec.js +++ b/src/libs/ReactNativeLauncher.spec.js @@ -69,6 +69,8 @@ describe('ReactNativeLauncher', () => { const ensureDirectoryExists = jest.fn() const addReferencesTo = jest.fn() const get = jest.fn().mockResolvedValue({ data: { _id: 'testfolderid' } }) + const create = jest.fn() + const waitFor = jest.fn() const statByPath = jest .fn() .mockResolvedValue({ data: { _id: 'testfolderid' } }) @@ -93,7 +95,9 @@ describe('ReactNativeLauncher', () => { get, statByPath, statById, - add + add, + create, + waitFor }), getInstanceOptions: jest.fn().mockReturnValueOnce({ locale: 'fr' }) } @@ -103,10 +107,12 @@ describe('ReactNativeLauncher', () => { client, findReferencedBy, get, + create, ensureDirectoryExists, addReferencesTo, statByPath, - add + add, + waitFor } } @@ -947,4 +953,39 @@ describe('ReactNativeLauncher', () => { ).rejects.toEqual(ERRORS.SET_WORKER_STATE_TOO_LONG_TO_INIT) }) }) + describe('runServerJob', () => { + it('should create job with proper parameters', async () => { + const { launcher, client, create, waitFor } = setup() + create.mockResolvedValueOnce({ data: { id: 'testjobid' } }) + waitFor.mockResolvedValueOnce({ data: { id: 'testjobid' } }) + const konnector = { + slug: 'konnectorslug', + clientSide: true + } + launcher.setStartContext({ + client, + konnector, + manifest: konnector, + launcherClient: { + setAppMetadata: () => null + }, + account: fixtures.account, + trigger: fixtures.trigger + }) + const result = await launcher.runServerJob({ test: 'testvalue' }) + expect(create).toHaveBeenCalledTimes(1) + expect(create).toHaveBeenCalledWith( + 'konnector', + { + test: 'testvalue', + account: 'normal_account_id', + folder_to_save: 'testfolderid', + konnector: 'konnectorslug' + }, + {}, + true + ) + expect(result).toEqual({ data: { id: 'testjobid' } }) + }) + }) }) From 8e4136947a7199d2c839fdc61defac1d89571b24 Mon Sep 17 00:00:00 2001 From: doubleface Date: Tue, 12 Nov 2024 16:29:17 +0100 Subject: [PATCH 3/3] feat: Allow clisk konnectors to have incognito mode in webview This can avoid the need to force logout each time the konnector is run --- src/libs/ReactNativeLauncher.js | 10 ++++++++++ src/screens/konnectors/LauncherView.js | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/src/libs/ReactNativeLauncher.js b/src/libs/ReactNativeLauncher.js index 9880bd668..12acf9958 100644 --- a/src/libs/ReactNativeLauncher.js +++ b/src/libs/ReactNativeLauncher.js @@ -151,6 +151,7 @@ class ReactNativeLauncher extends Launcher { 'saveIdentity', 'queryAll', 'setUserAgent', + 'setIncognito', 'getCredentials', 'saveCredentials', 'getCookiesByDomain', @@ -583,6 +584,15 @@ class ReactNativeLauncher extends Launcher { this.emit('SET_USER_AGENT', userAgent) } + /** + * Set incognito mode for worker webview + * + * @param {Boolean} incognito + */ + async setIncognito(incognito) { + this.emit('SET_INCOGNITO', incognito) + } + /** * Run the specified method in the worker and make it fail with WORKER_WILL_RELOAD message * if the worker page is reloaded diff --git a/src/screens/konnectors/LauncherView.js b/src/screens/konnectors/LauncherView.js index c560c2814..412d87161 100644 --- a/src/screens/konnectors/LauncherView.js +++ b/src/screens/konnectors/LauncherView.js @@ -57,6 +57,7 @@ export class LauncherView extends Component { this.workerWebview = null this.state = { userAgent: undefined, + incognito: false, konnector: null, workerInnerUrl: null, worker: {}, @@ -220,6 +221,9 @@ export class LauncherView extends Component { this.launcher.on('SET_USER_AGENT', userAgent => { this.setState({ userAgent }) }) + this.launcher.on('SET_INCOGNITO', incognito => { + this.setState({ incognito }) + }) this.launcher.on('CREATED_ACCOUNT', this.onCreatedAccount) this.launcher.on('CREATED_JOB', this.onCreatedJob) this.launcher.on('STOPPED_JOB', this.onStoppedJob) @@ -304,6 +308,7 @@ export class LauncherView extends Component { javaScriptEnabled={true} onContentProcessDidTerminate={this.onWorkerWebviewKilled} onRenderProcessGone={this.onWorkerWebviewKilled} + incognito={this.state.incognito} userAgent={this.state.userAgent} source={{ uri: this.state.worker.url