diff --git a/.gitignore b/.gitignore index 8bdcabe..aa4f431 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ dist dist-ssr coverage *.local +mx-xportalhub-extension-*/** /cypress/videos/ /cypress/screenshots/ diff --git a/manifest.json b/manifest.json index 4173f70..efb025e 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "XPortal Hub test", "description": "Test your xPortal Hub integration. Only for development purposes!!", - "version": "1.0.0", + "version": "1.1.0", "permissions": ["activeTab", "storage"], "action": { "default_popup": "index.html", diff --git a/package-lock.json b/package-lock.json index cdeb483..1fcec66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "mx-xportalhub-chrome", - "version": "0.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mx-xportalhub-chrome", - "version": "0.0.0", + "version": "1.1.0", "dependencies": { "@multiversx/sdk-core": "^12.2.0", "@multiversx/sdk-native-auth-client": "^1.0.2", "@multiversx/sdk-network-providers": "^1.4.0", "@multiversx/sdk-wallet": "^4.1.0", "buffer": "^6.0.3", + "readable-stream": "^4.4.0", "sass": "^1.62.1", "vue": "^3.2.47" }, @@ -552,9 +553,9 @@ } }, "node_modules/@multiversx/sdk-core": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-12.2.0.tgz", - "integrity": "sha512-PbHLlG9+UWiYY71NGPZ5bjrvaxFvwYU6NQxeU2Pnp99Csm1O42pySq5NVqaKhcZh3F6JDkUiNhvcjnKI3Lt+EQ==", + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-12.2.1.tgz", + "integrity": "sha512-jnVwr7ljZ3AD5rN/lifrNq5uZU7CenNVqnCzu8Ks1fVJ/TOZM6VUkVXoSvceweXY0vw7FyH6FBVRUQqWPWbZzg==", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", "bech32": "1.1.4", @@ -608,9 +609,9 @@ "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, "node_modules/@multiversx/sdk-wallet": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-4.1.0.tgz", - "integrity": "sha512-XjWWx/2LSFBJy6+YypLwOrOci+S0aYsawGVpYh61oNP0LAlh4HoDCFx2fzqqmOtmA4QTNrjkEaypneSdAaujUQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-4.2.0.tgz", + "integrity": "sha512-EjSb9AnqMcpmDjZ7ebkUpOzpTfxj1plTuVXwZ6AaqJsdpxMfrE2izbPy18+bg5xFlr8V27wYZcW8zOhkBR50BA==", "dependencies": { "@multiversx/sdk-bls-wasm": "0.3.5", "@noble/ed25519": "1.7.3", @@ -1265,6 +1266,17 @@ "integrity": "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -2360,6 +2372,22 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2816,6 +2844,19 @@ "node": ">=4" } }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -3287,6 +3328,19 @@ "node": ">=10.0.0" } }, + "node_modules/keccak/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4025,6 +4079,14 @@ "node": ">=6.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/protobufjs": { "version": "6.11.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", @@ -4133,16 +4195,17 @@ } }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz", + "integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/readdirp": { diff --git a/package.json b/package.json index 7442705..b05e7a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mx-xportalhub-chrome", - "version": "1.0.0", + "version": "1.1.0", "type": "module", "private": true, "scripts": { @@ -18,6 +18,7 @@ "@multiversx/sdk-network-providers": "^1.4.0", "@multiversx/sdk-wallet": "^4.1.0", "buffer": "^6.0.3", + "readable-stream": "^4.4.0", "sass": "^1.62.1", "vue": "^3.2.47" }, diff --git a/publish/mx-xportalhub-extension-1.0.0.zip b/publish/mx-xportalhub-extension-1.0.0.zip deleted file mode 100644 index 71703a4..0000000 Binary files a/publish/mx-xportalhub-extension-1.0.0.zip and /dev/null differ diff --git a/src/App.vue b/src/App.vue index ccac315..71d8803 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,6 +12,7 @@ Logout
+
Login in the current tab
@@ -23,6 +24,7 @@ import SignTransactions from "@/components/SignTransactions.vue"; import {useWallet} from "@/WalletManager"; import { onMounted, ref} from "vue"; import ObfuscatedAddress from "@/components/ObfuscatedAddress.vue"; +import TokenConfiguration from "@/components/TokenConfiguration.vue"; const { address, transactions, updateWallet, load, logout, generateNativeToken, transactionDetail } = useWallet(); async function onWalletChange(wallet: string) { @@ -39,10 +41,10 @@ async function login() { console.log("Tab", tabs) const [tab] = tabs; if(!tab?.url) return; - const separator = tab.url.includes("?") ? "&" : "?"; - const url = `${tab.url}${separator}accessToken=${token}`; - console.log("Update url", url); - chrome.tabs.update(tab.id, { url }); + const currentUrl = new URL(tab.url); + currentUrl.searchParams.set("accessToken", token); + console.log("currentUrl", currentUrl.toString()); + chrome.tabs.update(tab.id, { url: currentUrl.toString() }); }); } @@ -125,9 +127,11 @@ onMounted(() => { .login { display: flex; + flex-direction: column; margin-top: 40px; font-size: 1.5rem; height: 40%; + width: 100%; align-items: center; justify-content: center; } diff --git a/src/WalletManager.ts b/src/WalletManager.ts index a6afaf7..0c238bb 100644 --- a/src/WalletManager.ts +++ b/src/WalletManager.ts @@ -1,10 +1,11 @@ import {UserSigner} from "@multiversx/sdk-wallet"; import type {UserAddress} from "@multiversx/sdk-wallet/out/userAddress"; import {NativeAuthClient} from "@multiversx/sdk-native-auth-client"; -import {computed, reactive, ref} from "vue"; +import {computed, reactive, ref, watch} from "vue"; import {Transaction} from "@multiversx/sdk-core"; import {Signature} from "@multiversx/sdk-wallet/out/signature"; -import type {IPlainTransactionObject} from "@multiversx/sdk-core/out"; +import type {IPlainTransactionObject} from "@multiversx/sdk-core"; +import {Address, SignableMessage} from "@multiversx/sdk-core"; export type SignResult = { transactions?: IPlainTransactionObject[], @@ -13,35 +14,78 @@ export type SignResult = { export const useWallet = () => { const address = ref(undefined); const transactions = reactive([]); + const ttl = ref(); + const origin = ref(); let signer: UserSigner | undefined; const updateWallet = async (wallet: string)=> { + if(!wallet) { + return; + } signer = UserSigner.fromPem(wallet); address.value = signer.getAddress(); } + watch(ttl, async (newTtl, oldValue) => { + if(!newTtl || newTtl === oldValue) { + return; + } + console.log("new ttl", newTtl); + await chrome.storage.local.set({ttl: newTtl}) + }); + watch(origin, async (newOrigin, oldValue) => { + if(!newOrigin || newOrigin === oldValue) { + return; + } + console.log("new origin", newOrigin) + await chrome.storage.local.set({origin: newOrigin}) + }); + const load = async () => { - const result = await chrome.storage.local.get(["wallet", "transactions"]); + const result = await chrome.storage.local.get(["wallet", "transactions", "ttl", "origin"]); if(result.transactions) { transactions.splice(0); result.transactions .map((transaction: any) => Transaction.fromPlainObject(transaction)) .forEach((transaction: Transaction) => transactions.push(transaction)); } + await loadTokenConfig(); await updateWallet(result.wallet); } + const loadTokenConfig = async () => { + const result = await chrome.storage.local.get(["ttl", "origin"]); + if(result.ttl) { + console.log("setting ttl", result.ttl); + ttl.value = result.ttl; + } else { + console.log("setting default ttl", 86400); + ttl.value = 86400; + } + if(result.origin) { + console.log("setting origin", result.origin); + origin.value = result.origin; + } else { + console.log("setting default origin", "https://api.multiversx.com"); + origin.value = "https://api.multiversx.com"; + } + } const saveWallet = async (wallet: string) => { await updateWallet(wallet); await chrome.storage.local.set({wallet}); } - - - const sign = async (message: Buffer) => { + const sign = async (bech32Address: string, message: string) => { if (!signer) { throw new Error("Wallet not loaded"); } - return signer.sign(message); + const address = new Address(bech32Address); + const signableMessage = new SignableMessage({ + address, + message: Buffer.from(message, 'utf8'), + }); + + const cryptoMessage = Buffer.from(signableMessage.serializeForSigning().toString('hex'), "hex"); + return signer.sign(cryptoMessage); } const signTransactions = async (transactions: Transaction[]): Promise => { @@ -75,17 +119,21 @@ export const useWallet = () => { } } - const generateNativeToken = async (apiUrl:string = "https://api.multiversx.com") => { + const generateNativeToken = async () => { if (!signer || !address.value) { throw new Error("Wallet not loaded"); } - const client = new NativeAuthClient({ origin: apiUrl }); + const tokenConfig = await chrome.storage.local.get(["ttl", "origin"]); + const ttl = tokenConfig.ttl || 86400; + const origin = tokenConfig.origin || "https://api.multiversx.com"; + const client = new NativeAuthClient({ origin, expirySeconds: ttl }); + const bech32Address = address.value.bech32(); const init = await client.initialize({ timestamp: Date.now() }); console.log("init", init); console.log("address", bech32Address); - const message = Buffer.from(`${bech32Address}${init}`); - const signature = await sign(message); + const message = `${address.value}${init}`; + const signature = await sign(bech32Address, message); const hexSignature = signature.toString('hex'); console.log("stringSignature", hexSignature); return client.getToken(bech32Address, init, hexSignature); @@ -118,5 +166,5 @@ export const useWallet = () => { } }) - return { address, transactions, updateWallet, load, saveWallet, sign, generateNativeToken, logout, transactionDetail, signTransactions}; + return { address, transactions, ttl, origin, updateWallet, load, saveWallet, sign, generateNativeToken, logout, transactionDetail, signTransactions, loadTokenConfig}; } \ No newline at end of file diff --git a/src/XPortalHubSimulator.ts b/src/XPortalHubSimulator.ts deleted file mode 100644 index 2d179e2..0000000 --- a/src/XPortalHubSimulator.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { UserSigner } from "@multiversx/sdk-wallet"; -import { Transaction } from "@multiversx/sdk-core"; -import {Signature} from "@multiversx/sdk-wallet/out/signature"; - -let listenerAdded = false; -const useXportalHub = async () => { - if(listenerAdded) return; - - const headers = new Headers(); - headers.append('Content-Type','text/plain; charset=UTF-8'); - const walletPem = await fetch("/wallet.pem", { headers }); - const walletPemText = await walletPem.text(); - console.log("walletPem", walletPemText); - const signer = UserSigner.fromPem(walletPemText); - - window.addEventListener("message", async (event) => { - if(!event.data) return; - - try { - const eventData = JSON.parse(event.data); - if(!eventData?.type || eventData.type !== 'SIGN_TRANSACTIONS_REQUEST') { - return; - } - const transactions = []; - for(let i = 0; i < eventData.message.length; i++) { - const transaction = Transaction.fromPlainObject(eventData.message[i]); - const serializedTransaction = transaction.serializeForSigning(transaction.getSender()); - const transactionSignature = await signer.sign(serializedTransaction); - transaction.applySignature(new Signature(transactionSignature), transaction.getSender()); - transactions.push(transaction.toPlainObject()); - } - console.log("Sending message to xPortal Hub", transactions) - } catch(err) { - //Silent catch JSON parse error - console.log("Error parsing message from xPortal Hub", err) - } - }); - listenerAdded = true; -} -export default useXportalHub; \ No newline at end of file diff --git a/src/components/TokenConfiguration.vue b/src/components/TokenConfiguration.vue new file mode 100644 index 0000000..5e6e58a --- /dev/null +++ b/src/components/TokenConfiguration.vue @@ -0,0 +1,58 @@ + + + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 01433bc..c8772ec 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ import { createApp } from 'vue' -import App from './App.vue' +import App from '@/App.vue' createApp(App).mount('#app') diff --git a/vite.config.ts b/vite.config.ts index aeace51..329f36c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,7 +10,8 @@ export default defineConfig({ plugins: [vue(), crx({ manifest })], resolve: { alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) + '@': fileURLToPath(new URL('./src', import.meta.url)), + buffer: 'buffer', } }, optimizeDeps: {