diff --git a/README.md b/README.md index 27221d6..e3edb20 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,27 @@ Checks if there's any active session regardless of platform. Possible responses: Starts the sign process and returns the signed transaction in `Uint8Array` +#### `PeraWalletConnect.signData(data: PeraWalletArbitraryData[], signer: string): Promise` + +Starts the signing process for arbitrary data signing and returns the signed data in `Uint8Array`. Uses `signBytes` method of `algosdk` behind the scenes. `signer` should be a valid Algorand address that exists in the user's wallet. + +
+ See example + +```typescript +const signedData: Uint8Array[] = await peraWallet.signData([ + { + data: new Uint8Array(Buffer.from(`timestamp//${Date.now()}`)), + message: "Timestamp confirmation" + }, + { + data: new Uint8Array(Buffer.from(`agent//${navigator.userAgent}`)), + message: "User agent confirmation" + } +], "SAHBJDRHHRR72JHTWSXZR5VHQQUVC7S757TJZI656FWSDO3TZZWV3IGJV4"); +``` +
+ ## Customizing Style You can override the z-index using the `.pera-wallet-modal` class so that the modal does not conflict with another component on your application. diff --git a/package-lock.json b/package-lock.json index 97874c2..801a4a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@hipo/eslint-config-typescript": "^1.1.0", "@rollup/plugin-image": "^2.1.1", "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-replace": "^5.0.2", "@typescript-eslint/eslint-plugin": "^5.17.0", "@typescript-eslint/parser": "^5.17.0", "eslint": "^8.12.0", @@ -30,6 +31,7 @@ "rollup": "^2.70.1", "rollup-plugin-eslint": "^7.0.0", "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-sizes": "^1.0.5", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.31.2", "sass": "^1.49.11", @@ -379,6 +381,61 @@ "rollup": "^1.20.0 || ^2.0.0" } }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", + "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace/node_modules/@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace/node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@rollup/plugin-replace/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/@rollup/pluginutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", @@ -2484,6 +2541,15 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filesize": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-9.0.11.tgz", + "integrity": "sha512-gTAiTtI0STpKa5xesyTA9hA3LX4ga8sm2nWRcffEa1L/5vQwb4mj2MdzMkoHoGv4QzfDshQZuYscQSf8c4TKOA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3743,6 +3809,18 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -4047,6 +4125,12 @@ "node": ">=10" } }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", + "dev": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6328,6 +6412,19 @@ "postcss": "8.x" } }, + "node_modules/rollup-plugin-sizes": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-sizes/-/rollup-plugin-sizes-1.0.5.tgz", + "integrity": "sha512-d3HqHZF6aJeU7Sq0BpVhkdkQ7s3oZci0d3TS3/1srbqjRLHrCmrKocKftjW5TR8LwbvCld6GUP4U2HrOoPtScg==", + "dev": true, + "dependencies": { + "filesize": "^9.0.0", + "module-details-from-path": "^1.0.3" + }, + "peerDependencies": { + "rollup": "^2 || ^3" + } + }, "node_modules/rollup-plugin-terser": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", @@ -7868,6 +7965,41 @@ "@rollup/pluginutils": "^3.0.8" } }, + "@rollup/plugin-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", + "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.27.0" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + } + } + }, "@rollup/pluginutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", @@ -9448,6 +9580,12 @@ "flat-cache": "^3.0.4" } }, + "filesize": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-9.0.11.tgz", + "integrity": "sha512-gTAiTtI0STpKa5xesyTA9hA3LX4ga8sm2nWRcffEa1L/5vQwb4mj2MdzMkoHoGv4QzfDshQZuYscQSf8c4TKOA==", + "dev": true + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -10403,6 +10541,15 @@ "yallist": "^4.0.0" } }, + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -10628,6 +10775,12 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -12246,6 +12399,16 @@ "style-inject": "^0.3.0" } }, + "rollup-plugin-sizes": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-sizes/-/rollup-plugin-sizes-1.0.5.tgz", + "integrity": "sha512-d3HqHZF6aJeU7Sq0BpVhkdkQ7s3oZci0d3TS3/1srbqjRLHrCmrKocKftjW5TR8LwbvCld6GUP4U2HrOoPtScg==", + "dev": true, + "requires": { + "filesize": "^9.0.0", + "module-details-from-path": "^1.0.3" + } + }, "rollup-plugin-terser": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", diff --git a/package.json b/package.json index 22d9a95..669bdea 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "1.2.3", + "version": "1.2.4", "name": "@perawallet/connect", "description": "JavaScript SDK for integrating Pera Wallet to web applications.", "main": "dist/index.js", @@ -16,6 +16,7 @@ "@hipo/eslint-config-typescript": "^1.1.0", "@rollup/plugin-image": "^2.1.1", "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-replace": "^5.0.2", "@typescript-eslint/eslint-plugin": "^5.17.0", "@typescript-eslint/parser": "^5.17.0", "eslint": "^8.12.0", @@ -25,6 +26,7 @@ "rollup": "^2.70.1", "rollup-plugin-eslint": "^7.0.0", "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-sizes": "^1.0.5", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.31.2", "sass": "^1.49.11", diff --git a/rollup.config.js b/rollup.config.js index b4cbc33..286274f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,8 +1,12 @@ +import {version as PeraConnectVersion} from "./package.json"; + import typescript from "rollup-plugin-typescript2"; import {terser} from "rollup-plugin-terser"; import postcss from "rollup-plugin-postcss"; import image from "@rollup/plugin-image"; import json from "@rollup/plugin-json"; +import replace from "@rollup/plugin-replace"; +import sizes from "rollup-plugin-sizes"; export default [ { @@ -40,7 +44,12 @@ export default [ exclude: "**/__tests__/**", clean: true }), - json() + json(), + sizes(), + replace({ + PERA_CONNECT_VERSION: `v${PeraConnectVersion}`, + preventAssignment: true + }) ] } ]; diff --git a/src/PeraWalletConnect.ts b/src/PeraWalletConnect.ts index b8e3054..cfab518 100644 --- a/src/PeraWalletConnect.ts +++ b/src/PeraWalletConnect.ts @@ -10,12 +10,7 @@ import { PERA_WALLET_REDIRECT_MODAL_ID, openPeraWalletSignTxnToast, PERA_WALLET_SIGN_TXN_TOAST_ID, - openPeraWalletSignTxnModal, - closePeraWalletSignTxnModal, - PERA_WALLET_IFRAME_ID, - PERA_WALLET_MODAL_CLASSNAME, PeraWalletModalConfig, - PERA_WALLET_SIGN_TXN_MODAL_ID, setupPeraWalletConnectModalCloseListener } from "./modal/peraWalletConnectModalUtils"; import { @@ -26,23 +21,20 @@ import { getWalletPlatformFromStorage } from "./util/storage/storageUtils"; import {getPeraConnectConfig} from "./util/api/peraWalletConnectApi"; -import {PeraWalletTransaction, SignerTransaction} from "./util/model/peraWalletModels"; +import { + PeraWalletArbitraryData, + PeraWalletTransaction, + SignerTransaction +} from "./util/model/peraWalletModels"; import { base64ToUint8Array, composeTransaction, formatJsonRpcRequest } from "./util/transaction/transactionUtils"; -import {detectBrowser, isMobile} from "./util/device/deviceUtils"; +import {isMobile} from "./util/device/deviceUtils"; import {AlgorandChainIDs} from "./util/peraWalletTypes"; -import {generateEmbeddedWalletURL} from "./util/peraWalletUtils"; -import appTellerManager, {PeraTeller} from "./util/network/teller/appTellerManager"; -import {getPeraWebWalletURL} from "./util/peraWalletConstants"; -import { - getMetaInfo, - waitForTabOpening, - WAIT_FOR_TAB_MAX_TRY_COUNT, - WAIT_FOR_TAB_TRY_INTERVAL -} from "./util/dom/domUtils"; +import {runWebSignTransactionFlow} from "./util/sign/signTransactionFlow"; +import {runWebConnectFlow} from "./util/connect/connectFlow"; interface PeraWalletConnectOptions { bridge?: string; @@ -69,7 +61,7 @@ class PeraWalletConnect { bridge: string; connector: WalletConnect | null; shouldShowSignTxnToast: boolean; - chainId?: number; + chainId?: AlgorandChainIDs; constructor(options?: PeraWalletConnectOptions) { this.bridge = options?.bridge || ""; @@ -97,275 +89,6 @@ class PeraWalletConnect { return false; } - private connectWithWebWallet( - resolve: (accounts: string[]) => void, - reject: (reason?: any) => void, - webWalletURL: string, - chainId: number | undefined - ) { - const browser = detectBrowser(); - const webWalletURLs = getPeraWebWalletURL(webWalletURL); - const peraWalletIframe = document.createElement("iframe"); - - function onWebWalletConnect(peraWalletIframeWrapper: Element) { - if (browser === "Chrome") { - peraWalletIframe.setAttribute("id", PERA_WALLET_IFRAME_ID); - peraWalletIframe.setAttribute( - "src", - generateEmbeddedWalletURL(webWalletURLs.CONNECT) - ); - - peraWalletIframeWrapper.appendChild(peraWalletIframe); - - if (peraWalletIframe.contentWindow) { - let count = 0; - - const checkIframeIsInitialized = setInterval(() => { - count += 1; - - if (count === WAIT_FOR_TAB_MAX_TRY_COUNT) { - clearInterval(checkIframeIsInitialized); - - return; - } - - appTellerManager.sendMessage({ - message: { - type: "IFRAME_INITIALIZED" - }, - - origin: webWalletURLs.CONNECT, - targetWindow: peraWalletIframe.contentWindow! - }); - }, WAIT_FOR_TAB_TRY_INTERVAL); - - appTellerManager.setupListener({ - onReceiveMessage: (event: MessageEvent>) => { - if (event.data.message.type === "IFRAME_INITIALIZED_RECEIVED") { - clearInterval(checkIframeIsInitialized); - appTellerManager.sendMessage({ - message: { - type: "CONNECT", - data: { - ...getMetaInfo(), - chainId - } - }, - - origin: webWalletURLs.CONNECT, - targetWindow: peraWalletIframe.contentWindow! - }); - } else if (resolve && event.data.message.type === "CONNECT_CALLBACK") { - const accounts = event.data.message.data.addresses; - - saveWalletDetailsToStorage(accounts, "pera-wallet-web"); - - resolve(accounts); - - onClose(); - - document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); - } else if (event.data.message.type === "CONNECT_NETWORK_MISMATCH") { - reject( - new PeraWalletConnectError( - { - type: "CONNECT_NETWORK_MISMATCH", - detail: event.data.message.error - }, - event.data.message.error || - `Your wallet is connected to a different network to this dApp. Update your wallet to the correct network (MainNet or TestNet) to continue.` - ) - ); - - onClose(); - - document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); - } else if ( - ["CREATE_PASSCODE_EMBEDDED", "SELECT_ACCOUNT_EMBEDDED"].includes( - event.data.message.type - ) - ) { - if (event.data.message.type === "CREATE_PASSCODE_EMBEDDED") { - waitForTabOpening(webWalletURLs.CONNECT).then((newPeraWalletTab) => { - if (newPeraWalletTab) { - appTellerManager.sendMessage({ - message: { - type: "CONNECT", - data: { - ...getMetaInfo(), - chainId - } - }, - - origin: webWalletURLs.CONNECT, - targetWindow: newPeraWalletTab - }); - } - - const checkTabIsAliveInterval = setInterval(() => { - if (newPeraWalletTab?.closed === true) { - reject( - new PeraWalletConnectError( - { - type: "CONNECT_CANCELLED" - }, - "Connect is cancelled by user" - ) - ); - - onClose(); - clearInterval(checkTabIsAliveInterval); - } - - // eslint-disable-next-line no-magic-numbers - }, 2000); - - appTellerManager.setupListener({ - onReceiveMessage: ( - newTabEvent: MessageEvent> - ) => { - if ( - resolve && - newTabEvent.data.message.type === "CONNECT_CALLBACK" - ) { - const accounts = newTabEvent.data.message.data.addresses; - - saveWalletDetailsToStorage(accounts, "pera-wallet-web"); - - resolve(accounts); - - onClose(); - - newPeraWalletTab?.close(); - } - } - }); - }); - } else if (event.data.message.type === "SELECT_ACCOUNT_EMBEDDED") { - const peraWalletConnectModalWrapper = document.getElementById( - PERA_WALLET_CONNECT_MODAL_ID - ); - - const peraWalletConnectModal = peraWalletConnectModalWrapper - ?.querySelector("pera-wallet-connect-modal") - ?.shadowRoot?.querySelector(`.${PERA_WALLET_MODAL_CLASSNAME}`); - - const peraWalletConnectModalDesktopMode = peraWalletConnectModal - ?.querySelector("pera-wallet-modal-desktop-mode") - ?.shadowRoot?.querySelector( - ".pera-wallet-connect-modal-desktop-mode" - ); - - if (peraWalletConnectModal && peraWalletConnectModalDesktopMode) { - peraWalletConnectModal.classList.add( - `${PERA_WALLET_MODAL_CLASSNAME}--select-account` - ); - peraWalletConnectModal.classList.remove( - `${PERA_WALLET_MODAL_CLASSNAME}--create-passcode` - ); - peraWalletConnectModalDesktopMode.classList.add( - `pera-wallet-connect-modal-desktop-mode--select-account` - ); - peraWalletConnectModalDesktopMode.classList.remove( - `pera-wallet-connect-modal-desktop-mode--create-passcode` - ); - } - - appTellerManager.sendMessage({ - message: { - type: "SELECT_ACCOUNT_EMBEDDED_CALLBACK" - }, - origin: webWalletURLs.CONNECT, - targetWindow: peraWalletIframe.contentWindow! - }); - } - } - } - }); - } - } else { - waitForTabOpening(webWalletURLs.CONNECT) - .then((newPeraWalletTab) => { - if (newPeraWalletTab) { - appTellerManager.sendMessage({ - message: { - type: "CONNECT", - data: { - ...getMetaInfo(), - chainId - } - }, - - origin: webWalletURLs.CONNECT, - targetWindow: newPeraWalletTab - }); - } - - const checkTabIsAliveInterval = setInterval(() => { - if (newPeraWalletTab?.closed === true) { - reject( - new PeraWalletConnectError( - { - type: "CONNECT_CANCELLED" - }, - "Connect is cancelled by user" - ) - ); - - clearInterval(checkTabIsAliveInterval); - onClose(); - } - - // eslint-disable-next-line no-magic-numbers - }, 2000); - - appTellerManager.setupListener({ - onReceiveMessage: (event: MessageEvent>) => { - if (resolve && event.data.message.type === "CONNECT_CALLBACK") { - const accounts = event.data.message.data.addresses; - - saveWalletDetailsToStorage(accounts, "pera-wallet-web"); - - resolve(accounts); - - onClose(); - - newPeraWalletTab?.close(); - } else if (event.data.message.type === "CONNECT_NETWORK_MISMATCH") { - reject( - new PeraWalletConnectError( - { - type: "CONNECT_NETWORK_MISMATCH", - detail: event.data.message.error - }, - event.data.message.error || - `Your wallet is connected to a different network to this dApp. Update your wallet to the correct network (MainNet or TestNet) to continue.` - ) - ); - - onClose(); - - newPeraWalletTab?.close(); - } - } - }); - }) - .catch((error) => { - onClose(); - reject(error); - }); - } - } - - function onClose() { - removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); - } - - return { - onWebWalletConnect - }; - } - connect() { return new Promise(async (resolve, reject) => { try { @@ -387,12 +110,12 @@ class PeraWalletConnect { shouldUseSound } = await getPeraConnectConfig(); - const {onWebWalletConnect} = this.connectWithWebWallet( + const onWebWalletConnect = runWebConnectFlow({ resolve, reject, webWalletURL, - this.chainId - ); + chainId: this.chainId + }); if (isWebWalletAvailable) { // @ts-ignore ts-2339 @@ -414,7 +137,7 @@ class PeraWalletConnect { chainId: this.chainId || 4160 }); - setupPeraWalletConnectModalCloseListener(() => + setupPeraWalletConnectModalCloseListener(PERA_WALLET_CONNECT_MODAL_ID, () => reject( new PeraWalletConnectError( { @@ -480,9 +203,7 @@ class PeraWalletConnect { ); } } - // ================================================= // - // ================================================= // // Pera Mobile Wallet flow if (this.connector) { resolve(this.connector.accounts || []); @@ -497,7 +218,6 @@ class PeraWalletConnect { resolve(this.connector?.accounts || []); } - // ================================================= // // If there is no wallet details in storage, resolve the promise with empty array if (!this.isConnected) { @@ -578,247 +298,95 @@ class PeraWalletConnect { private signTransactionWithWeb( signTxnRequestParams: PeraWalletTransaction[], webWalletURL: string - ) { - return new Promise((resolve, reject) => { - const webWalletURLs = getPeraWebWalletURL(webWalletURL); - const browser = detectBrowser(); - - if (browser === "Chrome") { - openPeraWalletSignTxnModal() - .then((modal) => { - const peraWalletSignTxnModalIFrameWrapper = modal; - - const peraWalletIframe = document.createElement("iframe"); - const peraWalletIframeSrc = generateEmbeddedWalletURL( - webWalletURLs.TRANSACTION_SIGN - ); - const peraWalletIframeAllow = `hid ${peraWalletIframeSrc}; bluetooth ${peraWalletIframeSrc}`; - - peraWalletIframe.setAttribute("id", PERA_WALLET_IFRAME_ID); - peraWalletIframe.setAttribute("src", peraWalletIframeSrc); - peraWalletIframe.setAttribute("allow", peraWalletIframeAllow); - - peraWalletSignTxnModalIFrameWrapper?.appendChild(peraWalletIframe); - - const peraWalletSignTxnModalHeader = document - .getElementById(PERA_WALLET_SIGN_TXN_MODAL_ID) - ?.querySelector("pera-wallet-sign-txn-modal") - ?.shadowRoot?.querySelector(`pera-wallet-modal-header`); - - const peraWalletSignTxnModalCloseButton = - peraWalletSignTxnModalHeader?.shadowRoot?.getElementById( - "pera-wallet-modal-header-close-button" - ); - - if (peraWalletSignTxnModalCloseButton) { - peraWalletSignTxnModalCloseButton.addEventListener("click", () => { - reject( - new PeraWalletConnectError( - { - type: "SIGN_TXN_CANCELLED" - }, - "Transaction signing is cancelled by user." - ) - ); - - removeModalWrapperFromDOM(PERA_WALLET_SIGN_TXN_MODAL_ID); - }); - } - - if (peraWalletIframe.contentWindow) { - let count = 0; - - const checkIframeIsInitialized = setInterval(() => { - count += 1; - - if (count === WAIT_FOR_TAB_MAX_TRY_COUNT) { - clearInterval(checkIframeIsInitialized); - - return; - } - - appTellerManager.sendMessage({ - message: { - type: "IFRAME_INITIALIZED" - }, - - origin: webWalletURLs.CONNECT, - targetWindow: peraWalletIframe.contentWindow! - }); - }, WAIT_FOR_TAB_TRY_INTERVAL); - - appTellerManager.setupListener({ - onReceiveMessage: (event: MessageEvent>) => { - if (event.data.message.type === "IFRAME_INITIALIZED_RECEIVED") { - clearInterval(checkIframeIsInitialized); - - appTellerManager.sendMessage({ - message: { - type: "SIGN_TXN", - txn: signTxnRequestParams - }, - - origin: generateEmbeddedWalletURL(webWalletURLs.TRANSACTION_SIGN), - targetWindow: peraWalletIframe.contentWindow! - }); - } - - if (event.data.message.type === "SIGN_TXN_CALLBACK") { - document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); - closePeraWalletSignTxnModal(); - - resolve( - event.data.message.signedTxns.map((txn) => - base64ToUint8Array(txn.signedTxn) - ) - ); - } - - if (event.data.message.type === "SIGN_TXN_NETWORK_MISMATCH") { - reject( - new PeraWalletConnectError( - { - type: "SIGN_TXN_NETWORK_MISMATCH", - detail: event.data.message.error - }, - event.data.message.error || "Network mismatch" - ) - ); - } - - if (event.data.message.type === "SESSION_DISCONNECTED") { - document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); - closePeraWalletSignTxnModal(); - - resetWalletDetailsFromStorage(); - - reject( - new PeraWalletConnectError( - { - type: "SESSION_DISCONNECTED", - detail: event.data.message.error - }, - event.data.message.error - ) - ); - } - - if (event.data.message.type === "SIGN_TXN_CALLBACK_ERROR") { - document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); - closePeraWalletSignTxnModal(); - - reject( - new PeraWalletConnectError( - { - type: "SIGN_TXN_CANCELLED" - }, - event.data.message.error - ) - ); - } - } - }); - } - - // Returns a promise that waits for the response from the web wallet. - // The promise is resolved when the web wallet responds with the signed txn. - // The promise is rejected when the web wallet responds with an error. - }) - .catch((error) => { - console.log(error); - }); - } else { - waitForTabOpening(webWalletURLs.TRANSACTION_SIGN) - .then((newPeraWalletTab) => { - if (newPeraWalletTab) { - appTellerManager.sendMessage({ - message: { - type: "SIGN_TXN", - txn: signTxnRequestParams - }, + ): Promise { + return new Promise((resolve, reject) => + runWebSignTransactionFlow({ + signTxnRequestParams, + webWalletURL, + method: "SIGN_TXN", + resolve, + reject + }) + ); + } - origin: webWalletURLs.TRANSACTION_SIGN, - targetWindow: newPeraWalletTab - }); - } - - const checkTabIsAliveInterval = setInterval(() => { - if (newPeraWalletTab?.closed === true) { - reject( - new PeraWalletConnectError( - { - type: "SIGN_TXN_CANCELLED" - }, - "Transaction signing is cancelled by user." - ) - ); - - clearInterval(checkTabIsAliveInterval); - } - - // eslint-disable-next-line no-magic-numbers - }, 2000); - - appTellerManager.setupListener({ - onReceiveMessage: (event: MessageEvent>) => { - if (event.data.message.type === "SIGN_TXN_CALLBACK") { - newPeraWalletTab?.close(); - - resolve( - event.data.message.signedTxns.map((txn) => - base64ToUint8Array(txn.signedTxn) - ) - ); - } - - if (event.data.message.type === "SIGN_TXN_NETWORK_MISMATCH") { - reject( - new PeraWalletConnectError( - { - type: "SIGN_TXN_NETWORK_MISMATCH", - detail: event.data.message.error - }, - event.data.message.error || "Network mismatch" - ) - ); - } - - if (event.data.message.type === "SESSION_DISCONNECTED") { - newPeraWalletTab?.close(); - - resetWalletDetailsFromStorage(); - - reject( - new PeraWalletConnectError( - { - type: "SESSION_DISCONNECTED", - detail: event.data.message.error - }, - event.data.message.error - ) - ); - } - - if (event.data.message.type === "SIGN_TXN_CALLBACK_ERROR") { - newPeraWalletTab?.close(); - - reject( - new PeraWalletConnectError( - { - type: "SIGN_TXN_CANCELLED" - }, - event.data.message.error - ) - ); - } - } - }); - }) - .catch((error) => { - reject(error); - }); + private async signDataWithMobile({ + data, + signer, + chainId + }: { + data: PeraWalletArbitraryData[]; + signer: string; + chainId: AlgorandChainIDs; + }) { + const formattedSignTxnRequest = formatJsonRpcRequest( + "algo_signData", + data.map((item) => ({ + ...item, + + signer, + chainId + })) + ); + + try { + try { + const {silent} = await getPeraConnectConfig(); + + const response = await this.connector!.sendCustomRequest( + formattedSignTxnRequest, + { + forcePushNotification: !silent + } + ); + + // We send the full txn group to the mobile wallet. + // Therefore, we first filter out txns that were not signed by the wallet. + // These are received as `null`. + const nonNullResponse = response.filter(Boolean) as (string | number[])[]; + + return typeof nonNullResponse[0] === "string" + ? (nonNullResponse as string[]).map(base64ToUint8Array) + : (nonNullResponse as number[][]).map((item) => Uint8Array.from(item)); + } catch (error) { + return await Promise.reject( + new PeraWalletConnectError( + { + type: "SIGN_TRANSACTIONS", + detail: error + }, + error.message || "Failed to sign transaction" + ) + ); } - }); + } finally { + removeModalWrapperFromDOM(PERA_WALLET_REDIRECT_MODAL_ID); + removeModalWrapperFromDOM(PERA_WALLET_SIGN_TXN_TOAST_ID); + } + } + + private signDataWithWeb({ + data, + signer, + chainId, + webWalletURL + }: { + data: PeraWalletArbitraryData[]; + signer: string; + chainId: AlgorandChainIDs; + webWalletURL: string; + }): Promise { + return new Promise((resolve, reject) => + runWebSignTransactionFlow({ + method: "SIGN_DATA", + signTxnRequestParams: data, + signer, + chainId, + webWalletURL, + resolve, + reject + }) + ); } async signTransaction( @@ -846,20 +414,51 @@ class PeraWalletConnect { ) ); - // ================================================= // // Pera Wallet Web flow if (this.platform === "web") { const {webWalletURL} = await getPeraConnectConfig(); return this.signTransactionWithWeb(signTxnRequestParams, webWalletURL); } - // ================================================= // - // ================================================= // // Pera Mobile Wallet flow return this.signTransactionWithMobile(signTxnRequestParams); // ================================================= // } + + async signData(data: PeraWalletArbitraryData[], signer: string): Promise { + // eslint-disable-next-line no-magic-numbers + const chainId = this.chainId || 4160; + + if (this.platform === "mobile") { + if (isMobile()) { + // This is to automatically open the wallet app when trying to sign with it. + openPeraWalletRedirectModal(); + } else if (!isMobile() && this.shouldShowSignTxnToast) { + // This is to inform user go the wallet app when trying to sign with it. + openPeraWalletSignTxnToast(); + } + + if (!this.connector) { + throw new Error("PeraWalletConnect was not initialized correctly."); + } + } + + // Pera Wallet Web flow + if (this.platform === "web") { + const {webWalletURL} = await getPeraConnectConfig(); + + return this.signDataWithWeb({ + data, + signer, + chainId, + webWalletURL + }); + } + + // Pera Mobile Wallet flow + return this.signDataWithMobile({data, signer, chainId}); + } } export default PeraWalletConnect; diff --git a/src/modal/header/PeraWalletModalHeader.ts b/src/modal/header/PeraWalletModalHeader.ts index 541b64f..a835283 100644 --- a/src/modal/header/PeraWalletModalHeader.ts +++ b/src/modal/header/PeraWalletModalHeader.ts @@ -24,7 +24,11 @@ peraWalletModalHeader.innerHTML = ` : `
- Pera Connect +
+ Pera Connect + + PERA_CONNECT_VERSION +
` } diff --git a/src/modal/header/_pera-wallet-modal-header.scss b/src/modal/header/_pera-wallet-modal-header.scss index 99d5318..4e0bea2 100644 --- a/src/modal/header/_pera-wallet-modal-header.scss +++ b/src/modal/header/_pera-wallet-modal-header.scss @@ -36,14 +36,29 @@ align-items: center; gap: 10px; - font-size: 15px; + font-size: 14px; font-weight: 600; - line-height: 18px; - letter-spacing: -0.13px; + line-height: 24px; + letter-spacing: -0.1px; color: #ffffff; } +.pera-wallet-modal-header__brand-text { + display: flex; + align-items: center; + gap: 6px; +} + +.pera-wallet-modal-header__version-number { + color: #9d9dae; + + font-size: 12px; + font-weight: 400; + line-height: 18px; + letter-spacing: 0.01; +} + .pera-wallet-button { display: flex; align-items: center; diff --git a/src/modal/mode/desktop/PeraWalletConnectModalDesktopMode.ts b/src/modal/mode/desktop/PeraWalletConnectModalDesktopMode.ts index d2b4ceb..e753b25 100644 --- a/src/modal/mode/desktop/PeraWalletConnectModalDesktopMode.ts +++ b/src/modal/mode/desktop/PeraWalletConnectModalDesktopMode.ts @@ -12,7 +12,7 @@ import QRCodeStyling from "qr-code-styling"; import styles from "./_pera-wallet-connect-modal-desktop-mode.scss"; import accordionStyles from "./accordion/_pera-wallet-accordion.scss"; -import {detectBrowser} from "../../../util/device/deviceUtils"; +import {peraWalletFlowType} from "../../../util/device/deviceUtils"; const peraWalletConnectModalDesktopMode = document.createElement("template"); const styleSheet = document.createElement("style"); @@ -47,7 +47,7 @@ const peraWalletConnectModalDesktopModeDefaultView = `
${ - detectBrowser() === "Chrome" + peraWalletFlowType() === "EMBEDDED" ? `
` : `
@@ -190,7 +190,7 @@ export class PeraWalletModalDesktopMode extends HTMLElement { this.handleChangeView(); - if (detectBrowser() === "Chrome" && this.shadowRoot) { + if (peraWalletFlowType() === "EMBEDDED" && this.shadowRoot) { const iframeWrapper = this.shadowRoot.querySelector( ".pera-wallet-connect-modal-desktop-mode__web-wallet-iframe" ); diff --git a/src/modal/peraWalletConnectModalUtils.ts b/src/modal/peraWalletConnectModalUtils.ts index 1aa63ff..ee912ce 100644 --- a/src/modal/peraWalletConnectModalUtils.ts +++ b/src/modal/peraWalletConnectModalUtils.ts @@ -31,6 +31,12 @@ const PERA_WALLET_MODAL_CLASSNAME = "pera-wallet-modal"; const PERA_WALLET_WEB_WALLET_IFRAME_CLASSNAME = "pera-wallet-connect-modal-desktop-mode__web-wallet-iframe"; +/** + * Creates a Div element with the given ID and appends it to the DOM. + * @param modalId string + * + * @returns HTMLDivElement + */ function createModalWrapperOnDOM(modalId: string) { const wrapper = document.createElement("div"); @@ -41,6 +47,12 @@ function createModalWrapperOnDOM(modalId: string) { return wrapper; } +/** + * Creates a modal wrapper on the DOM and renders a PeraWalletConnectModal instance on it. + * @param modalConfig PeraWalletModalConfig + * + * @returns VoidFunction + */ function openPeraWalletConnectModal(modalConfig: PeraWalletModalConfig) { return (uri: string) => { if (!document.getElementById(PERA_WALLET_CONNECT_MODAL_ID)) { @@ -53,13 +65,22 @@ function openPeraWalletConnectModal(modalConfig: PeraWalletModalConfig) { }; } -function setupPeraWalletConnectModalCloseListener(onClose: VoidFunction) { - const peraWalletConnectModalWrapper = document.getElementById( - PERA_WALLET_CONNECT_MODAL_ID - ); +/** + * Adds a listener to the close button of the given modal. + * + * @param modalId string + * @param onClose VoidFunction + * + * @returns {void} + */ +function setupPeraWalletConnectModalCloseListener( + modalId: string, + onClose: VoidFunction +) { + const peraWalletConnectModalWrapper = document.getElementById(modalId); const peraWalletConnectModal = peraWalletConnectModalWrapper - ?.querySelector("pera-wallet-connect-modal") + ?.querySelector(modalId.replace("-wrapper", "")) ?.shadowRoot?.querySelector(`.${PERA_WALLET_MODAL_CLASSNAME}`); const closeButton = peraWalletConnectModal @@ -69,7 +90,7 @@ function setupPeraWalletConnectModalCloseListener(onClose: VoidFunction) { closeButton?.addEventListener("click", () => { onClose(); - removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); + removeModalWrapperFromDOM(modalId); }); } @@ -84,6 +105,11 @@ function openPeraWalletRedirectModal() { root.innerHTML = ""; } +/** + * Creates a PeraWalletSignTxnModal instance and renders it on the DOM. + * + * @returns {Promise} + */ function openPeraWalletSignTxnModal() { const root = createModalWrapperOnDOM(PERA_WALLET_SIGN_TXN_MODAL_ID); @@ -99,6 +125,12 @@ function openPeraWalletSignTxnModal() { : Promise.reject(); } +/** + * Close the PeraWalletSignTxnModal instance and remove it from the DOM. + * @param rejectPromise + * + * @returns {void} + */ function closePeraWalletSignTxnModal(rejectPromise?: (error: any) => void) { removeModalWrapperFromDOM(PERA_WALLET_SIGN_TXN_MODAL_ID); @@ -125,10 +157,21 @@ function openPeraWalletSignTxnToast() { root.innerHTML = ""; } +/** + * Close the PeraWalletSignTxnToast instance and remove it from the DOM. + * + * @returns {void} + */ function closePeraWalletSignTxnToast() { removeModalWrapperFromDOM(PERA_WALLET_SIGN_TXN_TOAST_ID); } +/** + * Removes the modal wrapper with given ID from the DOM. + * @param modalId string + * + * @returns {void} + */ function removeModalWrapperFromDOM(modalId: string) { const wrapper = document.getElementById(modalId); @@ -137,6 +180,26 @@ function removeModalWrapperFromDOM(modalId: string) { } } +/** + * Return the close button element in the header as a result + * @param type "connect" | "sign-txn" + * + * @returns HTMLElement | null | undefined + */ +function getHeaderCloseButton(type: "connect" | "sign-txn") { + const modalId = + type === "connect" ? PERA_WALLET_CONNECT_MODAL_ID : PERA_WALLET_SIGN_TXN_MODAL_ID; + const peraWalletConnectModal = document + .getElementById(modalId) + ?.querySelector(modalId.replace("-wrapper", "")) + ?.shadowRoot?.querySelector(`.${PERA_WALLET_MODAL_CLASSNAME}`); + const closeButton = peraWalletConnectModal + ?.querySelector("pera-wallet-modal-header") + ?.shadowRoot?.getElementById("pera-wallet-modal-header-close-button"); + + return closeButton; +} + export { PERA_WALLET_CONNECT_MODAL_ID, PERA_WALLET_REDIRECT_MODAL_ID, @@ -152,5 +215,6 @@ export { closePeraWalletSignTxnToast, removeModalWrapperFromDOM, openPeraWalletSignTxnModal, - closePeraWalletSignTxnModal + closePeraWalletSignTxnModal, + getHeaderCloseButton }; diff --git a/src/util/PeraWalletConnectError.ts b/src/util/PeraWalletConnectError.ts index 7075914..09b4fea 100644 --- a/src/util/PeraWalletConnectError.ts +++ b/src/util/PeraWalletConnectError.ts @@ -1,17 +1,25 @@ interface PeraWalletConnectErrorData { type: - | "SIGN_TRANSACTIONS" + | "MESSAGE_NOT_RECEIVED" + | "OPERATION_CANCELLED" + + // Connect + | "CONNECT_MODAL_CLOSED" + | "CONNECT_CANCELLED" + | "CONNECT_NETWORK_MISMATCH" + + // Reconnect | "SESSION_DISCONNECTED" | "SESSION_UPDATE" | "SESSION_CONNECT" | "SESSION_RECONNECT" - | "CONNECT_MODAL_CLOSED" - | "CONNECT_CANCELLED" + + // Sign + | "SIGN_TRANSACTIONS" | "SIGN_TXN_CANCELLED" - | "CONNECT_NETWORK_MISMATCH" | "SIGN_TXN_NETWORK_MISMATCH" - | "MESSAGE_NOT_RECEIVED" - | "OPERATION_CANCELLED"; + | "SIGN_DATA_CANCELLED" + | "SIGN_DATA_NETWORK_MISMATCH"; detail?: any; } diff --git a/src/util/connect/connectFlow.ts b/src/util/connect/connectFlow.ts new file mode 100644 index 0000000..a34b637 --- /dev/null +++ b/src/util/connect/connectFlow.ts @@ -0,0 +1,145 @@ +import { + PERA_WALLET_IFRAME_ID, + PERA_WALLET_CONNECT_MODAL_ID, + removeModalWrapperFromDOM, + getHeaderCloseButton +} from "../../modal/peraWalletConnectModalUtils"; +import PeraWalletConnectError from "../PeraWalletConnectError"; +import {peraWalletFlowType} from "../device/deviceUtils"; +import { + WAIT_FOR_TAB_MAX_TRY_COUNT, + WAIT_FOR_TAB_TRY_INTERVAL, + getMetaInfo, + waitForTabOpening +} from "../dom/domUtils"; +import appTellerManager, {PeraTeller} from "../network/teller/appTellerManager"; +import {getPeraWebWalletURL} from "../peraWalletConstants"; +import {generateEmbeddedWalletURL} from "../peraWalletUtils"; +import {RunWebConnectFlowTypes} from "./connectFlowModels"; +import { + embeddedConnectFlowTellerReducer, + newTabConnectFlowTellerReducer +} from "./connectFlowReducers"; + +function runWebConnectFlow({ + webWalletURL, + chainId, + resolve, + reject +}: RunWebConnectFlowTypes) { + const webWalletURLs = getPeraWebWalletURL(webWalletURL); + + if (peraWalletFlowType() === "EMBEDDED") { + return runEmbeddedWebConnectFlow; + } + + return runNewTabWebConnectFlow; + + // =========== Embedded Connect Flow =========== + function runEmbeddedWebConnectFlow(peraWalletIframeWrapper: Element) { + const peraWalletIframe = document.createElement("iframe"); + + peraWalletIframe.setAttribute("id", PERA_WALLET_IFRAME_ID); + peraWalletIframe.setAttribute( + "src", + generateEmbeddedWalletURL(webWalletURLs.CONNECT) + ); + + peraWalletIframeWrapper.appendChild(peraWalletIframe); + + if (peraWalletIframe.contentWindow) { + let count = 0; + + const isIframeInitializedChecker = setInterval(() => { + count += 1; + + if (count === WAIT_FOR_TAB_MAX_TRY_COUNT) { + clearInterval(isIframeInitializedChecker); + + return; + } + + appTellerManager.sendMessage({ + message: { + type: "IFRAME_INITIALIZED" + }, + + origin: webWalletURLs.CONNECT, + targetWindow: peraWalletIframe.contentWindow! + }); + + getHeaderCloseButton("connect")?.addEventListener("click", () => { + clearInterval(isIframeInitializedChecker); + }); + }, WAIT_FOR_TAB_TRY_INTERVAL); + + appTellerManager.setupListener({ + onReceiveMessage: (event: MessageEvent>) => + embeddedConnectFlowTellerReducer({ + event, + peraWalletIframe, + chainId, + isIframeInitializedChecker, + webWalletURLs, + resolve, + reject + }) + }); + } + } + + // =========== New Tab Connect Flow =========== + async function runNewTabWebConnectFlow() { + try { + const newPeraWalletTab = await waitForTabOpening(webWalletURLs.CONNECT); + + if (newPeraWalletTab) { + appTellerManager.sendMessage({ + message: { + type: "CONNECT", + data: { + ...getMetaInfo(), + chainId + } + }, + + origin: webWalletURLs.CONNECT, + targetWindow: newPeraWalletTab + }); + } + + const checkTabIsAliveInterval = setInterval(() => { + if (newPeraWalletTab?.closed === true) { + reject( + new PeraWalletConnectError( + { + type: "CONNECT_CANCELLED" + }, + "Connect is cancelled by user" + ) + ); + + clearInterval(checkTabIsAliveInterval); + closeWebConnectFlow(); + } + + // eslint-disable-next-line no-magic-numbers + }, 2000); + + appTellerManager.setupListener({ + onReceiveMessage: (event: MessageEvent>) => + newTabConnectFlowTellerReducer({event, newPeraWalletTab, resolve, reject}) + }); + } catch (error) { + closeWebConnectFlow(); + + reject(error); + } + } + + function closeWebConnectFlow() { + removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); + } +} + +export {runWebConnectFlow}; diff --git a/src/util/connect/connectFlowModels.ts b/src/util/connect/connectFlowModels.ts new file mode 100644 index 0000000..4cbc575 --- /dev/null +++ b/src/util/connect/connectFlowModels.ts @@ -0,0 +1,33 @@ +import {PeraTeller} from "../network/teller/appTellerManager"; +import {PeraWebWalletURLs} from "../peraWalletConstants"; +import {AlgorandChainIDs} from "../peraWalletTypes"; + +interface ConnectFlowPromise { + resolve: (accounts: string[]) => void; + reject: (reason?: any) => void; +} + +interface RunWebConnectFlowTypes extends ConnectFlowPromise { + webWalletURL: string; + chainId: AlgorandChainIDs | undefined; +} + +interface EmbeddedConnectFlowTellerReducerParams extends ConnectFlowPromise { + event: MessageEvent>; + peraWalletIframe: HTMLIFrameElement; + chainId: AlgorandChainIDs | undefined; + isIframeInitializedChecker: NodeJS.Timer; + webWalletURLs: PeraWebWalletURLs; +} + +interface NewTabConnectFlowTellerReducerParams extends ConnectFlowPromise { + event: MessageEvent>; + newPeraWalletTab: Window | null; +} + +export type { + ConnectFlowPromise, + RunWebConnectFlowTypes, + EmbeddedConnectFlowTellerReducerParams, + NewTabConnectFlowTellerReducerParams +}; diff --git a/src/util/connect/connectFlowReducers.ts b/src/util/connect/connectFlowReducers.ts new file mode 100644 index 0000000..c80f773 --- /dev/null +++ b/src/util/connect/connectFlowReducers.ts @@ -0,0 +1,209 @@ +import { + PERA_WALLET_IFRAME_ID, + PERA_WALLET_CONNECT_MODAL_ID, + PERA_WALLET_MODAL_CLASSNAME, + removeModalWrapperFromDOM +} from "../../modal/peraWalletConnectModalUtils"; +import PeraWalletConnectError from "../PeraWalletConnectError"; +import {getMetaInfo, waitForTabOpening} from "../dom/domUtils"; +import appTellerManager, {PeraTeller} from "../network/teller/appTellerManager"; +import {saveWalletDetailsToStorage} from "../storage/storageUtils"; +import { + EmbeddedConnectFlowTellerReducerParams, + NewTabConnectFlowTellerReducerParams +} from "./connectFlowModels"; + +// =========== Embedded Connect Flow =========== +function embeddedConnectFlowTellerReducer({ + event, + peraWalletIframe, + chainId, + isIframeInitializedChecker, + webWalletURLs, + resolve, + reject +}: EmbeddedConnectFlowTellerReducerParams) { + switch (event.data.message.type) { + case "IFRAME_INITIALIZED_RECEIVED": + clearInterval(isIframeInitializedChecker); + + appTellerManager.sendMessage({ + message: { + type: "CONNECT", + data: { + ...getMetaInfo(), + chainId + } + }, + + origin: webWalletURLs.CONNECT, + targetWindow: peraWalletIframe.contentWindow! + }); + break; + + case "CONNECT_CALLBACK": { + const accounts = event.data.message.data.addresses; + + saveWalletDetailsToStorage(accounts, "pera-wallet-web"); + + resolve(accounts); + + removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); + + document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); + + break; + } + + case "CONNECT_NETWORK_MISMATCH": + reject( + new PeraWalletConnectError( + { + type: "CONNECT_NETWORK_MISMATCH", + detail: event.data.message.error + }, + event.data.message.error || + `Your wallet is connected to a different network to this dApp. Update your wallet to the correct network (MainNet or TestNet) to continue.` + ) + ); + + removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); + + document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); + break; + + case "CREATE_PASSCODE_EMBEDDED": { + waitForTabOpening(webWalletURLs.CONNECT).then((newPeraWalletTab) => { + if (newPeraWalletTab) { + appTellerManager.sendMessage({ + message: { + type: "CONNECT", + data: { + ...getMetaInfo(), + chainId + } + }, + + origin: webWalletURLs.CONNECT, + targetWindow: newPeraWalletTab + }); + } + + const checkTabIsAliveInterval = setInterval(() => { + if (newPeraWalletTab?.closed === true) { + reject( + new PeraWalletConnectError( + { + type: "CONNECT_CANCELLED" + }, + "Connect is cancelled by user" + ) + ); + + removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); + clearInterval(checkTabIsAliveInterval); + } + + // eslint-disable-next-line no-magic-numbers + }, 2000); + + appTellerManager.setupListener({ + onReceiveMessage: (newTabEvent: MessageEvent>) => { + if (resolve && newTabEvent.data.message.type === "CONNECT_CALLBACK") { + const accounts = newTabEvent.data.message.data.addresses; + + saveWalletDetailsToStorage(accounts, "pera-wallet-web"); + + resolve(accounts); + + removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); + + newPeraWalletTab?.close(); + } + } + }); + }); + + break; + } + + case "SELECT_ACCOUNT_EMBEDDED": { + const peraWalletConnectModalWrapper = document.getElementById( + PERA_WALLET_CONNECT_MODAL_ID + ); + + const peraWalletConnectModal = peraWalletConnectModalWrapper + ?.querySelector("pera-wallet-connect-modal") + ?.shadowRoot?.querySelector(`.${PERA_WALLET_MODAL_CLASSNAME}`); + + const peraWalletConnectModalDesktopMode = peraWalletConnectModal + ?.querySelector("pera-wallet-modal-desktop-mode") + ?.shadowRoot?.querySelector(".pera-wallet-connect-modal-desktop-mode"); + + if (peraWalletConnectModal && peraWalletConnectModalDesktopMode) { + peraWalletConnectModal.classList.add( + `${PERA_WALLET_MODAL_CLASSNAME}--select-account` + ); + peraWalletConnectModal.classList.remove( + `${PERA_WALLET_MODAL_CLASSNAME}--create-passcode` + ); + peraWalletConnectModalDesktopMode.classList.add( + `pera-wallet-connect-modal-desktop-mode--select-account` + ); + peraWalletConnectModalDesktopMode.classList.remove( + `pera-wallet-connect-modal-desktop-mode--create-passcode` + ); + } + + appTellerManager.sendMessage({ + message: { + type: "SELECT_ACCOUNT_EMBEDDED_CALLBACK" + }, + origin: webWalletURLs.CONNECT, + targetWindow: peraWalletIframe.contentWindow! + }); + + break; + } + + default: + break; + } +} + +// =========== New Tab Connect Flow =========== +function newTabConnectFlowTellerReducer({ + event, + newPeraWalletTab, + resolve, + reject +}: NewTabConnectFlowTellerReducerParams) { + if (resolve && event.data.message.type === "CONNECT_CALLBACK") { + const accounts = event.data.message.data.addresses; + + saveWalletDetailsToStorage(accounts, "pera-wallet-web"); + + resolve(accounts); + + removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); + + newPeraWalletTab?.close(); + } else if (event.data.message.type === "CONNECT_NETWORK_MISMATCH") { + reject( + new PeraWalletConnectError( + { + type: "CONNECT_NETWORK_MISMATCH", + detail: event.data.message.error + }, + event.data.message.error || + `Your wallet is connected to a different network to this dApp. Update your wallet to the correct network (MainNet or TestNet) to continue.` + ) + ); + + removeModalWrapperFromDOM(PERA_WALLET_CONNECT_MODAL_ID); + + newPeraWalletTab?.close(); + } +} + +export {embeddedConnectFlowTellerReducer, newTabConnectFlowTellerReducer}; diff --git a/src/util/device/deviceUtils.ts b/src/util/device/deviceUtils.ts index 9f31565..3e88f9d 100644 --- a/src/util/device/deviceUtils.ts +++ b/src/util/device/deviceUtils.ts @@ -1,5 +1,7 @@ import Bowser from "bowser"; +import {PeraWalletFlowType} from "../peraWalletTypes"; + function isNavigatorAvailable() { return typeof navigator !== "undefined"; } @@ -43,4 +45,13 @@ function detectBrowser() { return browserName; } -export {isAndroid, isIOS, isMobile, detectBrowser}; +/** + * Returns the flow type based on the browser + * + * @returns {PeraWalletFlowType} EMBEDDED | NEW_TAB + */ +function peraWalletFlowType(): PeraWalletFlowType { + return detectBrowser() === "Chrome" ? "EMBEDDED" : "NEW_TAB"; +} + +export {isAndroid, isIOS, isMobile, detectBrowser, peraWalletFlowType}; diff --git a/src/util/model/peraWalletModels.ts b/src/util/model/peraWalletModels.ts index 408550e..9643e5a 100644 --- a/src/util/model/peraWalletModels.ts +++ b/src/util/model/peraWalletModels.ts @@ -77,5 +77,13 @@ export interface PeraWalletTransaction { */ message?: string; } - // Reference: https://github.com/jasonpaulos/algorand-walletconnect-example-dapp/blob/algorand/src/helpers/types.ts + +export interface PeraWalletArbitraryData { + data: Uint8Array; + + /** + * Required message explaining the reason for the action + */ + message: string; +} diff --git a/src/util/network/teller/appTellerManager.ts b/src/util/network/teller/appTellerManager.ts index 67d8b52..519d2f3 100644 --- a/src/util/network/teller/appTellerManager.ts +++ b/src/util/network/teller/appTellerManager.ts @@ -1,4 +1,8 @@ -import {PeraWalletTransaction} from "../../model/peraWalletModels"; +import { + PeraWalletArbitraryData, + PeraWalletTransaction +} from "../../model/peraWalletModels"; +import {AlgorandChainIDs} from "../../peraWalletTypes"; import Teller from "./Teller"; export type PeraTeller = @@ -53,6 +57,26 @@ export type PeraTeller = type: "SIGN_TXN_CALLBACK_ERROR"; error: string; } + | { + type: "SIGN_DATA"; + signer: string; + chainId: AlgorandChainIDs; + data: PeraWalletArbitraryData[]; + } + | { + type: "SIGN_DATA_CALLBACK"; + signedData: { + signedData: string; + }[]; + } + | { + type: "SIGN_DATA_NETWORK_MISMATCH"; + error: string; + } + | { + type: "SIGN_DATA_CALLBACK_ERROR"; + error: string; + } | { type: "SESSION_DISCONNECTED"; error: string; diff --git a/src/util/peraWalletConstants.ts b/src/util/peraWalletConstants.ts index 49d612b..79176c5 100644 --- a/src/util/peraWalletConstants.ts +++ b/src/util/peraWalletConstants.ts @@ -1,9 +1,15 @@ import {isAndroid} from "./device/deviceUtils"; -const PERA_WALLET_APP_DEEP_LINK = isAndroid() ? "algorand://" : "algorand-wc://"; +const PERA_WALLET_APP_DEEP_LINK = isAndroid() ? "algorand://" : "perawallet-wc://"; const PERA_DOWNLOAD_URL = "https://perawallet.app/download/"; -function getPeraWebWalletURL(webWalletURL: string) { +export interface PeraWebWalletURLs { + ROOT: string; + CONNECT: string; + TRANSACTION_SIGN: string; +} + +function getPeraWebWalletURL(webWalletURL: string): PeraWebWalletURLs { return { ROOT: `https://${webWalletURL}`, CONNECT: `https://${webWalletURL}/connect`, @@ -11,4 +17,8 @@ function getPeraWebWalletURL(webWalletURL: string) { }; } -export {PERA_WALLET_APP_DEEP_LINK, getPeraWebWalletURL, PERA_DOWNLOAD_URL}; +export { + PERA_WALLET_APP_DEEP_LINK, + getPeraWebWalletURL, + PERA_DOWNLOAD_URL +}; diff --git a/src/util/peraWalletTypes.ts b/src/util/peraWalletTypes.ts index c30c055..57cca09 100644 --- a/src/util/peraWalletTypes.ts +++ b/src/util/peraWalletTypes.ts @@ -1,5 +1,6 @@ type PeraWalletType = "pera-wallet" | "pera-wallet-web"; type PeraWalletPlatformType = "mobile" | "web" | null; +type PeraWalletFlowType = "EMBEDDED" | "NEW_TAB"; // eslint-disable-next-line no-magic-numbers type AlgorandChainIDs = 416001 | 416002 | 416003 | 4160; @@ -10,4 +11,10 @@ interface PeraWalletDetails { selectedAccount: string; } -export type {PeraWalletType, PeraWalletPlatformType, PeraWalletDetails, AlgorandChainIDs}; +export type { + PeraWalletType, + PeraWalletPlatformType, + PeraWalletDetails, + AlgorandChainIDs, + PeraWalletFlowType +}; diff --git a/src/util/sign/signTransactionFlow.ts b/src/util/sign/signTransactionFlow.ts new file mode 100644 index 0000000..a973bf1 --- /dev/null +++ b/src/util/sign/signTransactionFlow.ts @@ -0,0 +1,186 @@ +import { + openPeraWalletSignTxnModal, + PERA_WALLET_IFRAME_ID, + setupPeraWalletConnectModalCloseListener, + PERA_WALLET_SIGN_TXN_MODAL_ID, + getHeaderCloseButton +} from "../../modal/peraWalletConnectModalUtils"; +import PeraWalletConnectError from "../PeraWalletConnectError"; +import {peraWalletFlowType} from "../device/deviceUtils"; +import { + WAIT_FOR_TAB_MAX_TRY_COUNT, + WAIT_FOR_TAB_TRY_INTERVAL, + waitForTabOpening +} from "../dom/domUtils"; +import {PeraWalletArbitraryData, PeraWalletTransaction} from "../model/peraWalletModels"; +import appTellerManager, {PeraTeller} from "../network/teller/appTellerManager"; +import {getPeraWebWalletURL} from "../peraWalletConstants"; +import {generateEmbeddedWalletURL} from "../peraWalletUtils"; +import {RunSignTransactionFlowParams} from "./signTransactionFlowModels"; +import { + embeddedSignTransactionFlowTellerReducer, + newTabSignTransactionFlowTellerReducer +} from "./signTransactionFlowReducers"; + +function runWebSignTransactionFlow({ + method, + signTxnRequestParams, + signer, + chainId, + webWalletURL, + resolve, + reject +}: RunSignTransactionFlowParams) { + const webWalletURLs = getPeraWebWalletURL(webWalletURL); + + switch (peraWalletFlowType()) { + case "EMBEDDED": + runEmbeddedSignTransactionFlow(); + break; + + case "NEW_TAB": + runNewTabSignFlow(); + break; + + default: + break; + } + + // =========== Embedded Sign Flow =========== + async function runEmbeddedSignTransactionFlow() { + const modal = await openPeraWalletSignTxnModal(); + + const peraWalletSignTxnModalIFrameWrapper = modal; + + const peraWalletIframe = document.createElement("iframe"); + const peraWalletIframeSrc = generateEmbeddedWalletURL(webWalletURLs.TRANSACTION_SIGN); + const peraWalletIframeAllow = `hid ${peraWalletIframeSrc}; bluetooth ${peraWalletIframeSrc}`; + + peraWalletIframe.setAttribute("id", PERA_WALLET_IFRAME_ID); + peraWalletIframe.setAttribute("src", peraWalletIframeSrc); + peraWalletIframe.setAttribute("allow", peraWalletIframeAllow); + + peraWalletSignTxnModalIFrameWrapper?.appendChild(peraWalletIframe); + + setupPeraWalletConnectModalCloseListener(PERA_WALLET_SIGN_TXN_MODAL_ID, () => + reject( + new PeraWalletConnectError( + { + type: `${method}_CANCELLED` + }, + "Transaction signing is cancelled by user." + ) + ) + ); + + if (peraWalletIframe.contentWindow) { + let count = 0; + + const isIframeInitializedChecker = setInterval(() => { + count += 1; + + if (count === WAIT_FOR_TAB_MAX_TRY_COUNT) { + clearInterval(isIframeInitializedChecker); + + return; + } + + appTellerManager.sendMessage({ + message: { + type: "IFRAME_INITIALIZED" + }, + + origin: webWalletURLs.TRANSACTION_SIGN, + targetWindow: peraWalletIframe.contentWindow! + }); + + getHeaderCloseButton("sign-txn")?.addEventListener("click", () => { + clearInterval(isIframeInitializedChecker); + }); + }, WAIT_FOR_TAB_TRY_INTERVAL); + + appTellerManager.setupListener({ + onReceiveMessage: (event: MessageEvent>) => + embeddedSignTransactionFlowTellerReducer({ + event, + signTxnRequestParams, + peraWalletIframe, + isIframeInitializedChecker, + webWalletURLs, + method, + signer, + chainId, + resolve, + reject + }) + }); + } + } + + // =========== New Tab Sign Flow =========== + async function runNewTabSignFlow() { + try { + const newPeraWalletTab = await waitForTabOpening(webWalletURLs.TRANSACTION_SIGN); + + if (newPeraWalletTab) { + let message; + + if (method === "SIGN_TXN") { + message = { + type: "SIGN_TXN", + txn: signTxnRequestParams as PeraWalletTransaction[] + } as const; + } else if (method === "SIGN_DATA" && signer && chainId) { + message = { + type: "SIGN_DATA", + data: signTxnRequestParams as PeraWalletArbitraryData[], + + signer, + chainId + } as const; + } + + if (message) { + appTellerManager.sendMessage({ + message, + + origin: webWalletURLs.TRANSACTION_SIGN, + targetWindow: newPeraWalletTab + }); + } + } + + const checkTabIsAliveInterval = setInterval(() => { + if (newPeraWalletTab?.closed === true) { + reject( + new PeraWalletConnectError( + { + type: `${method}_CANCELLED` + }, + "Transaction signing is cancelled by user." + ) + ); + + clearInterval(checkTabIsAliveInterval); + } + + // eslint-disable-next-line no-magic-numbers + }, 2000); + + appTellerManager.setupListener({ + onReceiveMessage: (event: MessageEvent>) => + newTabSignTransactionFlowTellerReducer({ + event, + newPeraWalletTab, + method, + resolve, + reject + }) + }); + } catch (error) { + reject(error); + } + } +} + +export {runWebSignTransactionFlow}; diff --git a/src/util/sign/signTransactionFlowModels.ts b/src/util/sign/signTransactionFlowModels.ts new file mode 100644 index 0000000..f50e136 --- /dev/null +++ b/src/util/sign/signTransactionFlowModels.ts @@ -0,0 +1,45 @@ +import PeraWalletConnectError from "../PeraWalletConnectError"; +import {PeraWalletArbitraryData, PeraWalletTransaction} from "../model/peraWalletModels"; +import {PeraTeller} from "../network/teller/appTellerManager"; +import {PeraWebWalletURLs} from "../peraWalletConstants"; +import {AlgorandChainIDs} from "../peraWalletTypes"; + +type SignTransactionFlowMethod = "SIGN_TXN" | "SIGN_DATA"; + +interface SignTransactionFlowPromise { + resolve: (value: Uint8Array[] | PromiseLike) => void; + reject: (error: PeraWalletConnectError) => void; + + signer?: string; + chainId?: AlgorandChainIDs; +} + +interface RunSignTransactionFlowParams extends SignTransactionFlowPromise { + method: SignTransactionFlowMethod; + signTxnRequestParams: PeraWalletTransaction[] | PeraWalletArbitraryData[]; + webWalletURL: string; +} + +interface EmbeddedSignTransactionFlowTellerReducerParams + extends SignTransactionFlowPromise { + event: MessageEvent>; + signTxnRequestParams: PeraWalletTransaction[] | PeraWalletArbitraryData[]; + peraWalletIframe: HTMLIFrameElement; + isIframeInitializedChecker: NodeJS.Timer; + method: SignTransactionFlowMethod; + webWalletURLs: PeraWebWalletURLs; +} + +interface NewTabSignTransactionFlowTellerReducerParams + extends SignTransactionFlowPromise { + event: MessageEvent>; + newPeraWalletTab: Window | null; + method: SignTransactionFlowMethod; +} + +export type { + SignTransactionFlowPromise, + RunSignTransactionFlowParams, + EmbeddedSignTransactionFlowTellerReducerParams, + NewTabSignTransactionFlowTellerReducerParams +}; diff --git a/src/util/sign/signTransactionFlowReducers.ts b/src/util/sign/signTransactionFlowReducers.ts new file mode 100644 index 0000000..c3774a6 --- /dev/null +++ b/src/util/sign/signTransactionFlowReducers.ts @@ -0,0 +1,199 @@ +import { + PERA_WALLET_IFRAME_ID, + closePeraWalletSignTxnModal +} from "../../modal/peraWalletConnectModalUtils"; +import PeraWalletConnectError from "../PeraWalletConnectError"; +import {PeraWalletArbitraryData, PeraWalletTransaction} from "../model/peraWalletModels"; +import appTellerManager from "../network/teller/appTellerManager"; +import {generateEmbeddedWalletURL} from "../peraWalletUtils"; +import {resetWalletDetailsFromStorage} from "../storage/storageUtils"; +import {base64ToUint8Array} from "../transaction/transactionUtils"; +import { + EmbeddedSignTransactionFlowTellerReducerParams, + NewTabSignTransactionFlowTellerReducerParams +} from "./signTransactionFlowModels"; + +// =========== Embedded Sign Flow =========== +function embeddedSignTransactionFlowTellerReducer({ + event, + peraWalletIframe, + signTxnRequestParams, + isIframeInitializedChecker, + webWalletURLs, + signer, + chainId, + method, + resolve, + reject +}: EmbeddedSignTransactionFlowTellerReducerParams) { + switch (event.data.message.type) { + case "IFRAME_INITIALIZED_RECEIVED": { + clearInterval(isIframeInitializedChecker); + + let message; + + if (method === "SIGN_TXN") { + message = { + type: "SIGN_TXN", + txn: signTxnRequestParams as PeraWalletTransaction[] + } as const; + } else if (method === "SIGN_DATA" && signer && chainId) { + message = { + type: "SIGN_DATA", + data: signTxnRequestParams as PeraWalletArbitraryData[], + + signer, + chainId + } as const; + } + + if (message) { + appTellerManager.sendMessage({ + message, + + origin: generateEmbeddedWalletURL(webWalletURLs.TRANSACTION_SIGN), + targetWindow: peraWalletIframe.contentWindow! + }); + } + + break; + } + + case "SIGN_TXN_CALLBACK": + document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); + closePeraWalletSignTxnModal(); + + resolve( + event.data.message.signedTxns.map((txn) => base64ToUint8Array(txn.signedTxn)) + ); + break; + + case "SIGN_DATA_CALLBACK": + document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); + closePeraWalletSignTxnModal(); + + resolve( + event.data.message.signedData.map((data) => base64ToUint8Array(data.signedData)) + ); + break; + + case "SIGN_TXN_NETWORK_MISMATCH" || "SIGN_DATA_NETWORK_MISMATCH": + reject( + new PeraWalletConnectError( + { + type: `${method}_NETWORK_MISMATCH`, + detail: event.data.message.error + }, + event.data.message.error || "Network mismatch" + ) + ); + break; + + case "SIGN_TXN_CALLBACK_ERROR" || "SIGN_DATA_CALLBACK_ERROR": + document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); + closePeraWalletSignTxnModal(); + + reject( + new PeraWalletConnectError( + { + type: `${method}_CANCELLED` + }, + event.data.message.error + ) + ); + break; + + case "SESSION_DISCONNECTED": + document.getElementById(PERA_WALLET_IFRAME_ID)?.remove(); + closePeraWalletSignTxnModal(); + + resetWalletDetailsFromStorage(); + + reject( + new PeraWalletConnectError( + { + type: "SESSION_DISCONNECTED", + detail: event.data.message.error + }, + event.data.message.error + ) + ); + break; + + default: + break; + } +} + +// =========== New Tab Sign Flow =========== +function newTabSignTransactionFlowTellerReducer({ + event, + newPeraWalletTab, + method, + resolve, + reject +}: NewTabSignTransactionFlowTellerReducerParams) { + switch (event.data.message.type) { + case "SIGN_TXN_CALLBACK": + newPeraWalletTab?.close(); + + resolve( + event.data.message.signedTxns.map((txn) => base64ToUint8Array(txn.signedTxn)) + ); + break; + + case "SIGN_DATA_CALLBACK": + newPeraWalletTab?.close(); + + resolve( + event.data.message.signedData.map((data) => base64ToUint8Array(data.signedData)) + ); + break; + + case "SIGN_TXN_NETWORK_MISMATCH" || "SIGN_DATA_NETWORK_MISMATCH": + reject( + new PeraWalletConnectError( + { + type: `${method}_NETWORK_MISMATCH`, + detail: event.data.message.error + }, + event.data.message.error || "Network mismatch" + ) + ); + break; + + case "SIGN_TXN_CALLBACK_ERROR" || "SIGN_DATA_CALLBACK_ERROR": + newPeraWalletTab?.close(); + + reject( + new PeraWalletConnectError( + { + type: `${method}_CANCELLED` + }, + event.data.message.error + ) + ); + break; + + case "SESSION_DISCONNECTED": + newPeraWalletTab?.close(); + + resetWalletDetailsFromStorage(); + + reject( + new PeraWalletConnectError( + { + type: "SESSION_DISCONNECTED", + detail: event.data.message.error + }, + event.data.message.error + ) + ); + break; + + default: + break; + } +} + +export {embeddedSignTransactionFlowTellerReducer, newTabSignTransactionFlowTellerReducer};