Skip to content

Commit

Permalink
feat(data): 🗃️ backup, restore, lock & clear wallet data
Browse files Browse the repository at this point in the history
  • Loading branch information
jojobyte committed Jan 21, 2024
1 parent 9d4b3c5 commit 0b1d010
Show file tree
Hide file tree
Showing 14 changed files with 551 additions and 76 deletions.
13 changes: 8 additions & 5 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
- [x] Send & Request Funds to / from Contact
- [x] Remove Contact confirmation dialog
- [x] Show Pairing Information from Edit Contact dialog
- [ ] Backup
- [x] Display Recovery Phrase
- [x] All Wallet Data
- [ ] Keystore
- [x] Disconnect / Logout / Clear Wallet & Contacts
- [x] Lock Wallet (require re-entering decryption password)
- [ ] Fiat balance from:
- https://rates2.dashretail.org/rates?source=dashretail&%7B%7D=
- symbol=DASH${symbol}
Expand All @@ -38,8 +44,5 @@
- [ ] Send Btn
- [ ] Request Btn
- [ ] Connect to Dialogs on Click
- [ ] Display Recovery Phrase in Edit Profile
- [ ] Logout / Clear Wallet & Contacts
- [ ] Enforce Alias uniqueness on pairing & editing contact
- [ ] On Pairing check if XkeyID exists in contacts
- [ ] Lock Wallet (require re-entering decryption password)
- [ ] Enforce Alias uniqueness on pairing & editing contact
- [ ] On Pairing check if XkeyID exists in contacts
12 changes: 11 additions & 1 deletion src/components/contacts-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ const initialState = {
${state.footer(state)}
`,
item: c => {
// console.warn('contact list item', c)
if ('string' === typeof c) {
return html`
<article>
<address>
<h4>Encrypted Contact</h4>
</address>
</article>
`
}
let paired = Object.keys(c?.outgoing || {}).length > 0
let created = c.createdAt
? timeago(Date.now() - (new Date(c.createdAt)).getTime())
Expand All @@ -65,7 +75,7 @@ const initialState = {
: ''
let user = c.alias || c.info?.preferred_username
let name = c.info?.name || created
let inId = Object.keys(c.incoming)[0].split('/')[1]
let inId = Object.keys(c?.incoming || {})?.[0]?.split('/')[1]

let itemAlias = user
? `@${user}${ !paired ? ' - ' : '' }${finishPairing}`
Expand Down
6 changes: 4 additions & 2 deletions src/components/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,15 +367,17 @@ export function setupDialog(

return {
element: dialog,
show: () => new Promise((resolve, reject) => {
show: (callback) => new Promise((resolve, reject) => {
removeAllListeners()
addListeners(resolve, reject)
dialog.show()
callback?.()
}),
showModal: () => new Promise((resolve, reject) => {
showModal: (callback) => new Promise((resolve, reject) => {
removeAllListeners()
addListeners(resolve, reject)
dialog.showModal()
callback?.()
}),
close: returnVal => dialog.close(returnVal),
render,
Expand Down
22 changes: 21 additions & 1 deletion src/components/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,27 @@ const initialState = {
<use xlink:href="#icon-logo"></use>
</svg>
</a>
<a id="nav-alias" class="alias" href="/#me">@${state.data?.alias}</a>
<menu>
<li>
<a id="nav-alias" class="alias" href="/#me">
@${state.data?.alias}
</a>
<menu class="user hidden">
<li><a id="nav-edit-profile" class="profile" href="/#me">
Edit Profile
</a></li>
<li><a id="nav-backup" class="backup" href="/#backup">
Backup Wallet
</a></li>
<li><a id="nav-lock" class="lock" href="/#lock">
Lock
</a></li>
<li><a id="nav-disconnect" class="disconnect" href="/#disconnect">
Disconnect
</a></li>
</menu>
</li>
</menu>
`,
elements: {
nav: document.createElement('nav'),
Expand Down
134 changes: 134 additions & 0 deletions src/helpers/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,137 @@ export async function DatabaseSetup() {
aliases,
}
}

// https://gist.github.com/loilo/ed43739361ec718129a15ae5d531095b#file-idb-backup-and-restore-mjs
/**
* Export all data from an IndexedDB database
*
* @param {IDBDatabase} idbDatabase The database to export from
* @return {Promise<Object>}
*/
export function exportToJson(
idbDatabase,
excludeList = [],
// includeList = [],
) {
return new Promise((resolve, reject) => {
const exportObject = {}
let storeLength = idbDatabase.objectStoreNames.length
if (storeLength === 0) {
resolve(JSON.stringify(exportObject))
} else {
const transaction = idbDatabase.transaction(
idbDatabase.objectStoreNames,
'readonly'
)

transaction.addEventListener('error', reject)

for (const storeName of idbDatabase.objectStoreNames) {
if (excludeList.includes(storeName)) {
--storeLength;
console.log('eidb storeName', storeName)
continue;
}
const allObjects = {}
transaction
.objectStore(storeName)
.openCursor()
.addEventListener('success', event => {
const cursor = event.target.result
// console.log('eidb transaction cursor', cursor)
if (cursor) {
allObjects[cursor.primaryKey] = cursor.value
cursor.continue()
} else {
exportObject[storeName] = allObjects

if (
storeLength ===
Object.keys(exportObject).length
) {
resolve(exportObject)
}
}
})
}
}
})
}

/**
* Import data from JSON into an IndexedDB database.
* This does not delete any existing data from the database, so keys may clash.
*
* @param {Object} store Database to import into
* @param {Object | string} json Data to import, one key per object store
* @return {Promise<void>}
*/
export async function importFromJson(store, json) {
let importObject = json

if ('string' === typeof json) {
importObject = JSON.parse(json)
}

return new Promise((resolve, reject) => {
for (const storeName in importObject) {
let targetStore = store[storeName]
let importData = importObject[storeName]
// let count = 0
for (const itemKey in importData) {
targetStore.setItem(itemKey, importData[itemKey])
}
resolve()
}
})
}

// https://stackoverflow.com/a/65939108
export function saveJsonToFile(filename, dataObjToWrite) {
const blob = new Blob([JSON.stringify(dataObjToWrite)], { type: "text/json" });
const link = document.createElement("a");

link.download = filename;
link.href = window.URL.createObjectURL(blob);
link.dataset.downloadurl = ["text/json", link.download, link.href].join(":");

const evt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});

