Skip to content
Permalink
Browse files

feat(backups): enable dropbox backups

resolves #2215
  • Loading branch information...
korhaliv committed May 23, 2019
1 parent caeb5db commit 87b138eafbbed8355c3bbbfa29c7da64a6ea98fd
@@ -1,13 +1,21 @@
import EventEmitter from 'events'
import { forwardEvent } from '@zap/utils/events'

/**
* Base class for backup services that use tokens
*
* @export
* @class TokenBasedBackupService
* @extends {EventEmitter}
*/
export default class TokenBasedBackupService extends EventEmitter {
// backup service connection. represents concrete service API (such as dropbox or google drive)
connection = null

/**
* Initializes backup service connection and registers token listeners
*
*
* @param {function} createClient
* @param {function} createClient function that instantiates `connection` object
* @memberof TokenBasedBackupService
*/
async init(createClient) {
@@ -32,12 +40,12 @@ export default class TokenBasedBackupService extends EventEmitter {
/**
* Checks if client is setup for interactions. Also tests tokens for validity
*
* @returns
* @returns {boolean} whether service is currently connected
* @memberof BackupService
*/
async isLoggedIn() {
const { connection } = this
return connection && (await connection.testConnection())
return Boolean(connection && (await connection.testConnection()))
}

/**
@@ -54,6 +62,9 @@ export default class TokenBasedBackupService extends EventEmitter {
/**
* This service is token based and requires tokens to operate
* It also emits `tokensReceived` event
*
* @readonly
* @memberof TokenBasedBackupService
*/
get isUsingTokens() {
return true
@@ -4,6 +4,7 @@ import { Dropbox } from 'dropbox/lib'

import delay from '@zap/utils/delay'
import * as api from './dbApi'

/**
* Creates dropbox api connection
* @param {*} { clientId, authRedirectUrl, tokens }
@@ -21,11 +21,29 @@ export function getFileInfo(dbx, path) {
return dbx.filesGetMetadata({ path })
}

/**
* Downloads specified file as a `Buffer`
*
* @export
* @param {Object} dbx dropbox instance
* @param {string} path path to file
* @returns {Buffer}
*/
export async function downloadToBuffer(dbx, path) {
const { fileBinary } = await dbx.filesDownload({ path })
return fileBinary
}

/**
* Creates new file from buffer
*
* @export
* @param {Object} dbx dropbox instance
* @param {string} path path to file
* @param {Buffer} buffer `Buffer` instance
* @param {string} mode file write mode
* @returns {Object}
*/
export function uploadFromBuffer(dbx, path, buffer, mode = 'overwrite') {
return dbx.filesUpload({ path, contents: buffer, mode })
}
@@ -34,8 +52,8 @@ export function uploadFromBuffer(dbx, path, buffer, mode = 'overwrite') {
* Returns list of files metadata
*
* @export
* @param {*} drive
* @param {*} [params={}] {path, cursor} query params
* @param {Object} dbx dropbox instance
* @param {*} [params={}] {path, cursor,...} query params
* @returns {Array}
*/
export async function listFiles(dbx, params = {}) {
@@ -46,10 +64,22 @@ export async function listFiles(dbx, params = {}) {
return await dbx.filesListFolder({ path, ...rest })
}

export function createAuthWindow(clientId, redirectUrl) {
/**
* Initiates user authentication procedure via dropbox OAuth2 implicit mode
*
* @export
* @param {string} clientId dropbox client id
* @param {string} redirectUrl redirect url registered with the specified client id
* @param {Object} [windowParams={ width: 500, height: 700 }] Electron browser window properties
* @returns {Object} tokens object if operations was successful or error otherwise
*/
export function createAuthWindow(
clientId,
redirectUrl,
windowParams = { width: 500, height: 700 }
) {
const authWindow = new BrowserWindow({
width: 500,
height: 600,
...windowParams,
show: true,
webPreferences: {
nodeIntegration: false,
@@ -28,10 +28,10 @@ export function createOAuthClient(clientId, redirectUrl) {
*
* Updates existing file from buffer
* @export
* @param {*} drive - drive instance
* @param {*} fileId - google drive file id
* @param {*} buffer - `Buffer` instance
* @returns
* @param {Object} drive drive instance
* @param {string} fileId google drive file id
* @param {Buffer} buffer `Buffer` instance
* @returns {Object}
*/
export async function updateFromBuffer(drive, fileId, buffer) {
const res = await drive.files.update({
@@ -44,13 +44,13 @@ export async function updateFromBuffer(drive, fileId, buffer) {
}

/**
*
* Creates new file from buffer
*
* @export
* @param {*} drive - drive instance
* @param {*} name - desired file name
* @param {*} buffer - `Buffer` instance
* @returns
* @param {Object} drive drive instance
* @param {string} name desired file name
* @param {Buffer} buffer `Buffer` instance
* @returns {Object}
*/
export async function uploadFromBuffer(drive, name, buffer) {
const res = await drive.files.create({
@@ -68,8 +68,8 @@ export async function uploadFromBuffer(drive, name, buffer) {
* Retrieves file metadata
*
* @export
* @param {*} drive - drive instance
* @param {*} fileId - google drive file id
* @param {*} drive drive instance
* @param {*} fileId google drive file id
* @returns
*/
export async function getFileInfo(drive, fileId) {
@@ -80,11 +80,11 @@ export async function getFileInfo(drive, fileId) {
}

/**
*
* Downloads specified file as a `Buffer`
*
* @export
* @param {*} drive - drive instance
* @param {*} fileId - google drive file id
* @param {*} drive drive instance
* @param {*} fileId google drive file id
* @returns {Buffer}
*/
export function downloadToBuffer(drive, fileId) {
@@ -126,6 +126,15 @@ export async function listFiles(drive, params = {}) {
})
}

/**
* Initiates user authentication procedure via google OAuth2
*
* @export
* @param {Object} oAuthClient googleapis OAuth2 client
* @param {string} scope google drive access scope
* @param {Object} [windowParams={ width: 500, height: 600 }] Electron browser window properties
* @returns
*/
export function createAuthWindow(oAuthClient, scope, windowParams = { width: 500, height: 600 }) {
const authWindow = new BrowserWindow({
...windowParams,
@@ -177,8 +186,8 @@ export function createAuthWindow(oAuthClient, scope, windowParams = { width: 500
* Converts auth code to access tokens
*
* @export
* @param {*} oAuthClient - google OAuth2 client instance
* @param {*} code - auth code obtained through user authorization process
* @param {*} oAuthClient google OAuth2 client instance
* @param {*} code auth code obtained through user authorization process
* @returns
*/
export async function fetchAccessTokens(oAuthClient, code) {
@@ -20,14 +20,14 @@ class BackupService {
/**
* Saves specified backup
*
* @param {string} fileName backup dir path
* @param {string} walletId backup dir path
* @param {string} dir relative file path
* @param {Buffer} backup `Buffer` with backup data
* @returns {string} file name
* @memberof BackupService
*/
saveBackup = chainify(async (fileName, dir, backup) => {
const filePath = `${dir}/${fileName}`
saveBackup = chainify(async ({ walletId, fileId: dir, backup }) => {
const filePath = `${dir}/${walletId}`
await writeFileAsync(filePath, backup)
return dir
})
@@ -43,7 +43,10 @@ class BackupService {
}

/**
* This service doesn't use tokens and doesn't emit token events
* This service doesn't use tokens and doesn't emit token events
*
* @readonly
* @memberof BackupService
*/
get isUsingTokens() {
return false
@@ -6,7 +6,7 @@ export default function createBackupService(mainWindow) {
// helper func to send messages to the renderer process
const send = (msg, params) => mainWindow.webContents.send(msg, params)

// setups ipc to listen for backup service token updates and forward updates to
// sets up ipc to listen for backup service token updates and forward updates to
// the renderer process
const setupTokenUpdateListeners = (walletId, backupService) => {
const handleTokensReceived = tokens => {
@@ -50,11 +50,11 @@ export default function createBackupService(mainWindow) {
try {
const backupService = getBackupService(provider)
if (backupService) {
const backupId = await backupService.saveBackup(
nodePub,
backupMetadata && backupMetadata.backupId,
backup
)
const backupId = await backupService.saveBackup({
walletId: nodePub,
fileId: backupMetadata && backupMetadata.backupId,
backup,
})
mainLog.info('Backup updated, fileID: %s', backupId)
send('saveBackupSuccess', {
backupId,
@@ -1,5 +1,6 @@
import getGDrive from './gdrive'
import getLocal from './local'
import getDropbox from './dropbox'

export const GOOGLE_DRIVE = 'gdrive'
export const DROPBOX = 'dropbox'
@@ -10,7 +11,7 @@ export default function getBackupService(provider) {
case GOOGLE_DRIVE:
return getGDrive()
case DROPBOX:
throw new Error('not implemented')
return getDropbox()
case LOCAL:
return getLocal()
default:
@@ -351,11 +351,10 @@
"lodash.partition": "4.6.0",
"lodash.pick": "4.4.0",
"lodash.set": "4.3.2",
"lodash.snakecase": "4.1.1",
"lodash.throttle": "4.1.1",
"lodash.transform": "4.6.0",
"polished": "3.4.0",
"node-fetch": "2.6.0",
"polished": "3.4.0",
"prop-types": "15.7.2",
"qrcode.react": "0.9.3",
"react": "16.8.6",
@@ -64,14 +64,14 @@ class Onboarding extends React.Component {
resetOnboarding: PropTypes.func.isRequired,
seed: PropTypes.array,
setAutopilot: PropTypes.func.isRequired,
setBackupPathLocal: PropTypes.func.isRequired,
setBackupProvider: PropTypes.func.isRequired,
setConnectionCert: PropTypes.func.isRequired,
setConnectionHost: PropTypes.func.isRequired,
setConnectionMacaroon: PropTypes.func.isRequired,
setConnectionString: PropTypes.func.isRequired,
setConnectionType: PropTypes.func.isRequired,
setLndconnect: PropTypes.func.isRequired,
setLocalPath: PropTypes.func.isRequired,
setName: PropTypes.func.isRequired,
setNetwork: PropTypes.func.isRequired,
setPassphrase: PropTypes.func.isRequired,
@@ -151,7 +151,7 @@ class Onboarding extends React.Component {
unlockWallet,
setupBackupService,
setBackupProvider,
setLocalPath,
setBackupPathLocal,
} = this.props

let formSteps = []
@@ -173,7 +173,11 @@ class Onboarding extends React.Component {
<Wizard.Step key="Network" component={Network} {...{ network, setNetwork }} />,
<Wizard.Step key="Autopilot" component={Autopilot} {...{ autopilot, setAutopilot }} />,
<Wizard.Step key="BackupSetup" component={BackupSetup} {...{ setBackupProvider }} />,
<Wizard.Step key="BackupSetupLocal" component={BackupSetupLocal} {...{ setLocalPath }} />,
<Wizard.Step
key="BackupSetupLocal"
component={BackupSetupLocal}
{...{ setBackupPathLocal }}
/>,
<Wizard.Step
key="WalletCreate"
component={WalletCreate}
@@ -63,24 +63,23 @@ class BackupSetup extends React.Component {
<FormattedMessage {...messages.backup_header} />
</Heading.h1>
<Bar mb={6} />
<RadioGroup field="backupType" initialValue="gdrive" isRequired name="backupType">
<RadioGroup field="backupType" initialValue="local" isRequired name="backupType">
<Flex alignItems="space-around" justifyContent="center" mt={3}>
<BackupTypeItem
label={<FormattedMessage {...messages.backup_type_gdrive} />}
label={<FormattedMessage {...messages.backup_type_local} />}
mb={5}
mr={3}
value="gdrive"
value="local"
width={1 / 3}
/>
<BackupTypeItem
label={<FormattedMessage {...messages.backup_type_local} />}
label={<FormattedMessage {...messages.backup_type_gdrive} />}
mx={5}
value="local"
value="gdrive"
width={1 / 3}
/>

<BackupTypeItem
isDisabled
label={<FormattedMessage {...messages.backup_type_dropbox} />}
mb={5}
ml={3}
@@ -8,7 +8,8 @@ import messages from './messages'

class BackupSetupLocal extends React.Component {
static propTypes = {
setLocalPath: PropTypes.func.isRequired,
intl: intlShape.isRequired,
setBackupPathLocal: PropTypes.func.isRequired,
wizardApi: PropTypes.object,
wizardState: PropTypes.object,
}
@@ -23,16 +24,16 @@ class BackupSetupLocal extends React.Component {
}

handleSubmit = values => {
const { setLocalPath } = this.props
setLocalPath(values.path)
const { setBackupPathLocal } = this.props
setBackupPathLocal(values.path)
}

setFormApi = formApi => {
this.formApi = formApi
}

render() {
const { wizardApi, wizardState, setLocalPath, ...rest } = this.props
const { wizardApi, wizardState, setBackupPathLocal, ...rest } = this.props
const { getApi, onChange, onSubmit, onSubmitFailure } = wizardApi
const { currentItem } = wizardState

0 comments on commit 87b138e

Please sign in to comment.
You can’t perform that action at this time.