diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 75ba726..f378b8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - 'feature/**' jobs: build: diff --git a/package.json b/package.json index 0ae1401..e080512 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "metalet", "private": true, - "version": "2.0.4", + "version": "2.1.0", "type": "module", "scripts": { "dev": "vite --config vite.dev.config.ts", diff --git a/public/content.js b/public/content.js index 7315b14..8c797a4 100644 --- a/public/content.js +++ b/public/content.js @@ -1,161 +1,155 @@ -const m = (n = 32) => { - let e = ""; - const a = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (let r = 0; r < n; r++) - e += a.charAt(Math.floor(Math.random() * a.length)); - return e; -}; -async function t(n, e = "authorize", a) { - const r = `${e}-${n}`, u = m(16), l = window.location.host; - window.postMessage( - { - nonce: u, - channel: "to-metaidwallet", - action: r, - host: l, - icon: "", - params: a || {} - }, - "*" - ); - const h = (i) => { - const o = (c) => { - if (!(c.source !== window || c.data?.channel !== "from-metaidwallet")) { - if (c.data?.nonce === u) { - if (window.removeEventListener("message", o), c.data?.res?.error) - throw new Error(c.data.res.error); - i && typeof i == "function" && i(c.data); +;(function () { + 'use strict' + const l = (n = 32) => { + let r = '' + const a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + for (let t = 0; t < n; t++) r += a.charAt(Math.floor(Math.random() * a.length)) + return r + } + async function e(n, r = 'authorize', a, t) { + const x = `${r}-${n}`, + f = l(16), + N = window.location.host + window.postMessage({ nonce: f, channel: 'to-metaidwallet', action: x, host: N, icon: '', params: a || {} }, '*') + const v = (c) => { + const o = (i) => { + if (!(i.source !== window || i.data?.channel !== 'from-metaidwallet')) { + if (i.data?.nonce === f) { + if ((window.removeEventListener('message', o), i.data?.res?.error)) throw new Error(i.data.res.error) + c && typeof c == 'function' && c(i.data) + } + return !0 } - return !0; } - }; - window.addEventListener("message", o); - }; - return await new Promise((i) => { - h((o) => { - i(o.res); - }); - }); -} -async function s() { - return await t("Connect"); -} -async function y() { - return await t("Disconnect"); -} -async function T() { - return await t("IsConnected", "query"); -} -async function B() { - return await t("GetNetwork", "query"); -} -async function b() { - return await t("SwitchNetwork"); -} -async function q(n) { - return await t("GetAddress", "query", n); -} -async function G(n) { - return await t("GetPublicKey", "query", n); -} -async function P() { - return await t("GetXPublicKey", "query"); -} -async function w(n) { - return await t("GetBalance", "query", n); -} -async function S(n) { - return await t("GetUtxos", "query", n); -} -async function p(n) { - return await t("EciesEncrypt", "authorize", n); -} -async function z(n) { - return await t("EciesDecrypt", "authorize", n); -} -async function A(n) { - return await t("SignMessage", "authorize", n); -} -async function C(n) { - return await t("VerifySignature", "query", n); -} -async function E(n) { - return await t("PreviewTransaction", "query", n); -} -async function M(n) { - return await t("SignTransaction", "authorize", n); -} -async function K(n) { - return await t("SignTransactions", "authorize", n); -} -async function k(n) { - return await t("Pay", "authorize", n); -} -async function x(n) { - return await t("Transfer", "authorize", n); -} -async function N(n) { - return await t("Merge", "authorize", n); -} -async function g(n) { - return await t("GetTokenBalance", "query", n); -} -const f = { - query: [ - { name: "getBalance", action: "GetBTCBalance" }, - { name: "getAddress", action: "GetBTCAddress" }, - { name: "getPublicKey", action: "GetBTCPublicKey" }, - { name: "getUtxos", action: "GetBTCUtxos" } - ], - authorize: [ - { name: "signPsbt", action: "SignBTCPsbt" }, - { name: "signMessage", action: "SignBTCMessage" } - ] -}, d = { - connect: s, - isConnected: T, - disconnect: y, - getNetwork: B, - switchNetwork: b, - getAddress: q, - getPublicKey: G, - getXPublicKey: P, - getBalance: w, - getUtxos: S, - transfer: x, - merge: N, - previewTransaction: E, - signTransaction: M, - signTransactions: K, - pay: k, - signMessage: A, - verifySignature: C, - eciesEncrypt: p, - eciesDecrypt: z, - // signTransaction, - // transferAll, - token: { - getBalance: g - }, - nft: {}, - btc: {}, - // btc: { - // getBalance: () => {}, - // getAddress: () => {}, - // getPublicKey: () => {}, - // getUtxos: () => {}, - // }, - // Deprecating - requestAccount: s, - getAccount: s, - exitAccount: y, - getMvcBalance: w, - getSensibleFtBalance: g -}; -Object.keys(f).forEach((n) => { - const e = n; - f[e].forEach((a) => { - d.btc[a.name] = async (r) => await t(a.action, e, r); - }); -}); -window.metaidwallet = d; + window.addEventListener('message', o) + } + return await new Promise((c) => { + v((o) => { + c(o.res) + }) + }) + } + async function s() { + return await e('Connect') + } + async function u() { + return await e('Disconnect') + } + async function m() { + return await e('IsConnected', 'query') + } + async function h() { + return await e('GetNetwork', 'query') + } + async function T() { + return await e('SwitchNetwork') + } + async function B(n) { + return await e('GetAddress', 'query', n) + } + async function b(n) { + return await e('GetPublicKey', 'query', n) + } + async function q() { + return await e('GetXPublicKey', 'query') + } + async function w(n) { + return await e('GetBalance', 'query', n) + } + async function E(n) { + return await e('GetUtxos', 'query', n) + } + async function G(n) { + return await e('EciesEncrypt', 'authorize', n) + } + async function S(n) { + return await e('EciesDecrypt', 'authorize', n) + } + async function p(n) { + return await e('SignMessage', 'authorize', n) + } + async function M(n) { + return await e('VerifySignature', 'query', n) + } + async function P(n) { + return await e('PreviewTransaction', 'query', n) + } + async function z(n) { + return await e('SignTransaction', 'authorize', n) + } + async function A(n) { + return await e('SignTransactions', 'authorize', n) + } + async function C(n) { + return await e('Transfer', 'authorize', n) + } + async function L(n) { + return await e('Merge', 'authorize', n) + } + async function y(n) { + return await e('GetTokenBalance', 'query', n) + } + async function K(n, r) { + const a = (t) => { + if (t.data?.channel === 'removeListener' && t.data?.eventName === n) { + window.removeEventListener('message', a) + return + } + t.source !== window || t.data?.channel !== 'from-metaidwallet' || t.data?.eventName !== n || r(...t.data.args) + } + window.addEventListener('message', a) + } + async function k(n) { + window.postMessage({ eventName: n, channel: 'removeListener' }, '*') + } + const d = { + query: [ + { name: 'getBalance', action: 'GetBTCBalance' }, + { name: 'getAddress', action: 'GetBTCAddress' }, + { name: 'getPublicKey', action: 'GetBTCPublicKey' }, + { name: 'getUtxos', action: 'GetBTCUtxos' }, + ], + authorize: [ + { name: 'signPsbt', action: 'SignBTCPsbt' }, + { name: 'signMessage', action: 'SignBTCMessage' }, + ], + }, + g = { + connect: s, + isConnected: m, + disconnect: u, + getNetwork: h, + switchNetwork: T, + getAddress: B, + getPublicKey: b, + getXPublicKey: q, + getBalance: w, + getUtxos: E, + transfer: C, + merge: L, + previewTransaction: P, + signTransaction: z, + signTransactions: A, + signMessage: p, + verifySignature: M, + eciesEncrypt: G, + eciesDecrypt: S, + token: { getBalance: y }, + nft: {}, + btc: {}, + on: K, + removeListener: k, + requestAccount: s, + getAccount: s, + exitAccount: u, + getMvcBalance: w, + getSensibleFtBalance: y, + } + Object.keys(d).forEach((n) => { + const r = n + d[r].forEach((a) => { + g.btc[a.name] = async (t) => await e(a.action, r, t) + }) + }), + (window.metaidwallet = g) +})() diff --git a/public/manifest.json b/public/manifest.json index 0bf0fcf..f32e376 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "Metalet", "description": "An MVC Crypto Wallet Extension", - "version": "2.0.4", + "version": "2.1.0", "manifest_version": 3, "action": { "default_popup": "index.html", diff --git a/src/background.ts b/src/background.ts index 18c3333..808ce9e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,14 +1,19 @@ import browser from 'webextension-polyfill' import actions from './data/query-actions' +import exActions from './data/extension-actions' import { NOTIFICATION_HEIGHT, NOTIFICATION_WIDTH } from './data/config' import connector from './lib/connector' import { getCurrentAccount } from './lib/account' import { isLocked } from './lib/password' browser.runtime.onMessage.addListener(async (msg, sender) => { + if (msg.channel === 'inter-extension') { + return await exActions[msg.fn](...msg.args) + } + const account = await getCurrentAccount() const walletLocked = await isLocked() - const actionName = msg.action.replace('authorize-', '').replace('query-', '') + const actionName = msg.action.replace('authorize-', '').replace('query-', '').replace('event-', '') // 如果连接状态为未连接,且请求的 action 不是connect或者IsConnected,则返回错误 let failedStatus: string = '' @@ -33,6 +38,18 @@ browser.runtime.onMessage.addListener(async (msg, sender) => { return response } + + // event actions + // if (msg.action?.startsWith('event')) { + + // if (msg.params?.type === 'on') { + // console.log(`register ${actionName}`) + // register(actionName) + // } else if (msg.params?.type === 'removeListener') { + // unregister(actionName) + // } + // return + // } // authorize actions if (msg.action?.startsWith('authorize')) { diff --git a/src/content-script/actions.ts b/src/content-script/actions.ts index 60c2cc2..227048e 100644 --- a/src/content-script/actions.ts +++ b/src/content-script/actions.ts @@ -8,12 +8,13 @@ type Echo = { res: any } -export type ActionType = 'authorize' | 'query' +export type ActionType = 'authorize' | 'query' | 'event' export async function createAction( actionName: string, actionType: ActionType = 'authorize', - params?: any + params?: any, + handler?: any ): Promise { const action = `${actionType}-${actionName}` // nonce为32位随机字符串 @@ -165,6 +166,27 @@ export async function getTokenBalance(params?: { genesis: string; codehash: stri return await createAction('GetTokenBalance', 'query', params) } +// event on +export async function on(eventName: string, handler: Function) { + const handleFn = (event: MessageEvent) => { + if (event.data?.channel === 'removeListener' && event.data?.eventName === eventName) { + window.removeEventListener('message', handleFn) + return + } + if (event.source !== window || event.data?.channel !== 'from-metaidwallet' || event.data?.eventName !== eventName) { + return + } + + handler(...event.data.args) + } + window.addEventListener('message', handleFn) +} + +// event removeListener +export async function removeListener(eventName: string) { + window.postMessage({ eventName, channel: 'removeListener' }, '*') +} + export interface ActionItem { name: string action: string @@ -174,7 +196,7 @@ export type Keys = { [K in ActionType]: ActionItem[] } -export const btcKeys: Keys = { +export const btcKeys: Omit = { query: [ { name: 'getBalance', action: 'GetBTCBalance' }, { name: 'getAddress', action: 'GetBTCAddress' }, diff --git a/src/content-script/inject.ts b/src/content-script/inject.ts index bb75879..be10137 100644 --- a/src/content-script/inject.ts +++ b/src/content-script/inject.ts @@ -32,8 +32,8 @@ const callMetalet = async (params: MetaletParams) => { if (response?.channel === 'from-metaidwallet') { // post on console - console.log(`🚀 Reponse from Metalet on action ${response.action} 🚀`) - console.log(response?.res) + // console.log(`🚀 Response from Metalet on action ${response.action} 🚀`) + // console.log(response?.res) window.postMessage(response, '*') } diff --git a/src/content-script/main.ts b/src/content-script/main.ts index f3c53d7..630152f 100644 --- a/src/content-script/main.ts +++ b/src/content-script/main.ts @@ -22,7 +22,7 @@ import { pay, } from './actions' -import { btcKeys, createAction, ActionType } from './actions' +import { btcKeys, createAction, ActionType, on, removeListener } from './actions' type Metalet = { connect: any @@ -109,12 +109,9 @@ const metalet: any = { btc: {}, - // btc: { - // getBalance: () => {}, - // getAddress: () => {}, - // getPublicKey: () => {}, - // getUtxos: () => {}, - // }, + // event + on, + removeListener, // Deprecating requestAccount: connect, @@ -125,7 +122,7 @@ const metalet: any = { } Object.keys(btcKeys).forEach((type) => { - const actionType = type as ActionType + const actionType = type as Exclude btcKeys[actionType].forEach((item) => { metalet['btc'][item.name] = async (params?: any) => { return await createAction(item.action, actionType, params) diff --git a/src/data/config.ts b/src/data/config.ts index 1bce714..9d17dc8 100644 --- a/src/data/config.ts +++ b/src/data/config.ts @@ -5,4 +5,4 @@ export const DERIVE_MAX_DEPTH = 1000 export const NOTIFICATION_WIDTH = 360 export const NOTIFICATION_HEIGHT = 620 -export const VERSION = '2.0.4' +export const VERSION = '2.1.0' diff --git a/src/data/event-actions.ts b/src/data/event-actions.ts new file mode 100644 index 0000000..e97330d --- /dev/null +++ b/src/data/event-actions.ts @@ -0,0 +1,40 @@ +import browser from 'webextension-polyfill' + +interface EmitParams { + args: unknown[] + eventName: string +} + +// const eventSet = ref>(new Set()) + +// export function register(eventName: string) { +// eventSet.value.add(eventName) +// console.log('eventSet', eventSet.value) +// } + +// export function unregister(eventName: string) { +// eventSet.value.delete(eventName) +// } + +// export function isSend(eventName: string) { +// console.log('eventSet', eventSet.value) +// return eventSet.value.has(eventName) +// } + +async function emit(params: EmitParams) { + const channel = 'from-metaidwallet' + + const tab = await browser.tabs.query({ + active: true, + windowType: 'normal', + currentWindow: true, + }) + + if (tab[0].id) { + browser.tabs.sendMessage(tab[0].id, { ...params, channel }) + } +} + +export function createEmit(eventName: string) { + return (...args: unknown[]) => emit({ eventName, args }) +} diff --git a/src/data/extension-actions.ts b/src/data/extension-actions.ts new file mode 100644 index 0000000..945d5cb --- /dev/null +++ b/src/data/extension-actions.ts @@ -0,0 +1,27 @@ +import { deriveAllAddresses } from '@/lib/bip32-deriver' +import { addAssetsDisplay, getAssetsDisplay, removeAssetsDisplay } from '@/lib/assets' +import { + getCurrentAccount, + updateName, + addAccount, + getAddress, + getPrivateKey, + updateBtcPath, + migrateV2, + needsMigrationV2, +} from '@/lib/account' + +export default { + getCurrentAccount, + updateName, + addAccount, + getAddress, + getPrivateKey, + updateBtcPath, + migrateV2, + needsMigrationV2, + deriveAllAddresses, + getAssetsDisplay, + addAssetsDisplay, + removeAssetsDisplay, +} as { [key: string]: Function } diff --git a/src/lib/account.ts b/src/lib/account.ts index 53f839b..39b18c6 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -1,7 +1,11 @@ -import { Ref, ref } from 'vue' -import { fetchUtxos } from '../queries/utxos' import { mvc } from 'meta-contract' - +import { createEmit } from '@/data/event-actions' +import { getNetwork } from './network' +import { signMessage } from './crypto' +import { fetchUtxos } from '../queries/utxos' +import { getStorage, setStorage } from './storage' +import { generateRandomString, raise } from './helpers' +import { fetchSpaceBalance, fetchBtcBalance, doNothing } from '@/queries/balance' import { AddressType, deriveAddress, @@ -10,16 +14,12 @@ import { derivePublicKey, inferAddressType, } from './bip32-deriver' -import { fetchSpaceBalance, fetchBtcBalance, doNothing } from '@/queries/balance' -import { getStorage, setStorage } from './storage' -import { generateRandomString, raise } from './helpers' -import { getNetwork } from './network' -import { signMessage } from './crypto' const CURRENT_ACCOUNT_ID = 'currentAccountId' const ACCOUNT_STORAGE_CURRENT_KEY = 'accounts_v2' const ACCOUNT_STORAGE_HISTORY_KEYS = ['accounts'] +// TODO put in types.ts export type Chain = 'btc' | 'mvc' type DerivedAccountDetail = { @@ -128,6 +128,10 @@ export async function connectAccount(accountId: string) { await setStorage(CURRENT_ACCOUNT_ID, accountId) + const mvcAddress = await getAddress('mvc') + const btcAddress = await getAddress('btc') + createEmit('accountsChanged')({ mvcAddress, btcAddress }) + return true } @@ -300,14 +304,13 @@ export async function getBalance(chain: Chain = 'mvc', address?: string) { } } -export async function getUtxos(params?: { path?: string }) { +export async function getUtxos(chain: Chain = 'mvc', params?: { path?: string }) { const account = await getCurrentAccount() if (!account) { return null } - const address = await getAddress('mvc', params?.path) - - return await fetchUtxos(address) + const address = await getAddress(chain, params?.path) + return await fetchUtxos(chain, address) } export async function updateName(name: string) { @@ -431,7 +434,7 @@ type AccountManager = { getBalance: (chain: Chain, address?: string) => Promise> | null> getAddress: (chain: Chain, path?: string) => Promise getXPublicKey: () => Promise - getUtxos: (params?: any) => Promise + getUtxos: (chain: Chain, params?: any) => Promise updateName: (name: string) => Promise } diff --git a/src/lib/actions/btc/get-utxos.ts b/src/lib/actions/btc/get-utxos.ts index 2104ba9..d18c400 100644 --- a/src/lib/actions/btc/get-utxos.ts +++ b/src/lib/actions/btc/get-utxos.ts @@ -1,3 +1,5 @@ -export async function process(params: any, host: string) { - return 'getUtxos' +import { getUtxos } from '@/lib/account' + +export async function process() { + return await getUtxos('btc') } diff --git a/src/lib/actions/get-utxos.ts b/src/lib/actions/get-utxos.ts index 72624a2..457d00a 100644 --- a/src/lib/actions/get-utxos.ts +++ b/src/lib/actions/get-utxos.ts @@ -1,5 +1,5 @@ -import accountManager from '../account' +import { getUtxos } from '../account' export async function process(params: any, host: string) { - return await accountManager.getUtxos(params) + return await getUtxos('mvc', params) } diff --git a/src/lib/emitters/index.ts b/src/lib/emitters/index.ts new file mode 100644 index 0000000..d1dfdd6 --- /dev/null +++ b/src/lib/emitters/index.ts @@ -0,0 +1,15 @@ +import browser from 'webextension-polyfill' + +interface EmitParams { + fn: string + args: unknown[] +} + +async function emit(params: EmitParams): Promise { + const channel = 'inter-extension' + return await browser.runtime.sendMessage({ ...params, channel }) +} + +export function createEmit(fn: string) { + return (...args: unknown[]) => emit({ fn, args }) +} diff --git a/src/lib/network.ts b/src/lib/network.ts index 2d2e407..bab0d3f 100644 --- a/src/lib/network.ts +++ b/src/lib/network.ts @@ -1,5 +1,6 @@ import { ref } from 'vue' import storage from './storage' +import { createEmit } from '@/data/event-actions' export type Network = 'mainnet' | 'testnet' @@ -8,6 +9,8 @@ export const network = ref(await storage.get('network', { defaultValue: export async function setNetwork(_network: Network) { await storage.set('network', _network) network.value = _network + createEmit('networkChanged')(_network) + console.log("createEmit('networkChanged')", _network) } export async function getNetwork(): Promise { diff --git a/src/pages/accounts/Index.vue b/src/pages/accounts/Index.vue index 7cd15b6..df66ed6 100644 --- a/src/pages/accounts/Index.vue +++ b/src/pages/accounts/Index.vue @@ -1,26 +1,30 @@