link.dispatchEvent(evt);
link.remove()
}

// exportWalletData(localForageBaseCfg.name)
export function exportWalletData(name, version) {
var conn = indexedDB.open(name, version)
// console.log('exportWalletData', {name, version, conn})
conn.onsuccess = e => {
// @ts-ignore
var database = e.target.result
// console.log('exportWalletData onsuccess', {database})

exportToJson(
database,
[
'local-forage-detect-blob-support',
// 'addresses',
],
)
.then(d => {
// console.log('exportWalletData indexedDB exportToJson', d)

let walletId = Object.keys(d.wallets)?.[0] || ''

saveJsonToFile(
`incubator_wallet.${walletId}.${(new Date()).toISOString()}.json`,
d,
)
})
.catch(console.error)
}
}
24 changes: 23 additions & 1 deletion src/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,8 @@ export async function loadStoreObject(store, callback) {
// return result
// }
})
.then(() => callback(result))
.then(() => callback?.(result))
.then(() => result)
.catch(err => {
console.error('loadStoreObject', err)
return null
Expand Down Expand Up @@ -873,3 +874,24 @@ export function getAvatar(c) {

return `${avStr}">${initials}</div>`
}

export function readFile(file, callback) {
let reader = new FileReader();
let result

reader.addEventListener('load', () => {
try {
// @ts-ignore
result = JSON.parse(reader?.result || '{}');

console.log('parse loaded json', result);
callback?.(result)

// state[key] = result
} catch(err) {
throw new Error(`failed to parse JSON data from ${file.name}`)
}
});

reader.readAsText(file);
}
8 changes: 5 additions & 3 deletions src/helpers/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
DashSocket,
Cryptic,
} from '../imports.js'
import { DatabaseSetup } from './db.js'
import {
DatabaseSetup,
} from './db.js'
import { deriveWalletData } from './utils.js'
import {
STOREAGE_SALT, OIDC_CLAIMS,
Expand Down Expand Up @@ -276,7 +278,7 @@ export async function decryptData(
return await cryptic.decrypt(data, ks.iv)
}

export async function storedData(
export function storedData(
encryptionPassword, keystore,
) {
const SD = {}
Expand Down Expand Up @@ -999,4 +1001,4 @@ export async function sendTx(
console.log('instantSend result', result);

return result
}
}
Loading

0 comments on commit 0b1d010

Please sign in to comment.