mirror of
https://github.com/andrei0x309/clear-wallet.git
synced 2024-11-18 23:41:10 +00:00
dev: 1.0.3
This commit is contained in:
parent
1a4b531ae2
commit
6c18b2841d
@ -41,6 +41,12 @@ switch (route?.query?.route ?? "") {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "wallet-error": {
|
||||
router.push({
|
||||
path: `/wallet-error"/${rid}/${param}`
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
router.push({ path: "/", })
|
||||
}
|
||||
|
@ -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, "*");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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');
|
||||
// // (<any>window).ethereum.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, '111111111')});
|
||||
// // (<any>window).ethereum.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x99"}]}).then((res: any) => { console.log(res, '111111111')});
|
||||
// }, 3500)
|
||||
setTimeout(() => {
|
||||
console.log('Metamask clone test');
|
||||
// (<any>window).ethereum.request({method: 'eth_requestAccounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// (<any>window).ethereum.request({method: 'eth_accounts', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// (<any>window).ethereum.request({method: 'eth_chainId', params: Array(0)}).then((res: any) => { console.log(res, '111111111')});
|
||||
// (<any>window).ethereum.request({method: 'wallet_requestPermissions', params: [{eth_accounts: {}}]}).then((res: any) => { console.log(res, '111111111')});
|
||||
// (<any>window).ethereum.request({method: 'net_version', params: []}).then((res: any) => { console.log(res, '111111111')});
|
||||
// (<any>window).ethereum.request({method: 'wallet_switchEthereumChain', params: [{chainId: "0x99"}]}).then((res: any) => { console.log(res, '111111111')});
|
||||
(<any>window).ethereum.on('connect', ((a: any, b: any) => console.log('connect', a, b)));
|
||||
(<any>window).ethereum.on('accountsChanged', ((a: any, b: any) => console.log('accountsChanged', a, b)));
|
||||
(<any>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'}))
|
7
src/extension/listners.ts
Normal file
7
src/extension/listners.ts
Normal file
@ -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)
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
export type listnerType = 'accountsChanged' | 'connect' | 'disconnect' | 'chainChanged'
|
||||
|
||||
export interface HistoryItem {
|
||||
date: number
|
||||
txUrl?: string
|
||||
chainId?: number
|
||||
webiste?: string
|
||||
txHash: string
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ const routes: Array<RouteRecordRaw> = [
|
||||
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<RouteRecordRaw> = [
|
||||
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'),
|
||||
|
11
src/utils/misc.ts
Normal file
11
src/utils/misc.ts
Normal file
@ -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)
|
||||
}
|
@ -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<Prices> => {
|
||||
return (await storageGet('prices'))?.prices ?? {} as unknown as Prices
|
||||
}
|
||||
|
||||
export const getHistory = async (): Promise<HistoryItem[]> => {
|
||||
return (await storageGet('history'))?.history ?? [] as unknown as Prices
|
||||
}
|
||||
|
||||
export const addToHistory = async (historyItem: HistoryItem): Promise<void> => {
|
||||
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<Settings> => {
|
||||
return (await storageGet('settings'))?.settings ?? defaultSettings as unknown as Settings
|
||||
}
|
||||
|
@ -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<BigNumber> | null = null, pGasPrice : Promise<BigNumber> | null) => {
|
||||
|
@ -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)
|
||||
}
|
@ -20,6 +20,10 @@
|
||||
message="Copied to clipboard"
|
||||
:duration="1500"
|
||||
></ion-toast>
|
||||
<ion-item v-if="loading || accounts.length < 1">
|
||||
<ion-label>No EVM accounts found</ion-label>
|
||||
<ion-button @click="goToAddAccount">Add Account</ion-button>
|
||||
</ion-item>
|
||||
<ion-list v-for="account of accounts" :key="account.address">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
@ -32,7 +36,7 @@
|
||||
<ion-item>
|
||||
<ion-chip>View Pk</ion-chip>
|
||||
<ion-chip @click="deleteAccount(account.address)">Delete</ion-chip>
|
||||
<ion-chip @click="editName(account.address)">Edit Name</ion-chip>
|
||||
<ion-chip @click="editAccount(account.address)">Edit Name</ion-chip>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,8 @@
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Add Account</ion-title>
|
||||
<ion-title v-if="!isEdit" >Add Account</ion-title>
|
||||
<ion-title v-else >Edit Account</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
@ -15,11 +16,12 @@
|
||||
<ion-label>Get Random Name</ion-label>
|
||||
<ion-button @click="getRandomName" >Generate</ion-button>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-icon style="margin-right: 0.5rem;" @click="paste('pasteRpc')" :icon="clipboardOutline" button /><ion-label>PK</ion-label>
|
||||
<ion-item v-if="!isEdit">
|
||||
<ion-icon style="margin-right: 0.5rem;" @click="paste('pastePk')" :icon="clipboardOutline" button/>
|
||||
<ion-label button>PK</ion-label>
|
||||
<ion-input id="pastePk" v-model="pk"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-item v-if="!isEdit">
|
||||
<ion-label>Get Random PK</ion-label>
|
||||
<ion-button @click="generateRandomPk" >Generate</ion-button>
|
||||
</ion-item>
|
||||
@ -40,10 +42,12 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonItem, IonLabel, IonInput, IonButton, IonAlert, IonIcon } from "@ionic/vue";
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonItem, IonLabel, IonInput, IonButton, IonAlert, IonIcon, onIonViewWillEnter } from "@ionic/vue";
|
||||
import { ethers } from "ethers"
|
||||
import { saveSelectedAccount, getAccounts, saveAccount, getRandomPk, smallRandomString, paste } from "@/utils/platform";
|
||||
import router from "@/router";
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { Account } from '@/extension/types'
|
||||
|
||||
import { clipboardOutline } from "ionicons/icons";
|
||||
|
||||
@ -54,12 +58,27 @@ export default defineComponent({
|
||||
const pk = ref('')
|
||||
const alertOpen = ref(false)
|
||||
const alertMsg = ref('')
|
||||
const route = useRoute()
|
||||
const isEdit = route.path.includes('/edit')
|
||||
const paramAddress = route.params.address ?? ""
|
||||
let accountsProm: Promise<Account[] | undefined>
|
||||
|
||||
const resetFields = () => {
|
||||
name.value = ''
|
||||
pk.value = ''
|
||||
}
|
||||
|
||||
onIonViewWillEnter(async () => {
|
||||
if(isEdit && paramAddress) {
|
||||
accountsProm = getAccounts()
|
||||
const accounts = await accountsProm as Account[]
|
||||
const acc = accounts.find(account => account.address === paramAddress)
|
||||
if(acc) {
|
||||
name.value = acc.name
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const onAddAccount = async () => {
|
||||
let p1 = Promise.resolve()
|
||||
if(pk.value.length === 64){
|
||||
@ -73,7 +92,10 @@ export default defineComponent({
|
||||
|
||||
|
||||
const wallet = new ethers.Wallet(pk.value)
|
||||
const accounts = await getAccounts()
|
||||
if(!accountsProm) {
|
||||
accountsProm = getAccounts()
|
||||
}
|
||||
const accounts = await accountsProm as Account[]
|
||||
if((accounts.length ?? 0) < 1 ){
|
||||
p1 = saveSelectedAccount({
|
||||
address: wallet.address,
|
||||
@ -94,7 +116,11 @@ export default defineComponent({
|
||||
encPk: ''
|
||||
})
|
||||
await Promise.all([p1, p2])
|
||||
router.push('/')
|
||||
if(isEdit) {
|
||||
router.push('accounts')
|
||||
}else {
|
||||
router.push('/')
|
||||
}
|
||||
resetFields()
|
||||
}
|
||||
|
||||
@ -107,7 +133,11 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
router.push('/')
|
||||
if(isEdit) {
|
||||
router.push('accounts')
|
||||
}else {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@ -120,7 +150,8 @@ export default defineComponent({
|
||||
generateRandomPk,
|
||||
getRandomName,
|
||||
clipboardOutline,
|
||||
paste
|
||||
paste,
|
||||
isEdit
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -189,7 +189,11 @@ export default defineComponent({
|
||||
networks[chainId.value] = network
|
||||
const p2 = replaceNetworks(networks)
|
||||
await Promise.all([p1, p2])
|
||||
router.push('/')
|
||||
if(isEdit) {
|
||||
router.push('networks')
|
||||
}else {
|
||||
router.push('/')
|
||||
}
|
||||
resetFields()
|
||||
}
|
||||
|
||||
@ -198,7 +202,11 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
if(isEdit) {
|
||||
router.push('networks')
|
||||
}else {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
const fillTemplate = (network: typeof mainNets[1] ) =>{
|
||||
|
@ -5,16 +5,48 @@
|
||||
<ion-title>Assets</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">Schedule Tab</ion-content>
|
||||
<ion-content class="ion-padding">
|
||||
1
|
||||
</ion-content>
|
||||
<ion-content class="ion-padding">
|
||||
2
|
||||
</ion-content>
|
||||
<ion-content class="ion-padding">
|
||||
3
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from "@ionic/vue";
|
||||
import { defineComponent, Ref, ref } from "vue";
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, onIonViewWillEnter } from "@ionic/vue";
|
||||
import { getSelectedAccount } from "@/utils/platform"
|
||||
import type { Account } from "@/extension/types"
|
||||
|
||||
const yupAssetsApi = 'https://api.yup.io/profile'
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonContent, IonHeader, IonPage, IonTitle, IonToolbar },
|
||||
setup: () => {
|
||||
const selectedAccount = ref({}) as Ref<Account>
|
||||
const assets = ref({})
|
||||
const loading = ref(true)
|
||||
const isError = ref(false)
|
||||
const noAssets = ref(false)
|
||||
|
||||
onIonViewWillEnter(async () => {
|
||||
selectedAccount.value = await getSelectedAccount()
|
||||
const req = await fetch(`${yupAssetsApi}/${selectedAccount.value.address}`)
|
||||
if(req.ok) {
|
||||
assets.value = (await req.json()) ?? {}
|
||||
if(!('poaps' in assets.value) && !('tokens' in assets.value) && !('nfts' in assets.value)) {
|
||||
noAssets.value = true
|
||||
}
|
||||
}else {
|
||||
isError.value = true
|
||||
}
|
||||
loading .value = false
|
||||
})
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -6,15 +6,29 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">Schedule Tab</ion-content>
|
||||
<ion-content class="ion-padding">Not implemented</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from "@ionic/vue";
|
||||
import { defineComponent, Ref, ref } from "vue";
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, onIonViewWillEnter } from "@ionic/vue";
|
||||
import { getHistory } from '@/utils/platform'
|
||||
import type { HistoryItem } from '@/extension/types'
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonContent, IonHeader, IonPage, IonTitle, IonToolbar },
|
||||
setup: () => {
|
||||
const history = ref([]) as Ref<HistoryItem[]>;
|
||||
const loading = ref(true)
|
||||
onIonViewWillEnter(async () => {
|
||||
history.value = await getHistory()
|
||||
loading.value = false
|
||||
})
|
||||
return {
|
||||
history,
|
||||
loading
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<ion-label>Selected Account: {{ selectedAccount?.name }}</ion-label>
|
||||
<ion-button @click="accountsModal = true">Select</ion-button>
|
||||
</ion-item>
|
||||
<ion-item button @click="copyAddress(selectedAccount.address, getToastRef())">
|
||||
<ion-item button @click="copyAddress(selectedAccount?.address, getToastRef())">
|
||||
<p style="font-size: 0.7rem">{{ selectedAccount?.address }}</p>
|
||||
<ion-icon style="margin-left: 0.5rem" :icon="copyOutline"></ion-icon>
|
||||
</ion-item>
|
||||
@ -67,7 +67,7 @@
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<ion-list style="margin-bottom: 4rem">
|
||||
<ion-radio-group :value="selectedAccount.address">
|
||||
<ion-radio-group :value="selectedAccount?.address ?? ''">
|
||||
<ion-list-header>
|
||||
<ion-label>Accounts</ion-label>
|
||||
</ion-list-header>
|
||||
@ -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;
|
||||
|
@ -14,6 +14,11 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item v-if="loading || Object.keys(networks).length < 1">
|
||||
<ion-label>No EVM Networks found</ion-label>
|
||||
<ion-button @click="goToAddNetwork">Add Network</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-list v-for="network of networks" :key="network.chainId">
|
||||
<ion-item>
|
||||
<ion-avatar v-if="(mainNets as any)[network.chainId]?.icon" style="margin-right: 1rem; width: 1.8rem; height:1.8rem;">
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,8 @@
|
||||
</ion-item>
|
||||
<div class="ion-padding" slot="content">
|
||||
<ion-list>
|
||||
<ion-list>
|
||||
<ion-item v-if="noAccounts">You need at least one account to touch this settings</ion-item>
|
||||
<ion-list :disabled="noAccounts">
|
||||
<ion-item>
|
||||
<ion-label>Enable Storage Encryption</ion-label>
|
||||
<ion-toggle :key="updateKey" @ion-change="changeEncryption" slot="end" :checked="settings.s.enableStorageEnctyption"></ion-toggle>
|
||||
@ -24,16 +25,16 @@
|
||||
</ion-list>
|
||||
<ion-item :disabled="!settings.s.enableStorageEnctyption">
|
||||
<ion-label>Enable Auto Lock</ion-label>
|
||||
<ion-toggle :key="updateKey" slot="end" :checked="settings.s.lockOutEnabled"></ion-toggle>
|
||||
<ion-toggle :key="updateKey" @ion-change="changeAutoLock" slot="end" :checked="settings.s.lockOutEnabled"></ion-toggle>
|
||||
</ion-item>
|
||||
<ion-list>
|
||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || settings.s.lockOutEnabled">
|
||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
|
||||
<ion-label>Auto-lock Period: (2-120) minutes</ion-label>
|
||||
</ion-item>
|
||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || settings.s.lockOutEnabled">
|
||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
|
||||
<ion-input :key="updateKey" v-model="settings.s.lockOutPeriod" type="number"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || settings.s.lockOutEnabled">
|
||||
<ion-item :disabled="!settings.s.enableStorageEnctyption || !settings.s.lockOutEnabled">
|
||||
<ion-button @click="setTime">Set Auto-lock</ion-button>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
@ -136,9 +137,10 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="mpModal=false">Close</ion-button>
|
||||
<ion-button @click="modalGetPassword?.reject ? (() => { modalGetPassword.reject(); modalGetPassword = null })() : mpModal=false">Close</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Create Encryption Password</ion-title>
|
||||
<ion-title v-if="!settings.s.enableStorageEnctyption">Create Encryption Password</ion-title>
|
||||
<ion-title v-else>Enter Encryption Password</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
@ -166,13 +168,13 @@
|
||||
</ion-list>
|
||||
</div>
|
||||
<ion-item>
|
||||
<ion-button @click="confirmModal">Confirm</ion-button>
|
||||
<ion-button @click="modalGetPassword?.resolve ? (() => { modalGetPassword.resolve(); modalGetPassword = null })() : confirmModal()">Confirm</ion-button>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
</ion-modal>
|
||||
<ion-alert
|
||||
:is-open="alertOpen"
|
||||
header="Error"
|
||||
:header="alertHeader"
|
||||
:message="alertMsg"
|
||||
:buttons="['OK']"
|
||||
@didDismiss="alertOpen=false"
|
||||
@ -184,8 +186,9 @@
|
||||
<script lang="ts">
|
||||
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<HTMLInputElement>
|
||||
type ModalPromisePassword = null | { resolve: ((p?: unknown) => void), reject: ((p?: unknown) => void)}
|
||||
const modalGetPassword = ref(null) as Ref<ModalPromisePassword>
|
||||
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
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -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)
|
||||
|
93
src/views/WalletError.vue
Normal file
93
src/views/WalletError.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Contract Error</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<ion-item>
|
||||
<ion-label>Transaction was aboreted before being sent</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Error:</ion-label>
|
||||
<ion-textarea
|
||||
style="overflow-y: scroll"
|
||||
:rows="10"
|
||||
:cols="20"
|
||||
:value="error"
|
||||
readonly
|
||||
></ion-textarea>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-button @click="onCancel">Exit</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<ion-loading
|
||||
:is-open="loading"
|
||||
cssClass="my-custom-class"
|
||||
message="Please wait..."
|
||||
:duration="4000"
|
||||
@didDismiss="loading = false"
|
||||
>
|
||||
</ion-loading>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
IonTextarea,
|
||||
onIonViewWillEnter,
|
||||
IonLoading,
|
||||
} from "@ionic/vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonButton,
|
||||
IonTextarea,
|
||||
IonLoading,
|
||||
},
|
||||
setup: () => {
|
||||
const route = useRoute();
|
||||
const error = decodeURIComponent((route.params?.param as string) ?? "");
|
||||
const loading = ref(true);
|
||||
const contract = (route.params?.contract as string) ?? "";
|
||||
|
||||
const onCancel = () => {
|
||||
window.close();
|
||||
};
|
||||
|
||||
onIonViewWillEnter(async () => {
|
||||
(window as any)?.resizeTo?.(700, 600);
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
return {
|
||||
onCancel,
|
||||
contract,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user