From 00fcd7dac92d4262c0dc2b8e00463c03ad4d49a0 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 26 Jul 2024 18:35:44 +0200 Subject: [PATCH] feat: Allow to send Pouch databases through email for debug purpose By adding offline support through PouchDB, we expect database related bugs to happens in the future In order to ease debugging them, we want to allow exploring the local PouchDB files The easier way is to add the ability to extract them from the device and send them through email to cozy's support team --- src/hooks/useAppBootstrap.js | 5 ++ src/hooks/useAppBootstrap.spec.js | 1 + src/pouchdb/deeplinkHandler.ts | 23 ++++++ src/pouchdb/sendDbByEmail.ts | 112 ++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 src/pouchdb/deeplinkHandler.ts create mode 100644 src/pouchdb/sendDbByEmail.ts diff --git a/src/hooks/useAppBootstrap.js b/src/hooks/useAppBootstrap.js index c00d813a9..f2921b993 100644 --- a/src/hooks/useAppBootstrap.js +++ b/src/hooks/useAppBootstrap.js @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react' import { deconstructCozyWebLinkWithSlug } from 'cozy-client' import { handleLogsDeepLink } from '/app/domain/logger/deeplinkHandler' +import { handleDbDeepLink } from '/pouchdb/deeplinkHandler' import { SentryCustomTags, setSentryTag } from '/libs/monitoring/Sentry' import { manageIconCache } from '/libs/functions/iconTable' import { getDefaultIconParams } from '/libs/functions/openApp' @@ -160,6 +161,10 @@ export const useAppBootstrap = client => { return } + if (handleDbDeepLink(url, client)) { + return + } + if (!client) { const action = parseOnboardLink(url) diff --git a/src/hooks/useAppBootstrap.spec.js b/src/hooks/useAppBootstrap.spec.js index 799fd4c5f..64935073d 100644 --- a/src/hooks/useAppBootstrap.spec.js +++ b/src/hooks/useAppBootstrap.spec.js @@ -50,6 +50,7 @@ jest.mock('/libs/RootNavigation', () => ({ jest.mock('./useSplashScreen', () => ({ useSplashScreen: () => ({ hideSplashScreen: mockHideSplashScreen }) })) +jest.mock('/app/theme/SplashScreenService', () => ({})) jest.mock('/libs/functions/openApp', () => ({ getDefaultIconParams: jest.fn().mockReturnValue({}) diff --git a/src/pouchdb/deeplinkHandler.ts b/src/pouchdb/deeplinkHandler.ts new file mode 100644 index 000000000..0867436a6 --- /dev/null +++ b/src/pouchdb/deeplinkHandler.ts @@ -0,0 +1,23 @@ +import CozyClient from 'cozy-client' + +import strings from '/constants/strings.json' +import { sendDbByEmail } from '/pouchdb/sendDbByEmail' + +export const handleDbDeepLink = (url: string, client?: CozyClient): boolean => { + if (isSendDbDeepLink(url)) { + void sendDbByEmail(client) + + return true + } + + return false +} + +const isSendDbDeepLink = (url: string): boolean => { + const deepLinks = [ + `${strings.COZY_SCHEME}senddb`, + `${strings.UNIVERSAL_LINK_BASE}/senddb` + ] + + return deepLinks.includes(url.toLowerCase()) +} diff --git a/src/pouchdb/sendDbByEmail.ts b/src/pouchdb/sendDbByEmail.ts new file mode 100644 index 000000000..60a7659bb --- /dev/null +++ b/src/pouchdb/sendDbByEmail.ts @@ -0,0 +1,112 @@ +import { Alert, PermissionsAndroid } from 'react-native' +import Mailer from 'react-native-mail' +import RNFS from 'react-native-fs' +import RNFetchBlob from 'rn-fetch-blob' +import DeviceInfo from 'react-native-device-info' + +import type CozyClient from 'cozy-client' +import Minilog from 'cozy-minilog' + +import { fetchSupportMail } from '/app/domain/logger/supportEmail' +import { + hideSplashScreen, + showSplashScreen, + splashScreens +} from '/app/theme/SplashScreenService' +import { getInstanceAndFqdnFromClient } from '/libs/client' + +const log = Minilog('🗒️ DB Mailer') + +export const sendDbByEmail = async (client?: CozyClient): Promise => { + log.info('Send DB by email') + + if (!client) { + log.info('SendDbByEmail called with no client, return') + return + } + try { + + const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE + await PermissionsAndroid.request(permission) + + const supportEmail = await fetchSupportMail(client) + + const { fqdn } = getInstanceAndFqdnFromClient(client) + + const instance = client.getStackClient().uri ?? 'not logged app' + + const subject = `DB file for ${instance}` + + const files = await RNFS.readDir(RNFS.DocumentDirectoryPath) + + const dbFiles = files.filter(f => f.name.startsWith(`${fqdn}_`)) + + const externalFiles = [] + for (const dbFile of dbFiles) { + const dirs = RNFetchBlob.fs.dirs + + const internalPath = dbFile.path + + const date = Number(new Date()) + const externalPath = `${dirs.DCIMDir}/DbFile_${dbFile.name}${date}.sqlite` + + await RNFS.copyFile(internalPath, externalPath) + + externalFiles.push({ + path: externalPath + }) + } + + await showSplashScreen(splashScreens.SEND_LOG_EMAIL) + log.info('Start email intent') + Mailer.mail( + { + subject: subject, + recipients: [supportEmail], + body: buildMessageBody(), + customChooserTitle: 'This is my new title', // Android only (defaults to "Send Mail") + isHTML: true, + attachments: externalFiles + }, + (error, event) => { + Alert.alert( + error, + event, + [ + { + text: 'Ok', + onPress: (): void => log.debug('OK: Email Error Response') + }, + { + text: 'Cancel', + onPress: (): void => log.debug('CANCEL: Email Error Response') + } + ], + { cancelable: true } + ) + } + ) + log.info('Did finish email intent') + await hideSplashScreen(splashScreens.SEND_LOG_EMAIL) + +} +catch (err) { + console.log('🍎 ERORR WHILE EMAIL', err.message) +} +} + +const buildMessageBody = (): string => { + const appVersion = DeviceInfo.getVersion() + const appBuild = DeviceInfo.getBuildNumber() + const bundle = DeviceInfo.getBundleId() + const deviceBrand = DeviceInfo.getBrand() + const deviceModel = DeviceInfo.getModel() + const os = DeviceInfo.getSystemName() + const version = DeviceInfo.getSystemVersion() + + const appInfo = `App info: ${appVersion} (${appBuild})` + const bundleInfo = `App bundle: ${bundle}` + const deviceInfo = `Device info: ${deviceBrand} ${deviceModel} ${os} ${version}` + + return `${appInfo}\n${bundleInfo}\n${deviceInfo}` +}