edge-core-js
Version:
Edge account & wallet management library
180 lines (142 loc) • 4.44 kB
JavaScript
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
}