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 Aug 26, 2024
1 parent b8e02db commit 8b921ca
Show file tree
Hide file tree
Showing 4 changed files with 175 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())
}
146 changes: 146 additions & 0 deletions src/pouchdb/sendDbByEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Alert, PermissionsAndroid, Platform } 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'
import { getErrorMessage } from '/libs/functions/getErrorMessage'

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 files 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

if (Platform.OS === 'android') {
const date = Number(new Date())
const externalPath = `${dirs.DCIMDir}/DbFile_${dbFile.name}${date}.sqlite`

await RNFS.copyFile(internalPath, externalPath)

externalFiles.push({
path: externalPath
})
} else {
externalFiles.push({
path: dbFile.path,
type: 'pdf' // there is no compatible MIME type, so we use PDF one as replacement, this should change nothing expect the email aspect
})
}
}

await showSplashScreen(splashScreens.SEND_LOG_EMAIL)
log.info('Start email intent', externalFiles)
await sendMailPromise(subject, supportEmail, externalFiles).catch(
(errorData: sendMailError) => {
const { error, event } = errorData
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 (error) {
const errorMessage = getErrorMessage(error)
log.error('Error while trying to send DB email', errorMessage)
}
}

const sendMailPromise = (
subject: string,
email: string,
attachments: Attachment[]
): Promise<void> => {
return new Promise((resolve, reject) => {
Mailer.mail(
{
subject: subject,
recipients: ['[email protected]'],//email],
body: buildMessageBody(),
isHTML: true,
attachments: attachments
},
(error, event) => {
if (error) {
reject({ error, event })
} else {
resolve()
}
}
)
})
}

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}`
}

interface sendMailError {
error: string
event?: string
}

interface Attachment {
path: string
}

0 comments on commit 8b921ca

Please sign in to comment.