Skip to content

Commit

Permalink
feat: google drive integration with a sign-in flow and backup upload
Browse files Browse the repository at this point in the history
  • Loading branch information
piyushupadhyay19 authored Jul 3, 2024
1 parent 71eac99 commit 9ff7c88
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,5 @@ yarn-error.log
.env
google-services.json

# VS Code
.vscode/
8 changes: 8 additions & 0 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
global.Buffer = require('buffer').Buffer

import { AdeyaAgentProvider } from '@adeya/ssi'
import { GoogleSignin } from '@react-native-google-signin/google-signin'
import * as React from 'react'
import { useEffect, useMemo } from 'react'
import { StatusBar } from 'react-native'
import { Config } from 'react-native-config'
import SplashScreen from 'react-native-splash-screen'
import Toast from 'react-native-toast-message'

Expand Down Expand Up @@ -38,6 +40,12 @@ const App = () => {
// Hide the native splash / loading screen so that our
// RN version can be displayed
SplashScreen.hide()
GoogleSignin.configure({
webClientId: Config.GOOGLE_WEB_CLIENT_ID,
iosClientId: Config.GOOGLE_IOS_CLIENT_ID,
offlineAccess: true,
scopes: ['https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.metadata'],
})
}, [])

return (
Expand Down
33 changes: 32 additions & 1 deletion app/contexts/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import 'reflect-metadata'

import { isWalletPinCorrect } from '@adeya/ssi'
import React, { PropsWithChildren, createContext, useContext, useState } from 'react'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { GoogleSignin } from '@react-native-google-signin/google-signin'
import React, { PropsWithChildren, createContext, useContext, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { DeviceEventEmitter } from 'react-native'

Expand All @@ -29,15 +31,41 @@ export interface AuthContext {
setPIN: (PIN: string) => Promise<void>
commitPIN: (useBiometry: boolean) => Promise<boolean>
isBiometricsActive: () => Promise<boolean>
isGoogleAccountSignedIn: boolean
googleSignIn: () => Promise<void>
googleSignOut: () => Promise<void>
}

export const AuthContext = createContext<AuthContext>(null as unknown as AuthContext)

export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
const [walletSecret, setWalletSecret] = useState<WalletSecret>()
const [isGoogleAccountSignedIn, setIsGoogleAccountSignedIn] = useState(false)
const [, dispatch] = useStore()
const { t } = useTranslation()

useEffect(() => {
const checkGoogleSignInStatus = async () => {
const googleUserInfo = await AsyncStorage.getItem('googleUserInfo')
setIsGoogleAccountSignedIn(googleUserInfo !== null)
}
checkGoogleSignInStatus()
}, [])

const googleSignIn = async (): Promise<void> => {
setIsGoogleAccountSignedIn(true)
}

const googleSignOut = async (): Promise<void> => {
try {
await GoogleSignin.signOut()
await AsyncStorage.removeItem('googleUserInfo')
setIsGoogleAccountSignedIn(false)
} catch (error) {
// error message
}
}

const setPIN = async (PIN: string): Promise<void> => {
const secret = await secretForPIN(PIN)
await storeWalletSecret(secret)
Expand Down Expand Up @@ -128,6 +156,9 @@ export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
commitPIN,
setPIN,
isBiometricsActive,
isGoogleAccountSignedIn,
googleSignIn,
googleSignOut,
}}>
{children}
</AuthContext.Provider>
Expand Down
19 changes: 17 additions & 2 deletions app/localization/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ const translation = {
"ScanMyQR": "Scan my QR code",
"Developer": "Developer options",
"Backup": "backup wallet",
"GoogleDriveBackup": "Google Drive Backup",
"Confirmation" : 'Confirmation'
},
"TabStack": {
Expand Down Expand Up @@ -571,7 +572,8 @@ const translation = {
"DataRetention": "Data retention",
"Organization": "Explore",
"OrganizationConnection": "Connection",
"RenderCertificate": "Certificate"
"RenderCertificate": "Certificate",
"GoogleDriveSignIn": "Google Drive Sign In",
},
"Loading": {
"TakingTooLong": "This is taking longer than usual. You can return to home or continue waiting.",
Expand Down Expand Up @@ -695,6 +697,7 @@ const translation = {
"you_have_successfully": 'You have successfully selected the words.',
"complete_backup": 'Complete Backup',
"backup_wallet": 'Backup Wallet',
"backup_google_drive": 'Backup to Google Drive',
},
"PushNotifications": {
"BulletFour": "new messages",
Expand All @@ -710,7 +713,19 @@ const translation = {
},
"DIDs":{
"Dids": "My DID",
}
},
"GoogleDrive": {
"Backup": "Backup to Google Drive",
"BackupTitle": "Backup to Google Drive",
"SignInCancelled": "User cancelled the login flow",
"SignInProgress": "Operation is in progress already",
"PlayServicesNotAvailable": "Play services not available or outdated",
"SignInError": "An error occurred during sign-in",
"SignOutGoogle": "Sign Out of Google Account",
"SignOutGoogleSuccess": "Google Sign Out Successful",
"BackupFailed": "Backup failed to upload to Google Drive. Please try again later.",
"BackupSuccess": "Backup successfully uploaded to Google Drive.",
},
}

export default translation
6 changes: 6 additions & 0 deletions app/navigators/SettingStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CreateWallet from '../screens/CreateWallet'
import DataRetention from '../screens/DataRetention'
import ExportWallet from '../screens/ExportWallet'
import ExportWalletConfirmation from '../screens/ExportWalletConfirmation'
import GoogleDriveSignIn from '../screens/GoogleDriveSignIn'
import ImportSuccess from '../screens/ImportSuccess'
import ImportWalletVerify from '../screens/ImportWalletConfirmation'
import Language from '../screens/Language'
Expand Down Expand Up @@ -137,6 +138,11 @@ const SettingStack: React.FC = () => {
component={CreateWallet}
options={{ title: t('Screens.CreateWallet') }}
/>
<Stack.Screen
name={Screens.GoogleDriveSignIn}
component={GoogleDriveSignIn}
options={{ title: t('Screens.GoogleDriveSignIn') }}
/>
</Stack.Navigator>
)
}
Expand Down
6 changes: 4 additions & 2 deletions app/screens/ExportWallet.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { addWalletRecord, findWalletRecordsByQuery, useAdeyaAgent, utils } from '@adeya/ssi'
import { useNavigation } from '@react-navigation/core'
import { useNavigation, useRoute } from '@react-navigation/core'
import { generateMnemonic } from 'bip39'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand All @@ -15,6 +15,8 @@ const ExportWallet: React.FC = () => {
const { t } = useTranslation()
const [phraseData, setPhraseData] = useState<string[]>([])
const { agent } = useAdeyaAgent()
const route = useRoute()
const { backupType }: { backupType?: string } = route.params || {}

const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window')

Expand Down Expand Up @@ -157,7 +159,7 @@ const ExportWallet: React.FC = () => {
title={'Continue'}
accessibilityLabel={'Okay'}
buttonType={ButtonType.Primary}
onPress={() => navigation.navigate(Screens.ExportWalletConfirmation, { phraseData })}
onPress={() => navigation.navigate(Screens.ExportWalletConfirmation, { phraseData, backupType })}
/>
</View>
</ScrollView>
Expand Down
68 changes: 63 additions & 5 deletions app/screens/ExportWalletConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { exportWallet as exportAdeyaWallet } from '@adeya/ssi'
import { GoogleSignin } from '@react-native-google-signin/google-signin'
import { useNavigation, useRoute } from '@react-navigation/core'
import { GDrive, ListQueryBuilder, MimeTypes } from '@robinbobin/react-native-google-drive-api-wrapper'
import shuffle from 'lodash.shuffle'
import moment from 'moment'
import React, { useEffect, useState } from 'react'
Expand Down Expand Up @@ -171,11 +173,67 @@ function ExportWalletConfirmation() {

await RNFS.unlink(zipUpDirectory)

if (Platform.OS === 'ios') {
await Share.share({
title: 'Share backup zip file',
url: destinationZipPath,
})
if (parms?.params?.backupType === 'google_drive') {
const gdrive = new GDrive()
gdrive.accessToken = (await GoogleSignin.getTokens()).accessToken
gdrive.fetchCoercesTypes = true
gdrive.fetchRejectsOnHttpErrors = true
gdrive.fetchTimeout = 15000

const zipFileData = await RNFS.readFile(destinationZipPath, 'base64')
try {
const { result } = await gdrive.files.createIfNotExists(
{
q: new ListQueryBuilder()
.e('name', 'ADEYA Wallet Backups')
.and()
.e('mimeType', MimeTypes.FOLDER)
.and()
.in('root', 'parents'),
},
gdrive.files.newMetadataOnlyUploader().setRequestBody({
name: 'ADEYA Wallet Backups',
mimeType: MimeTypes.FOLDER,
parents: ['root'],
}),
)

const response = await gdrive.files
.newMultipartUploader()
.setData(zipFileData, 'application/zip')
.setIsBase64(true)
.setRequestBody({
name: zipFileName,
parents: [result.id],
})
.execute()

const folderData = await gdrive.files.getMetadata(result.id)
const fileData = await gdrive.files.getMetadata(response.id)
Toast.show({
type: ToastType.Success,
text1: t('GoogleDrive.BackupSuccess'),
})
setMatchPhrase(true)
navigation.navigate(Screens.Success, {
encryptedFileLocation: `Backup file uploaded successfully to Google Drive\n\nFolder: ${folderData.name}\n\nFile: ${fileData.name}`,
})
return
} catch (e) {
Toast.show({
type: ToastType.Error,
text1: t('GoogleDrive.BackupFailed'),
position: 'bottom',
})
return
}
} else {
if (Platform.OS === 'ios') {
await Share.share({
title: 'Share backup zip file',
url: destinationZipPath,
})
}
}

Toast.show({
Expand Down
60 changes: 60 additions & 0 deletions app/screens/GoogleDriveSignIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import { GoogleSignin, GoogleSigninButton, statusCodes, User } from '@react-native-google-signin/google-signin'
import { useNavigation } from '@react-navigation/core'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { View, StyleSheet, Alert } from 'react-native'

import { useAuth } from '../contexts/auth'
import { useTheme } from '../contexts/theme'
import { Screens } from '../types/navigators'

const GoogleDriveSignIn: React.FC = () => {
const [, setUserInfo] = useState<User | null>(null)
const { ColorPallet } = useTheme()
const navigation = useNavigation()
const { t } = useTranslation()
const { googleSignIn } = useAuth()
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: ColorPallet.brand.primaryBackground,
},
})

const authenticateWithGoogle = async () => {
try {
await GoogleSignin.hasPlayServices()
const googleUserInfo = await GoogleSignin.signIn()
setUserInfo(googleUserInfo)
await AsyncStorage.setItem('googleUserInfo', JSON.stringify(googleUserInfo))
googleSignIn()
navigation.replace(Screens.ExportWallet, { backupType: 'google_drive' })
} catch (error: any) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
Alert.alert('Sign-In Cancelled', t('GoogleDrive.SignInCancelled'))
} else if (error.code === statusCodes.IN_PROGRESS) {
Alert.alert('Sign-In In Progress', t('GoogleDrive.SignInProgress'))
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
Alert.alert('Play Services Not Available', t('GoogleDrive.PlayServicesNotAvailable'))
} else {
Alert.alert('Sign-In Error', t('GoogleDrive.SignInError'))
}
}
}

return (
<View style={styles.container}>
<GoogleSigninButton
size={GoogleSigninButton.Size.Wide}
color={GoogleSigninButton.Color.Dark}
onPress={authenticateWithGoogle}
disabled={false}
/>
</View>
)
}

export default GoogleDriveSignIn
27 changes: 26 additions & 1 deletion app/screens/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Toast from 'react-native-toast-message'
import Icon from 'react-native-vector-icons/MaterialIcons'

import { ToastType } from '../components/toast/BaseToast'
import { useAuth } from '../contexts/auth'
import { useConfiguration } from '../contexts/configuration'
import { DispatchAction } from '../contexts/reducers/store'
import { useStore } from '../contexts/store'
Expand Down Expand Up @@ -46,6 +47,7 @@ const Settings: React.FC<SettingsProps> = ({ navigation }) => {
const [enablePushNotifications, setEnablePushNotifications] = useState(false)
const [pushNotificationCapable, setPushNotificationCapable] = useState(true)
const [holderDid, setHolderDid] = useState('')
const { isGoogleAccountSignedIn, googleSignOut } = useAuth()

const languages = [{ id: Locales.en, value: t('Language.English') }]

Expand Down Expand Up @@ -208,7 +210,30 @@ const Settings: React.FC<SettingsProps> = ({ navigation }) => {
onPress: () => navigation.navigate(Screens.ExportWallet as never),
value: undefined,
},
],
{
title: t('Backup.backup_google_drive'),
accessibilityLabel: t('Settings.GoogleDriveBackup'),
testID: testIdWithKey('BackupGoogleDrive'),
onPress: async () => {
if (isGoogleAccountSignedIn) {
navigation.navigate(Screens.ExportWallet, { backupType: 'google_drive' })
} else {
navigation.navigate(Screens.GoogleDriveSignIn as never)
}
},
value: undefined,
},
isGoogleAccountSignedIn && {
title: t('GoogleDrive.SignOutGoogle'),
accessibilityLabel: t('GoogleDrive.SignOutGoogle'),
testID: testIdWithKey('SignOutGoogleAccount'),
onPress: async () => {
await googleSignOut()
navigation.navigate(Screens.Settings)
},
value: undefined,
},
].filter(Boolean),
},

{
Expand Down
1 change: 1 addition & 0 deletions app/types/navigators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export enum Screens {
Explore = 'Explore',
OrganizationDetails = 'Organization Details',
RenderCertificate = 'Render Certificate',
GoogleDriveSignIn = 'Google Drive Sign In',
}

export enum Stacks {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
"@react-native-community/netinfo": "^9.4.1",
"@react-native-firebase/app": "^18.4.0",
"@react-native-firebase/messaging": "^18.4.0",
"@react-native-google-signin/google-signin": "12.1.0",
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/core": "^6.4.9",
"@react-navigation/devtools": "^6.0.19",
"@react-navigation/native": "^6.1.7",
"@react-navigation/stack": "^6.3.17",
"@robinbobin/react-native-google-drive-api-wrapper": "^1.2.4",
"axios": "^1.6.0",
"bip39": "^3.1.0",
"events": "^1.1.1",
Expand Down
Loading

0 comments on commit 9ff7c88

Please sign in to comment.