UNPKG

edge-core-js

Version:

Edge account & wallet management library

180 lines (142 loc) 4.44 kB
function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }import { mergeDisklets, navigateDisklet } from 'disklet' import { base16, base64 } from 'rfc4648' import { asEdgeBox, wasEdgeBox } from '../../types/server-cleaners' import { sha256 } from '../../util/crypto/hashes' import { base58 } from '../../util/encoding' import { encryptDisklet } from './encrypt-disklet' const CHANGESET_MAX_ENTRIES = 100 export function makeLocalDisklet(io, walletId) { return navigateDisklet( io.disklet, 'local/' + base58.stringify(base64.parse(walletId)) ) } /** * Sets up the back-end folders needed to emulate Git on disk. * You probably don't want this. */ export function makeRepoPaths( io, storageKeys ) { const { dataKey, syncKey } = storageKeys const baseDisklet = navigateDisklet( io.disklet, 'repos/' + base58.stringify(sha256(sha256(syncKey))) ) const changesDisklet = navigateDisklet(baseDisklet, 'changes') const dataDisklet = navigateDisklet(baseDisklet, 'data') const disklet = encryptDisklet( io, dataKey, mergeDisklets(changesDisklet, dataDisklet) ) return { dataKey, syncKey, baseDisklet, changesDisklet, dataDisklet, disklet } } export function loadRepoStatus( paths ) { const fallback = { lastSync: 0, lastHash: undefined } return paths.baseDisklet .getText('status.json') .then(text => ({ lastSync: 0, ...JSON.parse(text) })) .catch(() => fallback) } /** * This will save a change-set into the local storage. * This function ignores folder-level deletes and overwrites, * but those can't happen under the current rules anyhow. */ export async function saveChanges( disklet, changes ) { await Promise.all( Object.keys(changes).map(path => { const box = changes[path] return box != null ? disklet.setText(path, JSON.stringify(wasEdgeBox(box))) : disklet.delete(path) }) ) } /** * Synchronizes the local store with the remote server. */ export async function syncRepo( syncClient, paths, status ) { const { changesDisklet, dataDisklet, syncKey } = paths const ourChanges = await deepListWithLimit(changesDisklet).then(paths => { return Promise.all( paths.map(async path => ({ path, box: asEdgeBox(JSON.parse(await changesDisklet.getText(path))) })) ) }) const syncKeyEncoded = base16.stringify(syncKey).toLowerCase() // Send a read request if no changes present locally, otherwise bundle the // changes with the a update request. const reply = await (() => { // Read the repo if no changes present locally. if (ourChanges.length === 0) { return syncClient.readRepo(syncKeyEncoded, status.lastHash) } // Write local changes to the repo. const changes = {} for (const change of ourChanges) { changes[change.path] = wasEdgeBox(change.box) } return syncClient.updateRepo(syncKeyEncoded, status.lastHash, { changes }) })() // Make the request: const { hash } = reply const changes = {} for (const path of Object.keys(_nullishCoalesce(reply.changes, () => ( {})))) { const json = reply.changes[path] changes[path] = json == null ? null : asEdgeBox(json) } // Save the incoming changes into our `data` folder: await saveChanges(dataDisklet, changes) // Delete any changed keys (since the upload is done): await Promise.all( ourChanges.map(change => changesDisklet.delete(change.path)) ) // Update the repo status: status.lastSync = Date.now() / 1000 if (hash != null) status.lastHash = hash await paths.baseDisklet.setText('status.json', JSON.stringify(status)) return { status, changes } } /** * Lists all files in a disklet, recursively up to a limit. * Returns a list of full paths. */ async function deepListWithLimit( disklet, path = '', limit = CHANGESET_MAX_ENTRIES ) { const list = await disklet.list(path) const paths = Object.keys(list).filter(path => list[path] === 'file') const folders = Object.keys(list).filter(path => list[path] === 'folder') // Loop over folders to get subpaths for (const folder of folders) { if (paths.length >= limit) break const remaining = limit - paths.length const subpaths = await deepListWithLimit(disklet, folder, remaining) paths.push(...subpaths.slice(0, remaining)) } return paths }