Skip to content

Commit

Permalink
feat: Allow to send Pouch databases through email for debug purpose
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Ldoppea committed Jul 30, 2024
1 parent ca76c14 commit 00fcd7d
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/hooks/useAppBootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -160,6 +161,10 @@ export const useAppBootstrap = client => {
return
}

if (handleDbDeepLink(url, client)) {
return
}

if (!client) {
const action = parseOnboardLink(url)

Expand Down
1 change: 1 addition & 0 deletions src/hooks/useAppBootstrap.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({})
Expand Down
23 changes: 23 additions & 0 deletions src/pouchdb/deeplinkHandler.ts
Original file line number Diff line number Diff line change
@@ -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())
}
112 changes: 112 additions & 0 deletions src/pouchdb/sendDbByEmail.ts
Original file line number Diff line number Diff line change
@@ -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<void> => {
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}`
}

0 comments on commit 00fcd7d

Please sign in to comment.