diff --git a/packages/cozy-client/src/models/file.js b/packages/cozy-client/src/models/file.js index 189ffe1a8f..c619377a13 100644 --- a/packages/cozy-client/src/models/file.js +++ b/packages/cozy-client/src/models/file.js @@ -1,3 +1,5 @@ +import { isFlagshipApp } from 'cozy-device-helper' + import get from 'lodash/get' import isString from 'lodash/isString' import has from 'lodash/has' @@ -654,3 +656,32 @@ export const fetchBlobFileById = async (client, fileId) => { return fileBlob } + +/** + * Download the requested file + * + * This method can be used in a web page context or in a WebView hosted by a Flagship app + * + * When used in a FlagshipApp WebView context, then the action is redirected to the host app + * that will process the download + * + * @param {object} params - The download parameters + * @param {CozyClient} params.client - Instance of CozyClient + * @param {import("../types").IOCozyFile} params.file - io.cozy.files metadata of the document to downloaded + * @param {string} [params.url] - Blob url that should be used to download encrypted files + * @param {import('cozy-intent').WebviewService} [params.webviewIntent] - webviewIntent that can be used to redirect the download to host Flagship app + * + * @returns {Promise} + */ +export const downloadFile = async ({ client, file, url, webviewIntent }) => { + const filesCollection = client.collection(DOCTYPE_FILES) + + if (isFlagshipApp() && webviewIntent && !isEncrypted(file)) { + return await webviewIntent.call('downloadFile', file) + } + + if (isEncrypted(file)) { + return filesCollection.forceFileDownload(url, file.name) + } + return filesCollection.download(file) +} diff --git a/packages/cozy-client/src/models/file.spec.js b/packages/cozy-client/src/models/file.spec.js index 73be1be605..7edc89694c 100644 --- a/packages/cozy-client/src/models/file.spec.js +++ b/packages/cozy-client/src/models/file.spec.js @@ -1,3 +1,5 @@ +import { isFlagshipApp } from 'cozy-device-helper' + import * as fileModel from './file' import { Qualification } from './document/qualification' import { QueryDefinition } from '../queries/dsl' @@ -5,6 +7,9 @@ const CozyClient = require('cozy-client/dist/CozyClient').default const CozyStackClient = require('cozy-stack-client').default jest.mock('cozy-stack-client') +jest.mock('cozy-device-helper', () => ({ + isFlagshipApp: jest.fn() +})) const cozyClient = new CozyClient({ stackClient: new CozyStackClient() @@ -19,6 +24,8 @@ const fetchFileContentByIdSpy = jest.fn().mockName('fetchFileContentById') const moveSpy = jest.fn().mockName('move') const moveToCozySpy = jest.fn().mockName('moveToCozy') const moveFromCozySpy = jest.fn().mockName('moveFromCozy') +const downloadFromCozySpy = jest.fn().mockName('downloadFromCozy') +const forceFileDownloadFromCozySpy = jest.fn().mockName('forceFileDownload') beforeAll(() => { cozyClient.stackClient.collection.mockReturnValue({ @@ -31,10 +38,16 @@ beforeAll(() => { fetchFileContentById: fetchFileContentByIdSpy, move: moveSpy, moveToCozy: moveToCozySpy, - moveFromCozy: moveFromCozySpy + moveFromCozy: moveFromCozySpy, + download: downloadFromCozySpy, + forceFileDownload: forceFileDownloadFromCozySpy }) }) +beforeEach(() => { + jest.clearAllMocks() +}) + describe('File Model', () => { it('should test if a file is a note or not', () => { const fileDocument = { @@ -860,3 +873,78 @@ describe('File qualification', () => { }) }) }) + +describe('downloadFile', () => { + it('should handle download in web page', async () => { + const file = { + _id: 'SOME_FILE_ID', + _type: 'io.cozy.file', + name: 'SOME_FILE_NAME' + } + + await fileModel.downloadFile({ + // @ts-ignore + client: cozyClient, + // @ts-ignore + file, + webviewIntent: null + }) + + expect(downloadFromCozySpy).toHaveBeenCalledWith(file) + }) + + it('should handle download in Flagship app', async () => { + isFlagshipApp.mockReturnValue(true) + const webviewIntent = { + call: jest.fn() + } + + const file = { + _id: 'SOME_FILE_ID', + _type: 'io.cozy.file', + name: 'SOME_FILE_NAME' + } + + await fileModel.downloadFile({ + // @ts-ignore + client: cozyClient, + // @ts-ignore + file, + // @ts-ignore + webviewIntent + }) + + expect(downloadFromCozySpy).not.toHaveBeenCalled() + expect(webviewIntent.call).toHaveBeenCalledWith('downloadFile', file) + }) + + it('should download encrypted files from web page as this is not supported yet by Flagship app', async () => { + isFlagshipApp.mockReturnValue(true) + const webviewIntent = { + call: jest.fn() + } + + const file = { + _id: 'SOME_FILE_ID', + _type: 'io.cozy.file', + name: 'SOME_FILE_NAME', + encrypted: true + } + + await fileModel.downloadFile({ + // @ts-ignore + client: cozyClient, + // @ts-ignore + file, + url: 'SOME_URL', + // @ts-ignore + webviewIntent + }) + + expect(forceFileDownloadFromCozySpy).toHaveBeenCalledWith( + 'SOME_URL', + 'SOME_FILE_NAME' + ) + expect(webviewIntent.call).not.toHaveBeenCalled() + }) +}) diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index a539709033..259448a7ec 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -453,6 +453,7 @@ import { QueryDefinition } from './queries/dsl' /** * @typedef {object} FileDocument - An io.cozy.files document * @property {string} _id - Id of the file + * @property {string} _rev - Rev of the file * @property {FilesDoctype} _type - Doctype of the file * @property {string} dir_id - Id of the parent folder * @property {string} [path] - Path of the file diff --git a/packages/cozy-stack-client/src/FileCollection.js b/packages/cozy-stack-client/src/FileCollection.js index 12881ba378..b6833603df 100644 --- a/packages/cozy-stack-client/src/FileCollection.js +++ b/packages/cozy-stack-client/src/FileCollection.js @@ -57,6 +57,7 @@ import logger from './logger' * * @typedef {object} FileDocument * @property {string} _id - Id of the file + * @property {string} _rev - Rev of the file * @property {FileAttributes} attributes - Attributes of the file * @property {object} meta - Meta * @property {object} relationships - Relationships