From 6c18b2841de5ebee0bfeb76b08ae90b3883248d1 Mon Sep 17 00:00:00 2001 From: Andrei O Date: Thu, 13 Oct 2022 23:48:07 +0300 Subject: [PATCH] dev: 1.0.3 --- src/App.vue | 6 ++ src/extension/content.ts | 22 ++++- src/extension/inject.ts | 30 +++--- src/extension/listners.ts | 7 ++ src/extension/serviceWorker.ts | 38 +++++++- src/extension/types.ts | 13 ++- src/router/index.ts | 8 ++ src/utils/misc.ts | 11 +++ src/utils/platform.ts | 17 +++- src/utils/wallet.ts | 1 + src/utils/webCrypto.ts | 25 +++-- src/views/AccountsTab.vue | 20 +++- src/views/AddAccount.vue | 49 ++++++++-- src/views/AddNetwork.vue | 10 +- src/views/AssetsTab.vue | 40 +++++++- src/views/HistoryTab.vue | 20 +++- src/views/HomeTab.vue | 12 ++- src/views/NetworksTab.vue | 15 ++- src/views/SettingsTab.vue | 161 ++++++++++++++++++++++++++++----- src/views/UnlockModal.vue | 5 +- src/views/WalletError.vue | 93 +++++++++++++++++++ 21 files changed, 519 insertions(+), 84 deletions(-) create mode 100644 src/extension/listners.ts create mode 100644 src/utils/misc.ts create mode 100644 src/views/WalletError.vue diff --git a/src/App.vue b/src/App.vue index 5694409..f283c2c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -41,6 +41,12 @@ switch (route?.query?.route ?? "") { }); break; } + case "wallet-error": { + router.push({ + path: `/wallet-error"/${rid}/${param}` + }); + break; + } default: { router.push({ path: "/", }) } diff --git a/src/extension/content.ts b/src/extension/content.ts index 74b551e..63b209f 100644 --- a/src/extension/content.ts +++ b/src/extension/content.ts @@ -1,3 +1,4 @@ +import { getSelectedNetwork, numToHexStr } from "@/utils/platform"; const allowedMethods = { 'eth_accounts': true, @@ -19,18 +20,33 @@ window.addEventListener("message", (event) => { if (event.source != window) return; - if (event.data.type && (event.data.type == "CLWALLET_CONTENT")) { + if (event.data.type && (event.data.type === "CLWALLET_CONTENT")) { event.data.data.resId = event.data.resId if((event?.data?.data?.method ?? 'x') in allowedMethods) { chrome.runtime.sendMessage(event.data.data, (res) => { - const data = { type: "CLWALLET_PAGE", data: res, resId: event.data.resId }; + const data = { type: "CLWALLET_PAGE", data: res, resId: event.data.resId, website: window?.location?.href ?? '' }; console.log('data back', data) window.postMessage(data, "*"); }) - } else { + } + else { const data = { type: "CLWALLET_PAGE", data: { error: true, message: 'Unknown method requested'}, resId: event.data.resId }; window.postMessage(data, "*"); } + } else if (event.data.type && (event.data.type === "CLWALLET_PING")) { + getSelectedNetwork().then(network => { + const data = { type: "CLWALLET_PAGE_LISTENER", data: { + listener: 'connected', + data: { + chainId: numToHexStr(network.chainId ?? 0) + } + }}; + window.postMessage(data, "*"); + }) + } else if (event.data.type && (event.data.type === "CLWALLET_EXT_LISTNER")) { + const data = { type: "CLWALLET_PAGE_LISTENER", data: event.data.data, }; + console.log('data listner', data) + window.postMessage(data, "*"); } }); diff --git a/src/extension/inject.ts b/src/extension/inject.ts index 282d717..32db4d9 100644 --- a/src/extension/inject.ts +++ b/src/extension/inject.ts @@ -37,11 +37,14 @@ const listner = function(event: any) { window.addEventListener("message",listner) -const sendMessage = (args: RequestArguments) => { +const sendMessage = (args: RequestArguments, ping = false) => { return new Promise((resolve, reject) => { const resId = crypto.randomUUID() promResolvers[resId] = { resolve, reject } const data = { type: "CLWALLET_CONTENT", data: args, resId}; + if (ping) { + data.type = 'CLWALLET_PING' + } console.log('data in', data) window.postMessage(data, "*"); }) @@ -200,17 +203,22 @@ const injectWallet = (win: any) => { console.log('Clear wallet injected', (window as any).ethereum, win) } injectWallet(this) +sendMessage({ + method: 'wallet_ready' +}, true) - -// setTimeout(() => { -// console.log('Metamask clone test'); -// // (window).ethereum.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')}); -// // (window).ethereum.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')}); -// // (window).ethereum.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, '111111111')}); -// // (window).ethereum.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, '111111111')}); -// // (window).ethereum.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, '111111111')}); -// // (window).ethereum.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x99"}]}).then((res: any) => { console.log(res, '111111111')}); -// }, 3500) +setTimeout(() => { + console.log('Metamask clone test'); + // (window).ethereum.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')}); + // (window).ethereum.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')}); + // (window).ethereum.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, '111111111')}); + // (window).ethereum.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, '111111111')}); + // (window).ethereum.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, '111111111')}); + // (window).ethereum.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x99"}]}).then((res: any) => { console.log(res, '111111111')}); + (window).ethereum.on('connect', ((a: any, b: any) => console.log('connect', a, b))); + (window).ethereum.on('accountsChanged', ((a: any, b: any) => console.log('accountsChanged', a, b))); + (window).ethereum.on('chainChanged', ((a: any, b: any) => console.log('chainChanged', a, typeof a))); +}, 3500) // console.log( (window as any).ethereum.request({method: 'eth_chainId'})) \ No newline at end of file diff --git a/src/extension/listners.ts b/src/extension/listners.ts new file mode 100644 index 0000000..6acc54e --- /dev/null +++ b/src/extension/listners.ts @@ -0,0 +1,7 @@ +import type { listnerType } from '@/extension/types' + +export const triggerListner = ( type: listnerType, listnerData: any ) => { + const data = { type: "CLWALLET_EXT_LISTNER", data: { listner: type, data: listnerData } } + window.postMessage(data, "*") + console.log('trigger', data) +} diff --git a/src/extension/serviceWorker.ts b/src/extension/serviceWorker.ts index 792d1d4..34aa672 100644 --- a/src/extension/serviceWorker.ts +++ b/src/extension/serviceWorker.ts @@ -1,4 +1,4 @@ -import { getAccounts, getSelectedAccount, getSelectedNetwork, smallRandomString, getSettings, clearPk, openTab, getUrl } from '@/utils/platform'; +import { getAccounts, getSelectedAccount, getSelectedNetwork, smallRandomString, getSettings, clearPk, openTab, getUrl, addToHistory } from '@/utils/platform'; import { userApprove, userReject, rIdWin, rIdData } from '@/extension/userRequest' import { signMsg, getBalance, getBlockNumber, estimateGas, sendTransaction, getGasPrice, getBlockByNumber } from '@/utils/wallet' import type { RequestArguments } from '@/extension/types' @@ -164,7 +164,22 @@ chrome.runtime.onMessage.addListener((message: RequestArguments, sender, sendRes break } const [account, network] = await Promise.all([getSelectedAccount(), getSelectedNetwork()]) - if(!account || !network) { + if(!account || !('address' in account)) { + await chrome.windows.create({ + height: 450, + width: 400, + url: chrome.runtime.getURL(`index.html?route=sign-tx¶m=${encodeURIComponent('No account is selected you need to have an account selected before trying to make a transaction')}&rid=${String(message?.resId ?? '')}`), + type: 'popup' + }) + return + } + if(!network || !('chainId' in network)) { + await chrome.windows.create({ + height: 450, + width: 400, + url: chrome.runtime.getURL(`index.html?route=sign-tx¶m=${encodeURIComponent('No network is selected you need to have a network selected before trying to make a transaction')}&rid=${String(message?.resId ?? '')}`), + type: 'popup' + }) return } params.from = account.address @@ -197,6 +212,13 @@ chrome.runtime.onMessage.addListener((message: RequestArguments, sender, sendRes sendResponse(tx) const buttons = {} as any const network = await getSelectedNetwork() + addToHistory({ + date: Date.now(), + txHash: tx.hash, + chainId: network.chainId, + ...(network.explorer ? {txUrl: `${network.explorer}/tx/${tx.hash}`.replace('//', '/') } : {}), + webiste: (message?.website) + }) const notificationId = crypto.randomUUID() if(network?.explorer) { notificationUrl = `${network.explorer}/tx/${tx.hash}`.replace('//', '/') @@ -246,6 +268,18 @@ chrome.runtime.onMessage.addListener((message: RequestArguments, sender, sendRes case ('personal_sign' || 'eth_sign'): { try { + const account = await getSelectedAccount() + + if(!account || !('address' in account)) { + await chrome.windows.create({ + height: 450, + width: 400, + url: chrome.runtime.getURL(`index.html?route=sign-tx¶m=${encodeURIComponent('No account is selected you need to have an account selected before trying sign a message')}&rid=${String(message?.resId ?? '')}`), + type: 'popup' + }) + return + } + await new Promise((resolve, reject) => { chrome.windows.create({ height: 450, diff --git a/src/extension/types.ts b/src/extension/types.ts index ac62ce0..2e4f274 100644 --- a/src/extension/types.ts +++ b/src/extension/types.ts @@ -27,6 +27,7 @@ export interface RequestArguments { method: string; params?: any[]; resId?: string + website?: string } export interface ProviderRpcError extends Error { @@ -51,4 +52,14 @@ export interface Settings { theme: 'system' | 'light' | 'dark' lastLock: number lockOutBlocked: boolean -} \ No newline at end of file +} + +export type listnerType = 'accountsChanged' | 'connect' | 'disconnect' | 'chainChanged' + +export interface HistoryItem { + date: number + txUrl?: string + chainId?: number + webiste?: string + txHash: string +} diff --git a/src/router/index.ts b/src/router/index.ts index 314c72c..bfb41dd 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -23,6 +23,10 @@ const routes: Array = [ path: '/contract-error/:rid/:param/:contract', component: () => import('@/views/ContractError.vue'), }, + { + path: '/wallet-error/:rid/:error', + component: () => import('@/views/WalletError.vue'), + }, { path: '/tabs/', component: AppTabs, @@ -59,6 +63,10 @@ const routes: Array = [ path: 'add-account', component: () => import('@/views/AddAccount.vue'), }, + { + path: 'add-account/edit/:address', + component: () => import('@/views/AddAccount.vue'), + }, { path: 'add-network', component: () => import('@/views/AddNetwork.vue'), diff --git a/src/utils/misc.ts b/src/utils/misc.ts new file mode 100644 index 0000000..4c503a6 --- /dev/null +++ b/src/utils/misc.ts @@ -0,0 +1,11 @@ +export const exportFile = (fileName: string, content: string, type = 'json') => { + const link = document.createElement('a') + const blob = new Blob([content], { type: `text/${type};charset=utf-8;` }) + const url = URL.createObjectURL(blob) + link.setAttribute('href', url) + link.setAttribute('download', fileName) + link.style.visibility = 'hidden' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } \ No newline at end of file diff --git a/src/utils/platform.ts b/src/utils/platform.ts index 434e690..5f34e40 100644 --- a/src/utils/platform.ts +++ b/src/utils/platform.ts @@ -1,4 +1,4 @@ -import type { Network, Account, Prices, Settings, Networks } from '@/extension/types' +import type { Network, Account, Prices, Settings, Networks, HistoryItem } from '@/extension/types' import type { Ref } from 'vue' const defaultSettings = { @@ -79,6 +79,21 @@ export const getPrices = async (): Promise => { return (await storageGet('prices'))?.prices ?? {} as unknown as Prices } +export const getHistory = async (): Promise => { + return (await storageGet('history'))?.history ?? [] as unknown as Prices +} + +export const addToHistory = async (historyItem: HistoryItem): Promise => { + const history = await getHistory() + if (history.length >= 100) { + history.pop() + history.unshift(historyItem) + } else { + history.unshift(historyItem) + } + await storageSave('history', history) +} + export const getSettings = async (): Promise => { return (await storageGet('settings'))?.settings ?? defaultSettings as unknown as Settings } diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 18a0675..7b52324 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -38,6 +38,7 @@ export const estimateGas = async ({to = '', from = '', data = '', value = '0x0' return await provider.estimateGas({to, from, data, value}) } + export const sendTransaction = async ({ data= '', gas='0x0', to='', from='', value='0x0', gasPrice='0x0'}: {to: string, from: string, data: string, value: string, gas: string, gasPrice: string}, gasEstimate: Promise | null = null, pGasPrice : Promise | null) => { diff --git a/src/utils/webCrypto.ts b/src/utils/webCrypto.ts index 48bd52c..fa9234c 100644 --- a/src/utils/webCrypto.ts +++ b/src/utils/webCrypto.ts @@ -48,31 +48,30 @@ async function getKey(passwordBytes: Uint8Array) { ); } -export const encrypt = async (password: string, data:string) => { +export const getCryptoParams = async(password: string): Promise<{ key: CryptoKey, iv: any }> => { + const enc = new TextEncoder() + const encKey = enc.encode(password) + return { key: await getKey(encKey), iv:await getIv() } +} + +export const encrypt = async (data: string, cryptoParams: { key: CryptoKey, iv: any }) => { const enc = new TextEncoder() const encData = enc.encode(data) - const encKey = enc.encode(password) - const key = await getKey(encKey) - const iv = await getIv() const encResult = await crypto.subtle.encrypt( { name: "AES-GCM", - iv, + iv: cryptoParams.iv, }, - key, + cryptoParams.key, encData, ) return JSON.stringify(new Uint8Array(encResult)) } -export const decrypt = async (encryptedData: string, password: string) => { - const enc = new TextEncoder() - const encKey = enc.encode(password) - const key = await getKey(encKey) - const iv = await getIv() - const encryptedUint= new Uint8Array(Object.values(JSON.parse(encryptedData))); +export const decrypt = async (encryptedData: string, cryptoParams: { key: CryptoKey, iv: any }) => { + const encryptedUint = new Uint8Array(Object.values(JSON.parse(encryptedData))); const contentBytes = new Uint8Array( - await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, encryptedUint) + await crypto.subtle.decrypt({ name: "AES-GCM", iv:cryptoParams.iv }, cryptoParams.key, encryptedUint) ); return new TextDecoder().decode(contentBytes) } \ No newline at end of file diff --git a/src/views/AccountsTab.vue b/src/views/AccountsTab.vue index f8c1137..295d841 100644 --- a/src/views/AccountsTab.vue +++ b/src/views/AccountsTab.vue @@ -20,6 +20,10 @@ message="Copied to clipboard" :duration="1500" > + + No EVM accounts found + Add Account + @@ -32,7 +36,7 @@ View Pk Delete - Edit Name + Edit Name @@ -60,6 +64,7 @@ import { } from "@ionic/vue"; import { addCircleOutline, copyOutline } from "ionicons/icons"; +import router from "@/router"; import type { Account } from '@/extension/types' export default defineComponent({ @@ -102,10 +107,15 @@ export default defineComponent({ await replaceAccounts([...accounts.value]) loading.value = false } - const editName = async (name: string) => { - // do nothing + + const editAccount = (address: string) => { + router.push(`add-account/edit/${address}`) } + const goToAddAccount = () => { + router.push("/tabs/add-account"); + }; + onIonViewWillEnter(() => { loadData() }) @@ -118,7 +128,9 @@ export default defineComponent({ copyAddress, getToastRef, deleteAccount, - editName + editAccount, + loading, + goToAddAccount } } diff --git a/src/views/AddAccount.vue b/src/views/AddAccount.vue index da14dac..0cb797f 100644 --- a/src/views/AddAccount.vue +++ b/src/views/AddAccount.vue @@ -2,7 +2,8 @@ - Add Account + Add Account + Edit Account @@ -15,11 +16,12 @@ Get Random Name Generate - - PK + + + PK - + Get Random PK Generate @@ -40,10 +42,12 @@ diff --git a/src/views/HistoryTab.vue b/src/views/HistoryTab.vue index d635f6f..d84818f 100644 --- a/src/views/HistoryTab.vue +++ b/src/views/HistoryTab.vue @@ -6,15 +6,29 @@ - Schedule Tab + Not implemented diff --git a/src/views/HomeTab.vue b/src/views/HomeTab.vue index e1e1574..a06b0e1 100644 --- a/src/views/HomeTab.vue +++ b/src/views/HomeTab.vue @@ -15,7 +15,7 @@ Selected Account: {{ selectedAccount?.name }} Select - +

{{ selectedAccount?.address }}

@@ -67,7 +67,7 @@ - + Accounts @@ -168,10 +168,12 @@ import { replaceNetworks, getUrl, saveSelectedNetwork, + numToHexStr } from "@/utils/platform"; import type { Network, Account, Networks } from "@/extension/types"; import { mainNets } from "@/utils/networks"; import router from "@/router"; +import { triggerListner } from '@/extension/listners' import { copyOutline } from "ionicons/icons"; @@ -251,8 +253,9 @@ export default defineComponent({ // console.log(({ [address]: accounts.value[address], ...accounts.value})) accounts.value.splice(findIndex, 1); accounts.value.splice(0,0,selectedAccount.value) - await replaceAccounts([...accounts.value]) - + const newAccounts = [...accounts.value] + await replaceAccounts(newAccounts) + triggerListner('accountsChanged', newAccounts.map(a => a.address)) } accountsModal.value = false; loading.value = false; @@ -266,6 +269,7 @@ export default defineComponent({ Object.assign({ [chainId]: networks.value[chainId] }, networks.value) ); selectedNetwork.value = networks.value[chainId]; + triggerListner('chainChanged', numToHexStr(chainId)) } networksModal.value = false; loading.value = false; diff --git a/src/views/NetworksTab.vue b/src/views/NetworksTab.vue index 0a7ed0e..4b334fd 100644 --- a/src/views/NetworksTab.vue +++ b/src/views/NetworksTab.vue @@ -14,6 +14,11 @@ + + No EVM Networks found + Add Network + + @@ -101,6 +106,12 @@ export default defineComponent({ router.push(`add-network/edit/${chainId}`) } + + const goToAddNetwork = () => { + router.push("/tabs/add-network"); + }; + + onIonViewWillEnter(() => { loadData() }) @@ -115,7 +126,9 @@ export default defineComponent({ getUrl, mainNets, deleteNetwork, - editNetwork + editNetwork, + loading, + goToAddNetwork } } diff --git a/src/views/SettingsTab.vue b/src/views/SettingsTab.vue index 676cbd7..6ef5cd4 100644 --- a/src/views/SettingsTab.vue +++ b/src/views/SettingsTab.vue @@ -13,7 +13,8 @@
- + You need at least one account to touch this settings + Enable Storage Encryption @@ -24,16 +25,16 @@ Enable Auto Lock - + - + Auto-lock Period: (2-120) minutes - + - + Set Auto-lock @@ -136,9 +137,10 @@ - Close + Close - Create Encryption Password + Create Encryption Password + Enter Encryption Password @@ -166,13 +168,13 @@
- Confirm + Confirm
import { defineComponent, ref, reactive, Ref } from "vue"; import { storageWipe, getSettings, setSettings, getAccounts, saveSelectedAccount, replaceAccounts } from "@/utils/platform"; -import { decrypt, encrypt } from "@/utils/webCrypto" -// import { Account } from '@/extension/type' +import { decrypt, encrypt, getCryptoParams } from "@/utils/webCrypto" +import { Account } from '@/extension/types' +import { exportFile } from '@/utils/misc' import type { Settings } from "@/extension/types" import { IonContent, @@ -244,7 +247,11 @@ export default defineComponent({ const alertMsg = ref(''); const toastState = ref(false); const toastMsg = ref(''); + const alertHeader = ref('Error') const importFile = ref(null) as unknown as Ref + type ModalPromisePassword = null | { resolve: ((p?: unknown) => void), reject: ((p?: unknown) => void)} + const modalGetPassword = ref(null) as Ref + const noAccounts = ref(true) const wipeStorage = async () => { loading.value = true; @@ -266,6 +273,12 @@ export default defineComponent({ updateKey.value++ } + const changeAutoLock = async () => { + settings.s.lockOutEnabled = !settings.s.lockOutEnabled + updateKey.value++ + await saveSettings() + } + const changeEncryption = async () => { loading.value = true mpModal.value = true @@ -276,6 +289,7 @@ export default defineComponent({ loading.value = true if(mpPass.value.length < 3) { loading.value = false + alertHeader.value = 'Error' alertMsg.value = 'Password is too short. More than 3 characters are required.'; alertOpen.value = true setEncryptToggle(settings.s.enableStorageEnctyption) @@ -285,14 +299,16 @@ export default defineComponent({ if (!settings.s.enableStorageEnctyption) { if (mpPass.value !== mpConfirm.value) { loading.value = false + alertHeader.value = 'Error' alertMsg.value = 'Password and confirm password do not match'; alertOpen.value = true setEncryptToggle(settings.s.enableStorageEnctyption) return } let accounts = await getAccounts() + const cryptoParams = await getCryptoParams(mpPass.value) const accProm = accounts.map(async a => { - a.encPk = await encrypt(mpPass.value, a.pk) + a.encPk = await encrypt(a.pk, cryptoParams) a.pk = '' return a }) @@ -307,12 +323,14 @@ export default defineComponent({ } else { try { let accounts = await getAccounts() + const cryptoParams = await getCryptoParams(mpPass.value) const accProm = accounts.map(async a => { if(a.encPk) { - a.pk = await decrypt(a.encPk, mpPass.value) + a.pk = await decrypt(a.encPk, cryptoParams) } return a }) + accProm.forEach( a => a.catch(e => console.log(e)) ) accounts = await Promise.all(accProm) await replaceAccounts(accounts) await saveSelectedAccount(accounts[0]) @@ -323,8 +341,10 @@ export default defineComponent({ mpPass.value = '' mpConfirm.value = '' mpModal.value = false - } catch { + } catch(error) { + console.log(error) loading.value = false + alertHeader.value = 'Error' alertMsg.value = 'Decryption failed, password is not correct.'; alertOpen.value = true setEncryptToggle(settings.s.enableStorageEnctyption) @@ -348,13 +368,13 @@ export default defineComponent({ reader.onload = (event) => { const json = JSON.parse(event?.target?.result as string) if(!json.length){ - return resolve({ error: 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key" },{...}]' }) + return resolve({ error: 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}]' }) } - const test = json.some((e:any) => ( !('pk' in e ) || !('name' in e) || !(e.pk.length !== 66 && e.pk.length !== 64))) + const test = json.some((e:any) => ( !('pk' in e ) || !('name' in e) || !('address' in e) || !(e.pk.length === 66 || e.pk.length === 64))) if(test) { - return resolve({ error: 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key" },{...}], Also PK must be valid' }) + return resolve({ error: 'JSON format is wrong. Corrrect JSON format is: [{ "name": "Account Name", "pk": "Private Key", "address": "0x..." },{...}], Also PK must be valid (66 || 64 length) !' }) } - return resolve({ error: false }) + return resolve({ error: false, json }) } reader.readAsText(importFile.value?.files?.[0] as File); @@ -366,6 +386,42 @@ export default defineComponent({ } }) } + + const getPassword = () => { + return new Promise( (resolve, reject) => { + modalGetPassword.value = { resolve, reject } + mpModal.value = true + }) + } + + const promptForPassword = async (accounts: Account[]) => { + let isCorectPass = false + do { + try { + await getPassword() + modalGetPassword.value = null + } catch { + alertHeader.value = 'Error' + alertMsg.value = "Password is required!" + alertOpen.value = true + mpModal.value = false + return false + } + try { + const cryptoParams = await getCryptoParams(mpPass.value) + if(accounts?.[0]?.encPk) { + await decrypt(accounts[0].encPk, cryptoParams) + } + isCorectPass = true + } catch { + isCorectPass = false + alertHeader.value = 'Error' + alertMsg.value = "Password is wrong!" + alertOpen.value = true + } + } while (!isCorectPass); + return true + } const importAcc = async () => { const validation = await validateFile() as { error: any } @@ -374,20 +430,71 @@ export default defineComponent({ alertOpen.value = true return } + const accounts = await getAccounts() + const newAccounts = (validation as unknown as { json: Account[] }).json + if(settings.s.enableStorageEnctyption) { + const hasPass = await promptForPassword(accounts) + if(hasPass) { + const cryptoParams = await getCryptoParams(mpPass.value) + const accProm = newAccounts.map(async a => { + if(a.pk.length === 64) { + a.pk = `0x${a.pk}` + } + a.encPk = await encrypt(a.pk, cryptoParams) + return a + }) + const encNewAccounts = await Promise.all(accProm) + await replaceAccounts([...accounts, ...encNewAccounts]) + alertHeader.value = 'Success' + alertMsg.value = "Successfully imported new accounts." + alertOpen.value = true + noAccounts.value = false + } + return false + } else { + await replaceAccounts([...accounts, ...newAccounts.map( a => { a.encPk = ''; return a })]) + alertHeader.value = 'Success' + alertMsg.value = "Successfully imported new accounts." + alertOpen.value = true + noAccounts.value = false + } } const exportAcc = async () => { - // + const accounts = await getAccounts() + if(!accounts.length) { + alertMsg.value = "You need at least one account to export." + alertOpen.value = true + } + if(settings.s.enableStorageEnctyption) { + const hasPass = await promptForPassword(accounts) + if(hasPass) { + const cryptoParams = await getCryptoParams(mpPass.value) + const accProm = accounts.map(async a => { + a.pk = await decrypt(a.encPk, cryptoParams) + return a + }) + const encNewAccounts = await Promise.all(accProm) + exportFile('wallet_export.json', JSON.stringify(encNewAccounts, null, 2)) + } + return false + } else { + exportFile('wallet_export.json', JSON.stringify(accounts, null, 2)) + } } - onIonViewWillEnter( () => { - getSettings().then((storeSettings) => + onIonViewWillEnter(async () => { + await Promise.all([getSettings().then((storeSettings) => { settings.s = storeSettings - loading.value = false - }) - + }), + getAccounts().then((accounts) => { + if(accounts.length) { + noAccounts.value = false + } + })]) + loading.value = false }) const setTime = async () => { @@ -428,7 +535,11 @@ export default defineComponent({ toastMsg, importAcc, exportAcc, - importFile + importFile, + modalGetPassword, + noAccounts, + alertHeader, + changeAutoLock }; }, }); diff --git a/src/views/UnlockModal.vue b/src/views/UnlockModal.vue index 5bb2ee3..77170b2 100644 --- a/src/views/UnlockModal.vue +++ b/src/views/UnlockModal.vue @@ -69,7 +69,7 @@ import { replaceAccounts, saveSelectedAccount } from "@/utils/platform"; -import { decrypt } from "@/utils/webCrypto" +import { decrypt, getCryptoParams } from "@/utils/webCrypto" export default defineComponent({ props: { @@ -107,8 +107,9 @@ export default defineComponent({ try { loading.value = true let accounts = await getAccounts() + const cryptoParams = await getCryptoParams(mpPass.value) const accProm = accounts.map(async a => { - a.pk = await decrypt(a.encPk, mpPass.value) + a.pk = await decrypt(a.encPk, cryptoParams) return a }) accounts = await Promise.all(accProm) diff --git a/src/views/WalletError.vue b/src/views/WalletError.vue new file mode 100644 index 0000000..9bfc980 --- /dev/null +++ b/src/views/WalletError.vue @@ -0,0 +1,93 @@ + + +