Skip to content

Commit

Permalink
Improved DB + Full Testnet DB support (#187)
Browse files Browse the repository at this point in the history
* Improved DB interface, Account class, type-safety

* Simple Testnet Database + Encryption support

* Fix TXs for unencrypted wallets

* Hide "Secure your wallet" during switches

* Fix "Import" flow breaking on network switch

* Add extra safeguards + improved UI resetting
  • Loading branch information
JSKitty authored Aug 29, 2023
1 parent 52f8f51 commit 97ffeab
Show file tree
Hide file tree
Showing 16 changed files with 504 additions and 205 deletions.
7 changes: 7 additions & 0 deletions locale/en/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ export const en_translation = {
settingsToggleDebug: 'Debug Mode', //
settingsToggleTestnet: 'Testnet Mode', //

// Network switching (mainnet <---> testnet)
netSwitchUnsavedWarningTitle: "Your {network} wallet isn't saved!", //
netSwitchUnsavedWarningSubtitle: 'Your {network} account is at risk!', //
netSwitchUnsavedWarningSubtext:
"If you switch to {network} before saving it, you'll lose the account!", //
netSwitchUnsavedWarningConfirmation: 'Are you really sure?', //

// Transparency Report
transparencyReport: 'Transparency Report',
hit: 'A ping indicating an app load, no unique data is sent.',
Expand Down
6 changes: 6 additions & 0 deletions locale/fr/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ export const fr_translation = {
settingsToggleDebug: 'Mode de débogage', //Debug Mode
settingsToggleTestnet: 'Mode testnet', //Testnet Mode

// Network switching (mainnet <---> testnet)
netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved!
netSwitchUnsavedWarningSubtitle: '', //Your {network} account is at risk!
netSwitchUnsavedWarningSubtext: '', //If you switch to {network} before saving it, you\'ll lose the account!
netSwitchUnsavedWarningConfirmation: '', //Are you really sure?

// Transparency Report
transparencyReport: 'Rapport de transparence', //Transparency Report
hit: "Un ping indiquant le chargement d'une application, aucune donnée unique n'est envoyée.", //A ping indicating an app load, no unique data is sent.
Expand Down
6 changes: 6 additions & 0 deletions locale/ph/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ export const ph_translation = {
settingsToggleDebug: 'Debug Mode', //Debug Mode
settingsToggleTestnet: 'Testnet Mode', //Testnet Mode

// Network switching (mainnet <---> testnet)
netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved!
netSwitchUnsavedWarningSubtitle: '', //Your {network} account is at risk!
netSwitchUnsavedWarningSubtext: '', //If you switch to {network} before saving it, you\'ll lose the account!
netSwitchUnsavedWarningConfirmation: '', //Are you really sure?

// Transparency Report
transparencyReport: 'Transparency Report', //Transparency Report
hit: 'Ang ping na nagpapahiwatig ng pag load ng app, walang unique na data ang naipadala.', //A ping indicating an app load, no unique data is sent.
Expand Down
6 changes: 6 additions & 0 deletions locale/pt-br/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ export const pt_br_translation = {
settingsToggleDebug: 'Modo de depuração', //Debug Mode
settingsToggleTestnet: 'Modo Testnet', //Testnet Mode

// Network switching (mainnet <---> testnet)
netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved!
netSwitchUnsavedWarningSubtitle: '', //Your {network} account is at risk!
netSwitchUnsavedWarningSubtext: '', //If you switch to {network} before saving it, you\'ll lose the account!
netSwitchUnsavedWarningConfirmation: '', //Are you really sure?

// Transparency Report
transparencyReport: '"Relatório de Transparência"', //Transparency Report
hit: '"Um ping para indicar o carregamento de uma aplicação, nenhum dado exclusivo é enviado."', //A ping indicating an app load, no unique data is sent.
Expand Down
6 changes: 6 additions & 0 deletions locale/pt-pt/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ export const pt_pt_translation = {
settingsToggleDebug: 'Modo de depuração', //Debug Mode
settingsToggleTestnet: 'Modo Testnet', //Testnet Mode

// Network switching (mainnet <---> testnet)
netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved!
netSwitchUnsavedWarningSubtitle: '', //Your {network} account is at risk!
netSwitchUnsavedWarningSubtext: '', //If you switch to {network} before saving it, you\'ll lose the account!
netSwitchUnsavedWarningConfirmation: '', //Are you really sure?

// Transparency Report
transparencyReport: '"Relatório de Transparência"', //Transparency Report
hit: '"Um ping a indicar o carregamento de uma aplicação, nenhum dado exclusivo é enviado."', //A ping indicating an app load, no unique data is sent.
Expand Down
6 changes: 6 additions & 0 deletions locale/template/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ var translation = {
settingsToggleDebug: '', //Debug Mode
settingsToggleTestnet: '', //Testnet Mode

// Network switching (mainnet <---> testnet)
netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved!
netSwitchUnsavedWarningSubtitle: '', //Your {network} account is at risk!
netSwitchUnsavedWarningSubtext: '', //If you switch to {network} before saving it, you\'ll lose the account!
netSwitchUnsavedWarningConfirmation: '', //Are you really sure?

// Transparency Report
transparencyReport: '', //Transparency Report
hit: '', //A ping indicating an app load, no unique data is sent.
Expand Down
8 changes: 8 additions & 0 deletions locale/uwu/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ export const uwu_translation = {
settingsToggleDebug: 'Debug Mowode', //Debug Mode
settingsToggleTestnet: 'Testnet Mowode', //Testnet Mode

// Network switching (mainnet <---> testnet)
netSwitchUnsavedWarningTitle: "Ur {network} wawwet isn't saved!", //Your {network} wallet isn\'t saved!
netSwitchUnsavedWarningSubtitle:
'Ur {network} account could get fucky-wuckied!', //Your {network} account is at risk!
netSwitchUnsavedWarningSubtext:
"If u switch to {network} befwore saving it, you'll lose the account!", //If you switch to {network} before saving it, you\'ll lose the account!
netSwitchUnsavedWarningConfirmation: 'Are u reaaaaaaaaally sure?', //Are you really sure?

// Transparency Report
transparencyReport: 'Twanspawency Repawt', //Transparency Report
hit: 'A ping indicating an app load, no unique data is sent.♡', //A ping indicated an app load, no unique data is sent.
Expand Down
61 changes: 61 additions & 0 deletions scripts/accounts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Contact } from './contacts-book';

Check warning on line 1 in scripts/accounts.js

View workflow job for this annotation

GitHub Actions / ESLint

scripts/accounts.js#L1

'Contact' is defined but never used (@typescript-eslint/no-unused-vars)

/**
* A local Account, containing sensitive user-data
*/
export class Account {
/**
* Create an Account.
* @param {Object} accountData - The account data.
* @param {String} accountData.publicKey - The public key.
* @param {String} [accountData.encWif] - The encrypted WIF.
* @param {Array<Object>} [accountData.localProposals] - The local proposals.
* @param {Array<Contact>} [accountData.contacts] - The Contacts saved in this account.
* @param {String} [account.name] - The Contact Name of the account.
*/
constructor(accountData) {
// Keys take the Constructor as priority, but if missing, default to their "Type" in empty form for type-safety
this.publicKey = accountData?.publicKey || '';
this.encWif = accountData?.encWif || '';
this.localProposals = accountData?.localProposals || [];
this.contacts = accountData?.contacts || [];
this.name = accountData?.name || '';
}

/** @type {String} The public key. */
publicKey = '';

/** @type {String} The encrypted WIF. */
encWif = '';

/** @type {Array<Object>} The local proposals. */
localProposals = [];

/** @type {Array<Contact>} The Contacts saved in this account. */
contacts = [];

/** @type {String} The Contact Name of the account. */
name = '';

/**
* Search for a Contact in this account, by specific properties
* @param {Object} settings
* @param {string?} settings.name - The Name of the contact to search for
* @param {string?} settings.pubkey - The Pubkey of the contact to search for
* @returns {Contact?} - A Contact, if found
*/
getContactBy({ name, pubkey }) {
if (!name && !pubkey)
throw Error(
'getContactBy(): At least ONE search parameter MUST be set!'
);

// Get by Name
if (name) return this.contacts.find((a) => a.label === name);
// Get by Pubkey
if (pubkey) return this.contacts.find((a) => a.pubkey === pubkey);

// Nothing found
return null;
}
}
102 changes: 25 additions & 77 deletions scripts/contacts-book.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Buffer } from 'buffer';
import { Account } from './accounts';

Check warning on line 2 in scripts/contacts-book.js

View workflow job for this annotation

GitHub Actions / ESLint

scripts/contacts-book.js#L2

'Account' is defined but never used (@typescript-eslint/no-unused-vars)
import { Database } from './database';
import { doms, toClipboard } from './global';
import { ALERTS, translation } from './i18n';
Expand Down Expand Up @@ -56,79 +57,41 @@ export class Contact {

/**
* Add a Contact to an Account's contact list
* @param {{publicKey: String, encWif: String?, localProposals: Array<any>, contacts: Array<Contact>}} account - The account to add the Contact to
* @param {Account} account - The account to add the Contact to
* @param {Contact} contact - The contact object
*/
export async function addContact(account, contact) {
// TODO: once multi-account is added, ensure this function adds the contact to the correct account (by pubkey)
const cDB = await Database.getInstance();

// Push contact in to the account
const arrContacts = account?.contacts || [];
arrContacts.push(contact);
account.contacts.push(contact);

// Save to the DB
await cDB.addAccount({
publicKey: account.publicKey,
encWif: account.encWif,
localProposals: account?.localProposals || [],
contacts: arrContacts,
name: account?.name || '',
});
}

/**
* Search for a Contact in a given Account, by specific properties
* @param {{publicKey: String, encWif: String?, localProposals: Array<any>, contacts: Array<Contact>}} account - The account to search for a Contact
* @param {Object} settings
* @param {string?} settings.name - The Name of the contact to search for
* @param {string?} settings.pubkey - The Pubkey of the contact to search for
* @returns {Contact?} - A Contact, if found
*/
export function getContactBy(account, { name, pubkey }) {
if (!name && !pubkey)
throw Error(
'getContactBy(): At least ONE search parameter MUST be set!'
);
const arrContacts = account?.contacts || [];

// Get by Name
if (name) return arrContacts.find((a) => a.label === name);
// Get by Pubkey
if (pubkey) return arrContacts.find((a) => a.pubkey === pubkey);

// Nothing found
return null;
await cDB.updateAccount(account);
}

/**
* Remove a Contact from an Account's contact list
* @param {{publicKey: String, encWif: String?, localProposals: Array<any>, contacts: Array<Contact>}} account - The account to remove the Contact from
* @param {Account} account - The account to remove the Contact from
* @param {string} pubkey - The contact pubkey
*/
export async function removeContact(account, pubkey) {
// TODO: once multi-account is added, ensure this function adds the contact to the correct account (by pubkey)
const cDB = await Database.getInstance();

// Find the contact by index, if it exists; splice it away
const arrContacts = account?.contacts || [];
const nIndex = arrContacts.findIndex((a) => a.pubkey === pubkey);
const nIndex = account.contacts.findIndex((a) => a.pubkey === pubkey);
if (nIndex > -1) {
// Splice out the contact, and save to DB
arrContacts.splice(nIndex, 1);
await cDB.addAccount({
publicKey: account.publicKey,
encWif: account.encWif,
localProposals: account?.localProposals || [],
contacts: account?.contacts || [],
name: account?.name || '',
});
account.contacts.splice(nIndex, 1);
await cDB.updateAccount(account, true);
}
}

/**
* Render an Account's contact list
* @param {{publicKey: String, encWif: String?, localProposals: Array<any>, contacts: Array<Contact>}} account
* @param {Account} account
* @param {boolean} fPrompt - If this is a Contact Selection prompt
*/
export async function renderContacts(account, fPrompt = false) {
Expand Down Expand Up @@ -348,20 +311,17 @@ export async function guiRenderContacts() {

/**
* Set the current Account's Contact name
* @param {{publicKey: String, encWif: String?, localProposals: Array<any>, contacts: Array<Contact>, name: String?}} account - The account to add the new Name to
* @param {Account} account - The account to add the new Name to
* @param {String} name - The name to set
*/
export async function setAccountContactName(account, name) {
const cDB = await Database.getInstance();

// Set the name
account.name = name;

// Save name to the DB
await cDB.addAccount({
publicKey: account.publicKey,
encWif: account.encWif,
localProposals: account?.localProposals || [],
contacts: account?.contacts || [],
name: name,
});
await cDB.updateAccount(account);
}

/**
Expand Down Expand Up @@ -597,8 +557,8 @@ export async function guiAddContact() {
const cAccount = await cDB.getAccount();

// Check this Contact isn't already saved, either fully or partially
const cContactByName = getContactBy(cAccount, { name: strName });
const cContactByPubkey = getContactBy(cAccount, { pubkey: strAddr });
const cContactByName = cAccount.getContactBy({ name: strName });
const cContactByPubkey = cAccount.getContactBy({ pubkey: strAddr });

// If both Name and Key are saved, then they just tried re-adding the same Contact twice
if (cContactByName && cContactByPubkey) {
Expand Down Expand Up @@ -698,8 +658,8 @@ export async function guiAddContactPrompt(
const cAccount = await cDB.getAccount();

// Check this Contact isn't already saved, either fully or partially
const cContactByName = getContactBy(cAccount, { name: strName });
const cContactByPubkey = getContactBy(cAccount, { pubkey: strPubkey });
const cContactByName = cAccount.getContactBy({ name: strName });
const cContactByPubkey = cAccount.getContactBy({ pubkey: strPubkey });

// If both Name and Key are saved, then they just tried re-adding the same Contact twice
if (cContactByName && cContactByPubkey) {
Expand Down Expand Up @@ -813,7 +773,7 @@ export async function guiEditContactNamePrompt(nIndex) {
}

// Check this new Name isn't already saved
const cContactByNewName = getContactBy(cAccount, { name: strNewName });
const cContactByNewName = cAccount.getContactBy({ name: strNewName });
if (cContactByNewName) {
createAlert(
'warning',
Expand All @@ -828,13 +788,7 @@ export async function guiEditContactNamePrompt(nIndex) {
cContact.label = strNewName;

// Commit to DB
await cDB.addAccount({
publicKey: cAccount.publicKey,
encWif: cAccount.encWif,
localProposals: cAccount?.localProposals || [],
contacts: cAccount?.contacts || [],
name: cAccount?.name,
});
await cDB.updateAccount(cAccount);

// Re-render the Contacts UI
await renderContacts(cAccount);
Expand Down Expand Up @@ -863,13 +817,7 @@ export async function guiAddContactImage(nIndex) {
cContact.icon = strImage;

// Commit to DB
await cDB.addAccount({
publicKey: cAccount.publicKey,
encWif: cAccount.encWif,
localProposals: cAccount?.localProposals || [],
contacts: cAccount?.contacts || [],
name: cAccount?.name,
});
await cDB.updateAccount(cAccount);

// Re-render the Contacts UI
await renderContacts(cAccount);
Expand Down Expand Up @@ -1003,7 +951,7 @@ export async function guiCheckRecipientInput(event) {
const cAccount = await cDB.getAccount();

// Check if this is a Contact
const cContact = getContactBy(cAccount, {
const cContact = cAccount?.getContactBy({
name: strInput,
pubkey: strInput,
});
Expand All @@ -1024,7 +972,7 @@ export async function guiCheckRecipientInput(event) {

/**
* Search for a Name of a Contact from a given Account and Address
* @param {object} cAccount - The Account to search for the Contact
* @param {Account} cAccount - The Account to search for the Contact
* @param {string} address - The address to search for a Contact with
* @returns {string} - The Name of the address Contact, or just the address if none is found
*/
Expand All @@ -1036,8 +984,8 @@ export function getNameOrAddress(cAccount, address) {

/**
* Convert the current Account's Contact to a Share URI
* @param {object?} - An optional Account to construct the Contact URI from, if omitted, the current DB account is used
* @param {string?} - An optional Master Public Key to attach to the Contact URI
* @param {Account?} account - An optional Account to construct the Contact URI from, if omitted, the current DB account is used
* @param {string?} pubkey - An optional Master Public Key to attach to the Contact URI
*/
export async function localContactToURI(account, pubkey) {
// Fetch the current Account
Expand Down
Loading

0 comments on commit 97ffeab

Please sign in to comment.