From cbd7574c39b13e264f8a26be123aa0792bc7b163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 15 Oct 2024 17:02:37 +0200 Subject: [PATCH 001/142] Vite build setup for the embedded wallet iframe (re)using ArConnect. --- index.html | 13 + package.json | 5 + src/components/popup/Navigation.tsx | 6 +- src/components/popup/WalletSwitcher.tsx | 2 +- src/iframe/browser/index.ts | 56 +++ src/iframe/main.tsx | 11 + src/wallets/hooks.ts | 2 +- tsconfig.json | 3 +- vite.config.js | 32 ++ yarn.lock | 549 +++++++++++++++++++++++- 10 files changed, 674 insertions(+), 5 deletions(-) create mode 100644 index.html create mode 100644 src/iframe/browser/index.ts create mode 100644 src/iframe/main.tsx create mode 100644 vite.config.js diff --git a/index.html b/index.html new file mode 100644 index 000000000..f6f7f7e90 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/package.json b/package.json index ea4701a1f..bbc6b28b9 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,9 @@ "build:chrome": "plasmo build --no-hoist", "dev:firefox": "plasmo dev --target=firefox-mv2", "build:firefox": "plasmo build --target=firefox-mv2 --no-hoist", + "dev:iframe": "vite", + "build:iframe": "vite build", + "preview:iframe": "vite preview", "fmt": "prettier --write .", "fmt:check": "prettier --check .", "prepare": "husky install" @@ -59,6 +62,7 @@ "@plasmohq/storage": "^1.7.2", "@segment/analytics-next": "^1.53.2", "@untitled-ui/icons-react": "^0.1.1", + "@vitejs/plugin-react": "^4.3.2", "ao-tokens": "^0.0.4", "ar-gql": "1.2.9", "arbundles": "^0.9.5", @@ -88,6 +92,7 @@ "styled-components": "^5.3.6", "typed-assert": "^1.0.9", "uuid": "^9.0.0", + "vite": "^5.4.9", "warp-contracts": "^1.2.13", "webextension-polyfill": "^0.10.0", "wouter": "^2.8.0-alpha.2" diff --git a/src/components/popup/Navigation.tsx b/src/components/popup/Navigation.tsx index 8c0495a11..60f785e03 100644 --- a/src/components/popup/Navigation.tsx +++ b/src/components/popup/Navigation.tsx @@ -15,24 +15,28 @@ import { useTheme } from "~utils/theme"; const buttons = [ { title: "Home", + dictionaryKey: "home", icon: , size: "24px", route: "/" }, { title: "Send", + dictionaryKey: "send", icon: , size: "24px", route: "/send/transfer" }, { title: "Receive", + dictionaryKey: "receive", icon: , size: "24px", route: "/receive" }, { title: "Explore", + dictionaryKey: "explore", icon: , size: "24px", @@ -71,7 +75,7 @@ export const NavigationBar = () => { {button.icon} -
{browser.i18n.getMessage(button.title)}
+
{browser.i18n.getMessage(button.dictionaryKey)}
); })} diff --git a/src/components/popup/WalletSwitcher.tsx b/src/components/popup/WalletSwitcher.tsx index f9039b3ed..87f09d42d 100644 --- a/src/components/popup/WalletSwitcher.tsx +++ b/src/components/popup/WalletSwitcher.tsx @@ -57,7 +57,7 @@ export default function WalletSwitcher({ useEffect( () => setWallets( - storedWallets.map((wallet) => ({ + (storedWallets || []).map((wallet) => ({ name: wallet.nickname, address: wallet.address, balance: "0", diff --git a/src/iframe/browser/index.ts b/src/iframe/browser/index.ts new file mode 100644 index 000000000..96f2e81f3 --- /dev/null +++ b/src/iframe/browser/index.ts @@ -0,0 +1,56 @@ +import enDic from "url:/assets/_locales/en/messages.json"; +import zhCnDic from "url:/assets/_locales/zh_CN/messages.json"; + +const dictionaries = { + en: enDic as unknown as Record< + string, + { message: string; description: string } + >, + "zh-CN": zhCnDic as unknown as Record< + string, + { message: string; description: string } + > +} as const; + +const i18n = { + getMessage: (key: string) => { + const dictionaryLanguage = + navigator.languages.find((language) => { + return dictionaries.hasOwnProperty(language); + }) || "en"; + + const dictionary = dictionaries[dictionaryLanguage]; + const value = dictionary[key]?.message; + + if (!value) { + console.warn(`Missing ${dictionaryLanguage} translation for ${key}.`); + } + + // TODO: Default to English instead? + return value || `<${key}>`; + } +}; + +/* +browser.tabs.create({ url: browser.runtime.getURL("tabs/welcome.html") }); +*/ + +const runtime = { + getURL: (path: string) => { + return `/${path}`; + } +}; + +const tabs = { + create: async ({ url }) => { + location.href = url; + } + + // query +}; + +export default { + i18n, + tabs, + runtime +}; diff --git a/src/iframe/main.tsx b/src/iframe/main.tsx new file mode 100644 index 000000000..11470b789 --- /dev/null +++ b/src/iframe/main.tsx @@ -0,0 +1,11 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import Popup from "../popup"; + +// import './index.css' + +createRoot(document.getElementById("root")).render( + + + +); diff --git a/src/wallets/hooks.ts b/src/wallets/hooks.ts index 6bafbcd58..05a0022de 100644 --- a/src/wallets/hooks.ts +++ b/src/wallets/hooks.ts @@ -77,7 +77,7 @@ export function useActiveWallet() { // active wallet const wallet = useMemo( - () => wallets.find(({ address }) => address === activeAddress), + () => wallets?.find(({ address }) => address === activeAddress), [activeAddress, wallets] ); diff --git a/tsconfig.json b/tsconfig.json index 053296969..e756108bf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ ".plasmo/**/*", "./**/*.ts", "./**/*.tsx", - "./shim.d.ts" + "./shim.d.ts", + "vite.config.js" ], "compilerOptions": { "paths": { diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 000000000..cd607decf --- /dev/null +++ b/vite.config.js @@ -0,0 +1,32 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import path from "path"; + +// https://vite.dev/config/ +export default defineConfig({ + // root: "./src/iframe/index.html", + plugins: [react()], + define: { + "process.env": process?.env || {} + }, + resolve: { + alias: { + "~": path.resolve(__dirname, "./src"), + "~api": path.resolve(__dirname, "./src/api"), + "~applications": path.resolve(__dirname, "./src/applications"), + "~components": path.resolve(__dirname, "./src/components"), + "~contacts": path.resolve(__dirname, "./src/contacts"), + "~gateways": path.resolve(__dirname, "./src/gateways"), + "~lib": path.resolve(__dirname, "./src/lib"), + "~notifications": path.resolve(__dirname, "./src/notifications"), + "~routes": path.resolve(__dirname, "./src/routes"), + "~settings": path.resolve(__dirname, "./src/settings"), + "~subscriptions": path.resolve(__dirname, "./src/subscriptions"), + "~tokens": path.resolve(__dirname, "./src/tokens"), + "~utils": path.resolve(__dirname, "./src/utils"), + "~wallets": path.resolve(__dirname, "./src/wallets"), + "url:/assets": path.resolve(__dirname, "./assets"), + "webextension-polyfill": path.resolve(__dirname, "./src/iframe/browser") + } + } +}); diff --git a/yarn.lock b/yarn.lock index d888d11db..eed9c95e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,11 +54,24 @@ "@babel/highlight" "^7.24.2" picocolors "^1.0.0" +"@babel/code-frame@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" + integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== + dependencies: + "@babel/highlight" "^7.25.7" + picocolors "^1.0.0" + "@babel/compat-data@^7.23.5": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== +"@babel/compat-data@^7.25.7": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.8.tgz#0376e83df5ab0eb0da18885c0140041f0747a402" + integrity sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA== + "@babel/core@^7.19.6": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a" @@ -80,6 +93,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.25.2": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.8.tgz#a57137d2a51bbcffcfaeba43cb4dd33ae3e0e1c6" + integrity sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.25.7" + "@babel/generator" "^7.25.7" + "@babel/helper-compilation-targets" "^7.25.7" + "@babel/helper-module-transforms" "^7.25.7" + "@babel/helpers" "^7.25.7" + "@babel/parser" "^7.25.8" + "@babel/template" "^7.25.7" + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.8" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3" @@ -90,6 +124,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56" + integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA== + dependencies: + "@babel/types" "^7.25.7" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" @@ -108,6 +152,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz#11260ac3322dda0ef53edfae6e97b961449f5fa4" + integrity sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A== + dependencies: + "@babel/compat-data" "^7.25.7" + "@babel/helper-validator-option" "^7.25.7" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" @@ -135,6 +190,14 @@ dependencies: "@babel/types" "^7.24.0" +"@babel/helper-module-imports@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz#dba00d9523539152906ba49263e36d7261040472" + integrity sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/helper-module-transforms@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545" @@ -146,11 +209,26 @@ "@babel/helper-split-export-declaration" "^7.24.5" "@babel/helper-validator-identifier" "^7.24.5" +"@babel/helper-module-transforms@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz#2ac9372c5e001b19bc62f1fe7d96a18cb0901d1a" + integrity sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ== + dependencies: + "@babel/helper-module-imports" "^7.25.7" + "@babel/helper-simple-access" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + "@babel/traverse" "^7.25.7" + "@babel/helper-plugin-utils@^7.24.0": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ== +"@babel/helper-plugin-utils@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz#8ec5b21812d992e1ef88a9b068260537b6f0e36c" + integrity sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw== + "@babel/helper-simple-access@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz#50da5b72f58c16b07fbd992810be6049478e85ba" @@ -158,6 +236,14 @@ dependencies: "@babel/types" "^7.24.5" +"@babel/helper-simple-access@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz#5eb9f6a60c5d6b2e0f76057004f8dacbddfae1c0" + integrity sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/helper-split-export-declaration@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6" @@ -170,16 +256,31 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== +"@babel/helper-string-parser@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" + integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== + "@babel/helper-validator-identifier@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== +"@babel/helper-validator-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== + "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== +"@babel/helper-validator-option@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz#97d1d684448228b30b506d90cace495d6f492729" + integrity sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ== + "@babel/helpers@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.5.tgz#fedeb87eeafa62b621160402181ad8585a22a40a" @@ -189,6 +290,14 @@ "@babel/traverse" "^7.24.5" "@babel/types" "^7.24.5" +"@babel/helpers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.7.tgz#091b52cb697a171fe0136ab62e54e407211f09c2" + integrity sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA== + dependencies: + "@babel/template" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/highlight@^7.24.2": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e" @@ -199,6 +308,23 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/highlight@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" + integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.7", "@babel/parser@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2" + integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ== + dependencies: + "@babel/types" "^7.25.8" + "@babel/parser@^7.20.15", "@babel/parser@^7.21.3", "@babel/parser@^7.24.0", "@babel/parser@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790" @@ -211,6 +337,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" +"@babel/plugin-transform-react-jsx-self@^7.24.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.7.tgz#3d11df143131fd8f5486a1f7d3839890f88f8c85" + integrity sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.7" + +"@babel/plugin-transform-react-jsx-source@^7.24.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.7.tgz#a0d8372310d5ea5b0447dfa03a8485f960eff7be" + integrity sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w== + dependencies: + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.1", "@babel/runtime@^7.5.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" @@ -227,6 +367,15 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/template@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769" + integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA== + dependencies: + "@babel/code-frame" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/traverse@^7.24.5", "@babel/traverse@^7.4.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8" @@ -243,6 +392,28 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8" + integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg== + dependencies: + "@babel/code-frame" "^7.25.7" + "@babel/generator" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/template" "^7.25.7" + "@babel/types" "^7.25.7" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.7", "@babel/types@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1" + integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg== + dependencies: + "@babel/helper-string-parser" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + to-fast-properties "^2.0.0" + "@babel/types@^7.20.0", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0", "@babel/types@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" @@ -490,116 +661,231 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + "@esbuild/android-arm@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + "@esbuild/android-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + "@esbuild/darwin-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + "@esbuild/darwin-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + "@esbuild/freebsd-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + "@esbuild/freebsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + "@esbuild/linux-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + "@esbuild/linux-arm@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + "@esbuild/linux-ia32@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + "@esbuild/linux-loong64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + "@esbuild/linux-mips64el@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + "@esbuild/linux-ppc64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + "@esbuild/linux-riscv64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + "@esbuild/linux-s390x@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + "@esbuild/linux-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + "@esbuild/netbsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + "@esbuild/openbsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + "@esbuild/sunos-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + "@esbuild/win32-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + "@esbuild/win32-ia32@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + "@esbuild/win32-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" @@ -2897,6 +3183,86 @@ dependencies: "@randlabs/communication-bridge" "1.0.1" +"@rollup/rollup-android-arm-eabi@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz#1661ff5ea9beb362795304cb916049aba7ac9c54" + integrity sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA== + +"@rollup/rollup-android-arm64@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz#2ffaa91f1b55a0082b8a722525741aadcbd3971e" + integrity sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA== + +"@rollup/rollup-darwin-arm64@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz#627007221b24b8cc3063703eee0b9177edf49c1f" + integrity sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA== + +"@rollup/rollup-darwin-x64@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz#0605506142b9e796c370d59c5984ae95b9758724" + integrity sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz#62dfd196d4b10c0c2db833897164d2d319ee0cbb" + integrity sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA== + +"@rollup/rollup-linux-arm-musleabihf@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz#53ce72aeb982f1f34b58b380baafaf6a240fddb3" + integrity sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw== + +"@rollup/rollup-linux-arm64-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz#1632990f62a75c74f43e4b14ab3597d7ed416496" + integrity sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA== + +"@rollup/rollup-linux-arm64-musl@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz#8c03a996efb41e257b414b2e0560b7a21f2d9065" + integrity sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw== + +"@rollup/rollup-linux-powerpc64le-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz#5b98729628d5bcc8f7f37b58b04d6845f85c7b5d" + integrity sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw== + +"@rollup/rollup-linux-riscv64-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz#48e42e41f4cabf3573cfefcb448599c512e22983" + integrity sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg== + +"@rollup/rollup-linux-s390x-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz#e0b4f9a966872cb7d3e21b9e412a4b7efd7f0b58" + integrity sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g== + +"@rollup/rollup-linux-x64-gnu@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz#78144741993100f47bd3da72fce215e077ae036b" + integrity sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A== + +"@rollup/rollup-linux-x64-musl@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz#d9fe32971883cd1bd858336bd33a1c3ca6146127" + integrity sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ== + +"@rollup/rollup-win32-arm64-msvc@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz#71fa3ea369316db703a909c790743972e98afae5" + integrity sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ== + +"@rollup/rollup-win32-ia32-msvc@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz#653f5989a60658e17d7576a3996deb3902e342e2" + integrity sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ== + +"@rollup/rollup-win32-x64-msvc@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz#0574d7e87b44ee8511d08cc7f914bcb802b70818" + integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw== + "@sec-ant/readable-stream@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" @@ -4278,6 +4644,39 @@ "@tufjs/canonical-json" "2.0.0" minimatch "^9.0.4" +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + "@types/chrome@^0.0.196": version "0.0.196" resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.196.tgz#9e0196a6fcdf3c0e12cfbcd086cd28feb5206990" @@ -4291,6 +4690,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/estree@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/filesystem@*": version "0.0.36" resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857" @@ -4422,6 +4826,17 @@ resolved "https://registry.yarnpkg.com/@untitled-ui/icons-react/-/icons-react-0.1.2.tgz#6474599a086796affaee9c1af5560ea5065b67af" integrity sha512-H4Ryn622t6kNxa5ldQEKL8i2LqtPskuKROA0mgTj54R1KSTNIx5jzrfOGTP6NUOtPyVMVRSyfex3RPqba3LPqg== +"@vitejs/plugin-react@^4.3.2": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz#1e13f666fe3135b477220d3c13b783704636b6e4" + integrity sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg== + dependencies: + "@babel/core" "^7.25.2" + "@babel/plugin-transform-react-jsx-self" "^7.24.7" + "@babel/plugin-transform-react-jsx-source" "^7.24.7" + "@types/babel__core" "^7.20.5" + react-refresh "^0.14.2" + "@vue/compiler-core@3.3.4": version "3.3.4" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128" @@ -5227,6 +5642,16 @@ browserslist@^4.22.2, browserslist@^4.6.6: node-releases "^2.0.14" update-browserslist-db "^1.0.13" +browserslist@^4.24.0: + version "4.24.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== + dependencies: + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -5381,6 +5806,11 @@ caniuse-lite@^1.0.30001541, caniuse-lite@^1.0.30001587: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz#809bc25f3f5027ceb33142a7d6c40759d7a901eb" integrity sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA== +caniuse-lite@^1.0.30001663: + version "1.0.30001668" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz#98e214455329f54bf7a4d70b49c9794f0fbedbed" + integrity sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw== + catering@^2.1.0, catering@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" @@ -6280,6 +6710,11 @@ electron-to-chromium@^1.4.535, electron-to-chromium@^1.4.668: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.764.tgz#6e817e9d767434eb48a85cc9915566485c5ba2c3" integrity sha512-ZXbPV46Y4dNCA+k7YHB+BYlzcoMtZ1yH6V0tQ1ul0wmA7RiwJfS29LSdRlE1myWBXRzEgm/Lz6tryj5WVQiLmg== +electron-to-chromium@^1.5.28: + version "1.5.38" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.38.tgz#c6a14e1506717c5a46d439580d7424d8d5860180" + integrity sha512-VbeVexmZ1IFh+5EfrYz1I0HTzHVIlJa112UEWhciPyeOcKJGeTv6N8WnG4wsQB81DGCaVEGhpSb6o6a8WYFXXg== + elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -6519,11 +6954,45 @@ esbuild@^0.18.2: "@esbuild/win32-ia32" "0.18.20" "@esbuild/win32-x64" "0.18.20" +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + escalade@^3.1.1, escalade@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-string-regexp@5.0.0, escape-string-regexp@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" @@ -6891,7 +7360,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: +fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -7965,6 +8434,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + json-bigint@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" @@ -9148,6 +9622,11 @@ node-releases@^2.0.13, node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + nopt@^7.0.0, nopt@^7.2.0: version "7.2.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7" @@ -9773,6 +10252,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -9901,6 +10385,15 @@ postcss@^8.1.10: picocolors "^1.0.0" source-map-js "^1.2.0" +postcss@^8.4.43: + version "8.4.47" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" + integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.0" + source-map-js "^1.2.1" + posthtml-parser@^0.10.1: version "0.10.2" resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.10.2.tgz#df364d7b179f2a6bf0466b56be7b98fd4e97c573" @@ -10245,6 +10738,11 @@ react-refresh@0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== +react-refresh@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" + integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== + react-refresh@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" @@ -10500,6 +10998,31 @@ rollup@^3.2.5: optionalDependencies: fsevents "~2.3.2" +rollup@^4.20.0: + version "4.24.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.0.tgz#c14a3576f20622ea6a5c9cad7caca5e6e9555d05" + integrity sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.24.0" + "@rollup/rollup-android-arm64" "4.24.0" + "@rollup/rollup-darwin-arm64" "4.24.0" + "@rollup/rollup-darwin-x64" "4.24.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.24.0" + "@rollup/rollup-linux-arm-musleabihf" "4.24.0" + "@rollup/rollup-linux-arm64-gnu" "4.24.0" + "@rollup/rollup-linux-arm64-musl" "4.24.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.24.0" + "@rollup/rollup-linux-riscv64-gnu" "4.24.0" + "@rollup/rollup-linux-s390x-gnu" "4.24.0" + "@rollup/rollup-linux-x64-gnu" "4.24.0" + "@rollup/rollup-linux-x64-musl" "4.24.0" + "@rollup/rollup-win32-arm64-msvc" "4.24.0" + "@rollup/rollup-win32-ia32-msvc" "4.24.0" + "@rollup/rollup-win32-x64-msvc" "4.24.0" + fsevents "~2.3.2" + rss-parser@^3.12.0: version "3.13.0" resolved "https://registry.yarnpkg.com/rss-parser/-/rss-parser-3.13.0.tgz#f1f83b0a85166b8310ec531da6fbaa53ff0f50f0" @@ -10911,6 +11434,11 @@ socks@^2.7.1: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + source-map@0.8.0-beta.0: version "0.8.0-beta.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" @@ -11781,6 +12309,14 @@ update-browserslist-db@^1.0.13: escalade "^3.1.2" picocolors "^1.0.0" +update-browserslist-db@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + url-join@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/url-join/-/url-join-5.0.0.tgz#c2f1e5cbd95fa91082a93b58a1f42fecb4bdbcf1" @@ -11848,6 +12384,17 @@ validate-npm-package-name@^5.0.0: resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== +vite@^5.4.9: + version "5.4.9" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.9.tgz#215c80cbebfd09ccbb9ceb8c0621391c9abdc19c" + integrity sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + vlq@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/vlq/-/vlq-2.0.4.tgz#6057b85729245b9829e3cc7755f95b228d4fe041" From 6150087c0a068512c5f174e94e3abd8b31f7974c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 16 Oct 2024 15:06:22 +0200 Subject: [PATCH 002/142] Mock enough stuff for the app to build and kind of run with Vite. --- src/components/popup/WalletHeader.tsx | 2 + src/contents/events.ts | 2 + src/iframe/browser/index.ts | 70 +++++++++++-- src/iframe/plasmohq/storage/hook/index.ts | 88 +++++++++++++++++ .../storage/implementations/local-storage.ts | 0 src/iframe/plasmohq/storage/index.ts | 98 +++++++++++++++++++ src/popup.tsx | 29 +++++- src/routes/popup/unlock.tsx | 7 +- src/tokens/index.ts | 2 +- src/wallets/auth.ts | 8 ++ src/wallets/index.ts | 11 ++- vite.config.js | 8 ++ 12 files changed, 312 insertions(+), 13 deletions(-) create mode 100644 src/iframe/plasmohq/storage/hook/index.ts create mode 100644 src/iframe/plasmohq/storage/implementations/local-storage.ts create mode 100644 src/iframe/plasmohq/storage/index.ts diff --git a/src/components/popup/WalletHeader.tsx b/src/components/popup/WalletHeader.tsx index 84528852d..bb07c08a9 100644 --- a/src/components/popup/WalletHeader.tsx +++ b/src/components/popup/WalletHeader.tsx @@ -69,6 +69,8 @@ export default function WalletHeader() { [] ); + console.log("wallets =", wallets); + // is the wallet selector open const [isOpen, setOpen] = useState(false); diff --git a/src/contents/events.ts b/src/contents/events.ts index dc4efcd33..022380920 100644 --- a/src/contents/events.ts +++ b/src/contents/events.ts @@ -1,6 +1,8 @@ import { onMessage } from "@arconnect/webext-bridge"; import type { PlasmoCSConfig } from "plasmo"; +console.trace("HERE"); + export const config: PlasmoCSConfig = { matches: ["file://*/*", "http://*/*", "https://*/*"], run_at: "document_end", diff --git a/src/iframe/browser/index.ts b/src/iframe/browser/index.ts index 96f2e81f3..4de2325e1 100644 --- a/src/iframe/browser/index.ts +++ b/src/iframe/browser/index.ts @@ -1,6 +1,15 @@ import enDic from "url:/assets/_locales/en/messages.json"; import zhCnDic from "url:/assets/_locales/zh_CN/messages.json"; +const alarms = { + create: null, + clear: null, + getAll: null, + onAlarm: { + addEventListener: null + } +}; + const dictionaries = { en: enDic as unknown as Record< string, @@ -31,26 +40,69 @@ const i18n = { } }; -/* -browser.tabs.create({ url: browser.runtime.getURL("tabs/welcome.html") }); -*/ +// The 2 polyfill below should address lines like this: +// browser.tabs.create({ url: browser.runtime.getURL("tabs/welcome.html") }); const runtime = { getURL: (path: string) => { - return `/${path}`; + console.trace("getURL()"); + + return new URL(path, document.location.origin).toString(); + }, + getManifest: () => { + return { + browser_action: { + default_popup: "popup.html" + } + }; } }; const tabs = { create: async ({ url }) => { - location.href = url; - } + console.log(`Go to ${url}`); - // query + // URL = + // browser.runtime.getURL("tabs/welcome.html") + // browser.runtime.getURL("tabs/dashboard.html#/contacts") + // browser.runtime.getURL("assets/animation/arweave.png"); + // browser.runtime.getURL("tabs/auth.html")}?${objectToUrlParams(...)} + + if (url === "tabs/welcome.html") { + location.hash = "/welcome"; + } else if (url.startsWith("tabs/dashboard.html#")) { + const hash = url.split("#").pop(); + + location.hash = hash; + } else if (url.startsWith("tabs/auth.html")) { + throw new Error(`Cannot create tab for URL = ${url}`); + } else if (url.startsWith("assets")) { + throw new Error(`Cannot create tab for URL = ${url}`); + } else { + throw new Error(`Cannot create tab for URL = ${url}`); + } + }, + query: async () => { + const parentURL = + window.location === window.parent.location + ? document.location.href + : document.referrer; + + return { url: parentURL }; // satisfies browser.Tabs.Tab + }, + onConnect: { + addListener: () => {}, + removeListener: () => {} + }, + onUpdated: { + addListener: () => {}, + removeListener: () => {} + } }; export default { + // alarms, i18n, - tabs, - runtime + runtime, + tabs }; diff --git a/src/iframe/plasmohq/storage/hook/index.ts b/src/iframe/plasmohq/storage/hook/index.ts new file mode 100644 index 000000000..9aae51b4f --- /dev/null +++ b/src/iframe/plasmohq/storage/hook/index.ts @@ -0,0 +1,88 @@ +import { useStorage as plasmoUseStorage } from "@plasmohq/storage/hook"; +import { useCallback, useState } from "react"; + +type UseStorageType = typeof plasmoUseStorage; + +// TODO: For the initial version, use localStorage or sessionStorage (that's for the "@plasmohq/storage" (Storage) polyfill) +// TODO: After that, consider whether some values must be persisted on the backend instead. + +type Setter = ((v?: T, isHydrated?: boolean) => T) | T; +/** + * isPublic: If true, the value will be synced with web API Storage + */ +type RawKey = + | string + | { + key: string; + // instance: BaseStorage; + instance: any; + }; + +export function useStorage( + rawKey: RawKey, + onInit: Setter +): [ + T, + (setter: Setter) => Promise, + { + readonly setRenderValue: React.Dispatch>; + readonly setStoreValue: (v: T) => Promise; + readonly remove: () => void; + } +]; +export function useStorage( + rawKey: RawKey +): [ + T | undefined, + (setter: Setter) => Promise, + { + readonly setRenderValue: React.Dispatch< + React.SetStateAction + >; + readonly setStoreValue: (v?: T) => Promise; + readonly remove: () => void; + } +]; +export function useStorage( + rawKey: RawKey, + onInit?: Setter +): ReturnType { + console.log("My useStorage()"); + + const initialStoredValue = + typeof onInit === "function" ? onInit() : undefined; + const initialRenderValue = + typeof onInit === "function" ? initialStoredValue : onInit; + + const [renderValue, setRenderValue] = useState(initialRenderValue); + + const setState = useCallback( + async (setter: Setter) => { + setRenderValue(typeof setter === "function" ? setter() : setter); + }, + [setRenderValue] + ); + + const setStoreValue = useCallback( + async (v?: T) => { + setRenderValue(v); + + return null; + }, + [setRenderValue] + ); + + const remove = useCallback(() => { + setRenderValue(undefined); + }, [setRenderValue]); + + return [ + renderValue, + setState, + { + setRenderValue, + setStoreValue, + remove + } + ]; +} diff --git a/src/iframe/plasmohq/storage/implementations/local-storage.ts b/src/iframe/plasmohq/storage/implementations/local-storage.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/iframe/plasmohq/storage/index.ts b/src/iframe/plasmohq/storage/index.ts new file mode 100644 index 000000000..7d50c9726 --- /dev/null +++ b/src/iframe/plasmohq/storage/index.ts @@ -0,0 +1,98 @@ +import type { + Storage as PlasmoStorage, + StorageCallbackMap +} from "@plasmohq/storage"; + +export class Storage implements PlasmoStorage { + #private: any; + + constructor() {} + + get area(): "local" | "sync" | "managed" | "session" { + throw new Error("Method not implemented."); + } + + protected rawGet: (key: string) => Promise; + + get: (key: string) => Promise; + getItem(key: string): Promise { + throw new Error("Method not implemented."); + } + + rawGetAll: () => Promise<{ [key: string]: any }>; + getAll: () => Promise>; + + protected parseValue: (rawValue: any) => Promise; + + protected rawSet: (key: string, value: string) => Promise; + + set: (key: string, rawValue: any) => Promise; + setItem(key: string, rawValue: any): Promise { + throw new Error("Method not implemented."); + } + + protected rawRemove: (key: string) => Promise; + + remove: (key: string) => Promise; + removeItem(key: string): Promise { + throw new Error("Method not implemented."); + } + removeAll: () => Promise; + + clear: (includeCopies?: boolean) => Promise; + + // Not implemented: + + get primaryClient(): chrome.storage.StorageArea { + throw new Error("Method not implemented."); + } + + get secondaryClient(): globalThis.Storage { + throw new Error("Method not implemented."); + } + + get hasWebApi(): boolean { + throw new Error("Method not implemented."); + } + + // Copy: + + get copiedKeySet(): Set { + throw new Error("Method not implemented."); + } + + setCopiedKeySet(keyList: string[]): void { + throw new Error("Method not implemented."); + } + + isCopied: (key: string) => boolean; + + copy: (key?: string) => Promise; + + get allCopied(): boolean { + throw new Error("Method not implemented."); + } + + // Extension API: + + getExtStorageApi: () => any; + + get hasExtensionApi(): boolean { + throw new Error("Method not implemented."); + } + + // Namespace: + + protected keyNamespace: string; + + isValidKey: (nsKey: string) => boolean; + getNamespacedKey: (key: string) => string; + getUnnamespacedKey: (nsKey: string) => string; + setNamespace: (namespace: string) => void; + + // Watch: + isWatchSupported: () => boolean; + watch: (callbackMap: StorageCallbackMap) => boolean; + unwatch: (callbackMap: StorageCallbackMap) => boolean; + unwatchAll: () => void; +} diff --git a/src/popup.tsx b/src/popup.tsx index 1c20dbe5e..faa0099b3 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -50,12 +50,19 @@ import NewContact from "~routes/popup/settings/contacts/new"; import NotificationSettings from "~routes/popup/settings/notifications"; import GenerateQR from "~routes/popup/settings/wallets/[address]/qr"; +// Welcome Flow: +import WelcomeHome from "~routes/welcome"; +import Start from "~routes/welcome/start"; +import Setup from "~routes/welcome/setup"; +import GettingStarted from "~routes/welcome/gettingStarted"; + export default function Popup() { const theme = useTheme(); const [expanded, setExpanded] = useState(false); + // TODO: This will redirect to Welcome and end up with an infinite redirect... // init popup - useSetUp(); + // useSetUp(); useEffect(() => { // sync ans labels @@ -76,6 +83,26 @@ export default function Popup() { + + + + {(params: { page: string }) => ( + + )} + + + + {(params: { + setupMode: "generate" | "load"; + page: string; + }) => ( + + )} + + diff --git a/src/routes/popup/unlock.tsx b/src/routes/popup/unlock.tsx index 8edee3c12..bdc836d7a 100644 --- a/src/routes/popup/unlock.tsx +++ b/src/routes/popup/unlock.tsx @@ -12,7 +12,6 @@ import { useToasts } from "@arconnect/components"; import HeadV2 from "~components/popup/HeadV2"; -import styled from "styled-components"; export default function Unlock() { // password input @@ -26,9 +25,13 @@ export default function Unlock() { // unlock ArConnect async function unlockWallet() { + console.log("unlockWallet()"); + // unlock using password const res = await unlock(passwordInput.state); + console.log("res =", res); + if (!res) { passwordInput.setStatus("error"); return setToast({ @@ -38,6 +41,8 @@ export default function Unlock() { }); } + console.log("REDIRECT HOME"); + push("/"); } diff --git a/src/tokens/index.ts b/src/tokens/index.ts index b3df03899..52549af91 100644 --- a/src/tokens/index.ts +++ b/src/tokens/index.ts @@ -259,7 +259,7 @@ export function useTokens() { })(); }, [tokens, activeAddress, updatedTokens]); - return tokens; + return tokens || []; } /** diff --git a/src/wallets/auth.ts b/src/wallets/auth.ts index fe7787cf7..1e4828fb4 100644 --- a/src/wallets/auth.ts +++ b/src/wallets/auth.ts @@ -11,17 +11,25 @@ import { ExtensionStorage } from "~utils/storage"; * @param password Password for unlocking */ export async function unlock(password: string) { + console.log("unlock 1"); + // validate password if (!(await checkPassword(password))) { return false; } + console.log("unlock 2"); + // save decryption key await setDecryptionKey(password); + console.log("unlock 3"); + // schedule the key for removal await scheduleKeyRemoval(); + console.log("unlock 4"); + return true; } diff --git a/src/wallets/index.ts b/src/wallets/index.ts index 0655bb0c1..68d8d92f4 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -41,6 +41,8 @@ export type StoredWallet = * @returns Wallets in storage */ export async function getWallets() { + console.log("getWallets()"); + let wallets: StoredWallet[] = await ExtensionStorage.get("wallets"); return wallets || []; @@ -49,8 +51,12 @@ export async function getWallets() { /** * Hook that opens a new tab if ArConnect has not been set up yet */ -export const useSetUp = () => +export function useSetUp() { + console.log("useSetUp()"); + useEffect(() => { + // TODO: #cover should only be removed after this... + (async () => { const activeAddress = await getActiveAddress(); const wallets = await getWallets(); @@ -68,11 +74,14 @@ export const useSetUp = () => } })(); }, []); +} /** * Hook to get if there are no wallets added */ export const useNoWallets = () => { + console.log("useNoWallets()"); + const [state, setState] = useState(false); useEffect(() => { diff --git a/vite.config.js b/vite.config.js index cd607decf..368204cc8 100644 --- a/vite.config.js +++ b/vite.config.js @@ -25,6 +25,14 @@ export default defineConfig({ "~tokens": path.resolve(__dirname, "./src/tokens"), "~utils": path.resolve(__dirname, "./src/utils"), "~wallets": path.resolve(__dirname, "./src/wallets"), + "plasmohq/storage": path.resolve( + __dirname, + "./src/iframe/plasmohq/storage" + ), + "plasmohq/storage/hook": path.resolve( + __dirname, + "./src/iframe/plasmohq/storage/hook" + ), "url:/assets": path.resolve(__dirname, "./assets"), "webextension-polyfill": path.resolve(__dirname, "./src/iframe/browser") } From 5beaf0c6f6409b78842622772d9217896d04d0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 16 Oct 2024 22:24:39 +0200 Subject: [PATCH 003/142] Implement route guard logic wihout using a regular Route. --- src/components/popup/HistoryProvider.tsx | 15 +- src/components/popup/Route.tsx | 2 +- src/components/popup/home/Balance.tsx | 5 +- src/iframe/browser/index.ts | 3 +- src/popup.tsx | 317 +++++++++++------------ src/tabs/dashboard.tsx | 2 + src/wallets/index.ts | 96 +++++-- vite.config.js | 5 +- 8 files changed, 245 insertions(+), 200 deletions(-) diff --git a/src/components/popup/HistoryProvider.tsx b/src/components/popup/HistoryProvider.tsx index 01f8e3b73..69f72680f 100644 --- a/src/components/popup/HistoryProvider.tsx +++ b/src/components/popup/HistoryProvider.tsx @@ -1,5 +1,4 @@ -import { type PropsWithChildren, useEffect, useState } from "react"; -import { getDecryptionKey } from "~wallets/auth"; +import { type PropsWithChildren, useState } from "react"; import { useLocation } from "wouter"; import { type BackAction, @@ -27,18 +26,6 @@ export default function HistoryProvider({ children }: PropsWithChildren<{}>) { history.back(); }; - // redirect to unlock if decryiption - // key is not available - useEffect(() => { - (async () => { - const decryptionKey = await getDecryptionKey(); - - if (!decryptionKey) { - push("/unlock"); - } - })(); - }, []); - return ( {children} diff --git a/src/components/popup/Route.tsx b/src/components/popup/Route.tsx index c7a3c78ae..48ed867fd 100644 --- a/src/components/popup/Route.tsx +++ b/src/components/popup/Route.tsx @@ -39,7 +39,7 @@ const PageWrapper = styled(Wrapper)` transition: background-color 0.23s ease-in-out; `; -const Page = ({ children }: PropsWithChildren) => { +export const Page = ({ children }: PropsWithChildren) => { const opacityAnimation: Variants = { initial: { opacity: 0 }, enter: { opacity: 1 }, diff --git a/src/components/popup/home/Balance.tsx b/src/components/popup/home/Balance.tsx index 4b0a62194..a07a96f29 100644 --- a/src/components/popup/home/Balance.tsx +++ b/src/components/popup/home/Balance.tsx @@ -111,16 +111,13 @@ export default function Balance() { })(); }, [activeAddress]); - // router push - const [push] = useHistory(); - // display theme const theme = useTheme(); // lock wallet and terminate session async function lockWallet() { + // TODO: Add option to menu and simply remove it? The router should be aware of this change. await removeDecryptionKey(); - push("/unlock"); } useEffect(() => { diff --git a/src/iframe/browser/index.ts b/src/iframe/browser/index.ts index 4de2325e1..655cd16ed 100644 --- a/src/iframe/browser/index.ts +++ b/src/iframe/browser/index.ts @@ -67,13 +67,14 @@ const tabs = { // browser.runtime.getURL("tabs/dashboard.html#/contacts") // browser.runtime.getURL("assets/animation/arweave.png"); // browser.runtime.getURL("tabs/auth.html")}?${objectToUrlParams(...)} + // `tabs/dashboard.html#/apps/${activeApp.url}` if (url === "tabs/welcome.html") { location.hash = "/welcome"; } else if (url.startsWith("tabs/dashboard.html#")) { const hash = url.split("#").pop(); - location.hash = hash; + location.hash = `/quick-settings${hash}`; } else if (url.startsWith("tabs/auth.html")) { throw new Error(`Cannot create tab for URL = ${url}`); } else if (url.startsWith("assets")) { diff --git a/src/popup.tsx b/src/popup.tsx index faa0099b3..ced796cb6 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,10 +1,10 @@ -import Route, { Wrapper } from "~components/popup/Route"; +import Route, { Page, Wrapper } from "~components/popup/Route"; import styled, { createGlobalStyle } from "styled-components"; import { GlobalStyle, useTheme } from "~utils/theme"; import { useHashLocation } from "~utils/hash_router"; import { Provider } from "@arconnect/components"; import { syncLabels, useSetUp } from "~wallets"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { Router } from "wouter"; import HardwareWalletTheme from "~components/hardware/HardwareWalletTheme"; @@ -49,20 +49,13 @@ import ContactSettings from "~routes/popup/settings/contacts/[address]"; import NewContact from "~routes/popup/settings/contacts/new"; import NotificationSettings from "~routes/popup/settings/notifications"; import GenerateQR from "~routes/popup/settings/wallets/[address]/qr"; - -// Welcome Flow: -import WelcomeHome from "~routes/welcome"; -import Start from "~routes/welcome/start"; -import Setup from "~routes/welcome/setup"; -import GettingStarted from "~routes/welcome/gettingStarted"; +import { AnimatePresence } from "framer-motion"; export default function Popup() { const theme = useTheme(); const [expanded, setExpanded] = useState(false); - // TODO: This will redirect to Welcome and end up with an infinite redirect... - // init popup - // useSetUp(); + const initialScreenType = useSetUp(); useEffect(() => { // sync ans labels @@ -74,164 +67,162 @@ export default function Popup() { } }, []); + console.log("initialScreenType =", initialScreenType); + + let content: React.ReactElement = null; + + if (initialScreenType === "cover") { + content = ( + + +

LOADING...

+
+
+ ); + } else if (initialScreenType === "locked") { + content = ( + + + + + + ); + } else if (initialScreenType === "generating") { + content = ( + + +

GENERATING...

+
+
+ ); + } else { + content = ( + + + + + + {(params: { quoteId: string }) => ( + + )} + + + {() => } + + {(params: { id?: string }) => } + + + {(params: { tokenID: string }) => ( + + )} + + + + + + + {(params: { address: string }) => ( + + )} + + + {(params: { address: string }) => ( + + )} + + + {(params: { address: string }) => ( + + )} + + + + {(params: { url: string }) => } + + + {(params: { url: string }) => } + + + + {(params: { id: string }) => } + + + + + {(params: { address: string }) => ( + + )} + + + + + {(params: { id: string }) => ( + + )} + + + {(params: { id: string }) => ( + + )} + + + {(params: { id: string }) => ( + + )} + + + + + {(params: { id: string }) => ( + + )} + + + + {(params: { id: string }) => } + + + + {(params: { id: string }) => } + + + {(params: { id: string; gateway?: string }) => ( + + )} + + + {(params: { token: string; qty: string }) => ( + + )} + + + {(params: { token: string; qty: string; message?: string }) => ( + + )} + + + + + ); + } + return ( - - - - - - - {(params: { page: string }) => ( - - )} - - - - {(params: { - setupMode: "generate" | "load"; - page: string; - }) => ( - - )} - - - - - - {(params: { quoteId: string }) => ( - - )} - - - {() => } - - {(params: { id?: string }) => } - - - {(params: { tokenID: string }) => ( - - )} - - - - - - - - {(params: { address: string }) => ( - - )} - - - {(params: { address: string }) => ( - - )} - - - {(params: { address: string }) => ( - - )} - - - - {(params: { url: string }) => ( - - )} - - - {(params: { url: string }) => ( - - )} - - - - {(params: { id: string }) => ( - - )} - - - - - {(params: { address: string }) => ( - - )} - - - - - {(params: { id: string }) => ( - - )} - - - {(params: { id: string }) => ( - - )} - - - {(params: { id: string }) => ( - - )} - - - - - {(params: { id: string }) => ( - - )} - - - - {(params: { id: string }) => } - - - - {(params: { id: string }) => } - - - {(params: { id: string; gateway?: string }) => ( - - )} - - - {(params: { token: string; qty: string }) => ( - - )} - - - {(params: { - token: string; - qty: string; - message?: string; - }) => ( - - )} - - - - - + {content} diff --git a/src/tabs/dashboard.tsx b/src/tabs/dashboard.tsx index b4bc4e2d1..d214449ae 100644 --- a/src/tabs/dashboard.tsx +++ b/src/tabs/dashboard.tsx @@ -16,6 +16,8 @@ export default function Dashboard() { syncLabels(); }, []); + // TODO: This doesn't use the HistoryProvider, so would it always allow me in even if the wallet is not unlocked? + return ( diff --git a/src/wallets/index.ts b/src/wallets/index.ts index 68d8d92f4..6fdcdbaec 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -48,32 +48,96 @@ export async function getWallets() { return wallets || []; } +type InitialScreenType = "cover" | "locked" | "generating" | "default"; + /** * Hook that opens a new tab if ArConnect has not been set up yet */ export function useSetUp() { - console.log("useSetUp()"); + console.log("useSetUp =", process.env.PLASMO_PUBLIC_APP_TYPE); + + const [initialScreenType, setInitialScreenType] = + useState("cover"); + + // TODO: Get all usages of `getDecryptionKey` as we won't be using this in the embedded wallet... + + // TODO: There's no "disconnect" in the embedded wallet. useEffect(() => { - // TODO: #cover should only be removed after this... + async function checkWalletState() { + const [activeAddress, wallets, decryptionKey] = await Promise.all([ + getActiveAddress(), + getWallets(), + getDecryptionKey() + ]); + + const hasWallets = activeAddress && wallets.length > 0; + + let nextInitialScreenType: InitialScreenType = "cover"; + + switch (process.env.PLASMO_PUBLIC_APP_TYPE) { + // TODO: There should be no undefined here but the env variables do not seem to work: + case undefined: + case "extension": { + console.log("LOADING..."); + + if (!hasWallets) { + await browser.tabs.create({ + url: browser.runtime.getURL("tabs/welcome.html") + }); + + window.top.close(); + } else if (!decryptionKey) { + nextInitialScreenType = "locked"; + } else { + nextInitialScreenType = "default"; + } + + break; + } + + case "embedded": { + nextInitialScreenType = !hasWallets ? "generating" : "default"; + + break; + } + + default: { + throw new Error( + `Unknown APP_TYPE = ${process.env.PLASMO_PUBLIC_APP_TYPE}` + ); + } + } - (async () => { - const activeAddress = await getActiveAddress(); - const wallets = await getWallets(); + console.log("nextInitialScreenType =", nextInitialScreenType); + + setInitialScreenType(nextInitialScreenType); - if ( - !activeAddress || - activeAddress === "" || - wallets.length === 0 || - !wallets - ) { - await browser.tabs.create({ - url: browser.runtime.getURL("tabs/welcome.html") - }); - window.top.close(); + const coverElement = document.getElementById("cover"); + + if (coverElement) { + if (nextInitialScreenType === "cover") { + coverElement.removeAttribute("aria-hidden"); + } else { + coverElement.setAttribute("aria-hidden", "true"); + } } - })(); + } + + ExtensionStorage.watch({ + decryption_key: checkWalletState + }); + + checkWalletState(); + + return () => { + ExtensionStorage.unwatch({ + decryption_key: checkWalletState + }); + }; }, []); + + return initialScreenType; } /** diff --git a/vite.config.js b/vite.config.js index 368204cc8..9cb312ec1 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,7 +7,10 @@ export default defineConfig({ // root: "./src/iframe/index.html", plugins: [react()], define: { - "process.env": process?.env || {} + "process.env": { + PLASMO_PUBLIC_APP_TYPE: "embedded", + ...(process?.env || {}) + } }, resolve: { alias: { From 7171c387d9a0c8e2c63af83ebe6b6dfc68990279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 22 Oct 2024 17:08:59 +0200 Subject: [PATCH 004/142] Minor changes in the mocked create tab functionality. --- index.html | 11 +-- src/iframe/browser/index.ts | 8 +- src/iframe/injected/iframe-injected.ts | 112 +++++++++++++++++++++++++ src/iframe/main.tsx | 2 + 4 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 src/iframe/injected/iframe-injected.ts diff --git a/index.html b/index.html index f6f7f7e90..789fde4aa 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,14 @@ - + - - - - Vite + React + ArConnect Embedded Wallet + + +
+ diff --git a/src/iframe/browser/index.ts b/src/iframe/browser/index.ts index 655cd16ed..cef0ebdb8 100644 --- a/src/iframe/browser/index.ts +++ b/src/iframe/browser/index.ts @@ -76,7 +76,13 @@ const tabs = { location.hash = `/quick-settings${hash}`; } else if (url.startsWith("tabs/auth.html")) { - throw new Error(`Cannot create tab for URL = ${url}`); + console.warn( + "Auth popup routes not added to the embedded wallet iframe router!" + ); + + const paramsAndHash = url.replace("tabs/auth.html", ""); + + location.hash = `/auth${paramsAndHash}`; } else if (url.startsWith("assets")) { throw new Error(`Cannot create tab for URL = ${url}`); } else { diff --git a/src/iframe/injected/iframe-injected.ts b/src/iframe/injected/iframe-injected.ts new file mode 100644 index 000000000..423b71e19 --- /dev/null +++ b/src/iframe/injected/iframe-injected.ts @@ -0,0 +1,112 @@ +import type { ApiCall, ApiResponse, Event } from "shim"; +import type { InjectedEvents } from "~utils/events"; +import { version } from "../package.json"; +import { nanoid } from "nanoid"; +import modules from "~api/foreground"; +import mitt from "mitt"; + +/** Init events */ +const events = mitt(); + +/** Init wallet API */ +const WalletAPI: Record = { + walletName: "ArConnect", + walletVersion: version, + events +}; + +/** Inject each module */ +for (const mod of modules) { + /** Handle foreground module and forward the result to the background */ + WalletAPI[mod.functionName] = (...params: any[]) => + new Promise(async (resolve, reject) => { + // execute foreground module + const foregroundResult = await mod.function(...params); + + // construct data to send to the background + const callID = nanoid(); + const data: ApiCall & { ext: "arconnect" } = { + type: `api_${mod.functionName}`, + ext: "arconnect", + callID, + data: { + params: foregroundResult || params + } + }; + + // send message to background + window.postMessage(data, window.location.origin); + + // wait for result from background + window.addEventListener("message", callback); + + async function callback(e: MessageEvent) { + let { data: res } = e; + + // validate return message + if (`${data.type}_result` !== res.type) return; + + // only resolve when the result matching our callID is deleivered + if (data.callID !== res.callID) return; + + window.removeEventListener("message", callback); + + // check for errors + if (res.error) { + return reject(res.data); + } + + // call the finalizer function if it exists + if (mod.finalizer) { + const finalizerResult = await mod.finalizer( + res.data, + foregroundResult, + params + ); + + // if the finalizer transforms data + // update the result + if (finalizerResult) { + res.data = finalizerResult; + } + } + + // check for errors after the finalizer + if (res.error) { + return reject(res.data); + } + + // resolve promise + return resolve(res.data); + } + }); +} + +// @ts-expect-error +window.arweaveWallet = WalletAPI; + +// at the end of the injected script, +// we dispatch the wallet loaded event +dispatchEvent(new CustomEvent("arweaveWalletLoaded", { detail: {} })); + +// send wallet loaded event again if page loaded +window.addEventListener("load", () => { + if (!window.arweaveWallet) return; + dispatchEvent(new CustomEvent("arweaveWalletLoaded", { detail: {} })); +}); + +/** Handle events */ +window.addEventListener( + "message", + ( + e: MessageEvent<{ + type: "arconnect_event"; + event: Event; + }> + ) => { + if (e.data.type !== "arconnect_event") return; + events.emit(e.data.event.name, e.data.event.value); + } +); + +export {}; diff --git a/src/iframe/main.tsx b/src/iframe/main.tsx index 11470b789..037d88ef9 100644 --- a/src/iframe/main.tsx +++ b/src/iframe/main.tsx @@ -4,6 +4,8 @@ import Popup from "../popup"; // import './index.css' +// TODO: Duplicate "Popup" as "Iframe" and move all routers to config to be able to combine Popup + Auth. + createRoot(document.getElementById("root")).render( From 0c5fa28bd6129427c88e5b8db407bc79d7f6cf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 23 Oct 2024 18:27:58 +0200 Subject: [PATCH 005/142] WIP to reorganize ArConnect injected scripts, setup logic and message passing flow. --- src/api/background.ts | 96 ---------- src/api/background/background-modules.ts | 94 ++++++++++ src/api/background/background-setup.ts | 100 +++++++++++ .../ao-tokens-cache-alarm.handler.ts | 65 +++++++ .../ao-tokens-import-alarm.handler.ts | 114 ++++++++++++ .../gateway-update-alarm.handler.ts | 47 +++++ .../key-removal/key-removal-alarm.handler.ts | 16 ++ .../notifications-alarm.handler.ts} | 111 +----------- .../notifications-alarm.utils.ts | 101 +++++++++++ .../subscriptions-alarm.handler.ts} | 7 +- .../sync-labels/sync-labels-alarm.handler.ts | 38 ++++ .../track-balance-alarm.handler.ts | 46 +++++ .../browser/install/install.handler.ts | 43 +++++ .../get-capabilities.handler.ts | 71 ++++++++ .../get-printers/get-printers.handler.ts | 22 +++ .../browser/printer/print/print.handler.ts} | 101 +---------- .../browser/printer/printer.constants.ts | 1 + .../browser/protocol/protocol.handler.ts | 33 ++++ .../window-close/window-close.handler.ts | 19 ++ .../api-call-message.handler.ts} | 75 +------- .../chunk-message/chunk-message.handler.ts | 70 ++++++++ .../active-address-change.handler.ts | 50 ++++++ .../app-config-change.handler.ts} | 11 +- .../storage/apps-change/app-change.handler.ts | 71 ++++++++ .../wallet-change/wallet-change.handler.ts} | 47 +---- src/api/foreground.ts | 139 --------------- src/api/foreground/foreground-modules.ts | 137 +++++++++++++++ src/api/foreground/foreground-setup.ts | 134 ++++++++++++++ src/api/module.ts | 10 +- .../active_address.background.ts | 4 +- .../modules/add_token/add_token.background.ts | 4 +- .../all_addresses/all_addresses.background.ts | 4 +- .../arweave_config.background.ts | 4 +- .../batch_sign_data_item.background.ts | 2 +- .../batch_sign_data_item.foreground.ts | 2 +- src/api/modules/connect/connect.background.ts | 4 +- src/api/modules/decrypt/decrypt.background.ts | 4 +- src/api/modules/decrypt/decrypt.foreground.ts | 2 +- .../disconnect/disconnect.background.ts | 4 +- src/api/modules/dispatch/allowance.ts | 2 +- .../modules/dispatch/dispatch.background.ts | 4 +- .../modules/dispatch/dispatch.foreground.ts | 2 +- src/api/modules/encrypt/encrypt.background.ts | 4 +- src/api/modules/encrypt/encrypt.foreground.ts | 2 +- src/api/modules/example/example.background.ts | 7 +- .../is_token_added.background.ts | 4 +- .../permissions/permissions.background.ts | 6 +- .../private_hash/private_hash.background.ts | 4 +- .../private_hash/private_hash.foreground.ts | 2 +- .../public_key/public_key.background.ts | 4 +- src/api/modules/sign/sign.background.ts | 4 +- src/api/modules/sign/sign.foreground.ts | 2 +- .../sign_data_item.background.ts | 4 +- .../sign_data_item.foreground.ts | 2 +- .../sign_message/sign_message.background.ts | 4 +- .../sign_message/sign_message.foreground.ts | 2 +- .../modules/signature/signature.background.ts | 4 +- .../modules/signature/signature.foreground.ts | 2 +- .../subscription/subscription.background.ts | 4 +- .../token_balance/token_balance.background.ts | 4 +- .../user_tokens/user_tokens.background.ts | 9 +- .../verify_message.background.ts | 4 +- .../wallet_names/wallet_names.background.ts | 4 +- src/applications/index.ts | 69 -------- src/background.ts | 94 +--------- src/contents/api.ts | 22 ++- src/contents/events.ts | 4 + src/contents/iframe-placeholder.ts | 28 +++ src/contents/injected.ts | 3 + src/gateways/ar_protocol.ts | 32 ---- src/gateways/cache.ts | 46 +---- src/injected.ts | 112 ------------ src/lib/ans.ts | 4 + src/popup.tsx | 16 +- src/routes/auth/batchSignDataItem.tsx | 2 +- src/routes/popup/purchase.tsx | 2 +- src/tabs/auth.tsx | 48 ++--- src/tabs/dashboard.tsx | 7 +- src/tokens/aoTokens/ao.ts | 72 +------- src/tokens/aoTokens/sync.ts | 165 ++---------------- src/utils/analytics.ts | 39 +---- src/utils/context_menus.ts | 2 + src/utils/promises/retry.ts | 71 ++++++++ src/utils/{ => promises}/sleep.ts | 0 src/utils/promises/timeout.ts | 15 ++ src/utils/retry.ts | 32 ---- src/utils/runtime.ts | 45 +---- src/wallets/auth.ts | 31 ---- src/wallets/index.ts | 38 +--- 89 files changed, 1578 insertions(+), 1414 deletions(-) delete mode 100644 src/api/background.ts create mode 100644 src/api/background/background-modules.ts create mode 100644 src/api/background/background-setup.ts create mode 100644 src/api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts create mode 100644 src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts create mode 100644 src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts create mode 100644 src/api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts rename src/{notifications/api.ts => api/background/handlers/alarms/notifications/notifications-alarm.handler.ts} (60%) create mode 100644 src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts rename src/{subscriptions/api.ts => api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts} (81%) create mode 100644 src/api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts create mode 100644 src/api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts create mode 100644 src/api/background/handlers/browser/install/install.handler.ts create mode 100644 src/api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts create mode 100644 src/api/background/handlers/browser/printer/get-printers/get-printers.handler.ts rename src/{lib/printer.ts => api/background/handlers/browser/printer/print/print.handler.ts} (64%) create mode 100644 src/api/background/handlers/browser/printer/printer.constants.ts create mode 100644 src/api/background/handlers/browser/protocol/protocol.handler.ts create mode 100644 src/api/background/handlers/browser/window-close/window-close.handler.ts rename src/api/{index.ts => background/handlers/message/api-call-message/api-call-message.handler.ts} (62%) create mode 100644 src/api/background/handlers/message/chunk-message/chunk-message.handler.ts create mode 100644 src/api/background/handlers/storage/active-address-change/active-address-change.handler.ts rename src/{applications/events.ts => api/background/handlers/storage/app-config-change/app-config-change.handler.ts} (88%) create mode 100644 src/api/background/handlers/storage/apps-change/app-change.handler.ts rename src/{wallets/event.ts => api/background/handlers/storage/wallet-change/wallet-change.handler.ts} (64%) delete mode 100644 src/api/foreground.ts create mode 100644 src/api/foreground/foreground-modules.ts create mode 100644 src/api/foreground/foreground-setup.ts create mode 100644 src/contents/iframe-placeholder.ts create mode 100644 src/contents/injected.ts delete mode 100644 src/injected.ts create mode 100644 src/utils/promises/retry.ts rename src/utils/{ => promises}/sleep.ts (100%) create mode 100644 src/utils/promises/timeout.ts delete mode 100644 src/utils/retry.ts diff --git a/src/api/background.ts b/src/api/background.ts deleted file mode 100644 index 2b5df2526..000000000 --- a/src/api/background.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { Module } from "./module"; - -// import modules -import permissionsModule from "./modules/permissions"; -import permissions from "./modules/permissions/permissions.background"; -import activeAddressModule from "./modules/active_address"; -import activeAddress from "./modules/active_address/active_address.background"; -import allAddressesModule from "./modules/all_addresses"; -import allAddresses from "./modules/all_addresses/all_addresses.background"; -import publicKeyModule from "./modules/public_key"; -import publicKey from "./modules/public_key/public_key.background"; -import walletNamesModule from "./modules/wallet_names"; -import walletNames from "./modules/wallet_names/wallet_names.background"; -import arweaveConfigModule from "./modules/arweave_config"; -import arweaveConfig from "./modules/arweave_config/arweave_config.background"; -import disconnectModule from "./modules/disconnect"; -import disconnect from "./modules/disconnect/disconnect.background"; -import connectModule from "./modules/connect"; -import connect from "./modules/connect/connect.background"; -import signModule from "./modules/sign"; -import sign from "./modules/sign/sign.background"; -import dispatchModule from "./modules/dispatch"; -import dispatch from "./modules/dispatch/dispatch.background"; -import encryptModule from "./modules/encrypt"; -import encrypt from "./modules/encrypt/encrypt.background"; -import decryptModule from "./modules/decrypt"; -import decrypt from "./modules/decrypt/decrypt.background"; -import signatureModule from "./modules/signature"; -import signature from "./modules/signature/signature.background"; -import addTokenModule from "./modules/add_token"; -import addToken from "./modules/add_token/add_token.background"; -import isTokenAddedModule from "./modules/is_token_added"; -import isTokenAdded from "./modules/is_token_added/is_token_added.background"; -import signMessageModule from "./modules/sign_message"; -import signMessage from "./modules/sign_message/sign_message.background"; -import privateHashModule from "./modules/private_hash"; -import privateHash from "./modules/private_hash/private_hash.background"; -import verifyMessageModule from "./modules/verify_message"; -import verifyMessage from "./modules/verify_message/verify_message.background"; -import signDataItemModule from "./modules/sign_data_item"; -import signDataItem from "./modules/sign_data_item/sign_data_item.background"; -import batchSignDataItemModule from "./modules/batch_sign_data_item"; -import batchSignDataItem from "./modules/batch_sign_data_item/batch_sign_data_item.background"; -import subscriptionModule from "./modules/subscription"; -import subscription from "./modules/subscription/subscription.background"; -import userTokensModule from "./modules/user_tokens"; -import userTokens from "./modules/user_tokens/user_tokens.background"; -import tokenBalanceModule from "./modules/token_balance"; -import tokenBalance from "./modules/token_balance/token_balance.background"; - -/** Background modules */ -const modules: BackgroundModule[] = [ - { ...permissionsModule, function: permissions }, - { ...activeAddressModule, function: activeAddress }, - { ...allAddressesModule, function: allAddresses }, - { ...publicKeyModule, function: publicKey }, - { ...walletNamesModule, function: walletNames }, - { ...arweaveConfigModule, function: arweaveConfig }, - { ...disconnectModule, function: disconnect }, - { ...connectModule, function: connect }, - { ...addTokenModule, function: addToken }, - { ...isTokenAddedModule, function: isTokenAdded }, - { ...signModule, function: sign }, - { ...dispatchModule, function: dispatch }, - { ...encryptModule, function: encrypt }, - { ...decryptModule, function: decrypt }, - { ...signatureModule, function: signature }, - { ...signMessageModule, function: signMessage }, - { ...privateHashModule, function: privateHash }, - { ...verifyMessageModule, function: verifyMessage }, - { ...signDataItemModule, function: signDataItem }, - { ...subscriptionModule, function: subscription }, - { ...userTokensModule, function: userTokens }, - { ...tokenBalanceModule, function: tokenBalance }, - { ...batchSignDataItemModule, function: batchSignDataItem } -]; - -export default modules; - -/** Extended module interface */ -interface BackgroundModule extends Module { - function: ModuleFunction; -} - -/** - * Extended module function - */ -export type ModuleFunction = ( - appData: ModuleAppData, - ...params: any[] -) => Promise | ResultType; - -export interface ModuleAppData { - appURL: string; - favicon?: string; -} diff --git a/src/api/background/background-modules.ts b/src/api/background/background-modules.ts new file mode 100644 index 000000000..5ece9201e --- /dev/null +++ b/src/api/background/background-modules.ts @@ -0,0 +1,94 @@ +import type { Module } from "../module"; + +// import modules +import permissionsModule from "../modules/permissions"; +import permissions from "../modules/permissions/permissions.background"; +import activeAddressModule from "../modules/active_address"; +import activeAddress from "../modules/active_address/active_address.background"; +import allAddressesModule from "../modules/all_addresses"; +import allAddresses from "../modules/all_addresses/all_addresses.background"; +import publicKeyModule from "../modules/public_key"; +import publicKey from "../modules/public_key/public_key.background"; +import walletNamesModule from "../modules/wallet_names"; +import walletNames from "../modules/wallet_names/wallet_names.background"; +import arweaveConfigModule from "../modules/arweave_config"; +import arweaveConfig from "../modules/arweave_config/arweave_config.background"; +import disconnectModule from "../modules/disconnect"; +import disconnect from "../modules/disconnect/disconnect.background"; +import connectModule from "../modules/connect"; +import connect from "../modules/connect/connect.background"; +import signModule from "../modules/sign"; +import sign from "../modules/sign/sign.background"; +import dispatchModule from "../modules/dispatch"; +import dispatch from "../modules/dispatch/dispatch.background"; +import encryptModule from "../modules/encrypt"; +import encrypt from "../modules/encrypt/encrypt.background"; +import decryptModule from "../modules/decrypt"; +import decrypt from "../modules/decrypt/decrypt.background"; +import signatureModule from "../modules/signature"; +import signature from "../modules/signature/signature.background"; +import addTokenModule from "../modules/add_token"; +import addToken from "../modules/add_token/add_token.background"; +import isTokenAddedModule from "../modules/is_token_added"; +import isTokenAdded from "../modules/is_token_added/is_token_added.background"; +import signMessageModule from "../modules/sign_message"; +import signMessage from "../modules/sign_message/sign_message.background"; +import privateHashModule from "../modules/private_hash"; +import privateHash from "../modules/private_hash/private_hash.background"; +import verifyMessageModule from "../modules/verify_message"; +import verifyMessage from "../modules/verify_message/verify_message.background"; +import signDataItemModule from "../modules/sign_data_item"; +import signDataItem from "../modules/sign_data_item/sign_data_item.background"; +import batchSignDataItemModule from "../modules/batch_sign_data_item"; +import batchSignDataItem from "../modules/batch_sign_data_item/batch_sign_data_item.background"; +import subscriptionModule from "../modules/subscription"; +import subscription from "../modules/subscription/subscription.background"; +import userTokensModule from "../modules/user_tokens"; +import userTokens from "../modules/user_tokens/user_tokens.background"; +import tokenBalanceModule from "../modules/token_balance"; +import tokenBalance from "../modules/token_balance/token_balance.background"; + +export interface ModuleAppData { + appURL: string; + favicon?: string; +} + +/** + * Extended module function + */ +export type BackgroundModuleFunction = ( + appData: ModuleAppData, + ...params: any[] +) => Promise | ResultType; + +/** Extended module interface */ +export interface BackgroundModule extends Module { + function: BackgroundModuleFunction; +} + +/** Background modules */ +export const backgroundModules: BackgroundModule[] = [ + { ...permissionsModule, function: permissions }, + { ...activeAddressModule, function: activeAddress }, + { ...allAddressesModule, function: allAddresses }, + { ...publicKeyModule, function: publicKey }, + { ...walletNamesModule, function: walletNames }, + { ...arweaveConfigModule, function: arweaveConfig }, + { ...disconnectModule, function: disconnect }, + { ...connectModule, function: connect }, + { ...addTokenModule, function: addToken }, + { ...isTokenAddedModule, function: isTokenAdded }, + { ...signModule, function: sign }, + { ...dispatchModule, function: dispatch }, + { ...encryptModule, function: encrypt }, + { ...decryptModule, function: decrypt }, + { ...signatureModule, function: signature }, + { ...signMessageModule, function: signMessage }, + { ...privateHashModule, function: privateHash }, + { ...verifyMessageModule, function: verifyMessage }, + { ...signDataItemModule, function: signDataItem }, + { ...subscriptionModule, function: subscription }, + { ...userTokensModule, function: userTokens }, + { ...tokenBalanceModule, function: tokenBalance }, + { ...batchSignDataItemModule, function: batchSignDataItem } +]; diff --git a/src/api/background/background-setup.ts b/src/api/background/background-setup.ts new file mode 100644 index 000000000..87780034e --- /dev/null +++ b/src/api/background/background-setup.ts @@ -0,0 +1,100 @@ +import { onMessage } from "@arconnect/webext-bridge"; +import { handleTabUpdate } from "~applications/tab"; +import handleFeeAlarm from "~api/modules/sign/fee"; +import { ExtensionStorage } from "~utils/storage"; +import browser from "webextension-polyfill"; +import { handleApiCallMessage } from "~api/background/handlers/message/api-call-message/api-call-message.handler"; +import { handleChunkMessage } from "~api/background/handlers/message/chunk-message/chunk-message.handler"; +import { handleInstall } from "~api/background/handlers/browser/install/install.handler"; +import { handleProtocol } from "~api/background/handlers/browser/protocol/protocol.handler"; +import { handleActiveAddressChange } from "~api/background/handlers/storage/active-address-change/active-address-change.handler"; +import { handleWalletsChange } from "~api/background/handlers/storage/wallet-change/wallet-change.handler"; +import { handleAppsChange } from "~api/background/handlers/storage/apps-change/app-change.handler"; +import { handleAppConfigChange } from "~api/background/handlers/storage/app-config-change/app-config-change.handler"; +import { handleTrackBalanceAlarm } from "~api/background/handlers/alarms/track-balance/track-balance-alarm.handler"; +import { handleGetPrinters } from "~api/background/handlers/browser/printer/get-printers/get-printers.handler"; +import { handleGetCapabilities } from "~api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler"; +import { handlePrint } from "~api/background/handlers/browser/printer/print/print.handler"; +import { handleNotificationsAlarm } from "~api/background/handlers/alarms/notifications/notifications-alarm.handler"; +import { handleSubscriptionsAlarm } from "~api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler"; +import { handleAoTokenCacheAlarm } from "~api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler"; +import { handleGatewayUpdateAlarm } from "~api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler"; +import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; +import { handleWindowClose } from "~api/background/handlers/browser/window-close/window-close.handler"; +import { handleKeyRemovalAlarm } from "~api/background/handlers/alarms/key-removal/key-removal-alarm.handler"; +import { handleAoTokensImportAlarm } from "~api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler"; + +export function setupBackgroundService() { + // MESSAGES: + // Watch for API call and chunk messages: + onMessage("api_call", handleApiCallMessage); + onMessage("chunk", handleChunkMessage); + + // LIFECYCLE: + + // Open welcome page on extension install. + // TODO: This needs to be adapted to work with the embedded wallet (it cannot just be removed / skipped): + browser.runtime.onInstalled.addListener(handleInstall); + + // ALARMS: + // TODO: Mock/polyfill alarms to work with setTimeout/clearTimeout and also a lazy version that just checks the last update time on start. + + browser.alarms.onAlarm.addListener(handleNotificationsAlarm); + browser.alarms.onAlarm.addListener(handleSubscriptionsAlarm); + browser.alarms.onAlarm.addListener(handleTrackBalanceAlarm); + browser.alarms.onAlarm.addListener(handleGatewayUpdateAlarm); + browser.alarms.onAlarm.addListener(handleSyncLabelsAlarm); + browser.alarms.onAlarm.addListener(handleKeyRemovalAlarm); + browser.alarms.onAlarm.addListener(handleAoTokenCacheAlarm); + browser.alarms.onAlarm.addListener(handleAoTokensImportAlarm); + + // handle keep alive alarm + browser.alarms.onAlarm.addListener((alarm) => { + if (alarm.name === "keep-alive") { + console.log("keep alive alarm"); + } + }); + + // handle fee alarm (send fees asynchronously) + // browser.alarms.onAlarm.addListener(handleFeeAlarm); + + // STORAGE: + + // watch for active address changes / app + // list changes + // and send them to the content script to + // fire the wallet switch event + ExtensionStorage.watch({ + apps: handleAppsChange, + active_address: handleActiveAddressChange, + wallets: handleWalletsChange + }); + + // listen for app config updates + browser.storage.onChanged.addListener(handleAppConfigChange); + + // ONLY EXTENSION: + + // When the last window connected to the extension is closed, the decryption key will be removed from memory. This is no needed in the embedded wallet because + // each wallet instance will be removed automatically when its tab/window is closed. + browser.windows.onRemoved.addListener(handleWindowClose); + + // handle tab change (icon, context menus) + browser.tabs.onUpdated.addListener((tabId) => handleTabUpdate(tabId)); + browser.tabs.onActivated.addListener(({ tabId }) => handleTabUpdate(tabId)); + + // handle ar:// protocol + browser.webNavigation.onBeforeNavigate.addListener(handleProtocol); + + // print to the permaweb (only on chrome) + if (typeof chrome !== "undefined") { + // @ts-expect-error + chrome.printerProvider.onGetCapabilityRequested.addListener( + handleGetCapabilities + ); + chrome.printerProvider.onGetPrintersRequested.addListener( + handleGetPrinters + ); + chrome.printerProvider.onPrintRequested.addListener(handlePrint); + } +} diff --git a/src/api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts b/src/api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts new file mode 100644 index 000000000..a955eb66c --- /dev/null +++ b/src/api/background/handlers/alarms/ao-tokens-cache/ao-tokens-cache-alarm.handler.ts @@ -0,0 +1,65 @@ +import { dryrun } from "@permaweb/aoconnect"; +import { ExtensionStorage } from "~utils/storage"; +import type { Alarms } from "webextension-polyfill"; +import { + getTagValue, + Id, + Owner, + type Message, + type TokenInfo +} from "~tokens/aoTokens/ao"; +import { timeoutPromise } from "~utils/promises/timeout"; + +/** + * Alarm handler for syncing ao tokens + */ +export async function handleAoTokenCacheAlarm(alarm?: Alarms.Alarm) { + if (alarm && !alarm.name.startsWith("update_ao_tokens")) return; + + const aoTokens = (await ExtensionStorage.get("ao_tokens")) || []; + + const updatedTokens = [...aoTokens]; + + for (const token of aoTokens) { + try { + const res = await timeoutPromise( + dryrun({ + Id, + Owner, + process: token.processId, + tags: [{ name: "Action", value: "Info" }] + }), + 6000 + ); + + if (res.Messages && Array.isArray(res.Messages)) { + for (const msg of res.Messages as Message[]) { + const Ticker = getTagValue("Ticker", msg.Tags); + const Name = getTagValue("Name", msg.Tags); + const Denomination = getTagValue("Denomination", msg.Tags); + const Logo = getTagValue("Logo", msg.Tags); + const updatedToken = { + Name, + Ticker, + Denomination: Number(Denomination), + processId: token.processId, + Logo, + lastUpdated: new Date().toISOString() + }; + + const index = updatedTokens.findIndex( + (t) => t.processId === token.processId + ); + + if (index !== -1) { + updatedTokens[index] = { ...updatedTokens[index], ...updatedToken }; + } + } + } + } catch (err) { + console.error(`Failed to update token with id ${token.processId}:`, err); + } + } + + await ExtensionStorage.set("ao_tokens", updatedTokens); +} diff --git a/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts b/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts new file mode 100644 index 000000000..165adf44b --- /dev/null +++ b/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts @@ -0,0 +1,114 @@ +import Arweave from "arweave"; +import type { Alarms } from "webextension-polyfill"; +import { + getAoTokens, + getAoTokensCache, + getAoTokensAutoImportRestrictedIds +} from "~tokens"; +import { getTokenInfo } from "~tokens/aoTokens/router"; +import { + AO_TOKENS, + AO_TOKENS_AUTO_IMPORT_RESTRICTED_IDS, + AO_TOKENS_IMPORT_TIMESTAMP, + gateway, + getNoticeTransactions +} from "~tokens/aoTokens/sync"; +import { withRetry } from "~utils/promises/retry"; +import { timeoutPromise } from "~utils/promises/timeout"; +import { ExtensionStorage } from "~utils/storage"; +import { getActiveAddress } from "~wallets"; + +/** + * Import AO Tokens + */ +export async function handleAoTokensImportAlarm(alarm: Alarms.Alarm) { + if (alarm?.name !== "import_ao_tokens") return; + + try { + const activeAddress = await getActiveAddress(); + + console.log("Importing AO tokens..."); + + let [aoTokens, aoTokensCache, removedTokenIds = []] = await Promise.all([ + getAoTokens(), + getAoTokensCache(), + getAoTokensAutoImportRestrictedIds() + ]); + + let aoTokensIds = new Set(aoTokens.map(({ processId }) => processId)); + const aoTokensCacheIds = new Set( + aoTokensCache.map(({ processId }) => processId) + ); + let tokenIdstoExclude = new Set([...aoTokensIds, ...removedTokenIds]); + const walletTokenIds = new Set([...tokenIdstoExclude, ...aoTokensCacheIds]); + + const arweave = new Arweave(gateway); + const { processIds } = await getNoticeTransactions( + arweave, + activeAddress, + Array.from(walletTokenIds) + ); + + const newProcessIds = Array.from( + new Set([...processIds, ...aoTokensCacheIds]) + ).filter((processId) => !tokenIdstoExclude.has(processId)); + + if (newProcessIds.length === 0) { + console.log("No new ao tokens found!"); + return; + } + + const promises = newProcessIds + .filter((processId) => !aoTokensCacheIds.has(processId)) + .map((processId) => + withRetry(async () => { + const token = await timeoutPromise(getTokenInfo(processId), 3000); + return { ...token, processId }; + }, 2) + ); + const results = await Promise.allSettled(promises); + + const tokens = []; + const tokensToRestrict = []; + results.forEach((result) => { + if (result.status === "fulfilled") { + const token = result.value; + if (token.Ticker) { + tokens.push(token); + } else if (!removedTokenIds.includes(token.processId)) { + tokensToRestrict.push(token); + } + } + }); + + const updatedTokens = [...aoTokensCache, ...tokens]; + + aoTokens = await getAoTokens(); + aoTokensIds = new Set(aoTokens.map(({ processId }) => processId)); + tokenIdstoExclude = new Set([...aoTokensIds, ...removedTokenIds]); + + if (tokensToRestrict.length > 0) { + removedTokenIds.push( + ...tokensToRestrict.map(({ processId }) => processId) + ); + await ExtensionStorage.set( + AO_TOKENS_AUTO_IMPORT_RESTRICTED_IDS, + removedTokenIds + ); + } + + const newTokens = updatedTokens.filter( + (token) => !tokenIdstoExclude.has(token.processId) + ); + if (newTokens.length === 0) return; + + newTokens.forEach((token) => aoTokens.push(token)); + await ExtensionStorage.set(AO_TOKENS, aoTokens); + + console.log("Imported ao tokens!"); + } catch (error: any) { + console.log("Error importing tokens: ", error?.message); + } finally { + await ExtensionStorage.set(AO_TOKENS_IMPORT_TIMESTAMP, 0); + } +} diff --git a/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts b/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts new file mode 100644 index 000000000..708ddb23b --- /dev/null +++ b/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts @@ -0,0 +1,47 @@ +import { extractGarItems, pingUpdater } from "~lib/wayfinder"; +import { type Alarms } from "webextension-polyfill"; +import { AOProcess } from "~lib/ao"; +import { AO_ARNS_PROCESS } from "~lib/arns"; +import type { + GatewayAddressRegistryCache, + ProcessedData +} from "~gateways/types"; +import { + RETRY_ALARM, + scheduleGatewayUpdate, + UPDATE_ALARM, + updateGatewayCache +} from "~gateways/cache"; + +/** + * Gateway cache update call. Usually called by an alarm, + * but will also be executed on install. + */ +export async function handleGatewayUpdateAlarm(alarm?: Alarms.Alarm) { + if (alarm && ![UPDATE_ALARM, RETRY_ALARM].includes(alarm.name)) { + return; + } + + let procData: ProcessedData[] = []; + + try { + // fetch cache + const ArIO = new AOProcess({ processId: AO_ARNS_PROCESS }); + const gateways = (await ArIO.read({ + tags: [{ name: "Action", value: "Gateways" }] + })) as GatewayAddressRegistryCache["gateways"]; + const garItems = extractGarItems({ gateways }); + + // healtcheck + await pingUpdater(garItems, (newData) => { + procData = [...newData]; + }); + + await updateGatewayCache(procData); + } catch (err) { + console.log("err in handle", err); + + // schedule to try again + await scheduleGatewayUpdate(true); + } +} diff --git a/src/api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts b/src/api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts new file mode 100644 index 000000000..a8c836f07 --- /dev/null +++ b/src/api/background/handlers/alarms/key-removal/key-removal-alarm.handler.ts @@ -0,0 +1,16 @@ +import type { Alarms } from "webextension-polyfill"; +import { getDecryptionKey, removeDecryptionKey } from "~wallets/auth"; + +/** + * Listener for the key removal alarm + */ +export async function handleKeyRemovalAlarm(alarm: Alarms.Alarm) { + if (alarm.name !== "remove_decryption_key_scheduled") return; + + // check if there is a decryption key + const decryptionKey = await getDecryptionKey(); + if (!decryptionKey) return; + + // remove the decryption key + await removeDecryptionKey(); +} diff --git a/src/notifications/api.ts b/src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts similarity index 60% rename from src/notifications/api.ts rename to src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts index 5600b72c5..14713a894 100644 --- a/src/notifications/api.ts +++ b/src/api/background/handlers/alarms/notifications/notifications-alarm.handler.ts @@ -2,53 +2,18 @@ import { ExtensionStorage } from "~utils/storage"; import { getActiveAddress } from "~wallets"; import iconUrl from "url:/assets/icon512.png"; import browser, { type Alarms } from "webextension-polyfill"; -import { gql } from "~gateways/api"; -import { suggestedGateways } from "~gateways/gateway"; +import { arNotificationsHandler } from "~api/background/handlers/alarms/notifications/notifications-alarm.utils"; import { + ALL_AR_RECEIVER_QUERY, + ALL_AR_SENT_QUERY, AO_RECEIVER_QUERY, AO_SENT_QUERY, AR_RECEIVER_QUERY, - AR_SENT_QUERY, - ALL_AR_RECEIVER_QUERY, - ALL_AR_SENT_QUERY, - combineAndSortTransactions, - processTransactions -} from "./utils"; -import BigNumber from "bignumber.js"; - -export type RawTransaction = { - node: { - id: string; - recipient: string; - owner: { - address: string; - }; - quantity: { - ar: string; - }; - block: { - timestamp: number; - height: number; - }; - tags: Array<{ - name: string; - value: string; - }>; - }; -}; - -export type Transaction = RawTransaction & { - transactionType: string; - quantity: string; - isAo?: boolean; - tokenId?: string; - warpContract?: boolean; -}; + AR_SENT_QUERY +} from "~notifications/utils"; -type ArNotificationsHandlerReturnType = [Transaction[], number, any[]]; - -export async function notificationsHandler(alarmInfo?: Alarms.Alarm) { - if (alarmInfo && !alarmInfo.name.startsWith("notifications")) return; +export async function handleNotificationsAlarm(alarm?: Alarms.Alarm) { + if (alarm && !alarm.name.startsWith("notifications")) return; const notificationSetting: boolean = await ExtensionStorage.get( "setting_notifications" @@ -179,65 +144,3 @@ export async function notificationsHandler(alarmInfo?: Alarms.Alarm) { console.error("Error updating notifications:", err); } } - -const arNotificationsHandler = async ( - address: string, - lastStoredHeight: number, - notificationSetting: boolean, - queriesConfig: { - query: string; - variables: Record; - isAllTxns?: boolean; - }[] -): Promise => { - try { - let transactionDiff = []; - - const queries = queriesConfig.map((config) => - gql(config.query, config.variables, suggestedGateways[1]) - ); - let responses = await Promise.all(queries); - responses = responses.map((response, index) => { - if ( - typeof queriesConfig[index].isAllTxns === "boolean" && - !queriesConfig[index].isAllTxns - ) { - response.data.transactions.edges = - response.data.transactions.edges.filter((edge) => - BigNumber(edge.node.quantity.ar).gt(0) - ); - } - return response; - }); - - const combinedTransactions = combineAndSortTransactions(responses); - - const enrichedTransactions = processTransactions( - combinedTransactions, - address - ); - - const newMaxHeight = Math.max( - ...enrichedTransactions - .filter((tx) => tx.node.block) // Filter out transactions without a block - .map((tx) => tx.node.block.height) - ); - // filters out transactions that are older than last stored height, - if (newMaxHeight !== lastStoredHeight) { - const newTransactions = enrichedTransactions.filter( - (transaction) => - transaction.node.block && - transaction.node.block.height > lastStoredHeight - ); - - // if it's the first time loading notifications, don't send a message && notifications are enabled - if (lastStoredHeight !== 0 && notificationSetting) { - await ExtensionStorage.set("new_notifications", true); - transactionDiff = newTransactions; - } - } - return [enrichedTransactions, newMaxHeight, transactionDiff]; - } catch (err) { - console.log("err", err); - } -}; diff --git a/src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts b/src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts new file mode 100644 index 000000000..2362b97d3 --- /dev/null +++ b/src/api/background/handlers/alarms/notifications/notifications-alarm.utils.ts @@ -0,0 +1,101 @@ +import BigNumber from "bignumber.js"; +import { gql } from "~gateways/api"; +import { suggestedGateways } from "~gateways/gateway"; +import { + combineAndSortTransactions, + processTransactions +} from "~notifications/utils"; +import { ExtensionStorage } from "~utils/storage"; + +export type RawTransaction = { + node: { + id: string; + recipient: string; + owner: { + address: string; + }; + quantity: { + ar: string; + }; + block: { + timestamp: number; + height: number; + }; + tags: Array<{ + name: string; + value: string; + }>; + }; +}; + +export type Transaction = RawTransaction & { + transactionType: string; + quantity: string; + isAo?: boolean; + tokenId?: string; + warpContract?: boolean; +}; + +type ArNotificationsHandlerReturnType = [Transaction[], number, any[]]; + +export async function arNotificationsHandler( + address: string, + lastStoredHeight: number, + notificationSetting: boolean, + queriesConfig: { + query: string; + variables: Record; + isAllTxns?: boolean; + }[] +): Promise { + try { + let transactionDiff = []; + + const queries = queriesConfig.map((config) => + gql(config.query, config.variables, suggestedGateways[1]) + ); + let responses = await Promise.all(queries); + responses = responses.map((response, index) => { + if ( + typeof queriesConfig[index].isAllTxns === "boolean" && + !queriesConfig[index].isAllTxns + ) { + response.data.transactions.edges = + response.data.transactions.edges.filter((edge) => + BigNumber(edge.node.quantity.ar).gt(0) + ); + } + return response; + }); + + const combinedTransactions = combineAndSortTransactions(responses); + + const enrichedTransactions = processTransactions( + combinedTransactions, + address + ); + + const newMaxHeight = Math.max( + ...enrichedTransactions + .filter((tx) => tx.node.block) // Filter out transactions without a block + .map((tx) => tx.node.block.height) + ); + // filters out transactions that are older than last stored height, + if (newMaxHeight !== lastStoredHeight) { + const newTransactions = enrichedTransactions.filter( + (transaction) => + transaction.node.block && + transaction.node.block.height > lastStoredHeight + ); + + // if it's the first time loading notifications, don't send a message && notifications are enabled + if (lastStoredHeight !== 0 && notificationSetting) { + await ExtensionStorage.set("new_notifications", true); + transactionDiff = newTransactions; + } + } + return [enrichedTransactions, newMaxHeight, transactionDiff]; + } catch (err) { + console.log("err", err); + } +} diff --git a/src/subscriptions/api.ts b/src/api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts similarity index 81% rename from src/subscriptions/api.ts rename to src/api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts index 2c431a96a..f9855ae9e 100644 --- a/src/subscriptions/api.ts +++ b/src/api/background/handlers/alarms/subscriptions/subscriptions-alarm.handler.ts @@ -1,9 +1,8 @@ -import type { SubscriptionData } from "./subscription"; import { addSubscription, getSubscriptionData } from "~subscriptions"; -import { ExtensionStorage } from "~utils/storage"; import { getActiveAddress } from "~wallets"; -import { handleSubscriptionPayment } from "./payments"; import type { Alarms } from "webextension-polyfill"; +import { handleSubscriptionPayment } from "~subscriptions/payments"; +import type { SubscriptionData } from "~subscriptions/subscription"; /** * + fetch subscription auto withdrawal allowance @@ -13,7 +12,7 @@ import type { Alarms } from "webextension-polyfill"; * + notify user of manual payments */ -export async function subscriptionsHandler(alarmInfo?: Alarms.Alarm) { +export async function handleSubscriptionsAlarm(alarmInfo?: Alarms.Alarm) { if (alarmInfo && !alarmInfo.name.startsWith("subscription-alarm-")) return; const prefixLength = "subscription-alarm-".length; diff --git a/src/api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts b/src/api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts new file mode 100644 index 000000000..c593ea466 --- /dev/null +++ b/src/api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler.ts @@ -0,0 +1,38 @@ +import { getAnsProfile, type AnsUser } from "~lib/ans"; +import type { Alarms } from "webextension-polyfill"; +import { ExtensionStorage } from "~utils/storage"; +import { getWallets } from "~wallets"; + +/** + * Sync nicknames with ANS labels + */ +export async function handleSyncLabelsAlarm(alarm?: Alarms.Alarm) { + // check alarm name if called from an alarm + if (alarm && alarm.name !== "sync_labels") { + return; + } + + // get wallets + const wallets = await getWallets(); + + if (wallets.length === 0) return; + + // get profiles + const profiles = (await getAnsProfile( + wallets.map((w) => w.address) + )) as AnsUser[]; + + const find = (addr: string) => + profiles.find((w) => w.user === addr)?.currentLabel; + + // save updated wallets + await ExtensionStorage.set( + "wallets", + wallets.map((wallet) => ({ + ...wallet, + nickname: find(wallet.address) + ? find(wallet.address) + ".ar" + : wallet.nickname + })) + ); +} diff --git a/src/api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts b/src/api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts new file mode 100644 index 000000000..9c4fcbba9 --- /dev/null +++ b/src/api/background/handlers/alarms/track-balance/track-balance-alarm.handler.ts @@ -0,0 +1,46 @@ +import { getWallets } from "~wallets"; +import Arweave from "arweave"; +import { defaultGateway } from "~gateways/gateway"; +import browser, { type Alarms } from "webextension-polyfill"; +import BigNumber from "bignumber.js"; +import { + EventType, + setToStartOfNextMonth, + trackDirect +} from "~utils/analytics"; + +export async function handleTrackBalanceAlarm(alarmInfo?: Alarms.Alarm) { + if (alarmInfo && !alarmInfo.name.startsWith("track-balance")) return; + + const wallets = await getWallets(); + const arweave = new Arweave(defaultGateway); + + let totalBalance = BigNumber("0"); + + await Promise.all( + wallets.map(async ({ address }) => { + try { + const balance = arweave.ar.winstonToAr( + await arweave.wallets.getBalance(address) + ); + totalBalance = totalBalance.plus(balance); + } catch (e) { + console.log("invalid", e); + } + }) + ); + + try { + await trackDirect(EventType.BALANCE, { + totalBalance: totalBalance.toFixed() + }); + + const timer = setToStartOfNextMonth(new Date()); + + browser.alarms.create("track-balance", { + when: timer.getTime() + }); + } catch (err) { + console.log("err tracking", err); + } +} diff --git a/src/api/background/handlers/browser/install/install.handler.ts b/src/api/background/handlers/browser/install/install.handler.ts new file mode 100644 index 000000000..04c61dfa6 --- /dev/null +++ b/src/api/background/handlers/browser/install/install.handler.ts @@ -0,0 +1,43 @@ +import { scheduleGatewayUpdate } from "~gateways/cache"; +import browser, { type Runtime } from "webextension-polyfill"; +import { loadTokens } from "~tokens/token"; +import { initializeARBalanceMonitor } from "~utils/analytics"; +import { updateAoToken } from "~utils/ao_import"; +import { handleGatewayUpdateAlarm } from "~api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler"; + +/** + * On extension installed event handler + */ +export async function handleInstall(details: Runtime.OnInstalledDetailsType) { + // only run on install + if (details.reason === "install") { + // open welcome page + browser.tabs.create({ + url: browser.runtime.getURL("tabs/welcome.html") + }); + } + + // init monthly AR + await initializeARBalanceMonitor(); + + // initialize alarm to fetch notifications + browser.alarms.create("notifications", { periodInMinutes: 1 }); + + // reset notifications + // await ExtensionStorage.set("show_announcement", true); + + // initialize alarm to update tokens once a week + browser.alarms.create("update_ao_tokens", { + periodInMinutes: 10080 + }); + + // initialize tokens in wallet + await loadTokens(); + + // update old ao token to new ao token + await updateAoToken(); + + // wayfinder + await scheduleGatewayUpdate(); + await handleGatewayUpdateAlarm(); +} diff --git a/src/api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts b/src/api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts new file mode 100644 index 000000000..df1b5c6ab --- /dev/null +++ b/src/api/background/handlers/browser/printer/get-capabilities/get-capabilities.handler.ts @@ -0,0 +1,71 @@ +import { ARCONNECT_PRINTER_ID } from "~api/background/handlers/browser/printer/printer.constants"; + +/** + * Printer capabilities request callback type + */ +type PrinterCapabilitiesCallback = (p: unknown) => void; + +/** + * Tells Chrome about the virtual printer's + * capabilities in CDD format + */ +export function handleGetCapabilities( + printerId: string, + callback: PrinterCapabilitiesCallback +) { + // only return capabilities for the ArConnect printer + if (printerId !== ARCONNECT_PRINTER_ID) return; + + // mimic a regular printer's capabilities + callback({ + version: "1.0", + printer: { + supported_content_type: [ + { content_type: "application/pdf" }, + { content_type: "image/pwg-raster" } + ], + color: { + option: [ + { type: "STANDARD_COLOR", is_default: true }, + { type: "STANDARD_MONOCHROME" } + ] + }, + copies: { + default_copies: 1, + max_copies: 100 + }, + media_size: { + option: [ + { + name: "ISO_A4", + width_microns: 210000, + height_microns: 297000, + is_default: true + }, + { + name: "NA_LETTER", + width_microns: 215900, + height_microns: 279400 + } + ] + }, + page_orientation: { + option: [ + { + type: "PORTRAIT", + is_default: true + }, + { type: "LANDSCAPE" }, + { type: "AUTO" } + ] + }, + duplex: { + option: [ + { type: "NO_DUPLEX", is_default: true }, + { type: "LONG_EDGE" }, + { type: "SHORT_EDGE" } + ] + } + } + }); +} diff --git a/src/api/background/handlers/browser/printer/get-printers/get-printers.handler.ts b/src/api/background/handlers/browser/printer/get-printers/get-printers.handler.ts new file mode 100644 index 000000000..6e7906db6 --- /dev/null +++ b/src/api/background/handlers/browser/printer/get-printers/get-printers.handler.ts @@ -0,0 +1,22 @@ +import { ARCONNECT_PRINTER_ID } from "~api/background/handlers/browser/printer/printer.constants"; + +/** + * Printer info request callback type + */ +type PrinterInfoCallback = (p: chrome.printerProvider.PrinterInfo[]) => void; + +/** + * Returns a list of "virtual" printers, + * in our case "Print/Publish to Arweave" + */ +export function handleGetPrinters(callback: PrinterInfoCallback) { + callback([ + { + id: ARCONNECT_PRINTER_ID, + // TODO: Add to i18n: + name: "Print to Arweave", + description: + "Publish the content you want to print on Arweave, permanently." + } + ]); +} diff --git a/src/lib/printer.ts b/src/api/background/handlers/browser/printer/print/print.handler.ts similarity index 64% rename from src/lib/printer.ts rename to src/api/background/handlers/browser/printer/print/print.handler.ts index 330fb0401..e126a20ab 100644 --- a/src/lib/printer.ts +++ b/src/api/background/handlers/browser/printer/print/print.handler.ts @@ -1,3 +1,4 @@ +import { ARCONNECT_PRINTER_ID } from "~api/background/handlers/browser/printer/printer.constants"; import { uploadDataToTurbo } from "~api/modules/dispatch/uploader"; import { getActiveKeyfile, type DecryptedWallet } from "~wallets"; import { freeDecryptedWallet } from "~wallets/encryption"; @@ -8,104 +9,17 @@ import browser from "webextension-polyfill"; import Arweave from "arweave"; import { signAuth } from "~api/modules/sign/sign_auth"; import { getActiveTab } from "~applications"; -import { sleep } from "~utils/sleep"; - -const ARCONNECT_PRINTER_ID = "arconnect-permaweb-printer"; - -/** - * Tells Chrome about the virtual printer's - * capabilities in CDD format - */ -export function getCapabilities( - printerId: string, - callback: PrinterCapabilitiesCallback -) { - // only return capabilities for the ArConnect printer - if (printerId !== ARCONNECT_PRINTER_ID) return; - - // mimic a regular printer's capabilities - callback({ - version: "1.0", - printer: { - supported_content_type: [ - { content_type: "application/pdf" }, - { content_type: "image/pwg-raster" } - ], - color: { - option: [ - { type: "STANDARD_COLOR", is_default: true }, - { type: "STANDARD_MONOCHROME" } - ] - }, - copies: { - default_copies: 1, - max_copies: 100 - }, - media_size: { - option: [ - { - name: "ISO_A4", - width_microns: 210000, - height_microns: 297000, - is_default: true - }, - { - name: "NA_LETTER", - width_microns: 215900, - height_microns: 279400 - } - ] - }, - page_orientation: { - option: [ - { - type: "PORTRAIT", - is_default: true - }, - { type: "LANDSCAPE" }, - { type: "AUTO" } - ] - }, - duplex: { - option: [ - { type: "NO_DUPLEX", is_default: true }, - { type: "LONG_EDGE" }, - { type: "SHORT_EDGE" } - ] - } - } - }); -} +import { sleep } from "~utils/promises/sleep"; /** - * Printer capabilities request callback type - */ -type PrinterCapabilitiesCallback = (p: unknown) => void; - -/** - * Returns a list of "virtual" printers, - * in our case "Print/Publish to Arweave" - */ -export function getPrinters(callback: PrinterInfoCallback) { - callback([ - { - id: ARCONNECT_PRINTER_ID, - name: "Print to Arweave", - description: - "Publish the content you want to print on Arweave, permanently." - } - ]); -} - -/** - * Printer info request callback type + * Print request (result) callback */ -type PrinterInfoCallback = (p: chrome.printerProvider.PrinterInfo[]) => void; +type PrintCallback = (result: string) => void; /** * Handles the request from the user to print the page to Arweave */ -export async function handlePrintRequest( +export async function handlePrint( printJob: chrome.printerProvider.PrintJob, resultCallback: PrintCallback ) { @@ -220,8 +134,3 @@ export async function handlePrintRequest( if (decryptedWallet?.type == "local") freeDecryptedWallet(decryptedWallet.keyfile); } - -/** - * Print request (result) callback - */ -type PrintCallback = (result: string) => void; diff --git a/src/api/background/handlers/browser/printer/printer.constants.ts b/src/api/background/handlers/browser/printer/printer.constants.ts new file mode 100644 index 000000000..831c7fbac --- /dev/null +++ b/src/api/background/handlers/browser/printer/printer.constants.ts @@ -0,0 +1 @@ +export const ARCONNECT_PRINTER_ID = "arconnect-permaweb-printer"; diff --git a/src/api/background/handlers/browser/protocol/protocol.handler.ts b/src/api/background/handlers/browser/protocol/protocol.handler.ts new file mode 100644 index 000000000..2b7b886cf --- /dev/null +++ b/src/api/background/handlers/browser/protocol/protocol.handler.ts @@ -0,0 +1,33 @@ +import { getRedirectURL } from "~gateways/ar_protocol"; +import { findGateway } from "~gateways/wayfinder"; +import browser, { type WebNavigation } from "webextension-polyfill"; + +/** + * Handle custom ar:// protocol, using the + * browser.webNavigation.onBeforeNavigate API. + * + * This is based on the issue in ipfs/ipfs-companion: + * https://github.com/ipfs/ipfs-companion/issues/164#issuecomment-328374052 + * + * Thank you ar.io for the updated method: + * https://github.com/ar-io/wayfinder/blob/main/background.js#L13 + */ +export async function handleProtocol( + details: WebNavigation.OnBeforeNavigateDetailsType +) { + const gateway = await findGateway({ + arns: true, + ensureStake: true + }); + + // parse redirect url + const redirectUrl = getRedirectURL(new URL(details.url), gateway); + + // don't do anything if it is not a protocol call + if (!redirectUrl) return; + + // update tab + browser.tabs.update(details.tabId, { + url: redirectUrl + }); +} diff --git a/src/api/background/handlers/browser/window-close/window-close.handler.ts b/src/api/background/handlers/browser/window-close/window-close.handler.ts new file mode 100644 index 000000000..ccb09e76e --- /dev/null +++ b/src/api/background/handlers/browser/window-close/window-close.handler.ts @@ -0,0 +1,19 @@ +import { removeDecryptionKey } from "~wallets/auth"; +import browser from "webextension-polyfill"; + +/** + * Listener for browser close. + * On browser closed, we remove the + * decryptionKey. + */ +export async function handleWindowClose() { + const windows = await browser.windows.getAll(); + + // return if there are still windows open + if (windows.length > 0) { + return; + } + + // remove the decryption key + await removeDecryptionKey(); +} diff --git a/src/api/index.ts b/src/api/background/handlers/message/api-call-message/api-call-message.handler.ts similarity index 62% rename from src/api/index.ts rename to src/api/background/handlers/message/api-call-message/api-call-message.handler.ts index 05d2fe800..a71edf086 100644 --- a/src/api/index.ts +++ b/src/api/background/handlers/message/api-call-message/api-call-message.handler.ts @@ -1,20 +1,21 @@ import { isExactly, isNotUndefined, isString } from "typed-assert"; import type { OnMessageCallback } from "@arconnect/webext-bridge"; -import { type Chunk, handleChunk } from "./modules/sign/chunks"; -import { isApiCall, isChunk } from "~utils/assertions"; +import { isApiCall } from "~utils/assertions"; import Application from "~applications/application"; import type { ApiCall, ApiResponse } from "shim"; import browser from "webextension-polyfill"; import { getTab } from "~applications/tab"; import { pushEvent } from "~utils/events"; import { getAppURL } from "~utils/format"; -import modules from "./background"; +import { backgroundModules } from "~api/background/background-modules"; -export const handleApiCalls: OnMessageCallback< +export const handleApiCallMessage: OnMessageCallback< // @ts-expect-error ApiCall, ApiResponse > = async ({ data, sender }) => { + console.log("HANDLE API CALL"); + // construct base message to extend and return const baseMessage: ApiResponse = { type: data.type + "_result", @@ -45,7 +46,9 @@ export const handleApiCalls: OnMessageCallback< // find module to execute const functionName = data.type.replace("api_", ""); - const mod = modules.find((mod) => mod.functionName === functionName); + const mod = backgroundModules.find( + (mod) => mod.functionName === functionName + ); // if we cannot find the module, we return with an error isNotUndefined(mod, `API function "${functionName}" not found`); @@ -119,65 +122,3 @@ export const handleApiCalls: OnMessageCallback< }; } }; - -export const handleChunkCalls: OnMessageCallback< - // @ts-expect-error - ApiCall, - ApiResponse -> = async ({ data, sender }) => { - // construct base message to extend and return - const baseMessage: ApiResponse = { - type: "chunk_result", - callID: data.callID - }; - - try { - // validate message - isExactly( - sender.context, - "content-script", - "Chunk calls are only accepted from the injected-script -> content-script" - ); - isChunk(data.data); - - // grab the tab where the chunk came - const tab = await getTab(sender.tabId); - - // if the tab is not found, reject the call - isString(tab?.url, "Call coming from invalid tab"); - - // raw url where the chunk originates from - let url = tab.url; - - // if the frame ID is defined, the API - // request is not coming from the main tab - // but from an iframe in the tab. - // we need to treat the iframe as a separate - // application to ensure the user does not - // mistake it for the actual app - if (typeof sender.frameId !== "undefined") { - const frame = await browser.webNavigation.getFrame({ - frameId: sender.frameId, - tabId: sender.tabId - }); - - // update url value with the url belonging to the frame - if (frame.url) url = frame.url; - } - - // call the chunk handler - const index = handleChunk(data.data, getAppURL(url)); - - return { - ...baseMessage, - data: index - }; - } catch (e) { - // return error - return { - ...baseMessage, - error: true, - data: e?.message || e - }; - } -}; diff --git a/src/api/background/handlers/message/chunk-message/chunk-message.handler.ts b/src/api/background/handlers/message/chunk-message/chunk-message.handler.ts new file mode 100644 index 000000000..5bd19ca60 --- /dev/null +++ b/src/api/background/handlers/message/chunk-message/chunk-message.handler.ts @@ -0,0 +1,70 @@ +import { isExactly, isString } from "typed-assert"; +import type { OnMessageCallback } from "@arconnect/webext-bridge"; +import { type Chunk, handleChunk } from "../../../../modules/sign/chunks"; +import { isChunk } from "~utils/assertions"; +import type { ApiCall, ApiResponse } from "shim"; +import browser from "webextension-polyfill"; +import { getTab } from "~applications/tab"; +import { getAppURL } from "~utils/format"; + +export const handleChunkMessage: OnMessageCallback< + // @ts-expect-error + ApiCall, + ApiResponse +> = async ({ data, sender }) => { + // construct base message to extend and return + const baseMessage: ApiResponse = { + type: "chunk_result", + callID: data.callID + }; + + try { + // validate message + isExactly( + sender.context, + "content-script", + "Chunk calls are only accepted from the injected-script -> content-script" + ); + isChunk(data.data); + + // grab the tab where the chunk came + const tab = await getTab(sender.tabId); + + // if the tab is not found, reject the call + isString(tab?.url, "Call coming from invalid tab"); + + // raw url where the chunk originates from + let url = tab.url; + + // if the frame ID is defined, the API + // request is not coming from the main tab + // but from an iframe in the tab. + // we need to treat the iframe as a separate + // application to ensure the user does not + // mistake it for the actual app + if (typeof sender.frameId !== "undefined") { + const frame = await browser.webNavigation.getFrame({ + frameId: sender.frameId, + tabId: sender.tabId + }); + + // update url value with the url belonging to the frame + if (frame.url) url = frame.url; + } + + // call the chunk handler + const index = handleChunk(data.data, getAppURL(url)); + + return { + ...baseMessage, + data: index + }; + } catch (e) { + // return error + return { + ...baseMessage, + error: true, + data: e?.message || e + }; + } +}; diff --git a/src/api/background/handlers/storage/active-address-change/active-address-change.handler.ts b/src/api/background/handlers/storage/active-address-change/active-address-change.handler.ts new file mode 100644 index 000000000..48f08ed24 --- /dev/null +++ b/src/api/background/handlers/storage/active-address-change/active-address-change.handler.ts @@ -0,0 +1,50 @@ +import { sendMessage } from "@arconnect/webext-bridge"; +import type { StorageChange } from "~utils/runtime"; +import Application from "~applications/application"; +import { forEachTab } from "~applications/tab"; +import { getAppURL } from "~utils/format"; + +/** + * Active address change event listener. + * Sends a message to fire the "walletSwitch" + * event in the tab. + */ +export async function handleActiveAddressChange({ + oldValue, + newValue: newAddress +}: StorageChange) { + if (!newAddress || oldValue === newAddress) return; + + // go through all tabs and check if they + // have the permissions to receive the + // wallet switch event + await forEachTab(async (tab) => { + const app = new Application(getAppURL(tab.url)); + + // check required permissions + const permissionCheck = await app.hasPermissions([ + "ACCESS_ALL_ADDRESSES", + "ACCESS_ADDRESS" + ]); + + // app not connected + if (permissionCheck.has.length === 0) return; + + // trigger emiter + await sendMessage( + "event", + { + name: "activeAddress", + value: permissionCheck.result ? newAddress : null + }, + `content-script@${tab.id}` + ); + + // trigger event via message + await sendMessage( + "switch_wallet_event", + permissionCheck ? newAddress : null, + `content-script@${tab.id}` + ); + }); +} diff --git a/src/applications/events.ts b/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts similarity index 88% rename from src/applications/events.ts rename to src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts index b46986f9a..9bc045520 100644 --- a/src/applications/events.ts +++ b/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts @@ -1,14 +1,15 @@ -import Application, { type InitAppParams, PREFIX } from "./application"; import { sendMessage } from "@arconnect/webext-bridge"; -import { getMissingPermissions } from "./permissions"; import type { StorageChange } from "~utils/runtime"; import { getStoredApps } from "~applications"; -import { compareGateways } from "../gateways/utils"; import { getAppURL } from "~utils/format"; -import { forEachTab } from "./tab"; import type { Event } from "shim"; +import { getMissingPermissions } from "~applications/permissions"; +import { forEachTab } from "~applications/tab"; +import { compareGateways } from "~gateways/utils"; +import type { InitAppParams } from "~applications/application"; +import Application, { PREFIX } from "~applications/application"; -export async function appConfigChangeListener( +export async function handleAppConfigChange( changes: Record>, areaName: string ) { diff --git a/src/api/background/handlers/storage/apps-change/app-change.handler.ts b/src/api/background/handlers/storage/apps-change/app-change.handler.ts new file mode 100644 index 000000000..971eaf019 --- /dev/null +++ b/src/api/background/handlers/storage/apps-change/app-change.handler.ts @@ -0,0 +1,71 @@ +import { createContextMenus } from "~utils/context_menus"; +import { sendMessage } from "@arconnect/webext-bridge"; +import type { StorageChange } from "~utils/runtime"; +import { getAppURL } from "~utils/format"; +import { updateIcon } from "~utils/icon"; +import { forEachTab } from "~applications/tab"; +import { getActiveTab } from "~applications"; +import Application from "~applications/application"; + +/** + * App disconnected listener. Sends a message + * to trigger the disconnected event. + */ +export async function handleAppsChange({ + oldValue, + newValue +}: StorageChange) { + // message to send the event + const triggerEvent = (tabID: number, type: "connect" | "disconnect") => + sendMessage( + "event", + { + name: type, + value: null + }, + `content-script@${tabID}` + ); + + // trigger events + forEachTab(async (tab) => { + // get app url + const appURL = getAppURL(tab.url); + + // if the new value is undefined + // and the old value was defined + // we need to emit the disconnect + // event for all tabs that were + // connected + if (!newValue && !!oldValue) { + if (!oldValue.includes(appURL)) return; + + return await triggerEvent(tab.id, "disconnect"); + } else if (!newValue) { + // if the new value is undefined + // and the old value was also + // undefined, we just return + return; + } + + const oldAppsList = oldValue || []; + + // if the new value includes the app url + // and the old value does not, than the + // app has just been connected + // if the reverse is true, than the app + // has just been disconnected + if (newValue.includes(appURL) && !oldAppsList.includes(appURL)) { + await triggerEvent(tab.id, "connect"); + } else if (!newValue.includes(appURL) && oldAppsList.includes(appURL)) { + await triggerEvent(tab.id, "disconnect"); + } + }); + + // update icon and context menus + const activeTab = await getActiveTab(); + const app = new Application(getAppURL(activeTab.url)); + const connected = await app.isConnected(); + + await updateIcon(connected); + await createContextMenus(connected); +} diff --git a/src/wallets/event.ts b/src/api/background/handlers/storage/wallet-change/wallet-change.handler.ts similarity index 64% rename from src/wallets/event.ts rename to src/api/background/handlers/storage/wallet-change/wallet-change.handler.ts index a1ae279f4..b1129d626 100644 --- a/src/wallets/event.ts +++ b/src/api/background/handlers/storage/wallet-change/wallet-change.handler.ts @@ -6,57 +6,12 @@ import { forEachTab } from "~applications/tab"; import { getAppURL } from "~utils/format"; import browser from "webextension-polyfill"; -/** - * Active address change event listener. - * Sends a message to fire the "walletSwitch" - * event in the tab. - */ -export async function addressChangeListener({ - oldValue, - newValue: newAddress -}: StorageChange) { - if (!newAddress || oldValue === newAddress) return; - - // go through all tabs and check if they - // have the permissions to receive the - // wallet switch event - await forEachTab(async (tab) => { - const app = new Application(getAppURL(tab.url)); - - // check required permissions - const permissionCheck = await app.hasPermissions([ - "ACCESS_ALL_ADDRESSES", - "ACCESS_ADDRESS" - ]); - - // app not connected - if (permissionCheck.has.length === 0) return; - - // trigger emiter - await sendMessage( - "event", - { - name: "activeAddress", - value: permissionCheck.result ? newAddress : null - }, - `content-script@${tab.id}` - ); - - // trigger event via message - await sendMessage( - "switch_wallet_event", - permissionCheck ? newAddress : null, - `content-script@${tab.id}` - ); - }); -} - /** * Added wallets change listener. * Fixup active address in case the current * active address' wallet has been removed. */ -export async function walletsChangeListener({ +export async function handleWalletsChange({ newValue, oldValue }: StorageChange) { diff --git a/src/api/foreground.ts b/src/api/foreground.ts deleted file mode 100644 index bd079a7b7..000000000 --- a/src/api/foreground.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { Module, ModuleFunction } from "./module"; - -// import modules -import permissionsModule from "./modules/permissions"; -import permissions from "./modules/permissions/permissions.foreground"; -import activeAddressModule from "./modules/active_address"; -import activeAddress from "./modules/active_address/active_address.foreground"; -import allAddressesModule from "./modules/all_addresses"; -import allAddresses from "./modules/all_addresses/all_addresses.foreground"; -import publicKeyModule from "./modules/public_key"; -import publicKey from "./modules/public_key/public_key.foreground"; -import walletNamesModule from "./modules/wallet_names"; -import walletNames from "./modules/wallet_names/wallet_names.foreground"; -import arweaveConfigModule from "./modules/arweave_config"; -import arweaveConfig from "./modules/arweave_config/arweave_config.foreground"; -import disconnectModule from "./modules/disconnect"; -import disconnect, { - finalizer as disconnectFinalizer -} from "./modules/disconnect/disconnect.foreground"; -import addTokenModule from "./modules/add_token"; -import addToken from "./modules/add_token/add_token.foreground"; -import isTokenAddedModule from "./modules/is_token_added"; -import isTokenAdded from "./modules/is_token_added/is_token_added.foreground"; -import connectModule from "./modules/connect"; -import connect from "./modules/connect/connect.foreground"; -import signModule from "./modules/sign"; -import sign, { - finalizer as signFinalizer -} from "./modules/sign/sign.foreground"; -import dispatchModule from "./modules/dispatch"; -import dispatch, { - finalizer as dispatchFinalizer -} from "./modules/dispatch/dispatch.foreground"; -import encryptModule from "./modules/encrypt"; -import encrypt, { - finalizer as encryptFinalizer -} from "./modules/encrypt/encrypt.foreground"; -import decryptModule from "./modules/decrypt"; -import decrypt, { - finalizer as decryptFinalizer -} from "./modules/decrypt/decrypt.foreground"; -import signatureModule from "./modules/signature"; -import signature, { - finalizer as signatureFinalizer -} from "./modules/signature/signature.foreground"; -import signMessageModule from "./modules/sign_message"; -import signMessage, { - finalizer as signMessageFinalizer -} from "./modules/sign_message/sign_message.foreground"; -import subscriptionModule from "./modules/subscription"; -import subscription from "./modules/subscription/subscription.foreground"; -import privateHashModule from "./modules/private_hash"; -import privateHash, { - finalizer as privateHashFinalizer -} from "./modules/private_hash/private_hash.foreground"; -import verifyMessageModule from "./modules/verify_message"; -import verifyMessage from "./modules/verify_message/verify_message.foreground"; -import batchSignDataItemModule from "./modules/batch_sign_data_item"; -import batchSignDataItem, { - finalizer as batchSignDataItemFinalizer -} from "./modules/batch_sign_data_item/batch_sign_data_item.foreground"; -import signDataItemModule from "./modules/sign_data_item"; -import signDataItem, { - finalizer as signDataItemFinalizer -} from "./modules/sign_data_item/sign_data_item.foreground"; -import userTokensModule from "./modules/user_tokens"; -import userTokens from "./modules/user_tokens/user_tokens.foreground"; -import tokenBalanceModule from "./modules/token_balance"; -import tokenBalance from "./modules/token_balance/token_balance.foreground"; - -/** Foreground modules */ -const modules: ForegroundModule[] = [ - { ...permissionsModule, function: permissions }, - { ...activeAddressModule, function: activeAddress }, - { ...allAddressesModule, function: allAddresses }, - { ...publicKeyModule, function: publicKey }, - { ...walletNamesModule, function: walletNames }, - { ...arweaveConfigModule, function: arweaveConfig }, - { ...disconnectModule, function: disconnect, finalizer: disconnectFinalizer }, - { ...connectModule, function: connect }, - { ...signModule, function: sign, finalizer: signFinalizer }, - { ...dispatchModule, function: dispatch, finalizer: dispatchFinalizer }, - { ...encryptModule, function: encrypt, finalizer: encryptFinalizer }, - { ...decryptModule, function: decrypt, finalizer: decryptFinalizer }, - { ...signatureModule, function: signature, finalizer: signatureFinalizer }, - { ...addTokenModule, function: addToken }, - { ...isTokenAddedModule, function: isTokenAdded }, - { - ...signMessageModule, - function: signMessage, - finalizer: signMessageFinalizer - }, - { - ...privateHashModule, - function: privateHash, - finalizer: privateHashFinalizer - }, - { ...verifyMessageModule, function: verifyMessage }, - { - ...signDataItemModule, - function: signDataItem, - finalizer: signDataItemFinalizer - }, - { ...subscriptionModule, function: subscription }, - { ...userTokensModule, function: userTokens }, - { ...tokenBalanceModule, function: tokenBalance }, - { - ...batchSignDataItemModule, - function: batchSignDataItem, - finalizer: batchSignDataItemFinalizer - } -]; - -export default modules; - -/** Extended module interface */ -interface ForegroundModule extends Module { - /** - * A function that runs after results were - * returned from the background script. - * This is optional and will be ignored if not set. - */ - finalizer?: ModuleFunction | TransformFinalizer; -} - -/** - * @param result The result from the background script - * @param params The params the background script received - * @param originalParams The params the injected function was called with - */ -export type TransformFinalizer< - ResultType, - ParamsType = any, - OriginalParamsType = any -> = ( - result: ResultType, - params: ParamsType, - originalParams: OriginalParamsType -) => any; diff --git a/src/api/foreground/foreground-modules.ts b/src/api/foreground/foreground-modules.ts new file mode 100644 index 000000000..64b5fef78 --- /dev/null +++ b/src/api/foreground/foreground-modules.ts @@ -0,0 +1,137 @@ +import type { Module, ModuleFunction } from "../module"; + +// import modules +import permissionsModule from "../modules/permissions"; +import permissions from "../modules/permissions/permissions.foreground"; +import activeAddressModule from "../modules/active_address"; +import activeAddress from "../modules/active_address/active_address.foreground"; +import allAddressesModule from "../modules/all_addresses"; +import allAddresses from "../modules/all_addresses/all_addresses.foreground"; +import publicKeyModule from "../modules/public_key"; +import publicKey from "../modules/public_key/public_key.foreground"; +import walletNamesModule from "../modules/wallet_names"; +import walletNames from "../modules/wallet_names/wallet_names.foreground"; +import arweaveConfigModule from "../modules/arweave_config"; +import arweaveConfig from "../modules/arweave_config/arweave_config.foreground"; +import disconnectModule from "../modules/disconnect"; +import disconnect, { + finalizer as disconnectFinalizer +} from "../modules/disconnect/disconnect.foreground"; +import addTokenModule from "../modules/add_token"; +import addToken from "../modules/add_token/add_token.foreground"; +import isTokenAddedModule from "../modules/is_token_added"; +import isTokenAdded from "../modules/is_token_added/is_token_added.foreground"; +import connectModule from "../modules/connect"; +import connect from "../modules/connect/connect.foreground"; +import signModule from "../modules/sign"; +import sign, { + finalizer as signFinalizer +} from "../modules/sign/sign.foreground"; +import dispatchModule from "../modules/dispatch"; +import dispatch, { + finalizer as dispatchFinalizer +} from "../modules/dispatch/dispatch.foreground"; +import encryptModule from "../modules/encrypt"; +import encrypt, { + finalizer as encryptFinalizer +} from "../modules/encrypt/encrypt.foreground"; +import decryptModule from "../modules/decrypt"; +import decrypt, { + finalizer as decryptFinalizer +} from "../modules/decrypt/decrypt.foreground"; +import signatureModule from "../modules/signature"; +import signature, { + finalizer as signatureFinalizer +} from "../modules/signature/signature.foreground"; +import signMessageModule from "../modules/sign_message"; +import signMessage, { + finalizer as signMessageFinalizer +} from "../modules/sign_message/sign_message.foreground"; +import subscriptionModule from "../modules/subscription"; +import subscription from "../modules/subscription/subscription.foreground"; +import privateHashModule from "../modules/private_hash"; +import privateHash, { + finalizer as privateHashFinalizer +} from "../modules/private_hash/private_hash.foreground"; +import verifyMessageModule from "../modules/verify_message"; +import verifyMessage from "../modules/verify_message/verify_message.foreground"; +import batchSignDataItemModule from "../modules/batch_sign_data_item"; +import batchSignDataItem, { + finalizer as batchSignDataItemFinalizer +} from "../modules/batch_sign_data_item/batch_sign_data_item.foreground"; +import signDataItemModule from "../modules/sign_data_item"; +import signDataItem, { + finalizer as signDataItemFinalizer +} from "../modules/sign_data_item/sign_data_item.foreground"; +import userTokensModule from "../modules/user_tokens"; +import userTokens from "../modules/user_tokens/user_tokens.foreground"; +import tokenBalanceModule from "../modules/token_balance"; +import tokenBalance from "../modules/token_balance/token_balance.foreground"; + +/** + * @param result The result from the background script + * @param params The params the background script received + * @param originalParams The params the injected function was called with + */ +export type TransformFinalizer< + ResultType, + ParamsType = any, + OriginalParamsType = any +> = ( + result: ResultType, + params: ParamsType, + originalParams: OriginalParamsType +) => any; + +/** Extended module interface */ +export interface ForegroundModule extends Module { + /** + * A function that runs after results were + * returned from the background script. + * This is optional and will be ignored if not set. + */ + finalizer?: ModuleFunction | TransformFinalizer; +} + +/** Foreground modules */ +export const foregroundModules: ForegroundModule[] = [ + { ...permissionsModule, function: permissions }, + { ...activeAddressModule, function: activeAddress }, + { ...allAddressesModule, function: allAddresses }, + { ...publicKeyModule, function: publicKey }, + { ...walletNamesModule, function: walletNames }, + { ...arweaveConfigModule, function: arweaveConfig }, + { ...disconnectModule, function: disconnect, finalizer: disconnectFinalizer }, + { ...connectModule, function: connect }, + { ...signModule, function: sign, finalizer: signFinalizer }, + { ...dispatchModule, function: dispatch, finalizer: dispatchFinalizer }, + { ...encryptModule, function: encrypt, finalizer: encryptFinalizer }, + { ...decryptModule, function: decrypt, finalizer: decryptFinalizer }, + { ...signatureModule, function: signature, finalizer: signatureFinalizer }, + { ...addTokenModule, function: addToken }, + { ...isTokenAddedModule, function: isTokenAdded }, + { + ...signMessageModule, + function: signMessage, + finalizer: signMessageFinalizer + }, + { + ...privateHashModule, + function: privateHash, + finalizer: privateHashFinalizer + }, + { ...verifyMessageModule, function: verifyMessage }, + { + ...signDataItemModule, + function: signDataItem, + finalizer: signDataItemFinalizer + }, + { ...subscriptionModule, function: subscription }, + { ...userTokensModule, function: userTokens }, + { ...tokenBalanceModule, function: tokenBalance }, + { + ...batchSignDataItemModule, + function: batchSignDataItem, + finalizer: batchSignDataItemFinalizer + } +]; diff --git a/src/api/foreground/foreground-setup.ts b/src/api/foreground/foreground-setup.ts new file mode 100644 index 000000000..225bc5fc3 --- /dev/null +++ b/src/api/foreground/foreground-setup.ts @@ -0,0 +1,134 @@ +import type { ApiCall, ApiResponse, Event } from "shim"; +import type { InjectedEvents } from "~utils/events"; +import { version } from "../../../package.json"; +import { nanoid } from "nanoid"; +import { foregroundModules } from "~api/foreground/foreground-modules"; +import mitt from "mitt"; + +export function injectWalletSDK() { + console.log("injectWallSDK()"); + + /** Init events */ + const events = mitt(); + + /** Init wallet API */ + const WalletAPI: Record = { + walletName: "ArConnect", + walletVersion: version, + events + }; + + /* + + + {"v" + browser.runtime.getManifest().version} + {(process.env.NODE_ENV === "development" || + !!process.env.BETA_VERSION) && ( + + {process.env.BETA_VERSION || + browser.i18n.getMessage("development_version").toUpperCase()} + + )} + + + */ + + /** Inject each module */ + for (const mod of foregroundModules) { + /** Handle foreground module and forward the result to the background */ + WalletAPI[mod.functionName] = (...params: any[]) => + new Promise(async (resolve, reject) => { + // execute foreground module + const foregroundResult = await mod.function(...params); + + // construct data to send to the background + const callID = nanoid(); + const data: ApiCall & { ext: "arconnect" } = { + type: `api_${mod.functionName}`, + ext: "arconnect", + callID, + data: { + params: foregroundResult || params + } + }; + + console.log("postMessage from =", window.location.origin); + + // send message to background + // TODO: Change window with iframe in ArConnect Embedded: + window.postMessage(data, window.location.origin); + + // wait for result from background + window.addEventListener("message", callback); + + async function callback(e: MessageEvent) { + let { data: res } = e; + + // validate return message + if (`${data.type}_result` !== res.type) return; + + // only resolve when the result matching our callID is deleivered + if (data.callID !== res.callID) return; + + window.removeEventListener("message", callback); + + // check for errors + if (res.error) { + return reject(res.data); + } + + // call the finalizer function if it exists + if (mod.finalizer) { + const finalizerResult = await mod.finalizer( + res.data, + foregroundResult, + params + ); + + // if the finalizer transforms data + // update the result + if (finalizerResult) { + res.data = finalizerResult; + } + } + + // check for errors after the finalizer + if (res.error) { + return reject(res.data); + } + + // resolve promise + return resolve(res.data); + } + }); + } + + // @ts-expect-error + window.arweaveWallet = WalletAPI; + + // at the end of the injected script, + // we dispatch the wallet loaded event + dispatchEvent(new CustomEvent("arweaveWalletLoaded", { detail: {} })); + + // send wallet loaded event again if page loaded + window.addEventListener("load", () => { + if (!window.arweaveWallet) return; + dispatchEvent(new CustomEvent("arweaveWalletLoaded", { detail: {} })); + }); + + /** Handle events */ + window.addEventListener( + "message", + ( + e: MessageEvent<{ + type: "arconnect_event"; + event: Event; + }> + ) => { + // console.log("EVENT", e); + + if (e.data.type !== "arconnect_event") return; + events.emit(e.data.event.name, e.data.event.value); + } + ); +} diff --git a/src/api/module.ts b/src/api/module.ts index 12a12dddd..6458752a1 100644 --- a/src/api/module.ts +++ b/src/api/module.ts @@ -19,14 +19,14 @@ export interface ModuleProperties { permissions: PermissionType[]; } -/** Full API module (background/foreground) */ -export interface Module extends ModuleProperties { - function: ModuleFunction; -} - /** * Function type for background and injected script API functions */ export type ModuleFunction = ( ...params: any[] ) => Promise | ResultType; + +/** Full API module (background/foreground) */ +export interface Module extends ModuleProperties { + function: ModuleFunction; +} diff --git a/src/api/modules/active_address/active_address.background.ts b/src/api/modules/active_address/active_address.background.ts index e63b41f57..0c5a66c55 100644 --- a/src/api/modules/active_address/active_address.background.ts +++ b/src/api/modules/active_address/active_address.background.ts @@ -1,7 +1,7 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { ExtensionStorage } from "~utils/storage"; -const background: ModuleFunction = async () => { +const background: BackgroundModuleFunction = async () => { const address = await ExtensionStorage.get("active_address"); if (!address) throw new Error("No active address"); diff --git a/src/api/modules/add_token/add_token.background.ts b/src/api/modules/add_token/add_token.background.ts index eaa4479a1..0bf759ced 100644 --- a/src/api/modules/add_token/add_token.background.ts +++ b/src/api/modules/add_token/add_token.background.ts @@ -1,9 +1,9 @@ import { isAddress, isTokenType, isValidURL } from "~utils/assertions"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import authenticate from "../connect/auth"; import { getTokens } from "~tokens"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( appData, id: unknown, type?: unknown, diff --git a/src/api/modules/all_addresses/all_addresses.background.ts b/src/api/modules/all_addresses/all_addresses.background.ts index 700d6966b..c3209af33 100644 --- a/src/api/modules/all_addresses/all_addresses.background.ts +++ b/src/api/modules/all_addresses/all_addresses.background.ts @@ -1,7 +1,7 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { getWallets } from "~wallets"; -const background: ModuleFunction = async () => { +const background: BackgroundModuleFunction = async () => { // retrive wallets const wallets = await getWallets(); diff --git a/src/api/modules/arweave_config/arweave_config.background.ts b/src/api/modules/arweave_config/arweave_config.background.ts index fa979aac0..0c4055760 100644 --- a/src/api/modules/arweave_config/arweave_config.background.ts +++ b/src/api/modules/arweave_config/arweave_config.background.ts @@ -1,8 +1,8 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import Application from "~applications/application"; import { type Gateway } from "~gateways/gateway"; -const background: ModuleFunction = async (appData) => { +const background: BackgroundModuleFunction = async (appData) => { const app = new Application(appData.appURL); const gateway = await app.getGatewayConfig(); diff --git a/src/api/modules/batch_sign_data_item/batch_sign_data_item.background.ts b/src/api/modules/batch_sign_data_item/batch_sign_data_item.background.ts index 600ebdf5e..a7e80689b 100644 --- a/src/api/modules/batch_sign_data_item/batch_sign_data_item.background.ts +++ b/src/api/modules/batch_sign_data_item/batch_sign_data_item.background.ts @@ -7,7 +7,7 @@ import { freeDecryptedWallet } from "~wallets/encryption"; import { ArweaveSigner, createData, DataItem } from "arbundles"; import type { RawDataItem } from "../sign_data_item/types"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( appData, dataItems: unknown[] ) => { diff --git a/src/api/modules/batch_sign_data_item/batch_sign_data_item.foreground.ts b/src/api/modules/batch_sign_data_item/batch_sign_data_item.foreground.ts index 094276e2b..e4e979aa4 100644 --- a/src/api/modules/batch_sign_data_item/batch_sign_data_item.foreground.ts +++ b/src/api/modules/batch_sign_data_item/batch_sign_data_item.foreground.ts @@ -1,4 +1,4 @@ -import type { TransformFinalizer } from "~api/foreground"; +import type { TransformFinalizer } from "~api/foreground/foreground-modules"; import type { ModuleFunction } from "~api/module"; import type { RawDataItem, SignDataItemParams } from "../sign_data_item/types"; import { isArrayBuffer } from "~utils/assertions"; diff --git a/src/api/modules/connect/connect.background.ts b/src/api/modules/connect/connect.background.ts index 5393dea0a..1b596fb48 100644 --- a/src/api/modules/connect/connect.background.ts +++ b/src/api/modules/connect/connect.background.ts @@ -6,14 +6,14 @@ import { } from "~utils/assertions"; import { getMissingPermissions } from "~applications/permissions"; import { createContextMenus } from "~utils/context_menus"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { updateIcon } from "~utils/icon"; import { getWallets } from "~wallets"; import Application from "~applications/application"; import browser from "webextension-polyfill"; import authenticate from "./auth"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( appData, permissions: unknown, appInfo: unknown = {}, diff --git a/src/api/modules/decrypt/decrypt.background.ts b/src/api/modules/decrypt/decrypt.background.ts index 0f87b2fdb..305c44ba9 100644 --- a/src/api/modules/decrypt/decrypt.background.ts +++ b/src/api/modules/decrypt/decrypt.background.ts @@ -1,5 +1,5 @@ import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { defaultGateway } from "~gateways/gateway"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; @@ -13,7 +13,7 @@ import { isRawArrayBuffer } from "~utils/assertions"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( _, data: unknown, options: Record diff --git a/src/api/modules/decrypt/decrypt.foreground.ts b/src/api/modules/decrypt/decrypt.foreground.ts index 4e74944a7..c0b3c6a71 100644 --- a/src/api/modules/decrypt/decrypt.foreground.ts +++ b/src/api/modules/decrypt/decrypt.foreground.ts @@ -1,4 +1,4 @@ -import { type TransformFinalizer } from "~api/foreground"; +import { type TransformFinalizer } from "~api/foreground/foreground-modules"; import type { ModuleFunction } from "~api/module"; const foreground: ModuleFunction = (data, options) => { diff --git a/src/api/modules/disconnect/disconnect.background.ts b/src/api/modules/disconnect/disconnect.background.ts index dd1f51d80..02d2bdf98 100644 --- a/src/api/modules/disconnect/disconnect.background.ts +++ b/src/api/modules/disconnect/disconnect.background.ts @@ -1,8 +1,8 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { removeApp } from "~applications"; import { updateIcon } from "~utils/icon"; -const background: ModuleFunction = async (appData) => { +const background: BackgroundModuleFunction = async (appData) => { // remove app await removeApp(appData.appURL); diff --git a/src/api/modules/dispatch/allowance.ts b/src/api/modules/dispatch/allowance.ts index e9fa3803e..994168580 100644 --- a/src/api/modules/dispatch/allowance.ts +++ b/src/api/modules/dispatch/allowance.ts @@ -1,6 +1,6 @@ import { freeDecryptedWallet } from "~wallets/encryption"; import type { Allowance, AllowanceBigNumber } from "~applications/allowance"; -import type { ModuleAppData } from "~api/background"; +import type { ModuleAppData } from "~api/background/background-modules"; import { defaultGateway } from "~gateways/gateway"; import type { JWKInterface } from "warp-contracts"; import { allowanceAuth } from "../sign/allowance"; diff --git a/src/api/modules/dispatch/dispatch.background.ts b/src/api/modules/dispatch/dispatch.background.ts index a3d44df7f..a8fe1255a 100644 --- a/src/api/modules/dispatch/dispatch.background.ts +++ b/src/api/modules/dispatch/dispatch.background.ts @@ -7,7 +7,7 @@ import { constructTransaction } from "../sign/transaction_builder"; import { arconfettiIcon, signNotification } from "../sign/utils"; import { cleanUpChunks, getChunks } from "../sign/chunks"; import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { createData, ArweaveSigner } from "arbundles"; import { getPrice, uploadDataToTurbo } from "./uploader"; import type { DispatchResult } from "./index"; @@ -26,7 +26,7 @@ type ReturnType = { res: DispatchResult; }; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( appData, tx: unknown, chunkCollectionID: unknown diff --git a/src/api/modules/dispatch/dispatch.foreground.ts b/src/api/modules/dispatch/dispatch.foreground.ts index e863b07fb..854e14008 100644 --- a/src/api/modules/dispatch/dispatch.foreground.ts +++ b/src/api/modules/dispatch/dispatch.foreground.ts @@ -1,7 +1,7 @@ import { deconstructTransaction } from "../sign/transaction_builder"; import { createCoinWithAnimation } from "../sign/animation"; import type Transaction from "arweave/web/lib/transaction"; -import type { TransformFinalizer } from "~api/foreground"; +import type { TransformFinalizer } from "~api/foreground/foreground-modules"; import type { ModuleFunction } from "~api/module"; import type { DispatchResult } from "./index"; import { sendChunk } from "../sign/chunks"; diff --git a/src/api/modules/encrypt/encrypt.background.ts b/src/api/modules/encrypt/encrypt.background.ts index abac1d311..09605aaec 100644 --- a/src/api/modules/encrypt/encrypt.background.ts +++ b/src/api/modules/encrypt/encrypt.background.ts @@ -1,5 +1,5 @@ import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { defaultGateway } from "~gateways/gateway"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; @@ -13,7 +13,7 @@ import { isRawArrayBuffer } from "~utils/assertions"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( _, data: unknown, options: Record diff --git a/src/api/modules/encrypt/encrypt.foreground.ts b/src/api/modules/encrypt/encrypt.foreground.ts index aeccd6f10..1c0ea8080 100644 --- a/src/api/modules/encrypt/encrypt.foreground.ts +++ b/src/api/modules/encrypt/encrypt.foreground.ts @@ -1,4 +1,4 @@ -import type { TransformFinalizer } from "~api/foreground"; +import type { TransformFinalizer } from "~api/foreground/foreground-modules"; import type { ModuleFunction } from "~api/module"; const foreground: ModuleFunction = (data, options) => { diff --git a/src/api/modules/example/example.background.ts b/src/api/modules/example/example.background.ts index 7580400a7..b0fe872ba 100644 --- a/src/api/modules/example/example.background.ts +++ b/src/api/modules/example/example.background.ts @@ -1,9 +1,12 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; /** * Background functionality of the module */ -const background: ModuleFunction = async (port, test: string) => { +const background: BackgroundModuleFunction = async ( + port, + test: string +) => { return "fffff"; }; diff --git a/src/api/modules/is_token_added/is_token_added.background.ts b/src/api/modules/is_token_added/is_token_added.background.ts index 9c5855c35..6323082c3 100644 --- a/src/api/modules/is_token_added/is_token_added.background.ts +++ b/src/api/modules/is_token_added/is_token_added.background.ts @@ -1,9 +1,9 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { isAddressFormat } from "~utils/format"; import { getTokens } from "~tokens"; import { isAddress } from "~utils/assertions"; -const background: ModuleFunction = async (_, id: string) => { +const background: BackgroundModuleFunction = async (_, id: string) => { // check id isAddress(id); diff --git a/src/api/modules/permissions/permissions.background.ts b/src/api/modules/permissions/permissions.background.ts index 03191adb5..eba7a0656 100644 --- a/src/api/modules/permissions/permissions.background.ts +++ b/src/api/modules/permissions/permissions.background.ts @@ -1,8 +1,10 @@ import type { PermissionType } from "~applications/permissions"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import Application from "~applications/application"; -const background: ModuleFunction = async (appData) => { +const background: BackgroundModuleFunction = async ( + appData +) => { // construct app const app = new Application(appData.appURL); diff --git a/src/api/modules/private_hash/private_hash.background.ts b/src/api/modules/private_hash/private_hash.background.ts index 188fbc600..694ce1874 100644 --- a/src/api/modules/private_hash/private_hash.background.ts +++ b/src/api/modules/private_hash/private_hash.background.ts @@ -1,5 +1,5 @@ import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; import Arweave from "arweave"; @@ -11,7 +11,7 @@ import { isSignMessageOptions } from "~utils/assertions"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( _, data: unknown, options: unknown diff --git a/src/api/modules/private_hash/private_hash.foreground.ts b/src/api/modules/private_hash/private_hash.foreground.ts index 42e43bd2a..02cef0ec1 100644 --- a/src/api/modules/private_hash/private_hash.foreground.ts +++ b/src/api/modules/private_hash/private_hash.foreground.ts @@ -1,5 +1,5 @@ import type { SignMessageOptions } from "../sign_message/types"; -import type { TransformFinalizer } from "~api/foreground"; +import type { TransformFinalizer } from "~api/foreground/foreground-modules"; import type { ModuleFunction } from "~api/module"; import { isArrayBuffer } from "~utils/assertions"; diff --git a/src/api/modules/public_key/public_key.background.ts b/src/api/modules/public_key/public_key.background.ts index 17d0d2931..53181bd05 100644 --- a/src/api/modules/public_key/public_key.background.ts +++ b/src/api/modules/public_key/public_key.background.ts @@ -1,10 +1,10 @@ import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { isNotCancelError } from "~utils/assertions"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; -const background: ModuleFunction = async () => { +const background: BackgroundModuleFunction = async () => { // grab the user's keyfile const decryptedWallet = await getActiveKeyfile().catch((e) => { isNotCancelError(e); diff --git a/src/api/modules/sign/sign.background.ts b/src/api/modules/sign/sign.background.ts index d1706233f..90940424f 100644 --- a/src/api/modules/sign/sign.background.ts +++ b/src/api/modules/sign/sign.background.ts @@ -1,7 +1,7 @@ import { arconfettiIcon, calculateReward, signNotification } from "./utils"; import { allowanceAuth, getAllowance, updateAllowance } from "./allowance"; import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { type JWKInterface } from "arweave/web/lib/wallet"; import { isNotCancelError, @@ -24,7 +24,7 @@ import Arweave from "arweave"; import { EventType, trackDirect } from "~utils/analytics"; import BigNumber from "bignumber.js"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( appData, tx: unknown, options: unknown | undefined | null, diff --git a/src/api/modules/sign/sign.foreground.ts b/src/api/modules/sign/sign.foreground.ts index 59273040d..f61474517 100644 --- a/src/api/modules/sign/sign.foreground.ts +++ b/src/api/modules/sign/sign.foreground.ts @@ -1,5 +1,5 @@ import type { SignatureOptions } from "arweave/web/lib/crypto/crypto-interface"; -import type { TransformFinalizer } from "~api/foreground"; +import type { TransformFinalizer } from "~api/foreground/foreground-modules"; import { createCoinWithAnimation } from "./animation"; import type { ModuleFunction } from "~api/module"; import { sendChunk } from "./chunks"; diff --git a/src/api/modules/sign_data_item/sign_data_item.background.ts b/src/api/modules/sign_data_item/sign_data_item.background.ts index 8655315f6..589e796c5 100644 --- a/src/api/modules/sign_data_item/sign_data_item.background.ts +++ b/src/api/modules/sign_data_item/sign_data_item.background.ts @@ -5,7 +5,7 @@ import { isRawDataItem } from "~utils/assertions"; import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { ArweaveSigner, createData } from "arbundles"; import Application from "~applications/application"; import { getPrice } from "../dispatch/uploader"; @@ -22,7 +22,7 @@ import BigNumber from "bignumber.js"; import { createDataItem } from "~utils/data_item"; import signMessage from "../sign_message"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( appData, dataItem: unknown ) => { diff --git a/src/api/modules/sign_data_item/sign_data_item.foreground.ts b/src/api/modules/sign_data_item/sign_data_item.foreground.ts index 431b55dd3..cef3840f2 100644 --- a/src/api/modules/sign_data_item/sign_data_item.foreground.ts +++ b/src/api/modules/sign_data_item/sign_data_item.foreground.ts @@ -1,4 +1,4 @@ -import type { TransformFinalizer } from "~api/foreground"; +import type { TransformFinalizer } from "~api/foreground/foreground-modules"; import type { ModuleFunction } from "~api/module"; import type { RawDataItem, SignDataItemParams } from "./types"; import { isArrayBuffer } from "~utils/assertions"; diff --git a/src/api/modules/sign_message/sign_message.background.ts b/src/api/modules/sign_message/sign_message.background.ts index c680c03b0..417b20d29 100644 --- a/src/api/modules/sign_message/sign_message.background.ts +++ b/src/api/modules/sign_message/sign_message.background.ts @@ -1,5 +1,5 @@ import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; import { @@ -11,7 +11,7 @@ import { import { signAuthKeystone, type AuthKeystoneData } from "../sign/sign_auth"; import Arweave from "arweave"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( _, data: unknown, options = { hashAlgorithm: "SHA-256" } diff --git a/src/api/modules/sign_message/sign_message.foreground.ts b/src/api/modules/sign_message/sign_message.foreground.ts index c7a367250..8032e6a54 100644 --- a/src/api/modules/sign_message/sign_message.foreground.ts +++ b/src/api/modules/sign_message/sign_message.foreground.ts @@ -1,4 +1,4 @@ -import type { TransformFinalizer } from "~api/foreground"; +import type { TransformFinalizer } from "~api/foreground/foreground-modules"; import type { SignMessageOptions } from "./types"; import type { ModuleFunction } from "~api/module"; import { isArrayBuffer } from "~utils/assertions"; diff --git a/src/api/modules/signature/signature.background.ts b/src/api/modules/signature/signature.background.ts index 085e310bf..78ef8eab0 100644 --- a/src/api/modules/signature/signature.background.ts +++ b/src/api/modules/signature/signature.background.ts @@ -8,13 +8,13 @@ import { } from "typed-assert"; import { freeDecryptedWallet } from "~wallets/encryption"; import { isNotCancelError, isSignatureAlgorithm } from "~utils/assertions"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { getWhitelistRegExp } from "./whitelist"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; import authenticate from "../connect/auth"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( appData, data: unknown, algorithm: unknown diff --git a/src/api/modules/signature/signature.foreground.ts b/src/api/modules/signature/signature.foreground.ts index c6d23d698..90fd5e189 100644 --- a/src/api/modules/signature/signature.foreground.ts +++ b/src/api/modules/signature/signature.foreground.ts @@ -1,4 +1,4 @@ -import type { TransformFinalizer } from "~api/foreground"; +import type { TransformFinalizer } from "~api/foreground/foreground-modules"; import type { ModuleFunction } from "~api/module"; // no need to transform anything in the foreground diff --git a/src/api/modules/subscription/subscription.background.ts b/src/api/modules/subscription/subscription.background.ts index de2ea4f85..b81a4fb32 100644 --- a/src/api/modules/subscription/subscription.background.ts +++ b/src/api/modules/subscription/subscription.background.ts @@ -4,7 +4,7 @@ import { isSubscriptionType } from "~utils/assertions"; import { getActiveAddress, getActiveKeyfile, getWallets } from "~wallets"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import authenticate from "../connect/auth"; import { getSubscriptionData } from "~subscriptions"; import { @@ -12,7 +12,7 @@ import { type SubscriptionData } from "~subscriptions/subscription"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( appData, subscriptionData: SubscriptionData ) => { diff --git a/src/api/modules/token_balance/token_balance.background.ts b/src/api/modules/token_balance/token_balance.background.ts index 6abd5eeaa..3cdc0d078 100644 --- a/src/api/modules/token_balance/token_balance.background.ts +++ b/src/api/modules/token_balance/token_balance.background.ts @@ -1,10 +1,10 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { ExtensionStorage } from "~utils/storage"; import { getAoTokenBalance, getNativeTokenBalance } from "~tokens/aoTokens/ao"; import { AO_NATIVE_TOKEN } from "~utils/ao_import"; import { isAddress } from "~utils/assertions"; -const background: ModuleFunction = async (_, id?: string) => { +const background: BackgroundModuleFunction = async (_, id?: string) => { // validate input isAddress(id); const address = await ExtensionStorage.get("active_address"); diff --git a/src/api/modules/user_tokens/user_tokens.background.ts b/src/api/modules/user_tokens/user_tokens.background.ts index 1b138990f..0747b08b8 100644 --- a/src/api/modules/user_tokens/user_tokens.background.ts +++ b/src/api/modules/user_tokens/user_tokens.background.ts @@ -1,4 +1,4 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { ExtensionStorage } from "~utils/storage"; import { getAoTokenBalance, @@ -8,10 +8,9 @@ import { } from "~tokens/aoTokens/ao"; import { AO_NATIVE_TOKEN } from "~utils/ao_import"; -const background: ModuleFunction = async ( - _, - options?: { fetchBalance?: boolean } -) => { +const background: BackgroundModuleFunction< + TokenInfoWithBalance[] | TokenInfo[] +> = async (_, options?: { fetchBalance?: boolean }) => { const address = await ExtensionStorage.get("active_address"); const tokens = (await ExtensionStorage.get("ao_tokens")) || []; diff --git a/src/api/modules/verify_message/verify_message.background.ts b/src/api/modules/verify_message/verify_message.background.ts index 81e086d26..5dccbf1ef 100644 --- a/src/api/modules/verify_message/verify_message.background.ts +++ b/src/api/modules/verify_message/verify_message.background.ts @@ -1,5 +1,5 @@ import { freeDecryptedWallet } from "~wallets/encryption"; -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { getActiveKeyfile } from "~wallets"; import browser from "webextension-polyfill"; import { isString } from "typed-assert"; @@ -11,7 +11,7 @@ import { isSignMessageOptions } from "~utils/assertions"; -const background: ModuleFunction = async ( +const background: BackgroundModuleFunction = async ( _, data: unknown, signature: unknown, diff --git a/src/api/modules/wallet_names/wallet_names.background.ts b/src/api/modules/wallet_names/wallet_names.background.ts index cd6415ce3..f7ecff239 100644 --- a/src/api/modules/wallet_names/wallet_names.background.ts +++ b/src/api/modules/wallet_names/wallet_names.background.ts @@ -1,11 +1,11 @@ -import type { ModuleFunction } from "~api/background"; +import type { BackgroundModuleFunction } from "~api/background/background-modules"; import { getWallets } from "~wallets"; type WalletNamesResult = { [address: string]: string; }; -const background: ModuleFunction = async () => { +const background: BackgroundModuleFunction = async () => { const wallets = await getWallets(); if (wallets.length === 0) { diff --git a/src/applications/index.ts b/src/applications/index.ts index 057fcc611..92f02be09 100644 --- a/src/applications/index.ts +++ b/src/applications/index.ts @@ -1,11 +1,5 @@ import Application, { type InitAppParams, PREFIX } from "./application"; -import { createContextMenus } from "~utils/context_menus"; -import { sendMessage } from "@arconnect/webext-bridge"; -import type { StorageChange } from "~utils/runtime"; import { ExtensionStorage } from "~utils/storage"; -import { getAppURL } from "~utils/format"; -import { updateIcon } from "~utils/icon"; -import { forEachTab } from "./tab"; import browser from "webextension-polyfill"; /** @@ -80,66 +74,3 @@ export const getActiveTab = async () => currentWindow: true }) )[0]; - -/** - * App disconnected listener. Sends a message - * to trigger the disconnected event. - */ -export async function appsChangeListener({ - oldValue, - newValue -}: StorageChange) { - // message to send the event - const triggerEvent = (tabID: number, type: "connect" | "disconnect") => - sendMessage( - "event", - { - name: type, - value: null - }, - `content-script@${tabID}` - ); - - // trigger events - forEachTab(async (tab) => { - // get app url - const appURL = getAppURL(tab.url); - - // if the new value is undefined - // and the old value was defined - // we need to emit the disconnect - // event for all tabs that were - // connected - if (!newValue && !!oldValue) { - if (!oldValue.includes(appURL)) return; - - return await triggerEvent(tab.id, "disconnect"); - } else if (!newValue) { - // if the new value is undefined - // and the old value was also - // undefined, we just return - return; - } - - const oldAppsList = oldValue || []; - - // if the new value includes the app url - // and the old value does not, than the - // app has just been connected - // if the reverse is true, than the app - // has just been disconnected - if (newValue.includes(appURL) && !oldAppsList.includes(appURL)) { - await triggerEvent(tab.id, "connect"); - } else if (!newValue.includes(appURL) && oldAppsList.includes(appURL)) { - await triggerEvent(tab.id, "disconnect"); - } - }); - - // update icon and context menus - const activeTab = await getActiveTab(); - const app = new Application(getAppURL(activeTab.url)); - const connected = await app.isConnected(); - - await updateIcon(connected); - await createContextMenus(connected); -} diff --git a/src/background.ts b/src/background.ts index bec171759..b4b0c29a2 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,95 +1,5 @@ -import { getCapabilities, getPrinters, handlePrintRequest } from "~lib/printer"; -import { addressChangeListener, walletsChangeListener } from "~wallets/event"; -import { keyRemoveAlarmListener, onWindowClose } from "~wallets/auth"; -import { appConfigChangeListener } from "~applications/events"; -import { handleApiCalls, handleChunkCalls } from "~api"; -import { handleGatewayUpdate } from "~gateways/cache"; -import { onMessage } from "@arconnect/webext-bridge"; -import { handleTabUpdate } from "~applications/tab"; -import protocolHandler from "~gateways/ar_protocol"; -import { notificationsHandler } from "~notifications/api"; -import { appsChangeListener } from "~applications"; -import handleFeeAlarm from "~api/modules/sign/fee"; -import { ExtensionStorage } from "~utils/storage"; -import { onInstalled } from "~utils/runtime"; -import browser from "webextension-polyfill"; -import { syncLabels } from "~wallets"; -import { trackBalance } from "~utils/analytics"; -import { subscriptionsHandler } from "~subscriptions/api"; -import { importAoTokens } from "~tokens/aoTokens/sync"; -import { aoTokensCacheHandler } from "~tokens/aoTokens/ao"; +import { setupBackgroundService } from "~api/background/background-setup"; -// watch for API calls -onMessage("api_call", handleApiCalls); - -// watch for chunks -onMessage("chunk", handleChunkCalls); - -// handle tab change (icon, context menus) -browser.tabs.onUpdated.addListener((tabId) => handleTabUpdate(tabId)); -browser.tabs.onActivated.addListener(({ tabId }) => handleTabUpdate(tabId)); - -// handle fee alarm (send fees asyncronously) -// browser.alarms.onAlarm.addListener(handleFeeAlarm); - -// handle notifications -browser.alarms.onAlarm.addListener(notificationsHandler); - -// handle subscriptions -browser.alarms.onAlarm.addListener(subscriptionsHandler); - -browser.alarms.onAlarm.addListener(trackBalance); - -// handle ao tokens info cache update -browser.alarms.onAlarm.addListener(aoTokensCacheHandler); - -// handle alarm for updating gateways -browser.alarms.onAlarm.addListener(handleGatewayUpdate); - -// handle sync label alarm -browser.alarms.onAlarm.addListener(syncLabels); - -// handle decryption key removal alarm -browser.alarms.onAlarm.addListener(keyRemoveAlarmListener); - -// handle importing ao tokens -browser.alarms.onAlarm.addListener(importAoTokens); - -// handle keep alive alarm -browser.alarms.onAlarm.addListener((alarm) => { - if (alarm.name === "keep-alive") { - console.log("keep alive alarm"); - } -}); - -// handle window close -browser.windows.onRemoved.addListener(onWindowClose); - -// watch for active address changes / app -// list changes -// and send them to the content script to -// fire the wallet switch event -ExtensionStorage.watch({ - active_address: addressChangeListener, - apps: appsChangeListener, - wallets: walletsChangeListener -}); - -// listen for app config updates -browser.storage.onChanged.addListener(appConfigChangeListener); - -// open welcome page on extension install -browser.runtime.onInstalled.addListener(onInstalled); - -// handle ar:// protocol -browser.webNavigation.onBeforeNavigate.addListener(protocolHandler); - -// print to the permaweb (only on chrome) -if (typeof chrome !== "undefined") { - // @ts-expect-error - chrome.printerProvider.onGetCapabilityRequested.addListener(getCapabilities); - chrome.printerProvider.onGetPrintersRequested.addListener(getPrinters); - chrome.printerProvider.onPrintRequested.addListener(handlePrintRequest); -} +setupBackgroundService(); export {}; diff --git a/src/contents/api.ts b/src/contents/api.ts index 34f4fd813..beb6eacb8 100644 --- a/src/contents/api.ts +++ b/src/contents/api.ts @@ -1,7 +1,13 @@ import { sendMessage } from "@arconnect/webext-bridge"; import type { PlasmoCSConfig } from "plasmo"; import type { ApiCall } from "shim"; -import injectedScript from "url:../injected.ts"; +import injectedScript from "url:./injected.ts"; + +console.log("contents/api.ts 2"); + +// TODO: Document message flow. + +// TODO: ArConnect Mobile also needs to call injected.ts, ar_protocol.ts and events.ts, so these changes can be reused. export const config: PlasmoCSConfig = { matches: ["file://*/*", "http://*/*", "https://*/*"], @@ -10,6 +16,7 @@ export const config: PlasmoCSConfig = { }; // inject API script into the window + const container = document.head || document.documentElement; const script = document.createElement("script"); @@ -20,7 +27,16 @@ script.setAttribute("src", injectedScript); container.insertBefore(script, container.children[0]); container.removeChild(script); -// receive API calls +// Receive API calls: +// +// Foreground modules (from `foreground-setup.ts`) will send messages as `window.postMessage(data, window.location.origin)`, which are received here. Because +// this is a (sandboxed) extension content script, it can use `sendMessage(...)` to talk to the background script. +// +// Note this part is not needed for ArConnect Embedded, because `postMessage(...)` can talk directly to the iframe: +// +// iframeElement.contentWindow.postMessage(...); +// + window.addEventListener( "message", async ({ data }: MessageEvent) => { @@ -34,6 +50,8 @@ window.addEventListener( throw new Error("The call does not have a callID"); } + console.log("content script message"); + // send call to the background const res = await sendMessage( data.type === "chunk" ? "chunk" : "api_call", diff --git a/src/contents/events.ts b/src/contents/events.ts index dc4efcd33..783f46d1e 100644 --- a/src/contents/events.ts +++ b/src/contents/events.ts @@ -11,6 +11,8 @@ export const config: PlasmoCSConfig = { onMessage("event", ({ data, sender }) => { if (sender.context !== "background") return; + console.log("onMessage event"); + // send to mitt instance postMessage({ type: "arconnect_event", @@ -36,6 +38,8 @@ onMessage("switch_wallet_event", ({ data, sender }) => { onMessage("copy_address", async ({ sender, data: addr }) => { if (sender.context !== "background") return; + console.log("copy_address"); + const input = document.createElement("input"); input.value = addr; diff --git a/src/contents/iframe-placeholder.ts b/src/contents/iframe-placeholder.ts new file mode 100644 index 000000000..167f2c704 --- /dev/null +++ b/src/contents/iframe-placeholder.ts @@ -0,0 +1,28 @@ +// This file is just a placeholder with some pseudo-code for the injected code on ArConnect Embedded. That is, the code +// that's loaded in the consumer site's context. + +import { injectWalletSDK } from "~api/foreground/foreground-setup"; + +// api.ts: + +// Because in ArConnect Embedded the injected code is not sandboxed, we can simply call `injectWalletSDK()` instead of +// having to inject `injected.ts` with a ` + + +
+ + diff --git a/src/iframe/injected/iframe-injected.ts b/src/iframe/injected/iframe-injected.ts deleted file mode 100644 index 423b71e19..000000000 --- a/src/iframe/injected/iframe-injected.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { ApiCall, ApiResponse, Event } from "shim"; -import type { InjectedEvents } from "~utils/events"; -import { version } from "../package.json"; -import { nanoid } from "nanoid"; -import modules from "~api/foreground"; -import mitt from "mitt"; - -/** Init events */ -const events = mitt(); - -/** Init wallet API */ -const WalletAPI: Record = { - walletName: "ArConnect", - walletVersion: version, - events -}; - -/** Inject each module */ -for (const mod of modules) { - /** Handle foreground module and forward the result to the background */ - WalletAPI[mod.functionName] = (...params: any[]) => - new Promise(async (resolve, reject) => { - // execute foreground module - const foregroundResult = await mod.function(...params); - - // construct data to send to the background - const callID = nanoid(); - const data: ApiCall & { ext: "arconnect" } = { - type: `api_${mod.functionName}`, - ext: "arconnect", - callID, - data: { - params: foregroundResult || params - } - }; - - // send message to background - window.postMessage(data, window.location.origin); - - // wait for result from background - window.addEventListener("message", callback); - - async function callback(e: MessageEvent) { - let { data: res } = e; - - // validate return message - if (`${data.type}_result` !== res.type) return; - - // only resolve when the result matching our callID is deleivered - if (data.callID !== res.callID) return; - - window.removeEventListener("message", callback); - - // check for errors - if (res.error) { - return reject(res.data); - } - - // call the finalizer function if it exists - if (mod.finalizer) { - const finalizerResult = await mod.finalizer( - res.data, - foregroundResult, - params - ); - - // if the finalizer transforms data - // update the result - if (finalizerResult) { - res.data = finalizerResult; - } - } - - // check for errors after the finalizer - if (res.error) { - return reject(res.data); - } - - // resolve promise - return resolve(res.data); - } - }); -} - -// @ts-expect-error -window.arweaveWallet = WalletAPI; - -// at the end of the injected script, -// we dispatch the wallet loaded event -dispatchEvent(new CustomEvent("arweaveWalletLoaded", { detail: {} })); - -// send wallet loaded event again if page loaded -window.addEventListener("load", () => { - if (!window.arweaveWallet) return; - dispatchEvent(new CustomEvent("arweaveWalletLoaded", { detail: {} })); -}); - -/** Handle events */ -window.addEventListener( - "message", - ( - e: MessageEvent<{ - type: "arconnect_event"; - event: Event; - }> - ) => { - if (e.data.type !== "arconnect_event") return; - events.emit(e.data.event.name, e.data.event.value); - } -); - -export {}; diff --git a/src/iframe/main.tsx b/src/iframe/main.tsx index 037d88ef9..3b0cbec5f 100644 --- a/src/iframe/main.tsx +++ b/src/iframe/main.tsx @@ -1,8 +1,9 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import Popup from "../popup"; +import { setupBackgroundService } from "~api/background/background-setup"; -// import './index.css' +import "../../assets/popup.css"; // TODO: Duplicate "Popup" as "Iframe" and move all routers to config to be able to combine Popup + Auth. @@ -11,3 +12,12 @@ createRoot(document.getElementById("root")).render(
); + +setupBackgroundService(); + +// TODO: Backend should expect a hash of any of the other key shards. Otherwise, won't send back its own shard. + +// TODO: Add artificial wait time for auth request (by ip, not ip-account) to the backend if auth fails. + +// TODO: Use the infra needed to implement the MPC wallets to also send notifications to wallets when someone +// is trying to access their account. From 76bc10cfac6a873bbc590bcad1e0c5b970bb423d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Mon, 25 Nov 2024 16:24:16 +0100 Subject: [PATCH 069/142] Partial restructuring of browser extension & embedded wallets setup hooks and entry points. --- src/iframe/{index.html => iframe.html} | 0 src/iframe/iframe.tsx | 61 ++++++++++ src/iframe/main.tsx | 4 +- src/popup.tsx | 23 +++- src/routes/common/unlock.view.tsx | 0 src/tabs/auth.tsx | 11 +- src/tabs/dashboard.tsx | 4 +- src/tabs/fullscreen.tsx | 4 +- src/tabs/welcome.tsx | 2 +- src/utils/auth/auth.provider.tsx | 2 +- src/wallets/index.ts | 109 ------------------ .../browser-extension-wallet-setup.hook.ts | 95 +++++++++++++++ .../embedded/embedded-wallet-setup.hook.ts | 95 +++++++++++++++ .../setup/non/non-wallet-setup.hook.ts | 13 +++ src/wallets/setup/wallet-setup.types.ts | 1 + vite.config.js | 1 - 16 files changed, 300 insertions(+), 125 deletions(-) rename src/iframe/{index.html => iframe.html} (100%) create mode 100644 src/iframe/iframe.tsx create mode 100644 src/routes/common/unlock.view.tsx create mode 100644 src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts create mode 100644 src/wallets/setup/embedded/embedded-wallet-setup.hook.ts create mode 100644 src/wallets/setup/non/non-wallet-setup.hook.ts create mode 100644 src/wallets/setup/wallet-setup.types.ts diff --git a/src/iframe/index.html b/src/iframe/iframe.html similarity index 100% rename from src/iframe/index.html rename to src/iframe/iframe.html diff --git a/src/iframe/iframe.tsx b/src/iframe/iframe.tsx new file mode 100644 index 000000000..c0fce7546 --- /dev/null +++ b/src/iframe/iframe.tsx @@ -0,0 +1,61 @@ +import { AnimatePresence } from "framer-motion"; +import { Router } from "wouter"; +import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; +import HistoryProvider from "~components/popup/HistoryProvider"; +import { NavigationBar } from "~components/popup/Navigation"; +import { Page } from "~components/popup/Route"; +import { AuthRequestsProvider } from "~utils/auth/auth.provider"; +import { useHashLocation } from "~utils/hash_router"; +import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; +import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; + +interface ArConnectEmbeddedAppProps { + initialScreenType: InitialScreenType; +} + +export function ArConnectEmbeddedApp({ + initialScreenType +}: ArConnectEmbeddedAppProps) { + let content: React.ReactElement = null; + + if (initialScreenType === "locked") { + content = ( + + + + ); + } else if (initialScreenType === "generating") { + // This can only happen in the embedded wallet: + content = ( + +

Generating Wallet...

+
+ ); + } else if (initialScreenType === "default") { + content = ( + + + + + + ); + } + + return <>{content}; +} + +export default function ArConnectEmbeddedAppRoot() { + const initialScreenType = useBrowserExtensionWalletSetUp(); + + return ( + + + + + + + + + + ); +} diff --git a/src/iframe/main.tsx b/src/iframe/main.tsx index 3b0cbec5f..75e77b173 100644 --- a/src/iframe/main.tsx +++ b/src/iframe/main.tsx @@ -1,7 +1,7 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import Popup from "../popup"; import { setupBackgroundService } from "~api/background/background-setup"; +import ArConnectEmbeddedAppRoot from "~iframe/iframe"; import "../../assets/popup.css"; @@ -9,7 +9,7 @@ import "../../assets/popup.css"; createRoot(document.getElementById("root")).render( - + ); diff --git a/src/popup.tsx b/src/popup.tsx index 81701371e..3a0dc52c1 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,6 +1,5 @@ import Route, { Page } from "~components/popup/Route"; import { useHashLocation } from "~utils/hash_router"; -import { useSetUp } from "~wallets"; import { Router, Switch } from "wouter"; import HistoryProvider from "~components/popup/HistoryProvider"; @@ -46,10 +45,16 @@ import NotificationSettings from "~routes/popup/settings/notifications"; import GenerateQR from "~routes/popup/settings/wallets/[address]/qr"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import { AnimatePresence } from "framer-motion"; +import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; +import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; -export default function Popup() { - const initialScreenType = useSetUp(); +interface ArConnectBrowserExtensionAppProps { + initialScreenType: InitialScreenType; +} +export function ArConnectBrowserExtensionApp({ + initialScreenType +}: ArConnectBrowserExtensionAppProps) { let content: React.ReactElement = null; if (initialScreenType === "locked") { @@ -190,9 +195,19 @@ export default function Popup() { ); } + return <>{content}; +} + +export function ArConnectBrowserExtensionAppRoot() { + const initialScreenType = useBrowserExtensionWalletSetUp(); + return ( - {content} + + + ); } + +export default ArConnectBrowserExtensionAppRoot; diff --git a/src/routes/common/unlock.view.tsx b/src/routes/common/unlock.view.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/tabs/auth.tsx b/src/tabs/auth.tsx index cd80a3b39..b7beb64ba 100644 --- a/src/tabs/auth.tsx +++ b/src/tabs/auth.tsx @@ -1,5 +1,4 @@ import Route, { Page } from "~components/popup/Route"; -import { useSetUp, type InitialScreenType } from "~wallets"; import { Router } from "wouter"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; @@ -22,6 +21,8 @@ import { } from "~utils/auth/auth.hooks"; import browser from "webextension-polyfill"; import { LoadingPage } from "~components/LoadingPage"; +import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; +import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; interface AuthAppProps { initialScreenType: InitialScreenType; @@ -33,6 +34,8 @@ export function AuthApp({ initialScreenType }: AuthAppProps) { let content: React.ReactElement = null; + // TODO: if initialScreenType === "generating" there was an error and this window muust be closed... + if (initialScreenType === "locked") { content = ( @@ -69,8 +72,8 @@ export function AuthApp({ initialScreenType }: AuthAppProps) { return <>{content}; } -export default function AuthAppRoot() { - const initialScreenType = useSetUp(); +export function AuthAppRoot() { + const initialScreenType = useBrowserExtensionWalletSetUp(); return ( @@ -82,3 +85,5 @@ export default function AuthAppRoot() { ); } + +export default AuthAppRoot; diff --git a/src/tabs/dashboard.tsx b/src/tabs/dashboard.tsx index f2154a2d3..69fc60a23 100644 --- a/src/tabs/dashboard.tsx +++ b/src/tabs/dashboard.tsx @@ -1,12 +1,12 @@ import { useHashLocation } from "~utils/hash_router"; -import { useSetUp } from "~wallets"; import { Router, Route } from "wouter"; import Settings from "~routes/dashboard"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; +import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; export default function Dashboard() { - useSetUp(); + useBrowserExtensionWalletSetUp(); return ( diff --git a/src/tabs/fullscreen.tsx b/src/tabs/fullscreen.tsx index 688a42c86..c9e0e89fb 100644 --- a/src/tabs/fullscreen.tsx +++ b/src/tabs/fullscreen.tsx @@ -1,5 +1,5 @@ -import Popup from "~popup"; +import { ArConnectBrowserExtensionAppRoot } from "~popup"; export default function () { - return ; + return ; } diff --git a/src/tabs/welcome.tsx b/src/tabs/welcome.tsx index 527b572c1..8030badfe 100644 --- a/src/tabs/welcome.tsx +++ b/src/tabs/welcome.tsx @@ -9,7 +9,7 @@ import Setup from "~routes/welcome/setup"; import makeCachedMatcher from "wouter/matcher"; import GettingStarted from "~routes/welcome/gettingStarted"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; -import { useRemoveCover } from "~wallets"; +import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; export default function Welcome() { useRemoveCover(); diff --git a/src/utils/auth/auth.provider.tsx b/src/utils/auth/auth.provider.tsx index 619093ccf..44b0c0909 100644 --- a/src/utils/auth/auth.provider.tsx +++ b/src/utils/auth/auth.provider.tsx @@ -32,9 +32,9 @@ import { } from "~api/modules/sign/transaction_builder"; import { isomorphicOnMessage } from "~utils/messaging/messaging.utils"; import type { IBridgeMessage } from "@arconnect/webext-bridge"; -import type { InitialScreenType } from "~wallets"; import { log, LOG_GROUP } from "~utils/log/log.utils"; import { isError } from "~utils/error/error.utils"; +import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; interface AuthRequestContextState { authRequests: AuthRequest[]; diff --git a/src/wallets/index.ts b/src/wallets/index.ts index ebedb7dfe..9a13011d1 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -12,12 +12,10 @@ import { } from "./encryption"; import { checkPassword, - getDecryptionKey, getDecryptionKeyOrRequestUnlock, setDecryptionKey } from "./auth"; import { ArweaveSigner } from "arbundles"; -import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; import { DEFAULT_MODULE_APP_DATA, ERR_MSG_NO_ACTIVE_WALLET, @@ -59,113 +57,6 @@ export async function getWallets() { return wallets || []; } -export type InitialScreenType = "cover" | "locked" | "generating" | "default"; - -/** - * Hook that opens a new tab if ArConnect has not been set up yet - */ -export function useSetUp() { - console.log( - "useSetUp() + PLASMO_PUBLIC_APP_TYPE =", - process.env.PLASMO_PUBLIC_APP_TYPE - ); - - const [initialScreenType, setInitialScreenType] = - useState("cover"); - - // TODO: Get all usages of `getDecryptionKey` as we won't be using this in the embedded wallet... - - // TODO: There's no "disconnect" in the embedded wallet. - - useEffect(() => { - async function checkWalletState() { - const [activeAddress, wallets, decryptionKey] = await Promise.all([ - getActiveAddress(), - getWallets(), - getDecryptionKey() - ]); - - const hasWallets = activeAddress && wallets.length > 0; - - let nextInitialScreenType: InitialScreenType = "cover"; - - switch (process.env.PLASMO_PUBLIC_APP_TYPE) { - // `undefined` has been added here just in case, so that the default behavior if nothing is specific is - // building the browser extension, just like it was before adding support for the embedded wallet: - case undefined: - case "extension": { - if (!hasWallets) { - // This should only happen when opening the regular popup, but not for the auth popup, as the - // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: - - openOrSelectWelcomePage(true); - - window.top.close(); - } else if (!decryptionKey) { - nextInitialScreenType = "locked"; - } else { - nextInitialScreenType = "default"; - } - - break; - } - - case "embedded": { - nextInitialScreenType = !hasWallets ? "generating" : "default"; - - break; - } - - default: { - throw new Error( - `Unknown APP_TYPE = ${process.env.PLASMO_PUBLIC_APP_TYPE}` - ); - } - } - - setInitialScreenType(nextInitialScreenType); - - const coverElement = document.getElementById("cover"); - - if (coverElement) { - if (nextInitialScreenType === "cover") { - coverElement.removeAttribute("aria-hidden"); - } else { - coverElement.setAttribute("aria-hidden", "true"); - } - } - } - - ExtensionStorage.watch({ - decryption_key: checkWalletState - }); - - checkWalletState(); - - handleSyncLabelsAlarm(); - - return () => { - ExtensionStorage.unwatch({ - decryption_key: checkWalletState - }); - }; - }, []); - - return initialScreenType; -} - -export function useRemoveCover() { - console.log("useRemoveCover()"); - - useEffect(() => { - const coverElement = document.getElementById("cover"); - - if (coverElement) { - coverElement.setAttribute("aria-hidden", "true"); - } - }, []); -} - /** * Hook to get if there are no wallets added */ diff --git a/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts b/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts new file mode 100644 index 000000000..b1417e5f6 --- /dev/null +++ b/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts @@ -0,0 +1,95 @@ +import { useState, useEffect } from "react"; +import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; +import { ExtensionStorage } from "~utils/storage"; +import { + getActiveAddress, + getWallets, + openOrSelectWelcomePage +} from "~wallets"; +import { getDecryptionKey } from "~wallets/auth"; +import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; + +/** + * Hook that opens a new tab if ArConnect has not been set up yet + */ +export function useBrowserExtensionWalletSetUp() { + console.log(`useBrowserExtensionWalletSetUp()`); + + const [initialScreenType, setInitialScreenType] = + useState("cover"); + + // TODO: Get all usages of `getDecryptionKey` as we won't be using this in the embedded wallet... + + // TODO: There's no "disconnect" in the embedded wallet. + + useEffect(() => { + async function checkWalletState() { + const [activeAddress, wallets, decryptionKey] = await Promise.all([ + getActiveAddress(), + getWallets(), + getDecryptionKey() + ]); + + const hasWallets = activeAddress && wallets.length > 0; + + let nextInitialScreenType: InitialScreenType = "cover"; + + switch (walletType) { + case "extension": { + if (!hasWallets) { + // This should only happen when opening the regular popup, but not for the auth popup, as the + // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: + + openOrSelectWelcomePage(true); + + window.top.close(); + } else if (!decryptionKey) { + nextInitialScreenType = "locked"; + } else { + nextInitialScreenType = "default"; + } + + break; + } + + case "embedded": { + nextInitialScreenType = !hasWallets ? "generating" : "default"; + + break; + } + + default: { + throw new Error(`Unknown APP_TYPE = ${walletType}`); + } + } + + setInitialScreenType(nextInitialScreenType); + + const coverElement = document.getElementById("cover"); + + if (coverElement) { + if (nextInitialScreenType === "cover") { + coverElement.removeAttribute("aria-hidden"); + } else { + coverElement.setAttribute("aria-hidden", "true"); + } + } + } + + ExtensionStorage.watch({ + decryption_key: checkWalletState + }); + + checkWalletState(); + + handleSyncLabelsAlarm(); + + return () => { + ExtensionStorage.unwatch({ + decryption_key: checkWalletState + }); + }; + }, []); + + return initialScreenType; +} diff --git a/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts b/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts new file mode 100644 index 000000000..962827544 --- /dev/null +++ b/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts @@ -0,0 +1,95 @@ +import { useState, useEffect } from "react"; +import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; +import { ExtensionStorage } from "~utils/storage"; +import { + getActiveAddress, + getWallets, + openOrSelectWelcomePage +} from "~wallets"; +import { getDecryptionKey } from "~wallets/auth"; +import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; + +/** + * Hook that opens a new tab if ArConnect has not been set up yet + */ +export function useEmbeddedWalletSetUp() { + console.log(`useEmbeddedWalletSetUp()`); + + const [initialScreenType, setInitialScreenType] = + useState("cover"); + + // TODO: Get all usages of `getDecryptionKey` as we won't be using this in the embedded wallet... + + // TODO: There's no "disconnect" in the embedded wallet. + + useEffect(() => { + async function checkWalletState() { + const [activeAddress, wallets, decryptionKey] = await Promise.all([ + getActiveAddress(), + getWallets(), + getDecryptionKey() + ]); + + const hasWallets = activeAddress && wallets.length > 0; + + let nextInitialScreenType: InitialScreenType = "cover"; + + switch (walletType) { + case "extension": { + if (!hasWallets) { + // This should only happen when opening the regular popup, but not for the auth popup, as the + // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: + + openOrSelectWelcomePage(true); + + window.top.close(); + } else if (!decryptionKey) { + nextInitialScreenType = "locked"; + } else { + nextInitialScreenType = "default"; + } + + break; + } + + case "embedded": { + nextInitialScreenType = !hasWallets ? "generating" : "default"; + + break; + } + + default: { + throw new Error(`Unknown APP_TYPE = ${walletType}`); + } + } + + setInitialScreenType(nextInitialScreenType); + + const coverElement = document.getElementById("cover"); + + if (coverElement) { + if (nextInitialScreenType === "cover") { + coverElement.removeAttribute("aria-hidden"); + } else { + coverElement.setAttribute("aria-hidden", "true"); + } + } + } + + ExtensionStorage.watch({ + decryption_key: checkWalletState + }); + + checkWalletState(); + + handleSyncLabelsAlarm(); + + return () => { + ExtensionStorage.unwatch({ + decryption_key: checkWalletState + }); + }; + }, []); + + return initialScreenType; +} diff --git a/src/wallets/setup/non/non-wallet-setup.hook.ts b/src/wallets/setup/non/non-wallet-setup.hook.ts new file mode 100644 index 000000000..70a637dd5 --- /dev/null +++ b/src/wallets/setup/non/non-wallet-setup.hook.ts @@ -0,0 +1,13 @@ +import { useEffect } from "react"; + +export function useRemoveCover() { + console.log("useRemoveCover()"); + + useEffect(() => { + const coverElement = document.getElementById("cover"); + + if (coverElement) { + coverElement.setAttribute("aria-hidden", "true"); + } + }, []); +} diff --git a/src/wallets/setup/wallet-setup.types.ts b/src/wallets/setup/wallet-setup.types.ts new file mode 100644 index 000000000..296fd432d --- /dev/null +++ b/src/wallets/setup/wallet-setup.types.ts @@ -0,0 +1 @@ +export type InitialScreenType = "cover" | "locked" | "generating" | "default"; diff --git a/vite.config.js b/vite.config.js index 9cb312ec1..07f4c8a20 100644 --- a/vite.config.js +++ b/vite.config.js @@ -8,7 +8,6 @@ export default defineConfig({ plugins: [react()], define: { "process.env": { - PLASMO_PUBLIC_APP_TYPE: "embedded", ...(process?.env || {}) } }, From 0b78ab6d377cbf3482930ba36ec3ec8d23b383fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 26 Nov 2024 18:38:42 +0100 Subject: [PATCH 070/142] Move routes to a config file (WIP). --- src/components/Page.tsx | 31 +++ src/components/popup/Head.tsx | 2 +- src/components/popup/HeadV2.tsx | 2 +- src/components/popup/HistoryProvider.tsx | 4 +- src/components/popup/Navigation.tsx | 2 +- src/components/popup/Route.tsx | 38 +--- src/components/popup/WalletHeader.tsx | 2 +- src/components/popup/WalletSwitcher.tsx | 2 +- src/components/popup/home/BuyButton.tsx | 2 +- src/components/popup/home/Collectibles.tsx | 2 +- src/components/popup/home/NoBalance.tsx | 2 +- src/components/popup/home/Tokens.tsx | 2 +- src/components/popup/home/Transactions.tsx | 2 +- src/iframe/iframe.tsx | 2 +- src/popup.tsx | 181 ++--------------- src/routes/auth/allowance.tsx | 2 +- src/routes/auth/batchSignDataItem.tsx | 2 +- src/routes/auth/connect.tsx | 3 +- src/routes/auth/sign.tsx | 10 +- src/routes/auth/signDataItem.tsx | 3 +- src/routes/auth/signKeystone.tsx | 10 +- src/routes/auth/signature.tsx | 4 +- src/routes/auth/subscription.tsx | 2 +- src/routes/auth/token.tsx | 2 +- src/routes/popup/collectible/[id].tsx | 13 +- src/routes/popup/collectibles.tsx | 4 +- src/routes/popup/confirm.tsx | 14 +- src/routes/popup/explore.tsx | 2 +- src/routes/popup/index.tsx | 4 +- src/routes/popup/notification/[id].tsx | 16 +- src/routes/popup/notifications.tsx | 4 +- src/routes/popup/pending.tsx | 4 +- src/routes/popup/purchase.tsx | 4 +- src/routes/popup/receive.tsx | 8 +- src/routes/popup/send/auth.tsx | 11 +- src/routes/popup/send/confirm.tsx | 19 +- src/routes/popup/send/index.tsx | 15 +- src/routes/popup/send/recipient.tsx | 21 +- .../popup/settings/apps/[url]/index.tsx | 13 +- .../popup/settings/apps/[url]/permissions.tsx | 12 +- src/routes/popup/settings/apps/index.tsx | 2 +- .../settings/contacts/[address]/index.tsx | 10 +- src/routes/popup/settings/contacts/index.tsx | 2 +- src/routes/popup/settings/contacts/new.tsx | 2 +- .../popup/settings/notifications/index.tsx | 2 +- src/routes/popup/settings/quickSettings.tsx | 17 +- .../popup/settings/tokens/[id]/index.tsx | 13 +- src/routes/popup/settings/tokens/index.tsx | 2 +- src/routes/popup/settings/tokens/new.tsx | 2 +- .../settings/wallets/[address]/export.tsx | 15 +- .../settings/wallets/[address]/index.tsx | 9 +- .../popup/settings/wallets/[address]/qr.tsx | 9 +- src/routes/popup/settings/wallets/index.tsx | 2 +- .../subscriptions/subscriptionDetails.tsx | 12 +- .../subscriptions/subscriptionManagement.tsx | 10 +- .../subscriptions/subscriptionPayment.tsx | 14 +- .../popup/subscriptions/subscriptions.tsx | 4 +- src/routes/popup/token/[id].tsx | 15 +- src/routes/popup/tokens.tsx | 4 +- src/routes/popup/transaction/[id].tsx | 23 ++- src/routes/popup/transaction/transactions.tsx | 4 +- src/tabs/auth.tsx | 41 +--- src/tabs/dashboard.tsx | 2 +- src/tabs/welcome.tsx | 2 +- src/utils/auth/auth.hooks.ts | 12 -- src/wallets/router/auth/auth-router.hook.ts | 11 + src/wallets/router/auth/auth.routes.ts | 53 +++++ .../router/hash/hash-router.hook.ts} | 4 - .../router/iframe/iframe-router.hook.ts | 17 ++ src/wallets/router/iframe/iframe.routes.ts | 28 +++ src/wallets/router/popup/popup.routes.ts | 189 ++++++++++++++++++ src/wallets/router/router.component.tsx | 67 +++++++ src/wallets/router/router.types.ts | 18 ++ src/wallets/router/router.utils.ts | 31 +++ 74 files changed, 726 insertions(+), 400 deletions(-) create mode 100644 src/components/Page.tsx create mode 100644 src/wallets/router/auth/auth-router.hook.ts create mode 100644 src/wallets/router/auth/auth.routes.ts rename src/{utils/hash_router.ts => wallets/router/hash/hash-router.hook.ts} (95%) create mode 100644 src/wallets/router/iframe/iframe-router.hook.ts create mode 100644 src/wallets/router/iframe/iframe.routes.ts create mode 100644 src/wallets/router/popup/popup.routes.ts create mode 100644 src/wallets/router/router.component.tsx create mode 100644 src/wallets/router/router.types.ts create mode 100644 src/wallets/router/router.utils.ts diff --git a/src/components/Page.tsx b/src/components/Page.tsx new file mode 100644 index 000000000..2f320abf9 --- /dev/null +++ b/src/components/Page.tsx @@ -0,0 +1,31 @@ +import { type Variants, motion } from "framer-motion"; +import type { PropsWithChildren } from "react"; +import styled from "styled-components"; + +export const Page = ({ children }: PropsWithChildren) => { + const opacityAnimation: Variants = { + initial: { opacity: 0 }, + enter: { opacity: 1 }, + exit: { opacity: 0, y: 0, transition: { duration: 0.2 } } + }; + + return ( +
+ {children} +
+ ); +}; + +const Main = styled(motion.main)` + position: relative; + top: 0; + width: 100%; + min-height: 100vh; + max-height: max-content; +`; diff --git a/src/components/popup/Head.tsx b/src/components/popup/Head.tsx index 2b6abc93b..e682605a0 100644 --- a/src/components/popup/Head.tsx +++ b/src/components/popup/Head.tsx @@ -15,7 +15,7 @@ import HardwareWalletIcon, { hwIconAnimateProps } from "~components/hardware/HardwareWalletIcon"; import { useHardwareApi } from "~wallets/hooks"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useEffect, useMemo, useState } from "react"; import keystoneLogo from "url:/assets/hardware/keystone.png"; import WalletSwitcher from "./WalletSwitcher"; diff --git a/src/components/popup/HeadV2.tsx b/src/components/popup/HeadV2.tsx index b9c07ad58..8455aec48 100644 --- a/src/components/popup/HeadV2.tsx +++ b/src/components/popup/HeadV2.tsx @@ -15,7 +15,7 @@ import HardwareWalletIcon, { hwIconAnimateProps } from "~components/hardware/HardwareWalletIcon"; import { useHardwareApi } from "~wallets/hooks"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import React, { useEffect, useMemo, useState } from "react"; import keystoneLogo from "url:/assets/hardware/keystone.png"; import WalletSwitcher from "./WalletSwitcher"; diff --git a/src/components/popup/HistoryProvider.tsx b/src/components/popup/HistoryProvider.tsx index 69f72680f..bf110071d 100644 --- a/src/components/popup/HistoryProvider.tsx +++ b/src/components/popup/HistoryProvider.tsx @@ -5,7 +5,9 @@ import { type HistoryAction, HistoryContext, type PushAction -} from "~utils/hash_router"; +} from "~wallets/router/hash/hash-router.hook"; + +// TODO: Do we really need this instead of simply calling history.back()? export default function HistoryProvider({ children }: PropsWithChildren<{}>) { // current history action diff --git a/src/components/popup/Navigation.tsx b/src/components/popup/Navigation.tsx index 3af99b03a..758d846a0 100644 --- a/src/components/popup/Navigation.tsx +++ b/src/components/popup/Navigation.tsx @@ -9,7 +9,7 @@ import { import browser from "webextension-polyfill"; import styled from "styled-components"; import { useLocation } from "wouter"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useTheme } from "~utils/theme"; const buttons = [ diff --git a/src/components/popup/Route.tsx b/src/components/popup/Route.tsx index bbdf397ab..3f6cae70d 100644 --- a/src/components/popup/Route.tsx +++ b/src/components/popup/Route.tsx @@ -1,11 +1,12 @@ -import { motion, type Variants } from "framer-motion"; -import { createElement, type PropsWithChildren } from "react"; +import { createElement } from "react"; import { useRoute, Route as BaseRoute } from "wouter"; -import styled from "styled-components"; +import { Page } from "~components/Page"; /** * Custom Route component that allows iOS-like animations */ + +/* const Route: typeof BaseRoute = ({ path, component, children }) => { const [matches, params] = useRoute(path); @@ -19,33 +20,4 @@ const Route: typeof BaseRoute = ({ path, component, children }) => { return {routeContent}; }; - -const PageWrapper = styled(motion.main)` - position: relative; - top: 0; - width: 100%; - min-height: 100vh; - max-height: max-content; -`; - -export const Page = ({ children }: PropsWithChildren) => { - const opacityAnimation: Variants = { - initial: { opacity: 0 }, - enter: { opacity: 1 }, - exit: { opacity: 0, y: 0, transition: { duration: 0.2 } } - }; - - return ( - - {children} - - ); -}; - -export default Route; +*/ diff --git a/src/components/popup/WalletHeader.tsx b/src/components/popup/WalletHeader.tsx index e4982584b..7007f2e09 100644 --- a/src/components/popup/WalletHeader.tsx +++ b/src/components/popup/WalletHeader.tsx @@ -50,7 +50,7 @@ import { Users01 } from "@untitled-ui/icons-react"; import { svgie } from "~utils/svgies"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import WalletMenu from "./WalletMenu"; export default function WalletHeader() { diff --git a/src/components/popup/WalletSwitcher.tsx b/src/components/popup/WalletSwitcher.tsx index f0962192b..469a1ed9c 100644 --- a/src/components/popup/WalletSwitcher.tsx +++ b/src/components/popup/WalletSwitcher.tsx @@ -27,7 +27,7 @@ import Arweave from "arweave"; import { svgie } from "~utils/svgies"; import { Action } from "./WalletHeader"; import copy from "copy-to-clipboard"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; export default function WalletSwitcher({ open, diff --git a/src/components/popup/home/BuyButton.tsx b/src/components/popup/home/BuyButton.tsx index 75a375ac7..336ceed9c 100644 --- a/src/components/popup/home/BuyButton.tsx +++ b/src/components/popup/home/BuyButton.tsx @@ -1,4 +1,4 @@ -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { Button, ButtonV2 } from "@arconnect/components"; import browser from "webextension-polyfill"; import styled from "styled-components"; diff --git a/src/components/popup/home/Collectibles.tsx b/src/components/popup/home/Collectibles.tsx index a96e9c0ff..b0b15ed6c 100644 --- a/src/components/popup/home/Collectibles.tsx +++ b/src/components/popup/home/Collectibles.tsx @@ -1,6 +1,6 @@ import { Heading, TokenCount, ViewAll } from "../Title"; import { Spacer, Text } from "@arconnect/components"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useTokens } from "~tokens"; import { useMemo } from "react"; import browser from "webextension-polyfill"; diff --git a/src/components/popup/home/NoBalance.tsx b/src/components/popup/home/NoBalance.tsx index 051d98e0d..c70b38149 100644 --- a/src/components/popup/home/NoBalance.tsx +++ b/src/components/popup/home/NoBalance.tsx @@ -1,6 +1,6 @@ import { ButtonV2, Section, Text } from "@arconnect/components"; import { ArrowRightIcon } from "@iconicicons/react"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import noBalanceArt from "url:/assets/ar/no_funds.png"; import browser from "webextension-polyfill"; import styled from "styled-components"; diff --git a/src/components/popup/home/Tokens.tsx b/src/components/popup/home/Tokens.tsx index 1938a00e1..7260b648a 100644 --- a/src/components/popup/home/Tokens.tsx +++ b/src/components/popup/home/Tokens.tsx @@ -1,6 +1,6 @@ import { Heading, TokenCount, ViewAll } from "../Title"; import { Spacer, Text } from "@arconnect/components"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useTokens } from "~tokens"; import { useMemo } from "react"; import browser from "webextension-polyfill"; diff --git a/src/components/popup/home/Transactions.tsx b/src/components/popup/home/Transactions.tsx index 92db09eca..cae4651db 100644 --- a/src/components/popup/home/Transactions.tsx +++ b/src/components/popup/home/Transactions.tsx @@ -13,7 +13,7 @@ import { AR_SENT_QUERY, PRINT_ARWEAVE_QUERY } from "~notifications/utils"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { getArPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import { printTxWorkingGateways, txHistoryGateways } from "~gateways/gateway"; diff --git a/src/iframe/iframe.tsx b/src/iframe/iframe.tsx index c0fce7546..5d69fb72b 100644 --- a/src/iframe/iframe.tsx +++ b/src/iframe/iframe.tsx @@ -5,7 +5,7 @@ import HistoryProvider from "~components/popup/HistoryProvider"; import { NavigationBar } from "~components/popup/Navigation"; import { Page } from "~components/popup/Route"; import { AuthRequestsProvider } from "~utils/auth/auth.provider"; -import { useHashLocation } from "~utils/hash_router"; +import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; diff --git a/src/popup.tsx b/src/popup.tsx index 3a0dc52c1..c7e8d7f02 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,52 +1,13 @@ -import Route, { Page } from "~components/popup/Route"; -import { useHashLocation } from "~utils/hash_router"; -import { Router, Switch } from "wouter"; - -import HistoryProvider from "~components/popup/HistoryProvider"; - -import Home from "~routes/popup"; -import Purchase from "~routes/popup/purchase"; -import ConfirmPurchase from "~routes/popup/confirm"; -import PendingPurchase from "~routes/popup/pending"; -import Receive from "~routes/popup/receive"; -import Send from "~routes/popup/send"; -import SendAuth from "~routes/popup/send/auth"; -import Explore from "~routes/popup/explore"; import Unlock from "~routes/popup/unlock"; -import Notifications from "~routes/popup/notifications"; -import Subscriptions from "~routes/popup/subscriptions/subscriptions"; -import Tokens from "~routes/popup/tokens"; -import Asset from "~routes/popup/token/[id]"; -import Collectibles from "~routes/popup/collectibles"; -import Collectible from "~routes/popup/collectible/[id]"; -import Transaction from "~routes/popup/transaction/[id]"; -import Recipient from "~routes/popup/send/recipient"; -import Confirm from "~routes/popup/send/confirm"; import { NavigationBar } from "~components/popup/Navigation"; -import MessageNotification from "~routes/popup/notification/[id]"; -import SubscriptionDetails from "~routes/popup/subscriptions/subscriptionDetails"; -import SubscriptionPayment from "~routes/popup/subscriptions/subscriptionPayment"; -import SubscriptionManagement from "~routes/popup/subscriptions/subscriptionManagement"; -import Transactions from "~routes/popup/transaction/transactions"; -import QuickSettings from "~routes/popup/settings/quickSettings"; -import Wallets from "~routes/popup/settings/wallets"; -import Wallet from "~routes/popup/settings/wallets/[address]"; -import ExportWallet from "~routes/popup/settings/wallets/[address]/export"; -import Applications from "~routes/popup/settings/apps"; -import AppSettings from "~routes/popup/settings/apps/[url]"; -import AppPermissions from "~routes/popup/settings/apps/[url]/permissions"; -import { default as QuickTokens } from "~routes/popup/settings/tokens"; -import TokenSettings from "~routes/popup/settings/tokens/[id]"; -import NewToken from "~routes/popup/settings/tokens/new"; -import Contacts from "~routes/popup/settings/contacts"; -import ContactSettings from "~routes/popup/settings/contacts/[address]"; -import NewContact from "~routes/popup/settings/contacts/new"; -import NotificationSettings from "~routes/popup/settings/notifications"; -import GenerateQR from "~routes/popup/settings/wallets/[address]/qr"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import { AnimatePresence } from "framer-motion"; import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; +import { Page } from "~components/Page"; +import { Router } from "~wallets/router/router.component"; +import { POPUP_ROUTES } from "~wallets/router/popup/popup.routes"; +import HistoryProvider from "~components/popup/HistoryProvider"; interface ArConnectBrowserExtensionAppProps { initialScreenType: InitialScreenType; @@ -72,140 +33,28 @@ export function ArConnectBrowserExtensionApp({ ); } else if (initialScreenType === "default") { content = ( - - - - - - {(params: { quoteId: string }) => ( - - )} - - - {() => } - - {(params: { id?: string }) => } - - - {(params: { tokenID: string }) => ( - - )} - - - - - - - {(params: { address: string }) => ( - - )} - - - {(params: { address: string }) => ( - - )} - - - {(params: { address: string }) => ( - - )} - - - - {(params: { url: string }) => } - - - {(params: { url: string }) => } - - - - - - {(params: { id: string }) => } - - - - - - - {(params: { address: string }) => ( - - )} - - - - - {(params: { id: string }) => ( - - )} - - - {(params: { id: string }) => ( - - )} - - - {(params: { id: string }) => ( - - )} - - - - - {(params: { id: string }) => ( - - )} - - - - {(params: { id: string }) => } - - - - {(params: { id: string }) => } - - - {(params: { id: string; gateway?: string }) => ( - - )} - - - {(params: { token: string; qty: string }) => ( - - )} - - - {(params: { token: string; qty: string; message?: string }) => ( - - )} - - - - + <> + + + ); } return <>{content}; } +// TODO: Move HistoryProvider below and add it manually instead. Add a HistoryProviderObserver... + export function ArConnectBrowserExtensionAppRoot() { const initialScreenType = useBrowserExtensionWalletSetUp(); return ( - - - + + + + + ); } diff --git a/src/routes/auth/allowance.tsx b/src/routes/auth/allowance.tsx index 4ef97379c..cc89e7dc0 100644 --- a/src/routes/auth/allowance.tsx +++ b/src/routes/auth/allowance.tsx @@ -25,7 +25,7 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; -export default function Allowance() { +export function AllowanceAuthRequestView() { const arweave = new Arweave(defaultGateway); const { authRequest, acceptRequest, rejectRequest } = diff --git a/src/routes/auth/batchSignDataItem.tsx b/src/routes/auth/batchSignDataItem.tsx index c28e9b2c3..89709ce60 100644 --- a/src/routes/auth/batchSignDataItem.tsx +++ b/src/routes/auth/batchSignDataItem.tsx @@ -22,7 +22,7 @@ import { timeoutPromise } from "~utils/promises/timeout"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; -export default function BatchSignDataItem() { +export function BatchSignDataItemAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("batchSignDataItem"); const { data, url } = authRequest; diff --git a/src/routes/auth/connect.tsx b/src/routes/auth/connect.tsx index d8c69c46e..65effe77f 100644 --- a/src/routes/auth/connect.tsx +++ b/src/routes/auth/connect.tsx @@ -40,8 +40,9 @@ import Permissions from "../../components/auth/Permissions"; import { Flex } from "~routes/popup/settings/apps/[url]"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function Connect() { +export function ConnectAuthRequestView() { // active address const [activeAddress] = useStorage({ key: "active_address", diff --git a/src/routes/auth/sign.tsx b/src/routes/auth/sign.tsx index 1ca57651c..c8a913997 100644 --- a/src/routes/auth/sign.tsx +++ b/src/routes/auth/sign.tsx @@ -18,13 +18,7 @@ import { TagValue, TransactionProperty } from "~routes/popup/transaction/[id]"; -import { - ButtonV2, - Section, - Spacer, - Text, - useToasts -} from "@arconnect/components"; +import { Section, Spacer, Text, useToasts } from "@arconnect/components"; import AnimatedQRScanner from "~components/hardware/AnimatedQRScanner"; import AnimatedQRPlayer from "~components/hardware/AnimatedQRPlayer"; import Wrapper from "~components/auth/Wrapper"; @@ -39,7 +33,7 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; -export default function Sign() { +export function SignAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("sign"); diff --git a/src/routes/auth/signDataItem.tsx b/src/routes/auth/signDataItem.tsx index 70fe4eefe..23d859a69 100644 --- a/src/routes/auth/signDataItem.tsx +++ b/src/routes/auth/signDataItem.tsx @@ -22,7 +22,6 @@ import Wrapper from "~components/auth/Wrapper"; import browser from "webextension-polyfill"; import { useEffect, useMemo, useRef, useState } from "react"; import styled from "styled-components"; -import HeadV2 from "~components/popup/HeadV2"; import { formatAddress } from "~utils/format"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; @@ -45,7 +44,7 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; -export default function SignDataItem() { +export function SignDataItemAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("signDataItem"); diff --git a/src/routes/auth/signKeystone.tsx b/src/routes/auth/signKeystone.tsx index 7da10c310..43b877fe5 100644 --- a/src/routes/auth/signKeystone.tsx +++ b/src/routes/auth/signKeystone.tsx @@ -7,13 +7,7 @@ import { useEffect, useState } from "react"; import { useScanner } from "@arconnect/keystone-sdk"; import { useActiveWallet } from "~wallets/hooks"; import type { UR } from "@ngraveio/bc-ur"; -import { - ButtonV2, - Section, - Spacer, - Text, - useToasts -} from "@arconnect/components"; +import { Section, Spacer, Text, useToasts } from "@arconnect/components"; import AnimatedQRScanner from "~components/hardware/AnimatedQRScanner"; import AnimatedQRPlayer from "~components/hardware/AnimatedQRPlayer"; import Wrapper from "~components/auth/Wrapper"; @@ -24,7 +18,7 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; -export default function SignKeystone() { +export function SignKeystoneAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("signKeystone"); diff --git a/src/routes/auth/signature.tsx b/src/routes/auth/signature.tsx index b67f804d5..c6d5e69cc 100644 --- a/src/routes/auth/signature.tsx +++ b/src/routes/auth/signature.tsx @@ -1,4 +1,4 @@ -import { ButtonV2, Section, Spacer, Text } from "@arconnect/components"; +import { Section, Text } from "@arconnect/components"; import Message from "~components/auth/Message"; import Wrapper from "~components/auth/Wrapper"; import browser from "webextension-polyfill"; @@ -7,7 +7,7 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; -export default function Signature() { +export function SignatureAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("signature"); diff --git a/src/routes/auth/subscription.tsx b/src/routes/auth/subscription.tsx index 45fcecbec..cbb5ea8d3 100644 --- a/src/routes/auth/subscription.tsx +++ b/src/routes/auth/subscription.tsx @@ -42,7 +42,7 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; -export default function Subscription() { +export function SubscriptionAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("subscription"); diff --git a/src/routes/auth/token.tsx b/src/routes/auth/token.tsx index 0840a371f..b2bb2a0ab 100644 --- a/src/routes/auth/token.tsx +++ b/src/routes/auth/token.tsx @@ -33,7 +33,7 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; -export default function Token() { +export function TokenAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = useCurrentAuthRequest("token"); diff --git a/src/routes/popup/collectible/[id].tsx b/src/routes/popup/collectible/[id].tsx index 1db001c60..ddc8d338a 100644 --- a/src/routes/popup/collectible/[id].tsx +++ b/src/routes/popup/collectible/[id].tsx @@ -16,8 +16,15 @@ import Title from "~components/popup/Title"; import styled from "styled-components"; import { useGateway } from "~gateways/wayfinder"; import HeadV2 from "~components/popup/HeadV2"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function Collectible({ id }: Props) { +export interface CollectibleViewParams { + id: string; +} + +export type CollectibleViewProps = CommonRouteProps; + +export function CollectibleView({ params: { id } }: CollectibleViewProps) { // load state const [state, setState] = useState(); const [loading, setLoading] = useState(false); @@ -147,10 +154,6 @@ export default function Collectible({ id }: Props) { ); } -interface Props { - id: string; -} - const Price = styled(Text)` font-size: 1.075rem; font-weight: 600; diff --git a/src/routes/popup/collectibles.tsx b/src/routes/popup/collectibles.tsx index 1e8be49cd..b962bfb11 100644 --- a/src/routes/popup/collectibles.tsx +++ b/src/routes/popup/collectibles.tsx @@ -1,4 +1,4 @@ -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { Section } from "@arconnect/components"; import { useTokens } from "~tokens"; import { useMemo } from "react"; @@ -7,7 +7,7 @@ import browser from "webextension-polyfill"; import styled from "styled-components"; import HeadV2 from "~components/popup/HeadV2"; -export default function Collectibles() { +export function CollectiblesView() { // all tokens const tokens = useTokens(); diff --git a/src/routes/popup/confirm.tsx b/src/routes/popup/confirm.tsx index 9ba01a61c..e5d533900 100644 --- a/src/routes/popup/confirm.tsx +++ b/src/routes/popup/confirm.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { ExtensionStorage } from "~utils/storage"; @@ -10,8 +10,18 @@ import HeadV2 from "~components/popup/HeadV2"; import { Line } from "./purchase"; import { useStorage } from "@plasmohq/storage/hook"; import { formatAddress } from "~utils/format"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function ConfirmPurchase({ id }: { id: string }) { +export interface ConfirmPurchaseViewParams { + quoteId: string; +} + +export type ConfirmPurchaseViewProps = + CommonRouteProps; + +export function ConfirmPurchaseView({ + params: { quoteId: id } +}: ConfirmPurchaseViewProps) { const [push] = useHistory(); const [activeAddress] = useStorage({ diff --git a/src/routes/popup/explore.tsx b/src/routes/popup/explore.tsx index 5f826f1df..88588d583 100644 --- a/src/routes/popup/explore.tsx +++ b/src/routes/popup/explore.tsx @@ -10,7 +10,7 @@ import { ShareIcon } from "@iconicicons/react"; import { apps, type App } from "~utils/apps"; import { useTheme } from "~utils/theme"; -export default function Explore() { +export function ExploreView() { const [filteredApps, setFilteredApps] = useState(apps); const searchInput = useInput(); const theme = useTheme(); diff --git a/src/routes/popup/index.tsx b/src/routes/popup/index.tsx index cf9173537..697daccb8 100644 --- a/src/routes/popup/index.tsx +++ b/src/routes/popup/index.tsx @@ -6,7 +6,7 @@ import NoBalance from "~components/popup/home/NoBalance"; import Balance from "~components/popup/home/Balance"; import { AnnouncementPopup } from "./announcement"; import { getDecryptionKey } from "~wallets/auth"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { trackEvent, EventType, @@ -24,7 +24,7 @@ import AoBanner from "~components/popup/home/AoBanner"; import { scheduleImportAoTokens } from "~tokens/aoTokens/sync"; import BigNumber from "bignumber.js"; -export default function Home() { +export function HomeView() { // get if the user has no balance const [noBalance, setNoBalance] = useState(false); const [loggedIn, setLoggedIn] = useState(false); diff --git a/src/routes/popup/notification/[id].tsx b/src/routes/popup/notification/[id].tsx index bbdde7c87..3589f0716 100644 --- a/src/routes/popup/notification/[id].tsx +++ b/src/routes/popup/notification/[id].tsx @@ -1,13 +1,19 @@ +import type { CommonRouteProps } from "~wallets/router/router.types"; import Transaction from "../transaction/[id]"; -export default function MessageNotification({ id }: Props) { +export interface MessageNotificationViewParams { + id: string; +} + +export type MessageNotificationViewProps = + CommonRouteProps; + +export function MessageNotificationView({ + params: { id } +}: MessageNotificationViewProps) { return ( <> ); } - -interface Props { - id: string; -} diff --git a/src/routes/popup/notifications.tsx b/src/routes/popup/notifications.tsx index 3b1deb563..898ebde6b 100644 --- a/src/routes/popup/notifications.tsx +++ b/src/routes/popup/notifications.tsx @@ -6,7 +6,7 @@ import { mergeAndSortNotifications } from "~utils/notifications"; import aoLogo from "url:/assets/ecosystem/ao-logo.svg"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { Loading } from "@arconnect/components"; import { formatAddress } from "~utils/format"; import HeadV2 from "~components/popup/HeadV2"; @@ -24,7 +24,7 @@ import { } from "~subscriptions/subscription"; import { checkTransactionError } from "~lib/transactions"; -export default function Notifications() { +export function NotificationsView() { const [notifications, setNotifications] = useState([]); const [formattedTxMsgs, setFormattedTxMsgs] = useState([]); const [subscriptions, setSubscriptions] = useState([]); diff --git a/src/routes/popup/pending.tsx b/src/routes/popup/pending.tsx index 76cbc6bd0..226224d38 100644 --- a/src/routes/popup/pending.tsx +++ b/src/routes/popup/pending.tsx @@ -9,9 +9,9 @@ import BuyButton from "~components/popup/home/BuyButton"; import { PageType, trackPage } from "~utils/analytics"; import { useStorage } from "@plasmohq/storage/hook"; import type { Quote } from "~lib/onramper"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; -export default function PendingPurchase() { +export function PendingPurchase() { const theme = useTheme(); const [push] = useHistory(); diff --git a/src/routes/popup/purchase.tsx b/src/routes/popup/purchase.tsx index 0ae12caab..812b944da 100644 --- a/src/routes/popup/purchase.tsx +++ b/src/routes/popup/purchase.tsx @@ -16,13 +16,13 @@ import HeadV2 from "~components/popup/HeadV2"; import { useEffect, useMemo, useState } from "react"; import { PageType, trackPage } from "~utils/analytics"; import type { PaymentType, Quote } from "~lib/onramper"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { ExtensionStorage } from "~utils/storage"; import { useDebounce } from "~wallets/hooks"; import { retryWithDelay } from "~utils/promises/retry"; import SliderMenu from "~components/SliderMenu"; -export default function Purchase() { +export function PurchaseView() { const [push] = useHistory(); const youPayInput = useInput(); const debouncedYouPayInput = useDebounce(youPayInput.state, 300); diff --git a/src/routes/popup/receive.tsx b/src/routes/popup/receive.tsx index a756905ce..d898ab8f2 100644 --- a/src/routes/popup/receive.tsx +++ b/src/routes/popup/receive.tsx @@ -10,17 +10,15 @@ import copy from "copy-to-clipboard"; import { useEffect, type MouseEventHandler, useState, useMemo } from "react"; import { PageType, trackPage } from "~utils/analytics"; import HeadV2 from "~components/popup/HeadV2"; -import { Degraded, WarningWrapper } from "./send"; -import { WarningIcon } from "~components/popup/Token"; -import { useActiveWallet } from "~wallets/hooks"; import { useLocation } from "wouter"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -interface ReceiveProps { +interface ReceiveViewProps extends CommonRouteProps { walletName?: string; walletAddress?: string; } -export default function Receive({ walletName, walletAddress }: ReceiveProps) { +export function ReceiveView({ walletName, walletAddress }: ReceiveViewProps) { // active address const [activeAddress] = useStorage({ key: "active_address", diff --git a/src/routes/popup/send/auth.tsx b/src/routes/popup/send/auth.tsx index 844f29002..948baf10c 100644 --- a/src/routes/popup/send/auth.tsx +++ b/src/routes/popup/send/auth.tsx @@ -12,7 +12,7 @@ import type { JWKInterface } from "arweave/web/lib/wallet"; import type { Tag } from "arweave/web/lib/transaction"; import { useScanner } from "@arconnect/keystone-sdk"; import { useActiveWallet } from "~wallets/hooks"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useEffect, useState } from "react"; import { getActiveKeyfile, getActiveWallet } from "~wallets"; import type { UR } from "@ngraveio/bc-ur"; @@ -47,10 +47,15 @@ import { } from "~utils/send"; import { EventType, trackEvent } from "~utils/analytics"; import BigNumber from "bignumber.js"; -interface Props { +import type { CommonRouteProps } from "~wallets/router/router.types"; + +export interface SendAuthViewParams { tokenID?: string; } -export default function SendAuth({ tokenID }: Props) { + +export type SendAuthViewProps = CommonRouteProps; + +export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { // loading const [loading, setLoading] = useState(false); diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index a32e82f15..17d3373e8 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -21,7 +21,7 @@ import { import { useEffect, useMemo, useState } from "react"; import { findGateway } from "~gateways/wayfinder"; import Arweave from "arweave"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { fallbackGateway, type Gateway } from "~gateways/gateway"; import AnimatedQRScanner from "~components/hardware/AnimatedQRScanner"; import AnimatedQRPlayer from "~components/hardware/AnimatedQRPlayer"; @@ -65,19 +65,26 @@ import { SubscriptionStatus } from "~subscriptions/subscription"; import { checkPassword } from "~wallets/auth"; import BigNumber from "bignumber.js"; import { SignType } from "@keystonehq/bc-ur-registry-arweave"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -interface Props { +function formatNumber(amount: string, decimalPlaces: number = 2): string { + return BigNumber(amount).toFixed(decimalPlaces); +} + +export interface ConfirmViewParams { tokenID: string; qty?: number; recipient?: string; subscription?: boolean; } -function formatNumber(amount: string, decimalPlaces: number = 2): string { - return BigNumber(amount).toFixed(decimalPlaces); -} +export type ConfirmViewProps = CommonRouteProps; + +export function ConfirmView({ + params: { tokenID, qty: qtyParam, subscription } +}: ConfirmViewProps) { + const qty = Number(qtyParam || "0"); -export default function Confirm({ tokenID, qty, subscription }: Props) { // TODO: Need to get Token information const [token, setToken] = useState(); const [amount, setAmount] = useState(""); diff --git a/src/routes/popup/send/index.tsx b/src/routes/popup/send/index.tsx index c62ccf5af..5d155efd1 100644 --- a/src/routes/popup/send/index.tsx +++ b/src/routes/popup/send/index.tsx @@ -50,7 +50,7 @@ import redstone from "redstone-api"; import { AnimatePresence, motion, type Variants } from "framer-motion"; import Collectible from "~components/popup/Collectible"; import { findGateway } from "~gateways/wayfinder"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { DREContract, DRENode } from "@arconnect/warp-dre"; import { isUToken } from "~utils/send"; import HeadV2 from "~components/popup/HeadV2"; @@ -68,6 +68,7 @@ import { useAoTokens } from "~tokens/aoTokens/ao"; import BigNumber from "bignumber.js"; import { AO_NATIVE_TOKEN } from "~utils/ao_import"; import { AnnouncementPopup } from "./announcement"; +import type { CommonRouteProps } from "~wallets/router/router.types"; // default size for the qty text const defaulQtytSize = 3.7; @@ -97,7 +98,13 @@ export interface TransactionData { isAo?: boolean; } -export default function Send({ id }: Props) { +export interface SendViewParams { + id?: string; +} + +export type SendViewProps = CommonRouteProps; + +export function SendView({ params: { id } }: SendViewProps) { // Segment useEffect(() => { trackPage(PageType.SEND); @@ -754,10 +761,6 @@ const SendForm = styled.div` justify-content: space-between; `; -interface Props { - id?: string; -} - type QtyMode = "fiat" | "token"; // Make this dynamic diff --git a/src/routes/popup/send/recipient.tsx b/src/routes/popup/send/recipient.tsx index fdc7677eb..f572a2e53 100644 --- a/src/routes/popup/send/recipient.tsx +++ b/src/routes/popup/send/recipient.tsx @@ -18,13 +18,24 @@ import type Transaction from "arweave/web/lib/transaction"; import { useEffect, useMemo, useState } from "react"; import { useStorage } from "@plasmohq/storage/hook"; import { findGateway } from "~gateways/wayfinder"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import browser from "webextension-polyfill"; import Head from "~components/popup/Head"; import styled from "styled-components"; import Arweave from "arweave"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function Recipient({ tokenID, qty, message }: Props) { +export interface RecipientViewParams { + tokenID: string; + qty: string; + message?: string; +} + +export type RecipientViewProps = CommonRouteProps; + +export function RecipientView({ + params: { tokenID, qty, message } +}: RecipientViewProps) { // transaction target input const targetInput = useInput(); @@ -221,9 +232,3 @@ const Address = styled(Text).attrs({ transform: scale(0.97); } `; - -interface Props { - tokenID: string; - qty: string; - message?: string; -} diff --git a/src/routes/popup/settings/apps/[url]/index.tsx b/src/routes/popup/settings/apps/[url]/index.tsx index 03cd96094..e797d118f 100644 --- a/src/routes/popup/settings/apps/[url]/index.tsx +++ b/src/routes/popup/settings/apps/[url]/index.tsx @@ -25,8 +25,15 @@ import { defaultGateway, suggestedGateways, testnets } from "~gateways/gateway"; import HeadV2 from "~components/popup/HeadV2"; import { useLocation } from "wouter"; import { ToggleSwitch } from "~routes/popup/subscriptions/subscriptionDetails"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function AppSettings({ url }: Props) { +export interface AppSettingsViewParams { + url: string; +} + +export type AppSettingsViewProps = CommonRouteProps; + +export function AppSettingsView({ params: { url } }: AppSettingsViewProps) { // app settings const app = new Application(decodeURIComponent(url)); const [settings, updateSettings] = app.hook(); @@ -372,10 +379,6 @@ export default function AppSettings({ url }: Props) { ); } -interface Props { - url: string; -} - const Wrapper = styled.div` display: flex; flex-direction: column; diff --git a/src/routes/popup/settings/apps/[url]/permissions.tsx b/src/routes/popup/settings/apps/[url]/permissions.tsx index 2145bd6cc..33d8a7125 100644 --- a/src/routes/popup/settings/apps/[url]/permissions.tsx +++ b/src/routes/popup/settings/apps/[url]/permissions.tsx @@ -6,8 +6,18 @@ import HeadV2 from "~components/popup/HeadV2"; import { permissionData, type PermissionType } from "~applications/permissions"; import Checkbox from "~components/Checkbox"; import { useLocation } from "wouter"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function AppPermissions({ url }: Props) { +export interface AppPermissionsViewParams { + url: string; +} + +export type AppPermissionsViewProps = + CommonRouteProps; + +export function AppPermissionsView({ + params: { url } +}: AppPermissionsViewProps) { // app settings const app = new Application(decodeURIComponent(url)); const [settings, updateSettings] = app.hook(); diff --git a/src/routes/popup/settings/apps/index.tsx b/src/routes/popup/settings/apps/index.tsx index 53d4b0814..e608a08f4 100644 --- a/src/routes/popup/settings/apps/index.tsx +++ b/src/routes/popup/settings/apps/index.tsx @@ -13,7 +13,7 @@ import HeadV2 from "~components/popup/HeadV2"; import useActiveTab from "~applications/useActiveTab"; import { getAppURL } from "~utils/format"; -export default function Applications() { +export function ApplicationsView() { // connected apps const [connectedApps] = useStorage( { diff --git a/src/routes/popup/settings/contacts/[address]/index.tsx b/src/routes/popup/settings/contacts/[address]/index.tsx index 7c3b2819c..fe882c0b5 100644 --- a/src/routes/popup/settings/contacts/[address]/index.tsx +++ b/src/routes/popup/settings/contacts/[address]/index.tsx @@ -3,12 +3,18 @@ import browser from "webextension-polyfill"; import { default as ContactSettingsComponent } from "~components/dashboard/subsettings/ContactSettings"; import styled from "styled-components"; import { useLocation } from "wouter"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -interface ContactSettingsProps { +export interface ContactSettingsViewParams { address: string; } -export default function ContactSettings({ address }: ContactSettingsProps) { +export type ContactSettingsViewProps = + CommonRouteProps; + +export function ContactSettingsView({ + params: { address } +}: ContactSettingsViewProps) { const [, setLocation] = useLocation(); return ( diff --git a/src/routes/popup/settings/contacts/index.tsx b/src/routes/popup/settings/contacts/index.tsx index 0d9d829f3..3448e5b94 100644 --- a/src/routes/popup/settings/contacts/index.tsx +++ b/src/routes/popup/settings/contacts/index.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import { default as ContactsComponent } from "~components/dashboard/Contacts"; import { useLocation } from "wouter"; -export default function Contacts() { +export function ContactsView() { const [, setLocation] = useLocation(); return ( diff --git a/src/routes/popup/settings/contacts/new.tsx b/src/routes/popup/settings/contacts/new.tsx index 647724e7d..4579c5d16 100644 --- a/src/routes/popup/settings/contacts/new.tsx +++ b/src/routes/popup/settings/contacts/new.tsx @@ -4,7 +4,7 @@ import AddContact from "~components/dashboard/subsettings/AddContact"; import styled from "styled-components"; import { useLocation } from "wouter"; -export default function NewContact() { +export function NewContactView() { const [, setLocation] = useLocation(); return ( diff --git a/src/routes/popup/settings/notifications/index.tsx b/src/routes/popup/settings/notifications/index.tsx index c1e2ba18c..57999dcf4 100644 --- a/src/routes/popup/settings/notifications/index.tsx +++ b/src/routes/popup/settings/notifications/index.tsx @@ -10,7 +10,7 @@ import { InformationIcon } from "@iconicicons/react"; import Checkbox from "~components/Checkbox"; import { useLocation } from "wouter"; -export default function NotificationSettings() { +export function NotificationSettingsView() { const [, setLocation] = useLocation(); const [notificationSettings, setNotificationSettings] = useStorage( diff --git a/src/routes/popup/settings/quickSettings.tsx b/src/routes/popup/settings/quickSettings.tsx index 0e0cfbbaa..6d6e89999 100644 --- a/src/routes/popup/settings/quickSettings.tsx +++ b/src/routes/popup/settings/quickSettings.tsx @@ -13,8 +13,16 @@ import { ListItem, ListItemIcon } from "@arconnect/components"; import type { Icon } from "~settings/setting"; import type { HTMLProps, ReactNode } from "react"; import styled from "styled-components"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function QuickSettings({ params }: Props) { +export interface QuickSettingsViewParams { + setting?: string; + subsetting?: string; +} + +export type QuickSettingsViewProps = CommonRouteProps; + +export function QuickSettingsView({ params }: QuickSettingsViewProps) { // router location const [, setLocation] = useLocation(); @@ -79,13 +87,6 @@ function SettingListItem({ ); } -interface Props { - params: { - setting?: string; - subsetting?: string; - }; -} - interface Setting extends SettingItemData { name: string; component?: (...args: any[]) => JSX.Element; diff --git a/src/routes/popup/settings/tokens/[id]/index.tsx b/src/routes/popup/settings/tokens/[id]/index.tsx index 351225292..0b3b98163 100644 --- a/src/routes/popup/settings/tokens/[id]/index.tsx +++ b/src/routes/popup/settings/tokens/[id]/index.tsx @@ -19,8 +19,15 @@ import { formatAddress } from "~utils/format"; import { CopyButton } from "~components/dashboard/subsettings/WalletSettings"; import HeadV2 from "~components/popup/HeadV2"; import { useLocation } from "wouter"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function TokenSettings({ id }: Props) { +export interface TokenSettingsParams { + id: string; +} + +export type TokenSettingsProps = CommonRouteProps; + +export function TokenSettingsView({ params: { id } }: TokenSettingsProps) { // tokens const [tokens, setTokens] = useStorage( { @@ -166,10 +173,6 @@ const TokenAddress = styled(Text).attrs({ gap: 0.37rem; `; -interface Props { - id: string; -} - const Property = styled.div` display: flex; gap: 4px; diff --git a/src/routes/popup/settings/tokens/index.tsx b/src/routes/popup/settings/tokens/index.tsx index 020306e98..d1a0a43ff 100644 --- a/src/routes/popup/settings/tokens/index.tsx +++ b/src/routes/popup/settings/tokens/index.tsx @@ -21,7 +21,7 @@ import arLogoDark from "url:/assets/ar/logo_dark.png"; import { getUserAvatar } from "~lib/avatar"; import SearchInput from "~components/dashboard/SearchInput"; -export default function Tokens() { +export function TokensSettingsView() { // tokens const [tokens] = useStorage( { diff --git a/src/routes/popup/settings/tokens/new.tsx b/src/routes/popup/settings/tokens/new.tsx index 6278b62e4..6b7f7e92a 100644 --- a/src/routes/popup/settings/tokens/new.tsx +++ b/src/routes/popup/settings/tokens/new.tsx @@ -4,7 +4,7 @@ import AddToken from "~components/dashboard/subsettings/AddToken"; import styled from "styled-components"; import { useLocation } from "wouter"; -export default function NewToken() { +export function NewTokenSettingsView() { const [, setLocation] = useLocation(); return ( diff --git a/src/routes/popup/settings/wallets/[address]/export.tsx b/src/routes/popup/settings/wallets/[address]/export.tsx index 1653dbb0a..5d93f3233 100644 --- a/src/routes/popup/settings/wallets/[address]/export.tsx +++ b/src/routes/popup/settings/wallets/[address]/export.tsx @@ -16,8 +16,17 @@ import browser from "webextension-polyfill"; import styled from "styled-components"; import HeadV2 from "~components/popup/HeadV2"; import { useLocation } from "wouter"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function ExportWallet({ address }: Props) { +export interface ExportWalletViewParams { + address: string; +} + +export type ExportWalletViewProps = CommonRouteProps; + +export function ExportWalletView({ + params: { address } +}: ExportWalletViewProps) { const [, setLocation] = useLocation(); // wallets @@ -111,7 +120,3 @@ const Wrapper = styled.div` flex-direction: column; padding: 0 1rem; `; - -interface Props { - address: string; -} diff --git a/src/routes/popup/settings/wallets/[address]/index.tsx b/src/routes/popup/settings/wallets/[address]/index.tsx index 998aa2fa2..4386d0096 100644 --- a/src/routes/popup/settings/wallets/[address]/index.tsx +++ b/src/routes/popup/settings/wallets/[address]/index.tsx @@ -25,8 +25,15 @@ import copy from "copy-to-clipboard"; import { formatAddress } from "~utils/format"; import HeadV2 from "~components/popup/HeadV2"; import { useLocation } from "wouter"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function Wallet({ address }: Props) { +export interface WalletViewParams { + address: string; +} + +export type WalletViewProps = CommonRouteProps; + +export function WalletView({ params: { address } }: WalletViewProps) { // wallets const [wallets, setWallets] = useStorage( { diff --git a/src/routes/popup/settings/wallets/[address]/qr.tsx b/src/routes/popup/settings/wallets/[address]/qr.tsx index 1fb0fcd56..b55537904 100644 --- a/src/routes/popup/settings/wallets/[address]/qr.tsx +++ b/src/routes/popup/settings/wallets/[address]/qr.tsx @@ -35,8 +35,15 @@ import { } from "~routes/popup/receive"; import { dataToFrames } from "qrloop"; import { checkPassword } from "~wallets/auth"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function GenerateQR({ address }: { address: string }) { +export interface GenerateQRViewParams { + address: string; +} + +export type GenerateQRViewProps = CommonRouteProps; + +export function GenerateQRView({ params: { address } }: GenerateQRViewProps) { const [wallet, setWallet] = useState(null); const [copied, setCopied] = useState(false); const [loading, setLoading] = useState(false); diff --git a/src/routes/popup/settings/wallets/index.tsx b/src/routes/popup/settings/wallets/index.tsx index 9600e358c..203404262 100644 --- a/src/routes/popup/settings/wallets/index.tsx +++ b/src/routes/popup/settings/wallets/index.tsx @@ -14,7 +14,7 @@ import WalletListItem from "~components/dashboard/list/WalletListItem"; import SearchInput from "~components/dashboard/SearchInput"; import HeadV2 from "~components/popup/HeadV2"; -export default function Wallets() { +export function WalletsView() { // wallets const [wallets, setWallets] = useStorage( { diff --git a/src/routes/popup/subscriptions/subscriptionDetails.tsx b/src/routes/popup/subscriptions/subscriptionDetails.tsx index 043e02bcf..fe4cf9b4f 100644 --- a/src/routes/popup/subscriptions/subscriptionDetails.tsx +++ b/src/routes/popup/subscriptions/subscriptionDetails.tsx @@ -32,17 +32,23 @@ import { } from "~components/dashboard/list/BaseElement"; import { formatAddress } from "~utils/format"; import { useTheme } from "~utils/theme"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { getPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import { PageType, trackPage } from "~utils/analytics"; import BigNumber from "bignumber.js"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -interface Props { +export interface SubscriptionDetailsViewParams { id?: string; } -export default function SubscriptionDetails({ id }: Props) { +export type SubscriptionDetailsViewProps = + CommonRouteProps; + +export function SubscriptionDetailsView({ + params: { id } +}: SubscriptionDetailsViewProps) { const theme = useTheme(); const [subData, setSubData] = useState(null); const [checked, setChecked] = useState(false); diff --git a/src/routes/popup/subscriptions/subscriptionManagement.tsx b/src/routes/popup/subscriptions/subscriptionManagement.tsx index fafd0ec9b..94d12a52c 100644 --- a/src/routes/popup/subscriptions/subscriptionManagement.tsx +++ b/src/routes/popup/subscriptions/subscriptionManagement.tsx @@ -9,12 +9,18 @@ import { ButtonV2, ListItem } from "@arconnect/components"; import { Degraded, WarningWrapper } from "../send"; import { WarningIcon } from "~components/popup/Token"; import Title from "~components/popup/Title"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -interface Props { +export interface SubscriptionManagementViewParams { id?: string; } -export default function SubscriptionManagement({ id }: Props) { +export type SubscriptionManagementViewProps = + CommonRouteProps; + +export function SubscriptionManagementView({ + params: { id } +}: SubscriptionManagementViewProps) { const [subData, setSubData] = useState(null); const [nextPayment, setNextPayment] = useState(null); diff --git a/src/routes/popup/subscriptions/subscriptionPayment.tsx b/src/routes/popup/subscriptions/subscriptionPayment.tsx index 1d3cf0334..0765fe028 100644 --- a/src/routes/popup/subscriptions/subscriptionPayment.tsx +++ b/src/routes/popup/subscriptions/subscriptionPayment.tsx @@ -20,12 +20,22 @@ import { ArrowRightIcon } from "@iconicicons/react"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { ButtonV2, useToasts } from "@arconnect/components"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { getPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import BigNumber from "bignumber.js"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function SubscriptionPayment({ id }: { id: string }) { +export interface SubscriptionPaymentViewParams { + id?: string; +} + +export type SubscriptionPaymentViewProps = + CommonRouteProps; + +export function SubscriptionPaymentView({ + params: { id } +}: SubscriptionPaymentViewProps) { const [subData, setSubData] = useState(null); const [price, setPrice] = useState("--"); const [currency] = useSetting("currency"); diff --git a/src/routes/popup/subscriptions/subscriptions.tsx b/src/routes/popup/subscriptions/subscriptions.tsx index 2ec1c447c..ab13548f1 100644 --- a/src/routes/popup/subscriptions/subscriptions.tsx +++ b/src/routes/popup/subscriptions/subscriptions.tsx @@ -10,7 +10,7 @@ import Squircle from "~components/Squircle"; import browser from "webextension-polyfill"; import { getSubscriptionData, updateSubscription } from "~subscriptions"; import dayjs from "dayjs"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { SettingIconWrapper, SettingImage @@ -19,7 +19,7 @@ import { useTheme } from "~utils/theme"; import type { DisplayTheme } from "@arconnect/components"; import { PageType, trackPage } from "~utils/analytics"; -export default function Subscriptions() { +export function SubscriptionsView() { const [subData, setSubData] = useState(null); const theme = useTheme(); diff --git a/src/routes/popup/token/[id].tsx b/src/routes/popup/token/[id].tsx index 82b1cad27..489893aae 100644 --- a/src/routes/popup/token/[id].tsx +++ b/src/routes/popup/token/[id].tsx @@ -13,7 +13,7 @@ import { getDreForToken, useTokens } from "~tokens"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { getCommunityUrl } from "~utils/format"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useTheme } from "~utils/theme"; import { ArrowDownLeftIcon, @@ -45,8 +45,15 @@ import useSetting from "~settings/hook"; import styled from "styled-components"; import { useGateway } from "~gateways/wayfinder"; import HeadV2 from "~components/popup/HeadV2"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export default function Asset({ id }: Props) { +export interface AssetViewParams { + id: string; +} + +export type AssetViewProps = CommonRouteProps; + +export function AssetView({ params: { id } }: AssetViewProps) { // load state const [state, setState] = useState(); const [validity, setValidity] = useState<{ [id: string]: boolean }>(); @@ -439,10 +446,6 @@ export default function Asset({ id }: Props) { ); } -interface Props { - id: string; -} - const opacityAnimation: Variants = { hidden: { opacity: 0 }, shown: { opacity: 1 } diff --git a/src/routes/popup/tokens.tsx b/src/routes/popup/tokens.tsx index e9d55346b..0ec9efb96 100644 --- a/src/routes/popup/tokens.tsx +++ b/src/routes/popup/tokens.tsx @@ -1,4 +1,4 @@ -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { ButtonV2, Section, useToasts, Loading } from "@arconnect/components"; import { EditIcon } from "@iconicicons/react"; import { @@ -20,7 +20,7 @@ import { ExtensionStorage } from "~utils/storage"; import { syncAoTokens } from "~tokens/aoTokens/sync"; import { useStorage } from "@plasmohq/storage/hook"; -export default function Tokens() { +export function TokensView() { const [isLoading, setIsLoading] = useState(false); const [hasNextPage, setHasNextPage] = useState( undefined diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 79349fe7f..7df7efcc3 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -15,7 +15,7 @@ import { type MutableRefObject } from "react"; import { useGateway } from "~gateways/wayfinder"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { ChevronDownIcon, ChevronUpIcon, @@ -49,6 +49,7 @@ import BigNumber from "bignumber.js"; import { fetchTokenByProcessId } from "~lib/transactions"; import { useStorage } from "@plasmohq/storage/hook"; import type { StoredWallet } from "~wallets"; +import type { CommonRouteProps } from "~wallets/router/router.types"; // pull contacts and check if to address is in contacts @@ -58,7 +59,18 @@ interface ao { tokenId?: string | null; } -export default function Transaction({ id: rawId, gw, message }: Props) { +export interface TransactionViewParams { + id: string; + // encodeURIComponent transformed gateway url + gw?: string; + message?: boolean; +} + +export type TransactionViewProps = CommonRouteProps; + +export function TransactionView({ + params: { id: rawId, gw, message } +}: TransactionViewProps) { // fixup id const id = useMemo(() => rawId.split("?")[0], [rawId]); @@ -837,10 +849,3 @@ const opacityAnimation: Variants = { hidden: { opacity: 0 }, shown: { opacity: 1 } }; - -interface Props { - id: string; - // encodeURIComponent transformed gateway url - gw?: string; - message?: boolean; -} diff --git a/src/routes/popup/transaction/transactions.tsx b/src/routes/popup/transaction/transactions.tsx index 865e3aafe..5b8078ec2 100644 --- a/src/routes/popup/transaction/transactions.tsx +++ b/src/routes/popup/transaction/transactions.tsx @@ -14,7 +14,7 @@ import { AR_SENT_QUERY_WITH_CURSOR, PRINT_ARWEAVE_QUERY_WITH_CURSOR } from "~notifications/utils"; -import { useHistory } from "~utils/hash_router"; +import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { getArPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import { printTxWorkingGateways, txHistoryGateways } from "~gateways/gateway"; @@ -36,7 +36,7 @@ import { retryWithDelay } from "~utils/promises/retry"; const defaultCursors = ["", "", "", "", ""]; const defaultHasNextPages = [true, true, true, true, true]; -export default function Transactions() { +export function TransactionsView() { const [cursors, setCursors] = useState(defaultCursors); const [hasNextPages, setHasNextPages] = useState(defaultHasNextPages); const [transactions, setTransactions] = useState({}); diff --git a/src/tabs/auth.tsx b/src/tabs/auth.tsx index b7beb64ba..0b9d1f269 100644 --- a/src/tabs/auth.tsx +++ b/src/tabs/auth.tsx @@ -1,28 +1,16 @@ -import Route, { Page } from "~components/popup/Route"; -import { Router } from "wouter"; - import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; - -import Allowance from "~routes/auth/allowance"; -import Signature from "~routes/auth/signature"; -import Connect from "~routes/auth/connect"; import Unlock from "~routes/auth/unlock"; -import SignDataItem from "~routes/auth/signDataItem"; -import Token from "~routes/auth/token"; -import Sign from "~routes/auth/sign"; -import Subscription from "~routes/auth/subscription"; -import SignKeystone from "~routes/auth/signKeystone"; -import BatchSignDataItem from "~routes/auth/batchSignDataItem"; import { AnimatePresence } from "framer-motion"; import { AuthRequestsProvider } from "~utils/auth/auth.provider"; -import { - useAuthRequestsLocation, - useCurrentAuthRequest -} from "~utils/auth/auth.hooks"; +import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import browser from "webextension-polyfill"; import { LoadingPage } from "~components/LoadingPage"; import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; +import { Router } from "~wallets/router/router.component"; +import { useAuthRequestsLocation } from "~wallets/router/auth/auth-router.hook"; +import { AUTH_ROUTES } from "~wallets/router/auth/auth.routes"; +import { Page } from "~components/Page"; interface AuthAppProps { initialScreenType: InitialScreenType; @@ -34,12 +22,13 @@ export function AuthApp({ initialScreenType }: AuthAppProps) { let content: React.ReactElement = null; - // TODO: if initialScreenType === "generating" there was an error and this window muust be closed... + // TODO: if initialScreenType === "generating" there was an error and this window must be closed... if (initialScreenType === "locked") { + // TODO: Create a HOC to wrap UnlockView as UnlockPage and rename the Auth one... content = ( - + ); } else if (!authRequest) { @@ -54,19 +43,7 @@ export function AuthApp({ initialScreenType }: AuthAppProps) { /> ); } else if (initialScreenType === "default") { - content = ( - - - - - - - - - - - - ); + content = ; } return <>{content}; diff --git a/src/tabs/dashboard.tsx b/src/tabs/dashboard.tsx index 69fc60a23..4a43d1b4c 100644 --- a/src/tabs/dashboard.tsx +++ b/src/tabs/dashboard.tsx @@ -1,4 +1,4 @@ -import { useHashLocation } from "~utils/hash_router"; +import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; import { Router, Route } from "wouter"; import Settings from "~routes/dashboard"; diff --git a/src/tabs/welcome.tsx b/src/tabs/welcome.tsx index 8030badfe..9f3f7d3bf 100644 --- a/src/tabs/welcome.tsx +++ b/src/tabs/welcome.tsx @@ -1,5 +1,5 @@ import { type Path, pathToRegexp } from "path-to-regexp"; -import { useHashLocation } from "~utils/hash_router"; +import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; import { Router, Route } from "wouter"; import Home from "~routes/welcome"; diff --git a/src/utils/auth/auth.hooks.ts b/src/utils/auth/auth.hooks.ts index 350672fde..5a814d989 100644 --- a/src/utils/auth/auth.hooks.ts +++ b/src/utils/auth/auth.hooks.ts @@ -64,15 +64,3 @@ export function useCurrentAuthRequest( rejectRequest }; } - -export const useAuthRequestsLocation: BaseLocationHook = () => { - const { authRequest: currentAuthRequest } = useCurrentAuthRequest("any"); - const currentAuthRequestType = `/${currentAuthRequest.type}`; - const currentAuthRequestID = currentAuthRequest.authID || ""; - - useEffect(() => { - window.scrollTo(0, 0); - }, [currentAuthRequestID]); - - return [currentAuthRequestType, (path: string) => ""]; -}; diff --git a/src/wallets/router/auth/auth-router.hook.ts b/src/wallets/router/auth/auth-router.hook.ts new file mode 100644 index 000000000..d12f6f387 --- /dev/null +++ b/src/wallets/router/auth/auth-router.hook.ts @@ -0,0 +1,11 @@ +import type { BaseLocationHook } from "wouter"; +import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; + +export const useAuthRequestsLocation: BaseLocationHook = () => { + const { authRequest: currentAuthRequest } = useCurrentAuthRequest("any"); + const currentAuthRequestType = `/${currentAuthRequest.type}`; + + // TODO: `authID` should be added to the URL for window.scrollTo(0, 0); to work automatically. + + return [currentAuthRequestType, (path: string) => ""]; +}; diff --git a/src/wallets/router/auth/auth.routes.ts b/src/wallets/router/auth/auth.routes.ts new file mode 100644 index 000000000..3288a7785 --- /dev/null +++ b/src/wallets/router/auth/auth.routes.ts @@ -0,0 +1,53 @@ +import { AllowanceAuthRequestView } from "~routes/auth/allowance"; +import { BatchSignDataItemAuthRequestView } from "~routes/auth/batchSignDataItem"; +import { ConnectAuthRequestView } from "~routes/auth/connect"; +import { SignAuthRequestView } from "~routes/auth/sign"; +import { SignatureAuthRequestView } from "~routes/auth/signature"; +import { SignDataItemAuthRequestView } from "~routes/auth/signDataItem"; +import { SignKeystoneAuthRequestView } from "~routes/auth/signKeystone"; +import { SubscriptionAuthRequestView } from "~routes/auth/subscription"; +import { TokenAuthRequestView } from "~routes/auth/token"; +import type { RouteConfig } from "~wallets/router/router.types"; + +// TODO: Rename all of them and add the "page" wrapper to them... + +// TODO: Add enum here but update in another PR... + +export const AUTH_ROUTES: RouteConfig[] = [ + { + path: "/connect", + component: ConnectAuthRequestView + }, + { + path: "/allowance", + component: AllowanceAuthRequestView + }, + { + path: "/token", + component: TokenAuthRequestView + }, + { + path: "/sign", + component: SignAuthRequestView + }, + { + path: "/signKeystone", + component: SignKeystoneAuthRequestView + }, + { + path: "/signature", + component: SignatureAuthRequestView + }, + { + path: "/subscription", + component: SubscriptionAuthRequestView + }, + { + path: "/signDataItem", + component: SignDataItemAuthRequestView + }, + { + path: "/batchSignDataItem", + component: BatchSignDataItemAuthRequestView + } +]; diff --git a/src/utils/hash_router.ts b/src/wallets/router/hash/hash-router.hook.ts similarity index 95% rename from src/utils/hash_router.ts rename to src/wallets/router/hash/hash-router.hook.ts index ebe21b98d..9cdb315f9 100644 --- a/src/utils/hash_router.ts +++ b/src/wallets/router/hash/hash-router.hook.ts @@ -17,10 +17,6 @@ export const useHashLocation: BaseLocationHook = () => { return () => window.removeEventListener("hashchange", handler); }, []); - useEffect(() => { - window.scrollTo(0, 0); - }, [loc]); - return [loc, navigate]; }; diff --git a/src/wallets/router/iframe/iframe-router.hook.ts b/src/wallets/router/iframe/iframe-router.hook.ts new file mode 100644 index 000000000..2f157b2e1 --- /dev/null +++ b/src/wallets/router/iframe/iframe-router.hook.ts @@ -0,0 +1,17 @@ +import type { BaseLocationHook } from "wouter"; +import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; +import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; + +export const useIFrameLocation: BaseLocationHook = () => { + const { authRequest: currentAuthRequest } = useCurrentAuthRequest("any"); + + // TODO: In the embedded wallet, instead of calling window.close, the AuthProvider should just "clear" the requests. + + if (currentAuthRequest) { + const currentAuthRequestType = `/${currentAuthRequest.type}`; + + return [currentAuthRequestType, (path: string) => ""]; + } + + return useHashLocation(); +}; diff --git a/src/wallets/router/iframe/iframe.routes.ts b/src/wallets/router/iframe/iframe.routes.ts new file mode 100644 index 000000000..f67e2f5a0 --- /dev/null +++ b/src/wallets/router/iframe/iframe.routes.ts @@ -0,0 +1,28 @@ +import { AUTH_ROUTES } from "~wallets/router/auth/auth.routes"; +import { POPUP_ROUTES } from "~wallets/router/popup/popup.routes"; +import type { RouteConfig } from "~wallets/router/router.types"; +import { prefixRoutes } from "~wallets/router/router.utils"; + +const IFRAME_OWN_ROUTES: RouteConfig[] = [ + { + path: "/auth/foo", + // TODO: Add placeholder pages here... + component: null + }, + { + path: "/auth/bar", + component: null + } +]; + +export const IFRAME_ROUTES: RouteConfig[] = [ + // popup.tsx: + ...POPUP_ROUTES, + + // auth.tsx: + // TODO: How to add this prefix to routes to when using push(), etc? ENV variable in the enum? + ...prefixRoutes(AUTH_ROUTES, "/auth-request"), + + // Embedded wallet only: + ...IFRAME_OWN_ROUTES +]; diff --git a/src/wallets/router/popup/popup.routes.ts b/src/wallets/router/popup/popup.routes.ts new file mode 100644 index 000000000..4d2bd3040 --- /dev/null +++ b/src/wallets/router/popup/popup.routes.ts @@ -0,0 +1,189 @@ +import { HomeView } from "~routes/popup"; +import { CollectibleView } from "~routes/popup/collectible/[id]"; +import { CollectiblesView } from "~routes/popup/collectibles"; +import { ConfirmPurchaseView } from "~routes/popup/confirm"; +import { ExploreView } from "~routes/popup/explore"; +import { MessageNotificationView } from "~routes/popup/notification/[id]"; +import { NotificationsView } from "~routes/popup/notifications"; +import { PendingPurchase } from "~routes/popup/pending"; +import { PurchaseView } from "~routes/popup/purchase"; +import { ReceiveView } from "~routes/popup/receive"; +import { SendView } from "~routes/popup/send"; +import { SendAuthView } from "~routes/popup/send/auth"; +import { ConfirmView } from "~routes/popup/send/confirm"; +import { RecipientView } from "~routes/popup/send/recipient"; +import { ApplicationsView } from "~routes/popup/settings/apps"; +import { AppSettingsView } from "~routes/popup/settings/apps/[url]"; +import { AppPermissionsView } from "~routes/popup/settings/apps/[url]/permissions"; +import { ContactsView } from "~routes/popup/settings/contacts"; +import { ContactSettingsView } from "~routes/popup/settings/contacts/[address]"; +import { NewContactView } from "~routes/popup/settings/contacts/new"; +import { NotificationSettingsView } from "~routes/popup/settings/notifications"; +import { QuickSettingsView } from "~routes/popup/settings/quickSettings"; +import { TokensSettingsView } from "~routes/popup/settings/tokens"; +import { TokenSettingsView } from "~routes/popup/settings/tokens/[id]"; +import { NewTokenSettingsView } from "~routes/popup/settings/tokens/new"; +import { WalletsView } from "~routes/popup/settings/wallets"; +import { WalletView } from "~routes/popup/settings/wallets/[address]"; +import { ExportWalletView } from "~routes/popup/settings/wallets/[address]/export"; +import { GenerateQRView } from "~routes/popup/settings/wallets/[address]/qr"; +import { SubscriptionDetailsView } from "~routes/popup/subscriptions/subscriptionDetails"; +import { SubscriptionManagementView } from "~routes/popup/subscriptions/subscriptionManagement"; +import { SubscriptionPaymentView } from "~routes/popup/subscriptions/subscriptionPayment"; +import { SubscriptionsView } from "~routes/popup/subscriptions/subscriptions"; +import { AssetView } from "~routes/popup/token/[id]"; +import { TokensView } from "~routes/popup/tokens"; +import { TransactionView } from "~routes/popup/transaction/[id]"; +import { TransactionsView } from "~routes/popup/transaction/transactions"; +import type { RouteConfig } from "~wallets/router/router.types"; + +export const POPUP_ROUTES: RouteConfig[] = [ + { + path: "/", + component: HomeView + }, + { + path: "/purchase", + component: PurchaseView + }, + { + path: "/confirm-purchase/:quoteId?", + component: ConfirmPurchaseView + }, + { + path: "/purchase-pending", + component: PendingPurchase + }, + { + path: "/receive", + component: ReceiveView + }, + { + path: "/send/transfer/:id?", + component: SendView + }, + { + path: "/send/auth/:tokenID?", + component: SendAuthView + }, + { + path: "/explore", + component: ExploreView + }, + { + path: "/subscriptions", + component: SubscriptionsView + }, + { + path: "/subscriptions/:id", + component: SubscriptionDetailsView + }, + { + path: "/subscriptions/:id/manage", + component: SubscriptionManagementView + }, + { + path: "/subscriptions/:id/payment", + component: SubscriptionPaymentView + }, + { + path: "/transactions", + component: TransactionsView + }, + { + path: "/notifications", + component: NotificationsView + }, + { + path: "/notification/:id", + component: MessageNotificationView + }, + { + path: "/tokens", + component: TokensView + }, + { + path: "/token/:id", + component: AssetView + }, + { + path: "/collectibles", + component: CollectiblesView + }, + { + path: "/collectible/:id", + component: CollectibleView + }, + { + path: "/transaction/:id/:gateway?", + component: TransactionView + }, + { + path: "/send/confirm/:token/:qty/:recipient/:message?", + component: ConfirmView + }, + { + path: "/send/recipient/:token/:qty/:message?", + component: RecipientView + }, + { + path: "/quick-settings", + component: QuickSettingsView + }, + { + path: "/quick-settings/wallets", + component: WalletsView + }, + { + path: "/quick-settings/wallets/:address", + component: WalletView + }, + { + path: "/quick-settings/wallets/:address/export", + component: ExportWalletView + }, + { + path: "/quick-settings/wallets/:address/qr", + component: GenerateQRView + }, + { + path: "/quick-settings/apps", + component: ApplicationsView + }, + { + path: "/quick-settings/apps/:url", + component: AppSettingsView + }, + { + path: "/quick-settings/apps/:url/permissions", + component: AppPermissionsView + }, + { + path: "/quick-settings/tokens", + component: TokensSettingsView + }, + { + path: "/quick-settings/tokens/new", + component: NewTokenSettingsView + }, + { + path: "/quick-settings/tokens/:id", + component: TokenSettingsView + }, + { + path: "/quick-settings/contacts", + component: ContactsView + }, + { + path: "/quick-settings/contacts/new", + component: NewContactView + }, + { + path: "/quick-settings/contacts/:address", + component: ContactSettingsView + }, + { + path: "/quick-settings/notifications", + component: NotificationSettingsView + } +]; diff --git a/src/wallets/router/router.component.tsx b/src/wallets/router/router.component.tsx new file mode 100644 index 000000000..ae6962717 --- /dev/null +++ b/src/wallets/router/router.component.tsx @@ -0,0 +1,67 @@ +import React, { useEffect, type PropsWithChildren } from "react"; +import { + Switch, + useLocation, + Router as Wouter, + Route as Woute, + type BaseLocationHook +} from "wouter"; +import { Page } from "~components/Page"; +import HistoryProvider from "~components/popup/HistoryProvider"; +import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; +import type { + CommonRouteProps, + RouteConfig +} from "~wallets/router/router.types"; +import { BodyScroller, HistoryObserver } from "~wallets/router/router.utils"; + +export interface RouterProps { + routes: RouteConfig[]; + hook?: BaseLocationHook; + pageComponent?: React.ComponentType; +} + +export function Router({ + routes, + hook = useHashLocation, + pageComponent: PageComponent = Page +}: RouterProps) { + // TODO: In development, check there are no duplicates... + + // TODO: Async-loaded components? + + // TODO: Create a base type for views and rename them all (component and file)... + + // TODO: Move motion wrapper here... + + return ( + + + + + + {routes.map((route) => { + const Component = route.component; + + const PageWithComponent: React.ComponentType = ( + props + ) => { + return ( + + + + ); + }; + + return ( + + ); + })} + + + ); +} diff --git a/src/wallets/router/router.types.ts b/src/wallets/router/router.types.ts new file mode 100644 index 000000000..4c4024748 --- /dev/null +++ b/src/wallets/router/router.types.ts @@ -0,0 +1,18 @@ +import type React from "react"; +import type { RouteComponentProps as WouteComponentProps } from "wouter"; + +export interface CommonRouteProps + extends Omit { + params: T; +} + +export type RouteString = `/${string}`; + +export type RouteAuthType = "auth" | "anon"; + +export interface RouteConfig { + key?: string; + path: RouteString; + component: React.ComponentType; + authType?: RouteAuthType; +} diff --git a/src/wallets/router/router.utils.ts b/src/wallets/router/router.utils.ts new file mode 100644 index 000000000..2edcbb086 --- /dev/null +++ b/src/wallets/router/router.utils.ts @@ -0,0 +1,31 @@ +import { useEffect } from "react"; +import { useLocation } from "wouter"; +import type { RouteConfig, RouteString } from "~wallets/router/router.types"; + +export function prefixRoutes( + routes: RouteConfig[], + prefix: RouteString +): RouteConfig[] { + return routes.map((route) => ({ + ...route, + path: `${prefix}${route.path}` + })); +} + +export function BodyScroller() { + const [location] = useLocation(); + + useEffect(() => { + window.scrollTo(0, 0); + }, [location]); + + return null; +} + +export function HistoryObserver() { + const [location] = useLocation(); + + // TODO: To be implemented... + + return null; +} From 48d7a70202c8c9222c6d177dd49c79128d6d05d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 27 Nov 2024 12:05:12 +0100 Subject: [PATCH 071/142] Finish router refactoring to make it config-driven. --- .../common/loading/loading.view.tsx} | 18 ++--- .../{Page.tsx => page/page.component.tsx} | 6 +- src/components/page/page.utils.tsx | 15 +++++ src/components/popup/Route.tsx | 23 ------- src/iframe/iframe.tsx | 63 +++++++++-------- src/popup.tsx | 44 ++++++------ src/routes/auth/unlock.tsx | 5 +- src/routes/common/unlock.view.tsx | 0 src/routes/popup/unlock.tsx | 13 ++-- src/tabs/arlocal.tsx | 4 +- src/tabs/auth.tsx | 36 +++++----- src/tabs/devtools.tsx | 3 +- src/wallets/index.ts | 4 -- src/wallets/router/router.component.tsx | 67 ------------------- src/wallets/router/routes.component.tsx | 62 +++++++++++++++++ .../browser-extension-wallet-setup.hook.ts | 36 +++------- .../embedded/embedded-wallet-setup.hook.ts | 33 ++------- src/wallets/setup/wallet-setup.types.ts | 2 +- 18 files changed, 191 insertions(+), 243 deletions(-) rename src/components/{LoadingPage.tsx => page/common/loading/loading.view.tsx} (53%) rename src/components/{Page.tsx => page/page.component.tsx} (85%) create mode 100644 src/components/page/page.utils.tsx delete mode 100644 src/components/popup/Route.tsx delete mode 100644 src/routes/common/unlock.view.tsx delete mode 100644 src/wallets/router/router.component.tsx create mode 100644 src/wallets/router/routes.component.tsx diff --git a/src/components/LoadingPage.tsx b/src/components/page/common/loading/loading.view.tsx similarity index 53% rename from src/components/LoadingPage.tsx rename to src/components/page/common/loading/loading.view.tsx index 739ade943..5ca335832 100644 --- a/src/components/LoadingPage.tsx +++ b/src/components/page/common/loading/loading.view.tsx @@ -1,22 +1,22 @@ import { Loading } from "@arconnect/components"; import styled from "styled-components"; -import { Page } from "~components/popup/Route"; +import { withPage } from "~components/page/page.utils"; -export interface LoadingPageProps { +export interface LoadingViewProps { label?: string; } -export const LoadingPage = ({ label }: LoadingPageProps) => { +export const LoadingView = ({ label }: LoadingViewProps) => { return ( - - - - {label || "Loading..."} - - + + + {label || "Loading..."} + ); }; +export const LoadingPage = withPage(LoadingView); + const DivWrapper = styled.div` display: flex; flex-direction: column; diff --git a/src/components/Page.tsx b/src/components/page/page.component.tsx similarity index 85% rename from src/components/Page.tsx rename to src/components/page/page.component.tsx index 2f320abf9..229188b3c 100644 --- a/src/components/Page.tsx +++ b/src/components/page/page.component.tsx @@ -2,7 +2,9 @@ import { type Variants, motion } from "framer-motion"; import type { PropsWithChildren } from "react"; import styled from "styled-components"; -export const Page = ({ children }: PropsWithChildren) => { +export interface PageProps extends PropsWithChildren {} + +export function Page({ children }: PageProps) { const opacityAnimation: Variants = { initial: { opacity: 0 }, enter: { opacity: 1 }, @@ -20,7 +22,7 @@ export const Page = ({ children }: PropsWithChildren) => { {children} ); -}; +} const Main = styled(motion.main)` position: relative; diff --git a/src/components/page/page.utils.tsx b/src/components/page/page.utils.tsx new file mode 100644 index 000000000..7fff31946 --- /dev/null +++ b/src/components/page/page.utils.tsx @@ -0,0 +1,15 @@ +import { Page } from "~components/page/page.component"; + +export function withPage

(Component: React.ComponentType

) { + const PageComponent = (props: P) => { + return ( + + + + ); + }; + + PageComponent.displayName = `${Component.displayName || "Anonymous"}Page`; + + return PageComponent; +} diff --git a/src/components/popup/Route.tsx b/src/components/popup/Route.tsx deleted file mode 100644 index 3f6cae70d..000000000 --- a/src/components/popup/Route.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { createElement } from "react"; -import { useRoute, Route as BaseRoute } from "wouter"; -import { Page } from "~components/Page"; - -/** - * Custom Route component that allows iOS-like animations - */ - -/* -const Route: typeof BaseRoute = ({ path, component, children }) => { - const [matches, params] = useRoute(path); - - if (!matches) return null; - - const routeContent = component - ? createElement(component, { params }) - : typeof children === "function" - ? children(params) - : children; - - return {routeContent}; -}; -*/ diff --git a/src/iframe/iframe.tsx b/src/iframe/iframe.tsx index 5d69fb72b..8b0074073 100644 --- a/src/iframe/iframe.tsx +++ b/src/iframe/iframe.tsx @@ -1,13 +1,17 @@ import { AnimatePresence } from "framer-motion"; -import { Router } from "wouter"; +import type { PropsWithChildren } from "react"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import HistoryProvider from "~components/popup/HistoryProvider"; import { NavigationBar } from "~components/popup/Navigation"; -import { Page } from "~components/popup/Route"; +import { UnlockPage } from "~routes/popup/unlock"; import { AuthRequestsProvider } from "~utils/auth/auth.provider"; -import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; -import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; +import { useIFrameLocation } from "~wallets/router/iframe/iframe-router.hook"; +import { IFRAME_ROUTES } from "~wallets/router/iframe/iframe.routes"; +import { Routes } from "~wallets/router/routes.component"; +import { BodyScroller, HistoryObserver } from "~wallets/router/router.utils"; +import { useEmbeddedWalletSetUp } from "~wallets/setup/embedded/embedded-wallet-setup.hook"; import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; +import { Router as Wouter } from "wouter"; interface ArConnectEmbeddedAppProps { initialScreenType: InitialScreenType; @@ -19,25 +23,13 @@ export function ArConnectEmbeddedApp({ let content: React.ReactElement = null; if (initialScreenType === "locked") { - content = ( - - - - ); - } else if (initialScreenType === "generating") { - // This can only happen in the embedded wallet: - content = ( - -

Generating Wallet...

-
- ); + content = ; } else if (initialScreenType === "default") { content = ( - - - - - + <> + + + ); } @@ -45,17 +37,30 @@ export function ArConnectEmbeddedApp({ } export default function ArConnectEmbeddedAppRoot() { - const initialScreenType = useBrowserExtensionWalletSetUp(); + const initialScreenType = useEmbeddedWalletSetUp(); return ( - - - - - - - + + + + + + + + + + + + + + ); } + +const AuthProvider = ({ children }: PropsWithChildren) => { + // TODO: To be implemented... + + return <>{children}; +}; diff --git a/src/popup.tsx b/src/popup.tsx index c7e8d7f02..f87d749a0 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,13 +1,15 @@ -import Unlock from "~routes/popup/unlock"; +import { UnlockPage } from "~routes/popup/unlock"; import { NavigationBar } from "~components/popup/Navigation"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; -import { AnimatePresence } from "framer-motion"; import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; -import { Page } from "~components/Page"; -import { Router } from "~wallets/router/router.component"; +import { Routes } from "~wallets/router/routes.component"; import { POPUP_ROUTES } from "~wallets/router/popup/popup.routes"; import HistoryProvider from "~components/popup/HistoryProvider"; +import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; +import { Router as Wouter } from "wouter"; +import { BodyScroller, HistoryObserver } from "~wallets/router/router.utils"; +import { AnimatePresence } from "framer-motion"; interface ArConnectBrowserExtensionAppProps { initialScreenType: InitialScreenType; @@ -19,22 +21,11 @@ export function ArConnectBrowserExtensionApp({ let content: React.ReactElement = null; if (initialScreenType === "locked") { - content = ( - - - - ); - } else if (initialScreenType === "generating") { - // This can only happen in the embedded wallet: - content = ( - -

Generating Wallet...

-
- ); + content = ; } else if (initialScreenType === "default") { content = ( <> - + ); @@ -43,18 +34,23 @@ export function ArConnectBrowserExtensionApp({ return <>{content}; } -// TODO: Move HistoryProvider below and add it manually instead. Add a HistoryProviderObserver... - export function ArConnectBrowserExtensionAppRoot() { const initialScreenType = useBrowserExtensionWalletSetUp(); return ( - - - - - + + + + + + + + + + ); } diff --git a/src/routes/auth/unlock.tsx b/src/routes/auth/unlock.tsx index 68ec048cb..4959e2247 100644 --- a/src/routes/auth/unlock.tsx +++ b/src/routes/auth/unlock.tsx @@ -11,8 +11,9 @@ import Wrapper from "~components/auth/Wrapper"; import browser from "webextension-polyfill"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; +import { withPage } from "~components/page/page.utils"; -export default function Unlock() { +export function UnlockAuthRequestView() { // password input const passwordInput = useInput(); @@ -73,3 +74,5 @@ export default function Unlock() {
); } + +export const UnlockAuthRequestPage = withPage(UnlockAuthRequestView); diff --git a/src/routes/common/unlock.view.tsx b/src/routes/common/unlock.view.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/routes/popup/unlock.tsx b/src/routes/popup/unlock.tsx index f7491b62b..b71cc0b31 100644 --- a/src/routes/popup/unlock.tsx +++ b/src/routes/popup/unlock.tsx @@ -11,8 +11,9 @@ import { useToasts } from "@arconnect/components"; import HeadV2 from "~components/popup/HeadV2"; +import { withPage } from "~components/page/page.utils"; -export default function Unlock() { +export function UnlockView() { // password input const passwordInput = useInput(); @@ -21,15 +22,12 @@ export default function Unlock() { // unlock ArConnect async function unlockWallet() { - console.log("unlockWallet()"); - // unlock using password const res = await unlock(passwordInput.state); - console.log("res =", res); - if (!res) { passwordInput.setStatus("error"); + return setToast({ type: "error", content: browser.i18n.getMessage("invalidPassword"), @@ -47,7 +45,9 @@ export default function Unlock() { back={() => {}} showBack={false} /> + +
{browser.i18n.getMessage("unlock_wallet_to_use")} @@ -67,6 +67,7 @@ export default function Unlock() { />
+
{browser.i18n.getMessage("unlock")} @@ -75,3 +76,5 @@ export default function Unlock() { ); } + +export const UnlockPage = withPage(UnlockView); diff --git a/src/tabs/arlocal.tsx b/src/tabs/arlocal.tsx index f10728c2f..bbf7d3e3e 100644 --- a/src/tabs/arlocal.tsx +++ b/src/tabs/arlocal.tsx @@ -1,12 +1,11 @@ import { InputWithBtn, InputWrapper } from "~components/arlocal/InputWrapper"; import { RefreshButton } from "~components/IconButton"; import { useEffect, useMemo, useState } from "react"; -import { useTheme } from "~utils/theme"; import { urlToGateway } from "~gateways/utils"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { RefreshIcon } from "@iconicicons/react"; -import { useNoWallets, useRemoveCover } from "~wallets"; +import { useNoWallets } from "~wallets"; import { ButtonV2 as Button, InputV2 as Input, @@ -31,6 +30,7 @@ import browser from "webextension-polyfill"; import Arweave from "arweave"; import axios from "axios"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; +import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; export default function ArLocal() { useRemoveCover(); diff --git a/src/tabs/auth.tsx b/src/tabs/auth.tsx index 0b9d1f269..795ac3c30 100644 --- a/src/tabs/auth.tsx +++ b/src/tabs/auth.tsx @@ -1,16 +1,17 @@ import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; -import Unlock from "~routes/auth/unlock"; -import { AnimatePresence } from "framer-motion"; +import { UnlockAuthRequestPage } from "~routes/auth/unlock"; import { AuthRequestsProvider } from "~utils/auth/auth.provider"; import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import browser from "webextension-polyfill"; -import { LoadingPage } from "~components/LoadingPage"; +import { LoadingPage } from "~components/page/common/loading/loading.view"; import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; -import { Router } from "~wallets/router/router.component"; +import { Routes } from "~wallets/router/routes.component"; import { useAuthRequestsLocation } from "~wallets/router/auth/auth-router.hook"; import { AUTH_ROUTES } from "~wallets/router/auth/auth.routes"; -import { Page } from "~components/Page"; +import { BodyScroller } from "~wallets/router/router.utils"; +import { AnimatePresence } from "framer-motion"; +import { Router as Wouter } from "wouter"; interface AuthAppProps { initialScreenType: InitialScreenType; @@ -22,15 +23,8 @@ export function AuthApp({ initialScreenType }: AuthAppProps) { let content: React.ReactElement = null; - // TODO: if initialScreenType === "generating" there was an error and this window must be closed... - if (initialScreenType === "locked") { - // TODO: Create a HOC to wrap UnlockView as UnlockPage and rename the Auth one... - content = ( - - - - ); + content = ; } else if (!authRequest) { content = ( ); } else if (initialScreenType === "default") { - content = ; + content = ; } return <>{content}; @@ -54,11 +48,15 @@ export function AuthAppRoot() { return ( - - - - - + + + + + + + + + ); } diff --git a/src/tabs/devtools.tsx b/src/tabs/devtools.tsx index e3b7cd420..8dc430ee5 100644 --- a/src/tabs/devtools.tsx +++ b/src/tabs/devtools.tsx @@ -4,7 +4,7 @@ import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { getTab } from "~applications/tab"; import { getAppURL } from "~utils/format"; -import { useNoWallets, useRemoveCover } from "~wallets"; +import { useNoWallets } from "~wallets"; import AppSettings from "~components/dashboard/subsettings/AppSettings"; import Connector from "~components/devtools/Connector"; import NoWallets from "~components/devtools/NoWallets"; @@ -12,6 +12,7 @@ import Application from "~applications/application"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; +import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; export default function DevTools() { useRemoveCover(); diff --git a/src/wallets/index.ts b/src/wallets/index.ts index 9a13011d1..c2ede7e7c 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -50,8 +50,6 @@ export type StoredWallet = * @returns Wallets in storage */ export async function getWallets() { - console.log("getWallets()"); - let wallets: StoredWallet[] = await ExtensionStorage.get("wallets"); return wallets || []; @@ -61,8 +59,6 @@ export async function getWallets() { * Hook to get if there are no wallets added */ export const useNoWallets = () => { - console.log("useNoWallets()"); - const [state, setState] = useState(false); useEffect(() => { diff --git a/src/wallets/router/router.component.tsx b/src/wallets/router/router.component.tsx deleted file mode 100644 index ae6962717..000000000 --- a/src/wallets/router/router.component.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useEffect, type PropsWithChildren } from "react"; -import { - Switch, - useLocation, - Router as Wouter, - Route as Woute, - type BaseLocationHook -} from "wouter"; -import { Page } from "~components/Page"; -import HistoryProvider from "~components/popup/HistoryProvider"; -import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; -import type { - CommonRouteProps, - RouteConfig -} from "~wallets/router/router.types"; -import { BodyScroller, HistoryObserver } from "~wallets/router/router.utils"; - -export interface RouterProps { - routes: RouteConfig[]; - hook?: BaseLocationHook; - pageComponent?: React.ComponentType; -} - -export function Router({ - routes, - hook = useHashLocation, - pageComponent: PageComponent = Page -}: RouterProps) { - // TODO: In development, check there are no duplicates... - - // TODO: Async-loaded components? - - // TODO: Create a base type for views and rename them all (component and file)... - - // TODO: Move motion wrapper here... - - return ( - - - - - - {routes.map((route) => { - const Component = route.component; - - const PageWithComponent: React.ComponentType = ( - props - ) => { - return ( - - - - ); - }; - - return ( - - ); - })} - - - ); -} diff --git a/src/wallets/router/routes.component.tsx b/src/wallets/router/routes.component.tsx new file mode 100644 index 000000000..618ae07d2 --- /dev/null +++ b/src/wallets/router/routes.component.tsx @@ -0,0 +1,62 @@ +import React, { useEffect, type PropsWithChildren } from "react"; +import { Switch, Route as Woute } from "wouter"; +import { Page } from "~components/page/page.component"; +import type { + CommonRouteProps, + RouteConfig +} from "~wallets/router/router.types"; + +export interface RoutesProps { + routes: RouteConfig[]; + pageComponent?: React.ComponentType; +} + +export function Routes({ + routes, + pageComponent: PageComponent = Page +}: RoutesProps) { + // In development, check there are no duplicate routes (paths): + + if (process.env.NODE_ENV === "development") { + useEffect(() => { + const uniqueRoutes = new Set(); + + routes.forEach(({ path }) => { + if (uniqueRoutes.has(path)) + throw new Error(`Duplicate route "${path}"`); + + uniqueRoutes.add(path); + }); + }, [routes]); + } + + // TODO: Async-loaded components? + + // TODO: Is the history actually used? + + return ( + + {routes.map((route) => { + const Component = route.component; + + const PageWithComponent: React.ComponentType = ( + props + ) => { + return ( + + + + ); + }; + + return ( + + ); + })} + + ); +} diff --git a/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts b/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts index b1417e5f6..df6f64f38 100644 --- a/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts +++ b/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts @@ -13,8 +13,6 @@ import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; * Hook that opens a new tab if ArConnect has not been set up yet */ export function useBrowserExtensionWalletSetUp() { - console.log(`useBrowserExtensionWalletSetUp()`); - const [initialScreenType, setInitialScreenType] = useState("cover"); @@ -34,33 +32,17 @@ export function useBrowserExtensionWalletSetUp() { let nextInitialScreenType: InitialScreenType = "cover"; - switch (walletType) { - case "extension": { - if (!hasWallets) { - // This should only happen when opening the regular popup, but not for the auth popup, as the - // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: - - openOrSelectWelcomePage(true); - - window.top.close(); - } else if (!decryptionKey) { - nextInitialScreenType = "locked"; - } else { - nextInitialScreenType = "default"; - } - - break; - } - - case "embedded": { - nextInitialScreenType = !hasWallets ? "generating" : "default"; + if (!hasWallets) { + // This should only happen when opening the regular popup, but not for the auth popup, as the + // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: - break; - } + openOrSelectWelcomePage(true); - default: { - throw new Error(`Unknown APP_TYPE = ${walletType}`); - } + window.top.close(); + } else if (!decryptionKey) { + nextInitialScreenType = "locked"; + } else { + nextInitialScreenType = "default"; } setInitialScreenType(nextInitialScreenType); diff --git a/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts b/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts index 962827544..29d237ee2 100644 --- a/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts +++ b/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts @@ -13,8 +13,6 @@ import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; * Hook that opens a new tab if ArConnect has not been set up yet */ export function useEmbeddedWalletSetUp() { - console.log(`useEmbeddedWalletSetUp()`); - const [initialScreenType, setInitialScreenType] = useState("cover"); @@ -34,33 +32,10 @@ export function useEmbeddedWalletSetUp() { let nextInitialScreenType: InitialScreenType = "cover"; - switch (walletType) { - case "extension": { - if (!hasWallets) { - // This should only happen when opening the regular popup, but not for the auth popup, as the - // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: - - openOrSelectWelcomePage(true); - - window.top.close(); - } else if (!decryptionKey) { - nextInitialScreenType = "locked"; - } else { - nextInitialScreenType = "default"; - } - - break; - } - - case "embedded": { - nextInitialScreenType = !hasWallets ? "generating" : "default"; - - break; - } - - default: { - throw new Error(`Unknown APP_TYPE = ${walletType}`); - } + if (!hasWallets) { + // TODO: This should redirect/force the auth flow... + } else { + nextInitialScreenType = "default"; } setInitialScreenType(nextInitialScreenType); diff --git a/src/wallets/setup/wallet-setup.types.ts b/src/wallets/setup/wallet-setup.types.ts index 296fd432d..f8578beff 100644 --- a/src/wallets/setup/wallet-setup.types.ts +++ b/src/wallets/setup/wallet-setup.types.ts @@ -1 +1 @@ -export type InitialScreenType = "cover" | "locked" | "generating" | "default"; +export type InitialScreenType = "default" | "cover" | "locked"; From f819c4ba3dcce8fa37ad8a0ff174c25eb07b3307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 27 Nov 2024 12:57:23 +0100 Subject: [PATCH 072/142] Memoize Routes, extract route paths to constant and add authID to Auth popup routes (so that scroll and transitions work properly). --- src/iframe/iframe.tsx | 20 ++-- src/iframe/main.tsx | 2 - src/tabs/auth.tsx | 10 +- src/wallets/router/auth/auth-router.hook.ts | 4 +- src/wallets/router/auth/auth.routes.ts | 36 +++--- src/wallets/router/popup/popup.routes.ts | 120 +++++++++++++------- src/wallets/router/routes.component.tsx | 56 ++++----- 7 files changed, 151 insertions(+), 97 deletions(-) diff --git a/src/iframe/iframe.tsx b/src/iframe/iframe.tsx index 8b0074073..db93df65c 100644 --- a/src/iframe/iframe.tsx +++ b/src/iframe/iframe.tsx @@ -41,20 +41,20 @@ export default function ArConnectEmbeddedAppRoot() { return ( - - - + + + + + - - - + - - - - + + + + ); } diff --git a/src/iframe/main.tsx b/src/iframe/main.tsx index 75e77b173..f8cc6aaa7 100644 --- a/src/iframe/main.tsx +++ b/src/iframe/main.tsx @@ -5,8 +5,6 @@ import ArConnectEmbeddedAppRoot from "~iframe/iframe"; import "../../assets/popup.css"; -// TODO: Duplicate "Popup" as "Iframe" and move all routers to config to be able to combine Popup + Auth. - createRoot(document.getElementById("root")).render( diff --git a/src/tabs/auth.tsx b/src/tabs/auth.tsx index 795ac3c30..1f3a16da0 100644 --- a/src/tabs/auth.tsx +++ b/src/tabs/auth.tsx @@ -48,15 +48,15 @@ export function AuthAppRoot() { return ( - - + + + - - - + + ); } diff --git a/src/wallets/router/auth/auth-router.hook.ts b/src/wallets/router/auth/auth-router.hook.ts index d12f6f387..f5e0d3d45 100644 --- a/src/wallets/router/auth/auth-router.hook.ts +++ b/src/wallets/router/auth/auth-router.hook.ts @@ -3,7 +3,9 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; export const useAuthRequestsLocation: BaseLocationHook = () => { const { authRequest: currentAuthRequest } = useCurrentAuthRequest("any"); - const currentAuthRequestType = `/${currentAuthRequest.type}`; + const currentAuthRequestType = currentAuthRequest + ? `/${currentAuthRequest.type}/${currentAuthRequest.authID}` + : ""; // TODO: `authID` should be added to the URL for window.scrollTo(0, 0); to work automatically. diff --git a/src/wallets/router/auth/auth.routes.ts b/src/wallets/router/auth/auth.routes.ts index 3288a7785..ae85e261f 100644 --- a/src/wallets/router/auth/auth.routes.ts +++ b/src/wallets/router/auth/auth.routes.ts @@ -9,45 +9,53 @@ import { SubscriptionAuthRequestView } from "~routes/auth/subscription"; import { TokenAuthRequestView } from "~routes/auth/token"; import type { RouteConfig } from "~wallets/router/router.types"; -// TODO: Rename all of them and add the "page" wrapper to them... +export const AuthPaths = { + Connect: "/connect/:authID", + Allowance: "/allowance/:authID", + Token: "/token/:authID", + Sign: "/sign/:authID", + SignKeystone: "/signKeystone/:authID", + Signature: "/signature/:authID", + Subscription: "/subscription/:authID", + SignDataItem: "/signDataItem/:authID", + BatchSignDataItem: "/batchSignDataItem/:authID" +} as const; -// TODO: Add enum here but update in another PR... - -export const AUTH_ROUTES: RouteConfig[] = [ +export const AUTH_ROUTES = [ { - path: "/connect", + path: AuthPaths.Connect, component: ConnectAuthRequestView }, { - path: "/allowance", + path: AuthPaths.Allowance, component: AllowanceAuthRequestView }, { - path: "/token", + path: AuthPaths.Token, component: TokenAuthRequestView }, { - path: "/sign", + path: AuthPaths.Sign, component: SignAuthRequestView }, { - path: "/signKeystone", + path: AuthPaths.SignKeystone, component: SignKeystoneAuthRequestView }, { - path: "/signature", + path: AuthPaths.Signature, component: SignatureAuthRequestView }, { - path: "/subscription", + path: AuthPaths.Subscription, component: SubscriptionAuthRequestView }, { - path: "/signDataItem", + path: AuthPaths.SignDataItem, component: SignDataItemAuthRequestView }, { - path: "/batchSignDataItem", + path: AuthPaths.BatchSignDataItem, component: BatchSignDataItemAuthRequestView } -]; +] as const satisfies RouteConfig[]; diff --git a/src/wallets/router/popup/popup.routes.ts b/src/wallets/router/popup/popup.routes.ts index 4d2bd3040..a2dab67f8 100644 --- a/src/wallets/router/popup/popup.routes.ts +++ b/src/wallets/router/popup/popup.routes.ts @@ -37,153 +37,195 @@ import { TransactionView } from "~routes/popup/transaction/[id]"; import { TransactionsView } from "~routes/popup/transaction/transactions"; import type { RouteConfig } from "~wallets/router/router.types"; -export const POPUP_ROUTES: RouteConfig[] = [ +// TODO: Update with functions to pass params and replace in all usages: + +export const PopupPaths = { + Home: "/", + Purchase: "/purchase", + ConfirmPurchase: "/confirm-purchase/:quoteId?", + PendingPurchase: "/purchase-pending", + Receive: "/receive", + Send: "/send/transfer/:id?", + SendAuth: "/send/auth/:tokenID?", + Explore: "/explore", + Subscriptions: "/subscriptions", + SubscriptionDetails: "/subscriptions/:id", + SubscriptionManagement: "/subscriptions/:id/manage", + SubscriptionPayment: "/subscriptions/:id/payment", + Transactions: "/transactions", + Notifications: "/notifications", + MessageNotification: "/notification/:id", + Tokens: "/tokens", + Asset: "/token/:id", + Collectibles: "/collectibles", + Collectible: "/collectible/:id", + Transaction: "/transaction/:id/:gateway?", + Confirm: "/send/confirm/:token/:qty/:recipient/:message?", + Recipient: "/send/recipient/:token/:qty/:message?", + QuickSettings: "/quick-settings", + Wallets: "/quick-settings/wallets", + Wallet: "/quick-settings/wallets/:address", + ExportWallet: "/quick-settings/wallets/:address/export", + GenerateQR: "/quick-settings/wallets/:address/qr", + Applications: "/quick-settings/apps", + AppSettings: "/quick-settings/apps/:url", + AppPermissions: "/quick-settings/apps/:url/permissions", + TokensSettings: "/quick-settings/tokens", + NewTokenSettings: "/quick-settings/tokens/new", + TokenSettings: "/quick-settings/tokens/:id", + Contacts: "/quick-settings/contacts", + NewContact: "/quick-settings/contacts/new", + ContactSettings: "/quick-settings/contacts/:address", + NotificationSettings: "/quick-settings/notifications" +} as const; + +export const POPUP_ROUTES = [ { - path: "/", + path: PopupPaths.Home, component: HomeView }, { - path: "/purchase", + path: PopupPaths.Purchase, component: PurchaseView }, { - path: "/confirm-purchase/:quoteId?", + path: PopupPaths.ConfirmPurchase, component: ConfirmPurchaseView }, { - path: "/purchase-pending", + path: PopupPaths.PendingPurchase, component: PendingPurchase }, { - path: "/receive", + path: PopupPaths.Receive, component: ReceiveView }, { - path: "/send/transfer/:id?", + path: PopupPaths.Send, component: SendView }, { - path: "/send/auth/:tokenID?", + path: PopupPaths.SendAuth, component: SendAuthView }, { - path: "/explore", + path: PopupPaths.Explore, component: ExploreView }, { - path: "/subscriptions", + path: PopupPaths.Subscriptions, component: SubscriptionsView }, { - path: "/subscriptions/:id", + path: PopupPaths.SubscriptionDetails, component: SubscriptionDetailsView }, { - path: "/subscriptions/:id/manage", + path: PopupPaths.SubscriptionManagement, component: SubscriptionManagementView }, { - path: "/subscriptions/:id/payment", + path: PopupPaths.SubscriptionPayment, component: SubscriptionPaymentView }, { - path: "/transactions", + path: PopupPaths.Transactions, component: TransactionsView }, { - path: "/notifications", + path: PopupPaths.Notifications, component: NotificationsView }, { - path: "/notification/:id", + path: PopupPaths.MessageNotification, component: MessageNotificationView }, { - path: "/tokens", + path: PopupPaths.Tokens, component: TokensView }, { - path: "/token/:id", + path: PopupPaths.Asset, component: AssetView }, { - path: "/collectibles", + path: PopupPaths.Collectibles, component: CollectiblesView }, { - path: "/collectible/:id", + path: PopupPaths.Collectible, component: CollectibleView }, { - path: "/transaction/:id/:gateway?", + path: PopupPaths.Transaction, component: TransactionView }, { - path: "/send/confirm/:token/:qty/:recipient/:message?", + path: PopupPaths.Confirm, component: ConfirmView }, { - path: "/send/recipient/:token/:qty/:message?", + path: PopupPaths.Recipient, component: RecipientView }, { - path: "/quick-settings", + path: PopupPaths.QuickSettings, component: QuickSettingsView }, { - path: "/quick-settings/wallets", + path: PopupPaths.Wallets, component: WalletsView }, { - path: "/quick-settings/wallets/:address", + path: PopupPaths.Wallet, component: WalletView }, { - path: "/quick-settings/wallets/:address/export", + path: PopupPaths.ExportWallet, component: ExportWalletView }, { - path: "/quick-settings/wallets/:address/qr", + path: PopupPaths.GenerateQR, component: GenerateQRView }, { - path: "/quick-settings/apps", + path: PopupPaths.Applications, component: ApplicationsView }, { - path: "/quick-settings/apps/:url", + path: PopupPaths.AppSettings, component: AppSettingsView }, { - path: "/quick-settings/apps/:url/permissions", + path: PopupPaths.AppPermissions, component: AppPermissionsView }, { - path: "/quick-settings/tokens", + path: PopupPaths.TokensSettings, component: TokensSettingsView }, { - path: "/quick-settings/tokens/new", + path: PopupPaths.NewTokenSettings, component: NewTokenSettingsView }, { - path: "/quick-settings/tokens/:id", + path: PopupPaths.TokenSettings, component: TokenSettingsView }, { - path: "/quick-settings/contacts", + path: PopupPaths.Contacts, component: ContactsView }, { - path: "/quick-settings/contacts/new", + path: PopupPaths.NewContact, component: NewContactView }, { - path: "/quick-settings/contacts/:address", + path: PopupPaths.ContactSettings, component: ContactSettingsView }, { - path: "/quick-settings/notifications", + path: PopupPaths.NotificationSettings, component: NotificationSettingsView } -]; +] as const satisfies RouteConfig[]; diff --git a/src/wallets/router/routes.component.tsx b/src/wallets/router/routes.component.tsx index 618ae07d2..e537dba61 100644 --- a/src/wallets/router/routes.component.tsx +++ b/src/wallets/router/routes.component.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, type PropsWithChildren } from "react"; -import { Switch, Route as Woute } from "wouter"; +import React, { useEffect, useMemo, type PropsWithChildren } from "react"; +import { Switch, useLocation, Route as Woute } from "wouter"; import { Page } from "~components/page/page.component"; import type { CommonRouteProps, @@ -30,33 +30,37 @@ export function Routes({ }, [routes]); } - // TODO: Async-loaded components? + const [location] = useLocation(); - // TODO: Is the history actually used? + const memoizedRoutes = useMemo(() => { + return ( + + {routes.map((route) => { + const Component = route.component; - return ( - - {routes.map((route) => { - const Component = route.component; + // TODO: Async-loaded components? + + const PageWithComponent: React.ComponentType = ( + props + ) => { + return ( + + + + ); + }; - const PageWithComponent: React.ComponentType = ( - props - ) => { return ( - - - + ); - }; - - return ( - - ); - })} - - ); + })} + + ); + }, [routes, location]); + + return memoizedRoutes; } From 26ef8d4f6ad3a07cf327366eed5dd6009e996d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 27 Nov 2024 13:03:48 +0100 Subject: [PATCH 073/142] Fix broken import. --- src/routes/popup/notification/[id].tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/routes/popup/notification/[id].tsx b/src/routes/popup/notification/[id].tsx index 3589f0716..a2d204cbe 100644 --- a/src/routes/popup/notification/[id].tsx +++ b/src/routes/popup/notification/[id].tsx @@ -1,5 +1,8 @@ import type { CommonRouteProps } from "~wallets/router/router.types"; -import Transaction from "../transaction/[id]"; +import { + TransactionView, + type TransactionViewParams +} from "../transaction/[id]"; export interface MessageNotificationViewParams { id: string; @@ -11,9 +14,10 @@ export type MessageNotificationViewProps = export function MessageNotificationView({ params: { id } }: MessageNotificationViewProps) { - return ( - <> - - - ); + const params: TransactionViewParams = { + id, + message: true + }; + + return ; } From 8e079978d27cf37f7ce0cddbfd55271f86ccb112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 27 Nov 2024 14:57:30 +0100 Subject: [PATCH 074/142] Add mock for the Alarms API. --- src/iframe/browser/index.ts | 120 +++++++++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 7 deletions(-) diff --git a/src/iframe/browser/index.ts b/src/iframe/browser/index.ts index cef0ebdb8..9f43195d2 100644 --- a/src/iframe/browser/index.ts +++ b/src/iframe/browser/index.ts @@ -1,13 +1,111 @@ import enDic from "url:/assets/_locales/en/messages.json"; import zhCnDic from "url:/assets/_locales/zh_CN/messages.json"; +import type { Alarms, Runtime } from "webextension-polyfill"; + +// TODO: Missing storage.onChanged.addListener + +interface AlarmWithTimer extends Alarms.Alarm { + timeoutID?: number; + intervalID?: number; +} + +const alarmsByName: Record = {}; + +type AlarmCallback = (alarm?: Alarms.Alarm) => void; + +const alarmCallbacks: AlarmCallback[] = []; + +function invokeAlarms(name: string) { + const alarmWithTimer = alarmsByName[name]; + + if (!alarmWithTimer) return; + + alarmCallbacks.forEach((alarmCallback) => { + alarmCallback({ + name: alarmWithTimer.name, + scheduledTime: alarmWithTimer.scheduledTime, + periodInMinutes: alarmWithTimer.periodInMinutes, + }); + }); +} const alarms = { - create: null, - clear: null, - getAll: null, + create: (name: string, alarmInfo: Alarms.CreateAlarmInfoType) => { + const periodInMs = (alarmInfo.periodInMinutes ?? -1) * 60000; + const delayInMs = alarmInfo.when ? ( + alarmInfo.when - Date.now() + ) : ( + (alarmInfo.delayInMinutes ?? -1) * 60000 + ); + + const alarmWithTimer: AlarmWithTimer = { + name, + scheduledTime: 0, + periodInMinutes: alarmInfo.periodInMinutes, + }; + + alarmsByName[name] = alarmWithTimer; + + // TODO: Record last alarm run in localStorage to continue the alarm when reloading... + + if (delayInMs === 0) { + alarmWithTimer.scheduledTime = Date.now(); + invokeAlarms(name); + } else if (delayInMs > 0) { + alarmWithTimer.scheduledTime = Date.now() + delayInMs; + + alarmWithTimer.timeoutID = setTimeout(() => { + delete alarmWithTimer.timeoutID; + + invokeAlarms(name); + + alarmWithTimer.scheduledTime = Date.now() + periodInMs; + + alarmWithTimer.intervalID = setInterval(() => { + alarmWithTimer.scheduledTime = Date.now() + periodInMs; + + invokeAlarms(name); + }, periodInMs); + }, delayInMs); + } + + if (delayInMs <= 0 && periodInMs > 0) { + alarmWithTimer.scheduledTime = Date.now() + periodInMs; + + alarmWithTimer.intervalID = setInterval(() => { + alarmWithTimer.scheduledTime = Date.now() + periodInMs; + + invokeAlarms(name); + }, periodInMs); + } + }, + clear: (name: string) => { + const alarmWithTimer = alarmsByName[name]; + + if (!alarmWithTimer) return; + + if (alarmWithTimer.timeoutID) clearTimeout(alarmWithTimer.timeoutID); + if (alarmWithTimer.intervalID) clearTimeout(alarmWithTimer.intervalID); + }, + getAll: () => { + return Promise.resolve(Object.values(alarmsByName) satisfies Alarms.Alarm[]); + }, + get: (name: string) => { + const alarmWithTimer = alarmsByName[name]; + + if (!alarmWithTimer) return; + + return { + name: alarmWithTimer.name, + scheduledTime: alarmWithTimer.scheduledTime, + periodInMinutes: alarmWithTimer.periodInMinutes, + }; + }, onAlarm: { - addEventListener: null - } + addListener: (alarmCallback: AlarmCallback) => { + alarmCallbacks.push(alarmCallback); + }, + }, }; const dictionaries = { @@ -55,7 +153,15 @@ const runtime = { default_popup: "popup.html" } }; - } + }, + onInstalled: { + addListener: (fn) => { + fn({ reason: "install", temporary: false } satisfies Runtime.OnInstalledDetailsType); + }, + }, + + // TODO: onConnect is probably not needed once I replace onMessage/sendMessage with isomorphicOnMessage and + // isomorphicSendMessage (using the env variable). }; const tabs = { @@ -108,7 +214,7 @@ const tabs = { }; export default { - // alarms, + alarms, i18n, runtime, tabs From 2ae4dd9ba007af659399c8c67b846e3ab5e32b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Thu, 28 Nov 2024 10:56:06 +0100 Subject: [PATCH 075/142] Add storage mock. --- src/iframe/browser/index.ts | 87 ++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/src/iframe/browser/index.ts b/src/iframe/browser/index.ts index 9f43195d2..7f9f39502 100644 --- a/src/iframe/browser/index.ts +++ b/src/iframe/browser/index.ts @@ -1,6 +1,7 @@ import enDic from "url:/assets/_locales/en/messages.json"; import zhCnDic from "url:/assets/_locales/zh_CN/messages.json"; import type { Alarms, Runtime } from "webextension-polyfill"; +import type { StorageChange } from "~utils/runtime"; // TODO: Missing storage.onChanged.addListener @@ -24,7 +25,7 @@ function invokeAlarms(name: string) { alarmCallback({ name: alarmWithTimer.name, scheduledTime: alarmWithTimer.scheduledTime, - periodInMinutes: alarmWithTimer.periodInMinutes, + periodInMinutes: alarmWithTimer.periodInMinutes }); }); } @@ -32,16 +33,14 @@ function invokeAlarms(name: string) { const alarms = { create: (name: string, alarmInfo: Alarms.CreateAlarmInfoType) => { const periodInMs = (alarmInfo.periodInMinutes ?? -1) * 60000; - const delayInMs = alarmInfo.when ? ( - alarmInfo.when - Date.now() - ) : ( - (alarmInfo.delayInMinutes ?? -1) * 60000 - ); + const delayInMs = alarmInfo.when + ? alarmInfo.when - Date.now() + : (alarmInfo.delayInMinutes ?? -1) * 60000; const alarmWithTimer: AlarmWithTimer = { name, scheduledTime: 0, - periodInMinutes: alarmInfo.periodInMinutes, + periodInMinutes: alarmInfo.periodInMinutes }; alarmsByName[name] = alarmWithTimer; @@ -88,7 +87,9 @@ const alarms = { if (alarmWithTimer.intervalID) clearTimeout(alarmWithTimer.intervalID); }, getAll: () => { - return Promise.resolve(Object.values(alarmsByName) satisfies Alarms.Alarm[]); + return Promise.resolve( + Object.values(alarmsByName) satisfies Alarms.Alarm[] + ); }, get: (name: string) => { const alarmWithTimer = alarmsByName[name]; @@ -98,14 +99,14 @@ const alarms = { return { name: alarmWithTimer.name, scheduledTime: alarmWithTimer.scheduledTime, - periodInMinutes: alarmWithTimer.periodInMinutes, + periodInMinutes: alarmWithTimer.periodInMinutes }; }, onAlarm: { addListener: (alarmCallback: AlarmCallback) => { alarmCallbacks.push(alarmCallback); - }, - }, + } + } }; const dictionaries = { @@ -143,7 +144,7 @@ const i18n = { const runtime = { getURL: (path: string) => { - console.trace("getURL()"); + console.trace(`getURL(${path})`); return new URL(path, document.location.origin).toString(); }, @@ -156,9 +157,12 @@ const runtime = { }, onInstalled: { addListener: (fn) => { - fn({ reason: "install", temporary: false } satisfies Runtime.OnInstalledDetailsType); - }, - }, + fn({ + reason: "install", + temporary: false + } satisfies Runtime.OnInstalledDetailsType); + } + } // TODO: onConnect is probably not needed once I replace onMessage/sendMessage with isomorphicOnMessage and // isomorphicSendMessage (using the env variable). @@ -213,9 +217,60 @@ const tabs = { } }; +const storage = { + local: { + get: (keys?: null | string | string[] | Record) => { + if (keys === undefined || keys === null) { + return localStorage; + } + + if (typeof keys === "string") { + return localStorage.getItem(keys); + } + + if (Array.isArray(keys)) { + return keys.map(key => localStorage.getItem(key)); + } + + if (typeof keys === "object") { + return Object.entries(keys).map(([key, defaultValue]) => localStorage.getItem(key) ?? defaultValue); + } + }, + }, + + onChanged: { + addListener: (callback: ( + changes: Record>, + areaName: string + ) => void) => { + // Note from the docs (meaning, this is probably not working / not needed in ArConnect Embedded): + // + // Note: This won't work on the same browsing context that is making the changes — it is really a way for other + // browsing contexts on the domain using the storage to sync any changes that are made. Browsing contexts on other + // domains can't access the same storage objects. + // + // TODO: Check if this is an issue for the extension. + // - If it is, find a solution. + // - If it is not, maybe the mock is not needed at all and this can be excluded from background-setup.ts. + + window.addEventListener("storage", (event: StorageEvent) => { + const changes: Record> = { + [event.key]: { + newValue: event.newValue, + oldValue: event.oldValue, + }, + }; + + callback(changes, "local"); + }); + }, + }, +} + export default { alarms, i18n, runtime, - tabs + tabs, + storage, }; From c7c79642207c70337cbc9f5dbea59c7501fe91c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Thu, 28 Nov 2024 11:45:05 +0100 Subject: [PATCH 076/142] Re-strucute browser mocks. --- .husky/pre-commit | 2 +- src/api/background/background-setup.ts | 23 +- .../browser/install/install.handler.ts | 2 + .../app-config-change.handler.ts | 2 +- src/contents/events.ts | 2 - src/iframe/browser/alarms/alarms.mock.ts | 109 +++++++ src/iframe/browser/i18n/i18n.mock.ts | 32 ++ src/iframe/browser/index.ts | 275 +----------------- src/iframe/browser/runtime/runtime.mock.ts | 29 ++ src/iframe/browser/storage/storage.mock.ts | 58 ++++ src/iframe/browser/tabs/tabs.mock.ts | 52 ++++ src/iframe/iframe.tsx | 6 +- src/iframe/main.tsx | 19 +- src/utils/auth/auth.provider.tsx | 7 + src/utils/storage.ts | 15 +- src/wallets/index.ts | 6 + .../browser-extension-wallet-setup.hook.ts | 4 - .../embedded/embedded-wallet-setup.hook.ts | 19 +- 18 files changed, 349 insertions(+), 313 deletions(-) create mode 100644 src/iframe/browser/alarms/alarms.mock.ts create mode 100644 src/iframe/browser/i18n/i18n.mock.ts create mode 100644 src/iframe/browser/runtime/runtime.mock.ts create mode 100644 src/iframe/browser/storage/storage.mock.ts create mode 100644 src/iframe/browser/tabs/tabs.mock.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index 3725dedca..54ba576f1 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,4 +2,4 @@ . "$(dirname -- "$0")/_/husky.sh" yarn fmt -git add -A \ No newline at end of file +git add -A diff --git a/src/api/background/background-setup.ts b/src/api/background/background-setup.ts index 69e6808a9..c99cdb17e 100644 --- a/src/api/background/background-setup.ts +++ b/src/api/background/background-setup.ts @@ -29,7 +29,10 @@ import { import { log, LOG_GROUP } from "~utils/log/log.utils"; export function setupBackgroundService() { - log(LOG_GROUP.SETUP, "background-setup.ts > setupBackgroundService()"); + log( + LOG_GROUP.SETUP, + `background-setup.ts > setupBackgroundService(PLASMO_PUBLIC_APP_TYPE = "${process.env.PLASMO_PUBLIC_APP_TYPE}")` + ); // MESSAGES: // Watch for API call and chunk messages: @@ -39,12 +42,9 @@ export function setupBackgroundService() { // LIFECYCLE: // Open welcome page on extension install. - // TODO: This needs to be adapted to work with the embedded wallet (it cannot just be removed / skipped): browser.runtime.onInstalled.addListener(handleInstall); // ALARMS: - // TODO: Mock/polyfill alarms to work with setTimeout/clearTimeout and also a lazy version that just checks the last update time on start. - browser.alarms.onAlarm.addListener(handleNotificationsAlarm); browser.alarms.onAlarm.addListener(handleSubscriptionsAlarm); browser.alarms.onAlarm.addListener(handleTrackBalanceAlarm); @@ -77,19 +77,24 @@ export function setupBackgroundService() { }); // listen for app config updates + // `ExtensionStorage.watch` requires a callbackMap param, so this cannot be done using `ExtensionStorage` directly. browser.storage.onChanged.addListener(handleAppConfigChange); - // ONLY EXTENSION: + if (process.env.PLASMO_PUBLIC_APP_TYPE !== "extension") return; + + // ONLY BROWSER EXTENSION BELOW THIS LINE: // When the last window connected to the extension is closed, the decryption key will be removed from memory. This is no needed in the embedded wallet because // each wallet instance will be removed automatically when its tab/window is closed. browser.windows.onRemoved.addListener(handleWindowClose); // handle tab change (icon, context menus) - browser.tabs.onUpdated.addListener((tabId, changeInfo) => - handleTabUpdate(tabId, changeInfo) - ); - browser.tabs.onActivated.addListener(({ tabId }) => handleTabUpdate(tabId)); + browser.tabs.onUpdated.addListener((tabId, changeInfo) => { + handleTabUpdate(tabId, changeInfo); + }); + browser.tabs.onActivated.addListener(({ tabId }) => { + handleTabUpdate(tabId); + }); browser.tabs.onRemoved.addListener(handleTabClosed); // handle ar:// protocol diff --git a/src/api/background/handlers/browser/install/install.handler.ts b/src/api/background/handlers/browser/install/install.handler.ts index c7a39a879..5982441c7 100644 --- a/src/api/background/handlers/browser/install/install.handler.ts +++ b/src/api/background/handlers/browser/install/install.handler.ts @@ -29,6 +29,8 @@ export async function handleInstall(details: Runtime.OnInstalledDetailsType) { periodInMinutes: 10080 }); + // TODO: Do these 4 call need to be done in series or can they be parallelized? + // initialize tokens in wallet await loadTokens(); diff --git a/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts b/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts index 9bc045520..ca7e04f56 100644 --- a/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts +++ b/src/api/background/handlers/storage/app-config-change/app-config-change.handler.ts @@ -48,7 +48,7 @@ export async function handleAppConfigChange( storedNewValue as unknown as string ) as InitAppParams; - // check if permission event emiting is needed + // check if permission event emitting is needed // get missing permissions const missingPermissions = getMissingPermissions( oldValue?.permissions || [], diff --git a/src/contents/events.ts b/src/contents/events.ts index 42e366e9a..ebfca8a90 100644 --- a/src/contents/events.ts +++ b/src/contents/events.ts @@ -4,8 +4,6 @@ import { log, LOG_GROUP } from "~utils/log/log.utils"; log(LOG_GROUP.SETUP, "events.content-script.ts"); -console.trace("HERE"); - export const config: PlasmoCSConfig = { matches: ["file://*/*", "http://*/*", "https://*/*"], run_at: "document_end", diff --git a/src/iframe/browser/alarms/alarms.mock.ts b/src/iframe/browser/alarms/alarms.mock.ts new file mode 100644 index 000000000..2de950188 --- /dev/null +++ b/src/iframe/browser/alarms/alarms.mock.ts @@ -0,0 +1,109 @@ +import type { Alarms } from "webextension-polyfill"; + +interface AlarmWithTimer extends Alarms.Alarm { + timeoutID?: number; + intervalID?: number; +} + +const alarmsByName: Record = {}; + +type AlarmCallback = (alarm?: Alarms.Alarm) => void; + +const alarmCallbacks: AlarmCallback[] = []; + +function invokeAlarms(name: string) { + const alarmWithTimer = alarmsByName[name]; + + if (!alarmWithTimer) return; + + alarmCallbacks.forEach((alarmCallback) => { + alarmCallback({ + name: alarmWithTimer.name, + scheduledTime: alarmWithTimer.scheduledTime, + periodInMinutes: alarmWithTimer.periodInMinutes + }); + }); +} + +export const alarms = { + create: (name: string, alarmInfo: Alarms.CreateAlarmInfoType) => { + const periodInMs = (alarmInfo.periodInMinutes ?? -1) * 60000; + const delayInMs = alarmInfo.when + ? alarmInfo.when - Date.now() + : (alarmInfo.delayInMinutes ?? -1) * 60000; + + const alarmWithTimer: AlarmWithTimer = { + name, + scheduledTime: 0, + periodInMinutes: alarmInfo.periodInMinutes + }; + + alarmsByName[name] = alarmWithTimer; + + // TODO: Record last alarm run in localStorage to continue the alarm when reloading... + + if (delayInMs === 0) { + alarmWithTimer.scheduledTime = Date.now(); + invokeAlarms(name); + } else if (delayInMs > 0) { + alarmWithTimer.scheduledTime = Date.now() + delayInMs; + + alarmWithTimer.timeoutID = setTimeout(() => { + delete alarmWithTimer.timeoutID; + + invokeAlarms(name); + + alarmWithTimer.scheduledTime = Date.now() + periodInMs; + + alarmWithTimer.intervalID = setInterval(() => { + alarmWithTimer.scheduledTime = Date.now() + periodInMs; + + invokeAlarms(name); + }, periodInMs); + }, delayInMs); + } + + if (delayInMs <= 0 && periodInMs > 0) { + alarmWithTimer.scheduledTime = Date.now() + periodInMs; + + alarmWithTimer.intervalID = setInterval(() => { + alarmWithTimer.scheduledTime = Date.now() + periodInMs; + + invokeAlarms(name); + }, periodInMs); + } + }, + + clear: (name: string) => { + const alarmWithTimer = alarmsByName[name]; + + if (!alarmWithTimer) return; + + if (alarmWithTimer.timeoutID) clearTimeout(alarmWithTimer.timeoutID); + if (alarmWithTimer.intervalID) clearTimeout(alarmWithTimer.intervalID); + }, + + getAll: () => { + return Promise.resolve( + Object.values(alarmsByName) satisfies Alarms.Alarm[] + ); + }, + + get: (name: string) => { + const alarmWithTimer = alarmsByName[name]; + + if (!alarmWithTimer) return; + + return { + name: alarmWithTimer.name, + scheduledTime: alarmWithTimer.scheduledTime, + periodInMinutes: alarmWithTimer.periodInMinutes + }; + }, + + onAlarm: { + addListener: (alarmCallback: AlarmCallback) => { + alarmCallbacks.push(alarmCallback); + } + } +}; diff --git a/src/iframe/browser/i18n/i18n.mock.ts b/src/iframe/browser/i18n/i18n.mock.ts new file mode 100644 index 000000000..aef85e225 --- /dev/null +++ b/src/iframe/browser/i18n/i18n.mock.ts @@ -0,0 +1,32 @@ +import enDic from "url:/assets/_locales/en/messages.json"; +import zhCnDic from "url:/assets/_locales/zh_CN/messages.json"; + +const dictionaries = { + en: enDic as unknown as Record< + string, + { message: string; description: string } + >, + "zh-CN": zhCnDic as unknown as Record< + string, + { message: string; description: string } + > +} as const; + +export const i18n = { + getMessage: (key: string) => { + const dictionaryLanguage = + navigator.languages.find((language) => { + return dictionaries.hasOwnProperty(language); + }) || "en"; + + const dictionary = dictionaries[dictionaryLanguage]; + const value = dictionary[key]?.message; + + if (!value) { + console.warn(`Missing ${dictionaryLanguage} translation for ${key}.`); + } + + // TODO: Default to English instead? + return value || `<${key}>`; + } +}; diff --git a/src/iframe/browser/index.ts b/src/iframe/browser/index.ts index 7f9f39502..3a5d48076 100644 --- a/src/iframe/browser/index.ts +++ b/src/iframe/browser/index.ts @@ -1,276 +1,13 @@ -import enDic from "url:/assets/_locales/en/messages.json"; -import zhCnDic from "url:/assets/_locales/zh_CN/messages.json"; -import type { Alarms, Runtime } from "webextension-polyfill"; -import type { StorageChange } from "~utils/runtime"; - -// TODO: Missing storage.onChanged.addListener - -interface AlarmWithTimer extends Alarms.Alarm { - timeoutID?: number; - intervalID?: number; -} - -const alarmsByName: Record = {}; - -type AlarmCallback = (alarm?: Alarms.Alarm) => void; - -const alarmCallbacks: AlarmCallback[] = []; - -function invokeAlarms(name: string) { - const alarmWithTimer = alarmsByName[name]; - - if (!alarmWithTimer) return; - - alarmCallbacks.forEach((alarmCallback) => { - alarmCallback({ - name: alarmWithTimer.name, - scheduledTime: alarmWithTimer.scheduledTime, - periodInMinutes: alarmWithTimer.periodInMinutes - }); - }); -} - -const alarms = { - create: (name: string, alarmInfo: Alarms.CreateAlarmInfoType) => { - const periodInMs = (alarmInfo.periodInMinutes ?? -1) * 60000; - const delayInMs = alarmInfo.when - ? alarmInfo.when - Date.now() - : (alarmInfo.delayInMinutes ?? -1) * 60000; - - const alarmWithTimer: AlarmWithTimer = { - name, - scheduledTime: 0, - periodInMinutes: alarmInfo.periodInMinutes - }; - - alarmsByName[name] = alarmWithTimer; - - // TODO: Record last alarm run in localStorage to continue the alarm when reloading... - - if (delayInMs === 0) { - alarmWithTimer.scheduledTime = Date.now(); - invokeAlarms(name); - } else if (delayInMs > 0) { - alarmWithTimer.scheduledTime = Date.now() + delayInMs; - - alarmWithTimer.timeoutID = setTimeout(() => { - delete alarmWithTimer.timeoutID; - - invokeAlarms(name); - - alarmWithTimer.scheduledTime = Date.now() + periodInMs; - - alarmWithTimer.intervalID = setInterval(() => { - alarmWithTimer.scheduledTime = Date.now() + periodInMs; - - invokeAlarms(name); - }, periodInMs); - }, delayInMs); - } - - if (delayInMs <= 0 && periodInMs > 0) { - alarmWithTimer.scheduledTime = Date.now() + periodInMs; - - alarmWithTimer.intervalID = setInterval(() => { - alarmWithTimer.scheduledTime = Date.now() + periodInMs; - - invokeAlarms(name); - }, periodInMs); - } - }, - clear: (name: string) => { - const alarmWithTimer = alarmsByName[name]; - - if (!alarmWithTimer) return; - - if (alarmWithTimer.timeoutID) clearTimeout(alarmWithTimer.timeoutID); - if (alarmWithTimer.intervalID) clearTimeout(alarmWithTimer.intervalID); - }, - getAll: () => { - return Promise.resolve( - Object.values(alarmsByName) satisfies Alarms.Alarm[] - ); - }, - get: (name: string) => { - const alarmWithTimer = alarmsByName[name]; - - if (!alarmWithTimer) return; - - return { - name: alarmWithTimer.name, - scheduledTime: alarmWithTimer.scheduledTime, - periodInMinutes: alarmWithTimer.periodInMinutes - }; - }, - onAlarm: { - addListener: (alarmCallback: AlarmCallback) => { - alarmCallbacks.push(alarmCallback); - } - } -}; - -const dictionaries = { - en: enDic as unknown as Record< - string, - { message: string; description: string } - >, - "zh-CN": zhCnDic as unknown as Record< - string, - { message: string; description: string } - > -} as const; - -const i18n = { - getMessage: (key: string) => { - const dictionaryLanguage = - navigator.languages.find((language) => { - return dictionaries.hasOwnProperty(language); - }) || "en"; - - const dictionary = dictionaries[dictionaryLanguage]; - const value = dictionary[key]?.message; - - if (!value) { - console.warn(`Missing ${dictionaryLanguage} translation for ${key}.`); - } - - // TODO: Default to English instead? - return value || `<${key}>`; - } -}; - -// The 2 polyfill below should address lines like this: -// browser.tabs.create({ url: browser.runtime.getURL("tabs/welcome.html") }); - -const runtime = { - getURL: (path: string) => { - console.trace(`getURL(${path})`); - - return new URL(path, document.location.origin).toString(); - }, - getManifest: () => { - return { - browser_action: { - default_popup: "popup.html" - } - }; - }, - onInstalled: { - addListener: (fn) => { - fn({ - reason: "install", - temporary: false - } satisfies Runtime.OnInstalledDetailsType); - } - } - - // TODO: onConnect is probably not needed once I replace onMessage/sendMessage with isomorphicOnMessage and - // isomorphicSendMessage (using the env variable). -}; - -const tabs = { - create: async ({ url }) => { - console.log(`Go to ${url}`); - - // URL = - // browser.runtime.getURL("tabs/welcome.html") - // browser.runtime.getURL("tabs/dashboard.html#/contacts") - // browser.runtime.getURL("assets/animation/arweave.png"); - // browser.runtime.getURL("tabs/auth.html")}?${objectToUrlParams(...)} - // `tabs/dashboard.html#/apps/${activeApp.url}` - - if (url === "tabs/welcome.html") { - location.hash = "/welcome"; - } else if (url.startsWith("tabs/dashboard.html#")) { - const hash = url.split("#").pop(); - - location.hash = `/quick-settings${hash}`; - } else if (url.startsWith("tabs/auth.html")) { - console.warn( - "Auth popup routes not added to the embedded wallet iframe router!" - ); - - const paramsAndHash = url.replace("tabs/auth.html", ""); - - location.hash = `/auth${paramsAndHash}`; - } else if (url.startsWith("assets")) { - throw new Error(`Cannot create tab for URL = ${url}`); - } else { - throw new Error(`Cannot create tab for URL = ${url}`); - } - }, - query: async () => { - const parentURL = - window.location === window.parent.location - ? document.location.href - : document.referrer; - - return { url: parentURL }; // satisfies browser.Tabs.Tab - }, - onConnect: { - addListener: () => {}, - removeListener: () => {} - }, - onUpdated: { - addListener: () => {}, - removeListener: () => {} - } -}; - -const storage = { - local: { - get: (keys?: null | string | string[] | Record) => { - if (keys === undefined || keys === null) { - return localStorage; - } - - if (typeof keys === "string") { - return localStorage.getItem(keys); - } - - if (Array.isArray(keys)) { - return keys.map(key => localStorage.getItem(key)); - } - - if (typeof keys === "object") { - return Object.entries(keys).map(([key, defaultValue]) => localStorage.getItem(key) ?? defaultValue); - } - }, - }, - - onChanged: { - addListener: (callback: ( - changes: Record>, - areaName: string - ) => void) => { - // Note from the docs (meaning, this is probably not working / not needed in ArConnect Embedded): - // - // Note: This won't work on the same browsing context that is making the changes — it is really a way for other - // browsing contexts on the domain using the storage to sync any changes that are made. Browsing contexts on other - // domains can't access the same storage objects. - // - // TODO: Check if this is an issue for the extension. - // - If it is, find a solution. - // - If it is not, maybe the mock is not needed at all and this can be excluded from background-setup.ts. - - window.addEventListener("storage", (event: StorageEvent) => { - const changes: Record> = { - [event.key]: { - newValue: event.newValue, - oldValue: event.oldValue, - }, - }; - - callback(changes, "local"); - }); - }, - }, -} +import { alarms } from "~iframe/browser/alarms/alarms.mock"; +import { i18n } from "~iframe/browser/i18n/i18n.mock"; +import { runtime } from "~iframe/browser/runtime/runtime.mock"; +import { storage } from "~iframe/browser/storage/storage.mock"; +import { tabs } from "~iframe/browser/tabs/tabs.mock"; export default { alarms, i18n, runtime, - tabs, storage, + tabs }; diff --git a/src/iframe/browser/runtime/runtime.mock.ts b/src/iframe/browser/runtime/runtime.mock.ts new file mode 100644 index 000000000..7d5478045 --- /dev/null +++ b/src/iframe/browser/runtime/runtime.mock.ts @@ -0,0 +1,29 @@ +import type { Runtime } from "webextension-polyfill"; + +export const runtime = { + // This should address lines like this: + // browser.tabs.create({ url: browser.runtime.getURL("tabs/welcome.html") }); + getURL: (path: string) => { + if (process.env.NODE_ENV === "development") + console.trace(`getURL(${path})`); + + return new URL(path, document.location.origin).toString(); + }, + + getManifest: () => { + return { + browser_action: { + default_popup: "popup.html" + } + }; + }, + + onInstalled: { + addListener: (fn) => { + fn({ + reason: "install", + temporary: false + } satisfies Runtime.OnInstalledDetailsType); + } + } +}; diff --git a/src/iframe/browser/storage/storage.mock.ts b/src/iframe/browser/storage/storage.mock.ts new file mode 100644 index 000000000..9775744d6 --- /dev/null +++ b/src/iframe/browser/storage/storage.mock.ts @@ -0,0 +1,58 @@ +import type { StorageChange } from "~utils/runtime"; + +export const storage = { + local: { + // TODO: Not needed once `browser.storage.local.get()` is replaced with + // `ExtensionStorage.getAll()` + + get: (keys?: null | string | string[] | Record) => { + if (keys === undefined || keys === null) { + return localStorage; + } + + if (typeof keys === "string") { + return localStorage.getItem(keys); + } + + if (Array.isArray(keys)) { + return keys.map((key) => localStorage.getItem(key)); + } + + if (typeof keys === "object") { + return Object.entries(keys).map( + ([key, defaultValue]) => localStorage.getItem(key) ?? defaultValue + ); + } + } + }, + + onChanged: { + addListener: ( + callback: ( + changes: Record>, + areaName: string + ) => void + ) => { + // Note from the docs (meaning, this is probably not working / not needed in ArConnect Embedded): + // + // Note: This won't work on the same browsing context that is making the changes — it is really a way for other + // browsing contexts on the domain using the storage to sync any changes that are made. Browsing contexts on other + // domains can't access the same storage objects. + // + // TODO: Check if this is an issue for the extension. + // - If it is, find a solution. + // - If it is not, maybe the mock is not needed at all and this can be excluded from background-setup.ts. + + window.addEventListener("storage", (event: StorageEvent) => { + const changes: Record> = { + [event.key]: { + newValue: event.newValue, + oldValue: event.oldValue + } + }; + + callback(changes, "local"); + }); + } + } +}; diff --git a/src/iframe/browser/tabs/tabs.mock.ts b/src/iframe/browser/tabs/tabs.mock.ts new file mode 100644 index 000000000..69538bc44 --- /dev/null +++ b/src/iframe/browser/tabs/tabs.mock.ts @@ -0,0 +1,52 @@ +export const tabs = { + create: async ({ url }) => { + if (process.env.NODE_ENV === "development") + console.log(`tabs.create({ ${url} })`); + + // URL = + // browser.runtime.getURL("tabs/welcome.html") + // browser.runtime.getURL("tabs/dashboard.html#/contacts") + // browser.runtime.getURL("assets/animation/arweave.png"); + // browser.runtime.getURL("tabs/auth.html")}?${objectToUrlParams(...)} + // `tabs/dashboard.html#/apps/${activeApp.url}` + + if (url === "tabs/welcome.html") { + location.hash = "/welcome"; + } else if (url.startsWith("tabs/dashboard.html#")) { + const hash = url.split("#").pop(); + + location.hash = `/quick-settings${hash}`; + } else if (url.startsWith("tabs/auth.html")) { + console.warn( + "Auth popup routes not added to the embedded wallet iframe router!" + ); + + const paramsAndHash = url.replace("tabs/auth.html", ""); + + location.hash = `/auth${paramsAndHash}`; + } else if (url.startsWith("assets")) { + throw new Error(`Cannot create tab for URL = ${url}`); + } else { + throw new Error(`Cannot create tab for URL = ${url}`); + } + }, + + query: async () => { + const parentURL = + window.location === window.parent.location + ? document.location.href + : document.referrer; + + return { url: parentURL }; // satisfies browser.Tabs.Tab + }, + + onConnect: { + addListener: () => {}, + removeListener: () => {} + }, + + onUpdated: { + addListener: () => {}, + removeListener: () => {} + } +}; diff --git a/src/iframe/iframe.tsx b/src/iframe/iframe.tsx index db93df65c..ad75da15f 100644 --- a/src/iframe/iframe.tsx +++ b/src/iframe/iframe.tsx @@ -1,5 +1,5 @@ import { AnimatePresence } from "framer-motion"; -import type { PropsWithChildren } from "react"; +import { type PropsWithChildren } from "react"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import HistoryProvider from "~components/popup/HistoryProvider"; import { NavigationBar } from "~components/popup/Navigation"; @@ -22,6 +22,8 @@ export function ArConnectEmbeddedApp({ }: ArConnectEmbeddedAppProps) { let content: React.ReactElement = null; + console.log("initialScreenType =", initialScreenType); + if (initialScreenType === "locked") { content = ; } else if (initialScreenType === "default") { @@ -36,7 +38,7 @@ export function ArConnectEmbeddedApp({ return <>{content}; } -export default function ArConnectEmbeddedAppRoot() { +export function ArConnectEmbeddedAppRoot() { const initialScreenType = useEmbeddedWalletSetUp(); return ( diff --git a/src/iframe/main.tsx b/src/iframe/main.tsx index f8cc6aaa7..594303fc3 100644 --- a/src/iframe/main.tsx +++ b/src/iframe/main.tsx @@ -1,21 +1,18 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { setupBackgroundService } from "~api/background/background-setup"; -import ArConnectEmbeddedAppRoot from "~iframe/iframe"; +import { ArConnectEmbeddedAppRoot } from "./iframe"; import "../../assets/popup.css"; +// createRoot(document.getElementById("root")).render( +// +// +// +// ); + createRoot(document.getElementById("root")).render( - - - + ); setupBackgroundService(); - -// TODO: Backend should expect a hash of any of the other key shards. Otherwise, won't send back its own shard. - -// TODO: Add artificial wait time for auth request (by ip, not ip-account) to the backend if auth fails. - -// TODO: Use the infra needed to implement the MPC wallets to also send notifications to wallets when someone -// is trying to access their account. diff --git a/src/utils/auth/auth.provider.tsx b/src/utils/auth/auth.provider.tsx index 44b0c0909..cc3fa12c5 100644 --- a/src/utils/auth/auth.provider.tsx +++ b/src/utils/auth/auth.provider.tsx @@ -420,12 +420,16 @@ export function AuthRequestsProvider({ // Close the popup if an AuthRequest doesn't arrive in less than `AUTH_POPUP_REQUEST_WAIT_MS` (1s), unless the // wallet is locked (no timeout in that case): timeoutID = setTimeout(() => { + // TODO: This also needs to be changed in the embedded wallet. Maybe we need to store (but not show) unlock + // requests? window.top.close(); }, AUTH_POPUP_REQUEST_WAIT_MS); } else if (initialScreenType !== "default") { // If the user doesn't unlock the wallet in 15 minutes, or somehow the popup gets stuck into any other state for // more than that, we close it: timeoutID = setTimeout(() => { + // TODO: This also needs to be changed in the embedded wallet. Maybe we need to store (but not show) unlock + // requests? window.top.close(); }, AUTH_POPUP_UNLOCK_REQUEST_TTL_MS); } else if (isDone) { @@ -435,9 +439,11 @@ export function AuthRequestsProvider({ if (AUTH_POPUP_CLOSING_DELAY_MS > 0) { timeoutID = setTimeout(() => { + // TODO: In the embedded wallet, this should clear the AuthRequests instead: window.top.close(); }, AUTH_POPUP_CLOSING_DELAY_MS); } else { + // TODO: In the embedded wallet, this should clear the AuthRequests instead: window.top.close(); } } @@ -456,6 +462,7 @@ export function AuthRequestsProvider({ }); } + // TODO: Not in the embedded wallet: window.addEventListener("beforeunload", handleBeforeUnload); return () => { diff --git a/src/utils/storage.ts b/src/utils/storage.ts index d70769002..3652f3086 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -6,12 +6,21 @@ import { Storage } from "@plasmohq/storage"; * Default local extension storage, with values * that are NOT copied to window.localStorage */ -export const ExtensionStorage = new Storage({ area: "local" }); +export const ExtensionStorage = new Storage({ + area: "local" + // This copies the data to localStorage: + // allCopied: true, +}); /** - * Temporary storage for submitted transfers + * Temporary storage for submitted transfers, with values + * that are NOT copied to window.sessionStorage */ -export const TempTransactionStorage = new Storage({ area: "session" }); +export const TempTransactionStorage = new Storage({ + area: "session" + // This copies the data to localStorage, NOT to sessionStorage: + // allCopied: true, +}); /** * Session storage raw transfer tx. This will diff --git a/src/wallets/index.ts b/src/wallets/index.ts index c2ede7e7c..1d08e6dfd 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -134,6 +134,12 @@ export async function setActiveWallet(address?: string) { export type DecryptedWallet = StoredWallet; export async function openOrSelectWelcomePage(force = false) { + if (process.env.PLASMO_PUBLIC_APP_TYPE !== "extension") { + log(LOG_GROUP.AUTH, `PREVENTED openOrSelectWelcomePage(${force})`); + + return; + } + log(LOG_GROUP.AUTH, `openOrSelectWelcomePage(${force})`); const url = browser.runtime.getURL("tabs/welcome.html"); diff --git a/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts b/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts index df6f64f38..21644993c 100644 --- a/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts +++ b/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts @@ -16,10 +16,6 @@ export function useBrowserExtensionWalletSetUp() { const [initialScreenType, setInitialScreenType] = useState("cover"); - // TODO: Get all usages of `getDecryptionKey` as we won't be using this in the embedded wallet... - - // TODO: There's no "disconnect" in the embedded wallet. - useEffect(() => { async function checkWalletState() { const [activeAddress, wallets, decryptionKey] = await Promise.all([ diff --git a/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts b/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts index 29d237ee2..d51697be6 100644 --- a/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts +++ b/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts @@ -1,11 +1,7 @@ import { useState, useEffect } from "react"; import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; import { ExtensionStorage } from "~utils/storage"; -import { - getActiveAddress, - getWallets, - openOrSelectWelcomePage -} from "~wallets"; +import { getActiveAddress, getWallets } from "~wallets"; import { getDecryptionKey } from "~wallets/auth"; import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; @@ -16,10 +12,6 @@ export function useEmbeddedWalletSetUp() { const [initialScreenType, setInitialScreenType] = useState("cover"); - // TODO: Get all usages of `getDecryptionKey` as we won't be using this in the embedded wallet... - - // TODO: There's no "disconnect" in the embedded wallet. - useEffect(() => { async function checkWalletState() { const [activeAddress, wallets, decryptionKey] = await Promise.all([ @@ -32,8 +24,13 @@ export function useEmbeddedWalletSetUp() { let nextInitialScreenType: InitialScreenType = "cover"; - if (!hasWallets) { - // TODO: This should redirect/force the auth flow... + if (!isAuthenticated) { + nextInitialScreenType = "/authenticate"; + } else if (!hasWallets) { + nextInitialScreenType = "/generate"; + } else if (!hasShards) { + // TODO: Add a passive warning about this and allow people to use the wallet in watch-only mode: + nextInitialScreenType = "/add-device"; } else { nextInitialScreenType = "default"; } From 8ddbc94a4496c3aa3f00adbd210c355c96b1c2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Thu, 28 Nov 2024 12:14:23 +0100 Subject: [PATCH 077/142] Remove tabs.create() mocks to open Welcome or Dashboard pages. --- src/iframe/browser/tabs/tabs.mock.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/iframe/browser/tabs/tabs.mock.ts b/src/iframe/browser/tabs/tabs.mock.ts index 69538bc44..8147afc8a 100644 --- a/src/iframe/browser/tabs/tabs.mock.ts +++ b/src/iframe/browser/tabs/tabs.mock.ts @@ -11,18 +11,18 @@ export const tabs = { // `tabs/dashboard.html#/apps/${activeApp.url}` if (url === "tabs/welcome.html") { - location.hash = "/welcome"; + throw new Error("Welcome routes not added to ArConnect Embedded"); + + // location.hash = "/welcome"; } else if (url.startsWith("tabs/dashboard.html#")) { - const hash = url.split("#").pop(); + throw new Error("Dashboard not added to ArConnect Embedded"); - location.hash = `/quick-settings${hash}`; + // const hash = url.split("#").pop(); + // location.hash = `/quick-settings${hash}`; } else if (url.startsWith("tabs/auth.html")) { - console.warn( - "Auth popup routes not added to the embedded wallet iframe router!" - ); + console.warn("Trying to open `tabs/auth.html`"); const paramsAndHash = url.replace("tabs/auth.html", ""); - location.hash = `/auth${paramsAndHash}`; } else if (url.startsWith("assets")) { throw new Error(`Cannot create tab for URL = ${url}`); From 3b9a7dcf1d9ed315ae1a2508a55a8c90a8080caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Thu, 28 Nov 2024 12:32:50 +0100 Subject: [PATCH 078/142] Replace window.top.close() in AuthRequestsProvider. --- src/utils/auth/auth.provider.tsx | 79 +++++++++++++++++++------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/src/utils/auth/auth.provider.tsx b/src/utils/auth/auth.provider.tsx index cc3fa12c5..9ac8cd08f 100644 --- a/src/utils/auth/auth.provider.tsx +++ b/src/utils/auth/auth.provider.tsx @@ -36,13 +36,13 @@ import { log, LOG_GROUP } from "~utils/log/log.utils"; import { isError } from "~utils/error/error.utils"; import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; -interface AuthRequestContextState { +interface AuthRequestsContextState { authRequests: AuthRequest[]; currentAuthRequestIndex: number; lastCompletedAuthRequest: null | AuthRequest; } -interface AuthRequestContextData extends AuthRequestContextState { +interface AuthRequestContextData extends AuthRequestsContextState { setCurrentAuthRequestIndex: (currentAuthRequestIndex: number) => void; completeAuthRequest: (authID: string, data: any) => Promise; } @@ -59,6 +59,12 @@ interface AuthRequestProviderPRops extends PropsWithChildren { initialScreenType: InitialScreenType; } +const AUTH_REQUESTS_CONTEXT_INITIAL_STATE: AuthRequestsContextState = { + authRequests: [], + currentAuthRequestIndex: 0, + lastCompletedAuthRequest: null +}; + export function AuthRequestsProvider({ children, initialScreenType @@ -66,11 +72,7 @@ export function AuthRequestsProvider({ const [ { authRequests, currentAuthRequestIndex, lastCompletedAuthRequest }, setAuthRequestContextState - ] = useState({ - authRequests: [], - currentAuthRequestIndex: 0, - lastCompletedAuthRequest: null - }); + ] = useState(AUTH_REQUESTS_CONTEXT_INITIAL_STATE); const setCurrentAuthRequestIndex = useCallback( (currentAuthRequestIndex: number) => { @@ -84,6 +86,30 @@ export function AuthRequestsProvider({ [] ); + const closeAuthPopup = useCallback((delay: number = 0) => { + // TODO: In the embedded wallet, maybe we need to store (but not show) unlock requests so that this doesn't + // clear automatically. + + function closeOrClear() { + if (process.env.PLASMO_PUBLIC_APP_TYPE !== "extension") { + // TODO: This might cause an infinite loop in the embedded wallet: + setAuthRequestContextState(AUTH_REQUESTS_CONTEXT_INITIAL_STATE); + } else { + window.top.close(); + } + } + + if (delay > 0) { + const timeoutID = setTimeout(closeOrClear, delay); + + return () => clearTimeout(timeoutID); + } + + closeOrClear(); + + return () => {}; + }, []); + const completeAuthRequest = useCallback( async (authID: string, data: any) => { const completedAuthRequest = authRequests.find( @@ -295,7 +321,7 @@ export function AuthRequestsProvider({ if (pendingRequestsCount === 0 && authRequests.length > 0) { // All tabs that sent AuthRequest also got closed/reloaded/disconnected, so close the popup immediately: - window.top.close(); + closeAuthPopup(); } // TODO: Consider automatically selecting the next pending AuthRequest. @@ -312,7 +338,7 @@ export function AuthRequestsProvider({ isomorphicOnMessage("auth_tab_closed", handleTabReloadedOrClosed); isomorphicOnMessage("auth_active_wallet_change", handleTabReloadedOrClosed); isomorphicOnMessage("auth_app_disconnected", handleTabReloadedOrClosed); - }, []); + }, [closeAuthPopup]); useEffect(() => { const chunksByCollectionID: Record = {}; @@ -410,7 +436,7 @@ export function AuthRequestsProvider({ }, []); useEffect(() => { - let timeoutID = 0; + let clearCloseAuthPopupTimeout = () => {}; const isDone = authRequests.length > 0 && @@ -419,33 +445,19 @@ export function AuthRequestsProvider({ if (initialScreenType === "default" && authRequests.length === 0) { // Close the popup if an AuthRequest doesn't arrive in less than `AUTH_POPUP_REQUEST_WAIT_MS` (1s), unless the // wallet is locked (no timeout in that case): - timeoutID = setTimeout(() => { - // TODO: This also needs to be changed in the embedded wallet. Maybe we need to store (but not show) unlock - // requests? - window.top.close(); - }, AUTH_POPUP_REQUEST_WAIT_MS); + clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_REQUEST_WAIT_MS); } else if (initialScreenType !== "default") { // If the user doesn't unlock the wallet in 15 minutes, or somehow the popup gets stuck into any other state for // more than that, we close it: - timeoutID = setTimeout(() => { - // TODO: This also needs to be changed in the embedded wallet. Maybe we need to store (but not show) unlock - // requests? - window.top.close(); - }, AUTH_POPUP_UNLOCK_REQUEST_TTL_MS); + clearCloseAuthPopupTimeout = closeAuthPopup( + AUTH_POPUP_UNLOCK_REQUEST_TTL_MS + ); } else if (isDone) { // Close the window if the last request has been handled: // TODO: Add setting to decide whether this closes automatically or stays open in a "done" state. - if (AUTH_POPUP_CLOSING_DELAY_MS > 0) { - timeoutID = setTimeout(() => { - // TODO: In the embedded wallet, this should clear the AuthRequests instead: - window.top.close(); - }, AUTH_POPUP_CLOSING_DELAY_MS); - } else { - // TODO: In the embedded wallet, this should clear the AuthRequests instead: - window.top.close(); - } + clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_CLOSING_DELAY_MS); } function handleBeforeUnload() { @@ -466,11 +478,16 @@ export function AuthRequestsProvider({ window.addEventListener("beforeunload", handleBeforeUnload); return () => { - clearTimeout(timeoutID); + clearCloseAuthPopupTimeout(); window.removeEventListener("beforeunload", handleBeforeUnload); }; - }, [initialScreenType, authRequests, currentAuthRequestIndex]); + }, [ + initialScreenType, + authRequests, + currentAuthRequestIndex, + closeAuthPopup + ]); return ( Date: Fri, 29 Nov 2024 16:01:29 +0100 Subject: [PATCH 079/142] Remove embedded wallet / iframe files from this branch. --- package.json | 4 - .../browser/install/install.handler.ts | 2 - src/components/popup/WalletHeader.tsx | 2 - src/iframe/browser/alarms/alarms.mock.ts | 109 ---------- src/iframe/browser/i18n/i18n.mock.ts | 32 --- src/iframe/browser/index.ts | 13 -- src/iframe/browser/runtime/runtime.mock.ts | 29 --- src/iframe/browser/storage/storage.mock.ts | 58 ----- src/iframe/browser/tabs/tabs.mock.ts | 52 ----- src/iframe/iframe-sdk-placeholder.ts | 204 ------------------ src/iframe/iframe.html | 12 -- src/iframe/iframe.tsx | 68 ------ src/iframe/main.tsx | 18 -- src/iframe/plasmohq/storage/hook/index.ts | 88 -------- .../storage/implementations/local-storage.ts | 0 src/iframe/plasmohq/storage/index.ts | 98 --------- 16 files changed, 789 deletions(-) delete mode 100644 src/iframe/browser/alarms/alarms.mock.ts delete mode 100644 src/iframe/browser/i18n/i18n.mock.ts delete mode 100644 src/iframe/browser/index.ts delete mode 100644 src/iframe/browser/runtime/runtime.mock.ts delete mode 100644 src/iframe/browser/storage/storage.mock.ts delete mode 100644 src/iframe/browser/tabs/tabs.mock.ts delete mode 100644 src/iframe/iframe-sdk-placeholder.ts delete mode 100644 src/iframe/iframe.html delete mode 100644 src/iframe/iframe.tsx delete mode 100644 src/iframe/main.tsx delete mode 100644 src/iframe/plasmohq/storage/hook/index.ts delete mode 100644 src/iframe/plasmohq/storage/implementations/local-storage.ts delete mode 100644 src/iframe/plasmohq/storage/index.ts diff --git a/package.json b/package.json index edce43754..67768a11a 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,6 @@ "build:chrome": "plasmo build --no-hoist", "dev:firefox": "plasmo dev --target=firefox-mv2", "build:firefox": "plasmo build --target=firefox-mv2 --no-hoist", - "dev:iframe": "vite", - "build:iframe": "vite build", - "preview:iframe": "vite preview", "nuke": "rm -rf node_modules build .plasmo", "fmt": "prettier --write .", "fmt:check": "prettier --check .", @@ -65,7 +62,6 @@ "@segment/analytics-next": "^1.53.2", "@swyg/corre": "^1.0.4", "@untitled-ui/icons-react": "^0.1.1", - "@vitejs/plugin-react": "^4.3.2", "ao-tokens": "^0.0.4", "ar-gql": "1.2.9", "arbundles": "^0.9.5", diff --git a/src/api/background/handlers/browser/install/install.handler.ts b/src/api/background/handlers/browser/install/install.handler.ts index 5982441c7..c7a39a879 100644 --- a/src/api/background/handlers/browser/install/install.handler.ts +++ b/src/api/background/handlers/browser/install/install.handler.ts @@ -29,8 +29,6 @@ export async function handleInstall(details: Runtime.OnInstalledDetailsType) { periodInMinutes: 10080 }); - // TODO: Do these 4 call need to be done in series or can they be parallelized? - // initialize tokens in wallet await loadTokens(); diff --git a/src/components/popup/WalletHeader.tsx b/src/components/popup/WalletHeader.tsx index 7007f2e09..ceb5b5dbd 100644 --- a/src/components/popup/WalletHeader.tsx +++ b/src/components/popup/WalletHeader.tsx @@ -69,8 +69,6 @@ export default function WalletHeader() { [] ); - console.log("wallets =", wallets); - // is the wallet selector open const [isOpen, setOpen] = useState(false); diff --git a/src/iframe/browser/alarms/alarms.mock.ts b/src/iframe/browser/alarms/alarms.mock.ts deleted file mode 100644 index 2de950188..000000000 --- a/src/iframe/browser/alarms/alarms.mock.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { Alarms } from "webextension-polyfill"; - -interface AlarmWithTimer extends Alarms.Alarm { - timeoutID?: number; - intervalID?: number; -} - -const alarmsByName: Record = {}; - -type AlarmCallback = (alarm?: Alarms.Alarm) => void; - -const alarmCallbacks: AlarmCallback[] = []; - -function invokeAlarms(name: string) { - const alarmWithTimer = alarmsByName[name]; - - if (!alarmWithTimer) return; - - alarmCallbacks.forEach((alarmCallback) => { - alarmCallback({ - name: alarmWithTimer.name, - scheduledTime: alarmWithTimer.scheduledTime, - periodInMinutes: alarmWithTimer.periodInMinutes - }); - }); -} - -export const alarms = { - create: (name: string, alarmInfo: Alarms.CreateAlarmInfoType) => { - const periodInMs = (alarmInfo.periodInMinutes ?? -1) * 60000; - const delayInMs = alarmInfo.when - ? alarmInfo.when - Date.now() - : (alarmInfo.delayInMinutes ?? -1) * 60000; - - const alarmWithTimer: AlarmWithTimer = { - name, - scheduledTime: 0, - periodInMinutes: alarmInfo.periodInMinutes - }; - - alarmsByName[name] = alarmWithTimer; - - // TODO: Record last alarm run in localStorage to continue the alarm when reloading... - - if (delayInMs === 0) { - alarmWithTimer.scheduledTime = Date.now(); - invokeAlarms(name); - } else if (delayInMs > 0) { - alarmWithTimer.scheduledTime = Date.now() + delayInMs; - - alarmWithTimer.timeoutID = setTimeout(() => { - delete alarmWithTimer.timeoutID; - - invokeAlarms(name); - - alarmWithTimer.scheduledTime = Date.now() + periodInMs; - - alarmWithTimer.intervalID = setInterval(() => { - alarmWithTimer.scheduledTime = Date.now() + periodInMs; - - invokeAlarms(name); - }, periodInMs); - }, delayInMs); - } - - if (delayInMs <= 0 && periodInMs > 0) { - alarmWithTimer.scheduledTime = Date.now() + periodInMs; - - alarmWithTimer.intervalID = setInterval(() => { - alarmWithTimer.scheduledTime = Date.now() + periodInMs; - - invokeAlarms(name); - }, periodInMs); - } - }, - - clear: (name: string) => { - const alarmWithTimer = alarmsByName[name]; - - if (!alarmWithTimer) return; - - if (alarmWithTimer.timeoutID) clearTimeout(alarmWithTimer.timeoutID); - if (alarmWithTimer.intervalID) clearTimeout(alarmWithTimer.intervalID); - }, - - getAll: () => { - return Promise.resolve( - Object.values(alarmsByName) satisfies Alarms.Alarm[] - ); - }, - - get: (name: string) => { - const alarmWithTimer = alarmsByName[name]; - - if (!alarmWithTimer) return; - - return { - name: alarmWithTimer.name, - scheduledTime: alarmWithTimer.scheduledTime, - periodInMinutes: alarmWithTimer.periodInMinutes - }; - }, - - onAlarm: { - addListener: (alarmCallback: AlarmCallback) => { - alarmCallbacks.push(alarmCallback); - } - } -}; diff --git a/src/iframe/browser/i18n/i18n.mock.ts b/src/iframe/browser/i18n/i18n.mock.ts deleted file mode 100644 index aef85e225..000000000 --- a/src/iframe/browser/i18n/i18n.mock.ts +++ /dev/null @@ -1,32 +0,0 @@ -import enDic from "url:/assets/_locales/en/messages.json"; -import zhCnDic from "url:/assets/_locales/zh_CN/messages.json"; - -const dictionaries = { - en: enDic as unknown as Record< - string, - { message: string; description: string } - >, - "zh-CN": zhCnDic as unknown as Record< - string, - { message: string; description: string } - > -} as const; - -export const i18n = { - getMessage: (key: string) => { - const dictionaryLanguage = - navigator.languages.find((language) => { - return dictionaries.hasOwnProperty(language); - }) || "en"; - - const dictionary = dictionaries[dictionaryLanguage]; - const value = dictionary[key]?.message; - - if (!value) { - console.warn(`Missing ${dictionaryLanguage} translation for ${key}.`); - } - - // TODO: Default to English instead? - return value || `<${key}>`; - } -}; diff --git a/src/iframe/browser/index.ts b/src/iframe/browser/index.ts deleted file mode 100644 index 3a5d48076..000000000 --- a/src/iframe/browser/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { alarms } from "~iframe/browser/alarms/alarms.mock"; -import { i18n } from "~iframe/browser/i18n/i18n.mock"; -import { runtime } from "~iframe/browser/runtime/runtime.mock"; -import { storage } from "~iframe/browser/storage/storage.mock"; -import { tabs } from "~iframe/browser/tabs/tabs.mock"; - -export default { - alarms, - i18n, - runtime, - storage, - tabs -}; diff --git a/src/iframe/browser/runtime/runtime.mock.ts b/src/iframe/browser/runtime/runtime.mock.ts deleted file mode 100644 index 7d5478045..000000000 --- a/src/iframe/browser/runtime/runtime.mock.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Runtime } from "webextension-polyfill"; - -export const runtime = { - // This should address lines like this: - // browser.tabs.create({ url: browser.runtime.getURL("tabs/welcome.html") }); - getURL: (path: string) => { - if (process.env.NODE_ENV === "development") - console.trace(`getURL(${path})`); - - return new URL(path, document.location.origin).toString(); - }, - - getManifest: () => { - return { - browser_action: { - default_popup: "popup.html" - } - }; - }, - - onInstalled: { - addListener: (fn) => { - fn({ - reason: "install", - temporary: false - } satisfies Runtime.OnInstalledDetailsType); - } - } -}; diff --git a/src/iframe/browser/storage/storage.mock.ts b/src/iframe/browser/storage/storage.mock.ts deleted file mode 100644 index 9775744d6..000000000 --- a/src/iframe/browser/storage/storage.mock.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { StorageChange } from "~utils/runtime"; - -export const storage = { - local: { - // TODO: Not needed once `browser.storage.local.get()` is replaced with - // `ExtensionStorage.getAll()` - - get: (keys?: null | string | string[] | Record) => { - if (keys === undefined || keys === null) { - return localStorage; - } - - if (typeof keys === "string") { - return localStorage.getItem(keys); - } - - if (Array.isArray(keys)) { - return keys.map((key) => localStorage.getItem(key)); - } - - if (typeof keys === "object") { - return Object.entries(keys).map( - ([key, defaultValue]) => localStorage.getItem(key) ?? defaultValue - ); - } - } - }, - - onChanged: { - addListener: ( - callback: ( - changes: Record>, - areaName: string - ) => void - ) => { - // Note from the docs (meaning, this is probably not working / not needed in ArConnect Embedded): - // - // Note: This won't work on the same browsing context that is making the changes — it is really a way for other - // browsing contexts on the domain using the storage to sync any changes that are made. Browsing contexts on other - // domains can't access the same storage objects. - // - // TODO: Check if this is an issue for the extension. - // - If it is, find a solution. - // - If it is not, maybe the mock is not needed at all and this can be excluded from background-setup.ts. - - window.addEventListener("storage", (event: StorageEvent) => { - const changes: Record> = { - [event.key]: { - newValue: event.newValue, - oldValue: event.oldValue - } - }; - - callback(changes, "local"); - }); - } - } -}; diff --git a/src/iframe/browser/tabs/tabs.mock.ts b/src/iframe/browser/tabs/tabs.mock.ts deleted file mode 100644 index 8147afc8a..000000000 --- a/src/iframe/browser/tabs/tabs.mock.ts +++ /dev/null @@ -1,52 +0,0 @@ -export const tabs = { - create: async ({ url }) => { - if (process.env.NODE_ENV === "development") - console.log(`tabs.create({ ${url} })`); - - // URL = - // browser.runtime.getURL("tabs/welcome.html") - // browser.runtime.getURL("tabs/dashboard.html#/contacts") - // browser.runtime.getURL("assets/animation/arweave.png"); - // browser.runtime.getURL("tabs/auth.html")}?${objectToUrlParams(...)} - // `tabs/dashboard.html#/apps/${activeApp.url}` - - if (url === "tabs/welcome.html") { - throw new Error("Welcome routes not added to ArConnect Embedded"); - - // location.hash = "/welcome"; - } else if (url.startsWith("tabs/dashboard.html#")) { - throw new Error("Dashboard not added to ArConnect Embedded"); - - // const hash = url.split("#").pop(); - // location.hash = `/quick-settings${hash}`; - } else if (url.startsWith("tabs/auth.html")) { - console.warn("Trying to open `tabs/auth.html`"); - - const paramsAndHash = url.replace("tabs/auth.html", ""); - location.hash = `/auth${paramsAndHash}`; - } else if (url.startsWith("assets")) { - throw new Error(`Cannot create tab for URL = ${url}`); - } else { - throw new Error(`Cannot create tab for URL = ${url}`); - } - }, - - query: async () => { - const parentURL = - window.location === window.parent.location - ? document.location.href - : document.referrer; - - return { url: parentURL }; // satisfies browser.Tabs.Tab - }, - - onConnect: { - addListener: () => {}, - removeListener: () => {} - }, - - onUpdated: { - addListener: () => {}, - removeListener: () => {} - } -}; diff --git a/src/iframe/iframe-sdk-placeholder.ts b/src/iframe/iframe-sdk-placeholder.ts deleted file mode 100644 index cea552ba0..000000000 --- a/src/iframe/iframe-sdk-placeholder.ts +++ /dev/null @@ -1,204 +0,0 @@ -// This file is just a placeholder with some pseudo-code for the injected code on ArConnect Embedded. That is, the code -// that's loaded in the consumer site's context. - -import type { IframeHTMLAttributes } from "react"; -import { replaceArProtocolLinks } from "~api/foreground/foreground-setup-ar-protocol-links"; -import { setupEventListeners } from "~api/foreground/foreground-setup-events"; -import { setupWalletSDK } from "~api/foreground/foreground-setup-wallet-sdk"; -import { isomorphicOnMessage } from "~utils/messaging/messaging.utils"; - -let isWalletInitialized = false; - -export type ArConnectEmbeddedVariant = "hidden" | "logo" | "summary"; - -export type ArConnectEmbeddedVariantPosition = "" | ""; - -export interface ArConnectEmbeddedHiddenVariantOptions { - type: "hidden"; -} - -export interface ArConnectEmbeddedLogoVariantOptions { - type: "logo"; - position: ArConnectEmbeddedVariantPosition; - margin: string; - // TODO: Missing theming options (colors, font...) -} - -export interface ArConnectEmbeddedSummaryVariantOptions { - type: "summary"; - position: ArConnectEmbeddedVariantPosition; - margin: string; - // TODO: Missing theming options (colors, font...) -} - -export type ArConnectEmbeddedVariantOptions = - | ArConnectEmbeddedHiddenVariantOptions - | ArConnectEmbeddedLogoVariantOptions - | ArConnectEmbeddedSummaryVariantOptions; - -export interface CommonArConnectEmbeddedOptions { - // UI: - variant: ArConnectEmbeddedVariant | ArConnectEmbeddedVariantOptions; - iframe?: HTMLIFrameElement | IframeHTMLAttributes; - - // Events: - onOpen?: () => boolean; - onClose?: () => boolean; - onResize?: () => boolean; - onAuth?: (userDetails: UserDetails) => boolean; - onBalance?: (balances: Record) => boolean; - onInfo?: (data: AuthRequest | Notification) => boolean; - - // Other: - replaceArProtocolLinks?: boolean; -} - -export interface ArConnectEmbeddedOptionsWithURL - extends CommonArConnectEmbeddedOptions { - url?: string; -} - -export interface ArConnectEmbeddedOptionsWithClientID - extends CommonArConnectEmbeddedOptions { - clientID?: string; -} - -export type ArConnectEmbeddedOptions = - | ArConnectEmbeddedOptionsWithURL - | ArConnectEmbeddedOptionsWithClientID; - -export function isArConnectEmbeddedOptionsWithURL( - options: ArConnectEmbeddedOptions -): options is ArConnectEmbeddedOptionsWithURL { - return options.hasOwnProperty("url"); -} - -export function isIFrameElement( - iframe: HTMLIFrameElement | IframeHTMLAttributes -): iframe is HTMLIFrameElement { - return iframe instanceof HTMLIFrameElement; -} - -export function initArConnectEmbedded(options: ArConnectEmbeddedOptions) { - if (isWalletInitialized) { - throw new Error("ArConnect Embedded has already been initialized"); - } - - const src = isArConnectEmbeddedOptionsWithURL(options) - ? options.url - : `https://${options.clientID}.embedded.arconnect.io/`; - - if (!/https:\/\/(\w{12,16})\.embedded\.arconnect\.io\/?/.test(src)) { - throw new Error("Invalid URL or clientID"); - } - - // Create the iframe: - - let iframeElement: HTMLIFrameElement; - - if (isIFrameElement(options.iframe)) { - iframeElement = options.iframe; - - if (iframeElement.src) { - throw new Error( - "The provided `options.iframe` already has a `src` value" - ); - } - } else { - iframeElement = document.createElement("iframe"); - - Object.entries(options.iframe).forEach(([key, value]) => { - iframeElement.setAttribute(key, value); - }); - } - - iframeElement.id ||= "arConnectEmbeddedIframe"; - iframeElement.src = src; - - // TODO: Create the rest of the HTML elements for the "floating button" (depending on options) - - // TODO: How to inject theme and fonts? - // TODO: Add a shadow DOM to add the styles. - - document.body.appendChild(iframeElement); - - const iframeWindow = iframeElement.contentWindow; - - // api.ts: - - // Because in ArConnect Embedded the injected code is not sandboxed, we can simply call `injectWalletSDK()` instead of - // having to inject `injected.ts` with a ` - - -
- - diff --git a/src/iframe/iframe.tsx b/src/iframe/iframe.tsx deleted file mode 100644 index ad75da15f..000000000 --- a/src/iframe/iframe.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { AnimatePresence } from "framer-motion"; -import { type PropsWithChildren } from "react"; -import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; -import HistoryProvider from "~components/popup/HistoryProvider"; -import { NavigationBar } from "~components/popup/Navigation"; -import { UnlockPage } from "~routes/popup/unlock"; -import { AuthRequestsProvider } from "~utils/auth/auth.provider"; -import { useIFrameLocation } from "~wallets/router/iframe/iframe-router.hook"; -import { IFRAME_ROUTES } from "~wallets/router/iframe/iframe.routes"; -import { Routes } from "~wallets/router/routes.component"; -import { BodyScroller, HistoryObserver } from "~wallets/router/router.utils"; -import { useEmbeddedWalletSetUp } from "~wallets/setup/embedded/embedded-wallet-setup.hook"; -import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; -import { Router as Wouter } from "wouter"; - -interface ArConnectEmbeddedAppProps { - initialScreenType: InitialScreenType; -} - -export function ArConnectEmbeddedApp({ - initialScreenType -}: ArConnectEmbeddedAppProps) { - let content: React.ReactElement = null; - - console.log("initialScreenType =", initialScreenType); - - if (initialScreenType === "locked") { - content = ; - } else if (initialScreenType === "default") { - content = ( - <> - - - - ); - } - - return <>{content}; -} - -export function ArConnectEmbeddedAppRoot() { - const initialScreenType = useEmbeddedWalletSetUp(); - - return ( - - - - - - - - - - - - - - - - - ); -} - -const AuthProvider = ({ children }: PropsWithChildren) => { - // TODO: To be implemented... - - return <>{children}; -}; diff --git a/src/iframe/main.tsx b/src/iframe/main.tsx deleted file mode 100644 index 594303fc3..000000000 --- a/src/iframe/main.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import { setupBackgroundService } from "~api/background/background-setup"; -import { ArConnectEmbeddedAppRoot } from "./iframe"; - -import "../../assets/popup.css"; - -// createRoot(document.getElementById("root")).render( -// -// -// -// ); - -createRoot(document.getElementById("root")).render( - -); - -setupBackgroundService(); diff --git a/src/iframe/plasmohq/storage/hook/index.ts b/src/iframe/plasmohq/storage/hook/index.ts deleted file mode 100644 index 9aae51b4f..000000000 --- a/src/iframe/plasmohq/storage/hook/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useStorage as plasmoUseStorage } from "@plasmohq/storage/hook"; -import { useCallback, useState } from "react"; - -type UseStorageType = typeof plasmoUseStorage; - -// TODO: For the initial version, use localStorage or sessionStorage (that's for the "@plasmohq/storage" (Storage) polyfill) -// TODO: After that, consider whether some values must be persisted on the backend instead. - -type Setter = ((v?: T, isHydrated?: boolean) => T) | T; -/** - * isPublic: If true, the value will be synced with web API Storage - */ -type RawKey = - | string - | { - key: string; - // instance: BaseStorage; - instance: any; - }; - -export function useStorage( - rawKey: RawKey, - onInit: Setter -): [ - T, - (setter: Setter) => Promise, - { - readonly setRenderValue: React.Dispatch>; - readonly setStoreValue: (v: T) => Promise; - readonly remove: () => void; - } -]; -export function useStorage( - rawKey: RawKey -): [ - T | undefined, - (setter: Setter) => Promise, - { - readonly setRenderValue: React.Dispatch< - React.SetStateAction - >; - readonly setStoreValue: (v?: T) => Promise; - readonly remove: () => void; - } -]; -export function useStorage( - rawKey: RawKey, - onInit?: Setter -): ReturnType { - console.log("My useStorage()"); - - const initialStoredValue = - typeof onInit === "function" ? onInit() : undefined; - const initialRenderValue = - typeof onInit === "function" ? initialStoredValue : onInit; - - const [renderValue, setRenderValue] = useState(initialRenderValue); - - const setState = useCallback( - async (setter: Setter) => { - setRenderValue(typeof setter === "function" ? setter() : setter); - }, - [setRenderValue] - ); - - const setStoreValue = useCallback( - async (v?: T) => { - setRenderValue(v); - - return null; - }, - [setRenderValue] - ); - - const remove = useCallback(() => { - setRenderValue(undefined); - }, [setRenderValue]); - - return [ - renderValue, - setState, - { - setRenderValue, - setStoreValue, - remove - } - ]; -} diff --git a/src/iframe/plasmohq/storage/implementations/local-storage.ts b/src/iframe/plasmohq/storage/implementations/local-storage.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/iframe/plasmohq/storage/index.ts b/src/iframe/plasmohq/storage/index.ts deleted file mode 100644 index 7d50c9726..000000000 --- a/src/iframe/plasmohq/storage/index.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { - Storage as PlasmoStorage, - StorageCallbackMap -} from "@plasmohq/storage"; - -export class Storage implements PlasmoStorage { - #private: any; - - constructor() {} - - get area(): "local" | "sync" | "managed" | "session" { - throw new Error("Method not implemented."); - } - - protected rawGet: (key: string) => Promise; - - get: (key: string) => Promise; - getItem(key: string): Promise { - throw new Error("Method not implemented."); - } - - rawGetAll: () => Promise<{ [key: string]: any }>; - getAll: () => Promise>; - - protected parseValue: (rawValue: any) => Promise; - - protected rawSet: (key: string, value: string) => Promise; - - set: (key: string, rawValue: any) => Promise; - setItem(key: string, rawValue: any): Promise { - throw new Error("Method not implemented."); - } - - protected rawRemove: (key: string) => Promise; - - remove: (key: string) => Promise; - removeItem(key: string): Promise { - throw new Error("Method not implemented."); - } - removeAll: () => Promise; - - clear: (includeCopies?: boolean) => Promise; - - // Not implemented: - - get primaryClient(): chrome.storage.StorageArea { - throw new Error("Method not implemented."); - } - - get secondaryClient(): globalThis.Storage { - throw new Error("Method not implemented."); - } - - get hasWebApi(): boolean { - throw new Error("Method not implemented."); - } - - // Copy: - - get copiedKeySet(): Set { - throw new Error("Method not implemented."); - } - - setCopiedKeySet(keyList: string[]): void { - throw new Error("Method not implemented."); - } - - isCopied: (key: string) => boolean; - - copy: (key?: string) => Promise; - - get allCopied(): boolean { - throw new Error("Method not implemented."); - } - - // Extension API: - - getExtStorageApi: () => any; - - get hasExtensionApi(): boolean { - throw new Error("Method not implemented."); - } - - // Namespace: - - protected keyNamespace: string; - - isValidKey: (nsKey: string) => boolean; - getNamespacedKey: (key: string) => string; - getUnnamespacedKey: (nsKey: string) => string; - setNamespace: (namespace: string) => void; - - // Watch: - isWatchSupported: () => boolean; - watch: (callbackMap: StorageCallbackMap) => boolean; - unwatch: (callbackMap: StorageCallbackMap) => boolean; - unwatchAll: () => void; -} From 35ab26a3be14a218d2e0d7bf56a0df0e195380d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 29 Nov 2024 16:02:06 +0100 Subject: [PATCH 080/142] Update yarn.lock. --- yarn.lock | 263 ------------------------------------------------------ 1 file changed, 263 deletions(-) diff --git a/yarn.lock b/yarn.lock index 241c64ace..19be033fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,24 +54,11 @@ "@babel/highlight" "^7.24.2" picocolors "^1.0.0" -"@babel/code-frame@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" - integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== - dependencies: - "@babel/highlight" "^7.25.7" - picocolors "^1.0.0" - "@babel/compat-data@^7.23.5": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== -"@babel/compat-data@^7.25.7": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.8.tgz#0376e83df5ab0eb0da18885c0140041f0747a402" - integrity sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA== - "@babel/core@^7.19.6": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a" @@ -93,27 +80,6 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/core@^7.25.2": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.8.tgz#a57137d2a51bbcffcfaeba43cb4dd33ae3e0e1c6" - integrity sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.25.7" - "@babel/generator" "^7.25.7" - "@babel/helper-compilation-targets" "^7.25.7" - "@babel/helper-module-transforms" "^7.25.7" - "@babel/helpers" "^7.25.7" - "@babel/parser" "^7.25.8" - "@babel/template" "^7.25.7" - "@babel/traverse" "^7.25.7" - "@babel/types" "^7.25.8" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - "@babel/generator@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3" @@ -124,16 +90,6 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/generator@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56" - integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA== - dependencies: - "@babel/types" "^7.25.7" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" @@ -152,17 +108,6 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-compilation-targets@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz#11260ac3322dda0ef53edfae6e97b961449f5fa4" - integrity sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A== - dependencies: - "@babel/compat-data" "^7.25.7" - "@babel/helper-validator-option" "^7.25.7" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" @@ -190,14 +135,6 @@ dependencies: "@babel/types" "^7.24.0" -"@babel/helper-module-imports@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz#dba00d9523539152906ba49263e36d7261040472" - integrity sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw== - dependencies: - "@babel/traverse" "^7.25.7" - "@babel/types" "^7.25.7" - "@babel/helper-module-transforms@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545" @@ -209,26 +146,11 @@ "@babel/helper-split-export-declaration" "^7.24.5" "@babel/helper-validator-identifier" "^7.24.5" -"@babel/helper-module-transforms@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz#2ac9372c5e001b19bc62f1fe7d96a18cb0901d1a" - integrity sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ== - dependencies: - "@babel/helper-module-imports" "^7.25.7" - "@babel/helper-simple-access" "^7.25.7" - "@babel/helper-validator-identifier" "^7.25.7" - "@babel/traverse" "^7.25.7" - "@babel/helper-plugin-utils@^7.24.0": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ== -"@babel/helper-plugin-utils@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz#8ec5b21812d992e1ef88a9b068260537b6f0e36c" - integrity sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw== - "@babel/helper-simple-access@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz#50da5b72f58c16b07fbd992810be6049478e85ba" @@ -236,14 +158,6 @@ dependencies: "@babel/types" "^7.24.5" -"@babel/helper-simple-access@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz#5eb9f6a60c5d6b2e0f76057004f8dacbddfae1c0" - integrity sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ== - dependencies: - "@babel/traverse" "^7.25.7" - "@babel/types" "^7.25.7" - "@babel/helper-split-export-declaration@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6" @@ -256,31 +170,16 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== -"@babel/helper-string-parser@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" - integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== - "@babel/helper-validator-identifier@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== -"@babel/helper-validator-identifier@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" - integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== - "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== -"@babel/helper-validator-option@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz#97d1d684448228b30b506d90cace495d6f492729" - integrity sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ== - "@babel/helpers@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.5.tgz#fedeb87eeafa62b621160402181ad8585a22a40a" @@ -290,14 +189,6 @@ "@babel/traverse" "^7.24.5" "@babel/types" "^7.24.5" -"@babel/helpers@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.7.tgz#091b52cb697a171fe0136ab62e54e407211f09c2" - integrity sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA== - dependencies: - "@babel/template" "^7.25.7" - "@babel/types" "^7.25.7" - "@babel/highlight@^7.24.2": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e" @@ -308,23 +199,6 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/highlight@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" - integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== - dependencies: - "@babel/helper-validator-identifier" "^7.25.7" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.7", "@babel/parser@^7.25.8": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2" - integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ== - dependencies: - "@babel/types" "^7.25.8" - "@babel/parser@^7.20.15", "@babel/parser@^7.21.3", "@babel/parser@^7.24.0", "@babel/parser@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790" @@ -337,20 +211,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-react-jsx-self@^7.24.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.7.tgz#3d11df143131fd8f5486a1f7d3839890f88f8c85" - integrity sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.7" - -"@babel/plugin-transform-react-jsx-source@^7.24.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.7.tgz#a0d8372310d5ea5b0447dfa03a8485f960eff7be" - integrity sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w== - dependencies: - "@babel/helper-plugin-utils" "^7.25.7" - "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.1", "@babel/runtime@^7.5.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" @@ -367,15 +227,6 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" -"@babel/template@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769" - integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA== - dependencies: - "@babel/code-frame" "^7.25.7" - "@babel/parser" "^7.25.7" - "@babel/types" "^7.25.7" - "@babel/traverse@^7.24.5", "@babel/traverse@^7.4.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8" @@ -392,28 +243,6 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/traverse@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8" - integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg== - dependencies: - "@babel/code-frame" "^7.25.7" - "@babel/generator" "^7.25.7" - "@babel/parser" "^7.25.7" - "@babel/template" "^7.25.7" - "@babel/types" "^7.25.7" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.7", "@babel/types@^7.25.8": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1" - integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg== - dependencies: - "@babel/helper-string-parser" "^7.25.7" - "@babel/helper-validator-identifier" "^7.25.7" - to-fast-properties "^2.0.0" - "@babel/types@^7.20.0", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0", "@babel/types@^7.24.5": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" @@ -4596,39 +4425,6 @@ "@tufjs/canonical-json" "2.0.0" minimatch "^9.0.4" -"@types/babel__core@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.8" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" - integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*": - version "7.20.6" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" - integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== - dependencies: - "@babel/types" "^7.20.7" - "@types/chrome@^0.0.196": version "0.0.196" resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.196.tgz#9e0196a6fcdf3c0e12cfbcd086cd28feb5206990" @@ -4778,17 +4574,6 @@ resolved "https://registry.yarnpkg.com/@untitled-ui/icons-react/-/icons-react-0.1.2.tgz#6474599a086796affaee9c1af5560ea5065b67af" integrity sha512-H4Ryn622t6kNxa5ldQEKL8i2LqtPskuKROA0mgTj54R1KSTNIx5jzrfOGTP6NUOtPyVMVRSyfex3RPqba3LPqg== -"@vitejs/plugin-react@^4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz#1e13f666fe3135b477220d3c13b783704636b6e4" - integrity sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg== - dependencies: - "@babel/core" "^7.25.2" - "@babel/plugin-transform-react-jsx-self" "^7.24.7" - "@babel/plugin-transform-react-jsx-source" "^7.24.7" - "@types/babel__core" "^7.20.5" - react-refresh "^0.14.2" - "@vue/compiler-core@3.3.4": version "3.3.4" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128" @@ -5594,16 +5379,6 @@ browserslist@^4.22.2, browserslist@^4.6.6: node-releases "^2.0.14" update-browserslist-db "^1.0.13" -browserslist@^4.24.0: - version "4.24.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" - integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== - dependencies: - caniuse-lite "^1.0.30001663" - electron-to-chromium "^1.5.28" - node-releases "^2.0.18" - update-browserslist-db "^1.1.0" - bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -5758,11 +5533,6 @@ caniuse-lite@^1.0.30001541, caniuse-lite@^1.0.30001587: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz#809bc25f3f5027ceb33142a7d6c40759d7a901eb" integrity sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA== -caniuse-lite@^1.0.30001663: - version "1.0.30001668" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz#98e214455329f54bf7a4d70b49c9794f0fbedbed" - integrity sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw== - catering@^2.1.0, catering@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" @@ -6662,11 +6432,6 @@ electron-to-chromium@^1.4.535, electron-to-chromium@^1.4.668: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.764.tgz#6e817e9d767434eb48a85cc9915566485c5ba2c3" integrity sha512-ZXbPV46Y4dNCA+k7YHB+BYlzcoMtZ1yH6V0tQ1ul0wmA7RiwJfS29LSdRlE1myWBXRzEgm/Lz6tryj5WVQiLmg== -electron-to-chromium@^1.5.28: - version "1.5.38" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.38.tgz#c6a14e1506717c5a46d439580d7424d8d5860180" - integrity sha512-VbeVexmZ1IFh+5EfrYz1I0HTzHVIlJa112UEWhciPyeOcKJGeTv6N8WnG4wsQB81DGCaVEGhpSb6o6a8WYFXXg== - elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -6940,11 +6705,6 @@ escalade@^3.1.1, escalade@^3.1.2: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - escape-string-regexp@5.0.0, escape-string-regexp@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" @@ -8372,11 +8132,6 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsesc@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== - json-bigint@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" @@ -9560,11 +9315,6 @@ node-releases@^2.0.13, node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== -node-releases@^2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== - nopt@^7.0.0, nopt@^7.2.0: version "7.2.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7" @@ -10666,11 +10416,6 @@ react-refresh@0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-refresh@^0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" - integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== - react-refresh@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" @@ -12229,14 +11974,6 @@ update-browserslist-db@^1.0.13: escalade "^3.1.2" picocolors "^1.0.0" -update-browserslist-db@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" - integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.0" - url-join@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/url-join/-/url-join-5.0.0.tgz#c2f1e5cbd95fa91082a93b58a1f42fecb4bdbcf1" From 6a94bfe3c1420fef86635902b8aa3750aeca8a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 29 Nov 2024 16:03:50 +0100 Subject: [PATCH 081/142] Update yarn.lock. --- package.json | 1 - yarn.lock | 286 +-------------------------------------------------- 2 files changed, 1 insertion(+), 286 deletions(-) diff --git a/package.json b/package.json index 67768a11a..0273b0b56 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,6 @@ "styled-components": "^5.3.6", "typed-assert": "^1.0.9", "uuid": "^9.0.0", - "vite": "^5.4.9", "warp-contracts": "^1.2.13", "webextension-polyfill": "^0.10.0", "wouter": "^2.8.0-alpha.2" diff --git a/yarn.lock b/yarn.lock index 19be033fb..8c6049065 100644 --- a/yarn.lock +++ b/yarn.lock @@ -490,231 +490,116 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== -"@esbuild/aix-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" - integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== - "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== -"@esbuild/android-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" - integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== - "@esbuild/android-arm@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== -"@esbuild/android-arm@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" - integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== - "@esbuild/android-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== -"@esbuild/android-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" - integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== - "@esbuild/darwin-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== -"@esbuild/darwin-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" - integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== - "@esbuild/darwin-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== -"@esbuild/darwin-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" - integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== - "@esbuild/freebsd-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== -"@esbuild/freebsd-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" - integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== - "@esbuild/freebsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== -"@esbuild/freebsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" - integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== - "@esbuild/linux-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== -"@esbuild/linux-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" - integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== - "@esbuild/linux-arm@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== -"@esbuild/linux-arm@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" - integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== - "@esbuild/linux-ia32@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== -"@esbuild/linux-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" - integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== - "@esbuild/linux-loong64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== -"@esbuild/linux-loong64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" - integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== - "@esbuild/linux-mips64el@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== -"@esbuild/linux-mips64el@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" - integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== - "@esbuild/linux-ppc64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== -"@esbuild/linux-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" - integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== - "@esbuild/linux-riscv64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== -"@esbuild/linux-riscv64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" - integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== - "@esbuild/linux-s390x@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== -"@esbuild/linux-s390x@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" - integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== - "@esbuild/linux-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== -"@esbuild/linux-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" - integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== - "@esbuild/netbsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== -"@esbuild/netbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" - integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== - "@esbuild/openbsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== -"@esbuild/openbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" - integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== - "@esbuild/sunos-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== -"@esbuild/sunos-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" - integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== - "@esbuild/win32-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== -"@esbuild/win32-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" - integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== - "@esbuild/win32-ia32@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== -"@esbuild/win32-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" - integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== - "@esbuild/win32-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== -"@esbuild/win32-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" - integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== - "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" @@ -2959,86 +2844,6 @@ dependencies: "@randlabs/communication-bridge" "1.0.1" -"@rollup/rollup-android-arm-eabi@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz#1661ff5ea9beb362795304cb916049aba7ac9c54" - integrity sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA== - -"@rollup/rollup-android-arm64@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz#2ffaa91f1b55a0082b8a722525741aadcbd3971e" - integrity sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA== - -"@rollup/rollup-darwin-arm64@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz#627007221b24b8cc3063703eee0b9177edf49c1f" - integrity sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA== - -"@rollup/rollup-darwin-x64@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz#0605506142b9e796c370d59c5984ae95b9758724" - integrity sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ== - -"@rollup/rollup-linux-arm-gnueabihf@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz#62dfd196d4b10c0c2db833897164d2d319ee0cbb" - integrity sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA== - -"@rollup/rollup-linux-arm-musleabihf@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz#53ce72aeb982f1f34b58b380baafaf6a240fddb3" - integrity sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw== - -"@rollup/rollup-linux-arm64-gnu@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz#1632990f62a75c74f43e4b14ab3597d7ed416496" - integrity sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA== - -"@rollup/rollup-linux-arm64-musl@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz#8c03a996efb41e257b414b2e0560b7a21f2d9065" - integrity sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw== - -"@rollup/rollup-linux-powerpc64le-gnu@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz#5b98729628d5bcc8f7f37b58b04d6845f85c7b5d" - integrity sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw== - -"@rollup/rollup-linux-riscv64-gnu@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz#48e42e41f4cabf3573cfefcb448599c512e22983" - integrity sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg== - -"@rollup/rollup-linux-s390x-gnu@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz#e0b4f9a966872cb7d3e21b9e412a4b7efd7f0b58" - integrity sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g== - -"@rollup/rollup-linux-x64-gnu@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz#78144741993100f47bd3da72fce215e077ae036b" - integrity sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A== - -"@rollup/rollup-linux-x64-musl@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz#d9fe32971883cd1bd858336bd33a1c3ca6146127" - integrity sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ== - -"@rollup/rollup-win32-arm64-msvc@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz#71fa3ea369316db703a909c790743972e98afae5" - integrity sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ== - -"@rollup/rollup-win32-ia32-msvc@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz#653f5989a60658e17d7576a3996deb3902e342e2" - integrity sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ== - -"@rollup/rollup-win32-x64-msvc@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz#0574d7e87b44ee8511d08cc7f914bcb802b70818" - integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw== - "@sec-ant/readable-stream@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" @@ -4438,11 +4243,6 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/estree@1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" - integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== - "@types/filesystem@*": version "0.0.36" resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857" @@ -6671,35 +6471,6 @@ esbuild@^0.18.2: "@esbuild/win32-ia32" "0.18.20" "@esbuild/win32-x64" "0.18.20" -esbuild@^0.21.3: - version "0.21.5" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" - integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== - optionalDependencies: - "@esbuild/aix-ppc64" "0.21.5" - "@esbuild/android-arm" "0.21.5" - "@esbuild/android-arm64" "0.21.5" - "@esbuild/android-x64" "0.21.5" - "@esbuild/darwin-arm64" "0.21.5" - "@esbuild/darwin-x64" "0.21.5" - "@esbuild/freebsd-arm64" "0.21.5" - "@esbuild/freebsd-x64" "0.21.5" - "@esbuild/linux-arm" "0.21.5" - "@esbuild/linux-arm64" "0.21.5" - "@esbuild/linux-ia32" "0.21.5" - "@esbuild/linux-loong64" "0.21.5" - "@esbuild/linux-mips64el" "0.21.5" - "@esbuild/linux-ppc64" "0.21.5" - "@esbuild/linux-riscv64" "0.21.5" - "@esbuild/linux-s390x" "0.21.5" - "@esbuild/linux-x64" "0.21.5" - "@esbuild/netbsd-x64" "0.21.5" - "@esbuild/openbsd-x64" "0.21.5" - "@esbuild/sunos-x64" "0.21.5" - "@esbuild/win32-arm64" "0.21.5" - "@esbuild/win32-ia32" "0.21.5" - "@esbuild/win32-x64" "0.21.5" - escalade@^3.1.1, escalade@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" @@ -7058,7 +6829,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2, fsevents@~2.3.3: +fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -9940,11 +9711,6 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picocolors@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== - picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -10063,15 +9829,6 @@ postcss@^8.1.10: picocolors "^1.0.0" source-map-js "^1.2.0" -postcss@^8.4.43: - version "8.4.47" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" - integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== - dependencies: - nanoid "^3.3.7" - picocolors "^1.1.0" - source-map-js "^1.2.1" - posthtml-parser@^0.10.1: version "0.10.2" resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.10.2.tgz#df364d7b179f2a6bf0466b56be7b98fd4e97c573" @@ -10671,31 +10428,6 @@ rollup@^3.2.5: optionalDependencies: fsevents "~2.3.2" -rollup@^4.20.0: - version "4.24.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.0.tgz#c14a3576f20622ea6a5c9cad7caca5e6e9555d05" - integrity sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg== - dependencies: - "@types/estree" "1.0.6" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.24.0" - "@rollup/rollup-android-arm64" "4.24.0" - "@rollup/rollup-darwin-arm64" "4.24.0" - "@rollup/rollup-darwin-x64" "4.24.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.24.0" - "@rollup/rollup-linux-arm-musleabihf" "4.24.0" - "@rollup/rollup-linux-arm64-gnu" "4.24.0" - "@rollup/rollup-linux-arm64-musl" "4.24.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.24.0" - "@rollup/rollup-linux-riscv64-gnu" "4.24.0" - "@rollup/rollup-linux-s390x-gnu" "4.24.0" - "@rollup/rollup-linux-x64-gnu" "4.24.0" - "@rollup/rollup-linux-x64-musl" "4.24.0" - "@rollup/rollup-win32-arm64-msvc" "4.24.0" - "@rollup/rollup-win32-ia32-msvc" "4.24.0" - "@rollup/rollup-win32-x64-msvc" "4.24.0" - fsevents "~2.3.2" - rss-parser@^3.12.0: version "3.13.0" resolved "https://registry.yarnpkg.com/rss-parser/-/rss-parser-3.13.0.tgz#f1f83b0a85166b8310ec531da6fbaa53ff0f50f0" @@ -11107,11 +10839,6 @@ socks@^2.7.1: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== -source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - source-map@0.8.0-beta.0: version "0.8.0-beta.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" @@ -12041,17 +11768,6 @@ validate-npm-package-name@^5.0.0: resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== -vite@^5.4.9: - version "5.4.9" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.9.tgz#215c80cbebfd09ccbb9ceb8c0621391c9abdc19c" - integrity sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg== - dependencies: - esbuild "^0.21.3" - postcss "^8.4.43" - rollup "^4.20.0" - optionalDependencies: - fsevents "~2.3.3" - vlq@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/vlq/-/vlq-2.0.4.tgz#6057b85729245b9829e3cc7755f95b228d4fe041" From 46ea22bd6146a6ccad5448c6c538bb99aaab9fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 29 Nov 2024 16:15:37 +0100 Subject: [PATCH 082/142] Remove debugging code. --- src/wallets/auth.ts | 8 -------- src/wallets/router/auth/auth-router.hook.ts | 5 +++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/wallets/auth.ts b/src/wallets/auth.ts index 6fbb0b0b5..08ab54a54 100644 --- a/src/wallets/auth.ts +++ b/src/wallets/auth.ts @@ -19,25 +19,17 @@ import { log, LOG_GROUP } from "~utils/log/log.utils"; * @param password Password for unlocking */ export async function unlock(password: string) { - console.log("unlock 1"); - // validate password if (!(await checkPassword(password))) { return false; } - console.log("unlock 2"); - // save decryption key await setDecryptionKey(password); - console.log("unlock 3"); - // schedule the key for removal await scheduleKeyRemoval(); - console.log("unlock 4"); - return true; } diff --git a/src/wallets/router/auth/auth-router.hook.ts b/src/wallets/router/auth/auth-router.hook.ts index f5e0d3d45..588df77e4 100644 --- a/src/wallets/router/auth/auth-router.hook.ts +++ b/src/wallets/router/auth/auth-router.hook.ts @@ -3,11 +3,12 @@ import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; export const useAuthRequestsLocation: BaseLocationHook = () => { const { authRequest: currentAuthRequest } = useCurrentAuthRequest("any"); + + // The authID has been added to the URL so that the auto-scroll and view transition effect work when switching + // between different `AuthRequest`s of the same type: const currentAuthRequestType = currentAuthRequest ? `/${currentAuthRequest.type}/${currentAuthRequest.authID}` : ""; - // TODO: `authID` should be added to the URL for window.scrollTo(0, 0); to work automatically. - return [currentAuthRequestType, (path: string) => ""]; }; From 13006150b19f72b8628322788e13fe9a9abd3a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 29 Nov 2024 16:35:32 +0100 Subject: [PATCH 083/142] Remove code that doesn't belong in this PR. --- src/utils/auth/auth.provider.tsx | 5 ++-- .../router/iframe/iframe-router.hook.ts | 17 ----------- src/wallets/router/iframe/iframe.routes.ts | 28 ------------------- 3 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 src/wallets/router/iframe/iframe-router.hook.ts delete mode 100644 src/wallets/router/iframe/iframe.routes.ts diff --git a/src/utils/auth/auth.provider.tsx b/src/utils/auth/auth.provider.tsx index 9ac8cd08f..0480c3a47 100644 --- a/src/utils/auth/auth.provider.tsx +++ b/src/utils/auth/auth.provider.tsx @@ -460,12 +460,12 @@ export function AuthRequestsProvider({ clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_CLOSING_DELAY_MS); } + // Not needed in the embedded wallet, but can be left alone. It won't do anything: function handleBeforeUnload() { - // Send cancel event for all pending requests if the popup is closed by the user: - authRequests.forEach((authRequest) => { if (authRequest.status !== "pending") return; + // Send cancel event for all pending requests if the popup is closed by the user: replyToAuthRequest( authRequest.type, authRequest.authID, @@ -474,7 +474,6 @@ export function AuthRequestsProvider({ }); } - // TODO: Not in the embedded wallet: window.addEventListener("beforeunload", handleBeforeUnload); return () => { diff --git a/src/wallets/router/iframe/iframe-router.hook.ts b/src/wallets/router/iframe/iframe-router.hook.ts deleted file mode 100644 index 2f157b2e1..000000000 --- a/src/wallets/router/iframe/iframe-router.hook.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { BaseLocationHook } from "wouter"; -import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; -import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; - -export const useIFrameLocation: BaseLocationHook = () => { - const { authRequest: currentAuthRequest } = useCurrentAuthRequest("any"); - - // TODO: In the embedded wallet, instead of calling window.close, the AuthProvider should just "clear" the requests. - - if (currentAuthRequest) { - const currentAuthRequestType = `/${currentAuthRequest.type}`; - - return [currentAuthRequestType, (path: string) => ""]; - } - - return useHashLocation(); -}; diff --git a/src/wallets/router/iframe/iframe.routes.ts b/src/wallets/router/iframe/iframe.routes.ts deleted file mode 100644 index f67e2f5a0..000000000 --- a/src/wallets/router/iframe/iframe.routes.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { AUTH_ROUTES } from "~wallets/router/auth/auth.routes"; -import { POPUP_ROUTES } from "~wallets/router/popup/popup.routes"; -import type { RouteConfig } from "~wallets/router/router.types"; -import { prefixRoutes } from "~wallets/router/router.utils"; - -const IFRAME_OWN_ROUTES: RouteConfig[] = [ - { - path: "/auth/foo", - // TODO: Add placeholder pages here... - component: null - }, - { - path: "/auth/bar", - component: null - } -]; - -export const IFRAME_ROUTES: RouteConfig[] = [ - // popup.tsx: - ...POPUP_ROUTES, - - // auth.tsx: - // TODO: How to add this prefix to routes to when using push(), etc? ENV variable in the enum? - ...prefixRoutes(AUTH_ROUTES, "/auth-request"), - - // Embedded wallet only: - ...IFRAME_OWN_ROUTES -]; From 2e3c95b1e15afa96299e6c469957ecb59a46aa5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 29 Nov 2024 16:38:38 +0100 Subject: [PATCH 084/142] Remove code that doesn't belong in this PR. --- .../embedded/embedded-wallet-setup.hook.ts | 67 ------------------- tsconfig.json | 3 +- 2 files changed, 1 insertion(+), 69 deletions(-) delete mode 100644 src/wallets/setup/embedded/embedded-wallet-setup.hook.ts diff --git a/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts b/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts deleted file mode 100644 index d51697be6..000000000 --- a/src/wallets/setup/embedded/embedded-wallet-setup.hook.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { useState, useEffect } from "react"; -import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; -import { ExtensionStorage } from "~utils/storage"; -import { getActiveAddress, getWallets } from "~wallets"; -import { getDecryptionKey } from "~wallets/auth"; -import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; - -/** - * Hook that opens a new tab if ArConnect has not been set up yet - */ -export function useEmbeddedWalletSetUp() { - const [initialScreenType, setInitialScreenType] = - useState("cover"); - - useEffect(() => { - async function checkWalletState() { - const [activeAddress, wallets, decryptionKey] = await Promise.all([ - getActiveAddress(), - getWallets(), - getDecryptionKey() - ]); - - const hasWallets = activeAddress && wallets.length > 0; - - let nextInitialScreenType: InitialScreenType = "cover"; - - if (!isAuthenticated) { - nextInitialScreenType = "/authenticate"; - } else if (!hasWallets) { - nextInitialScreenType = "/generate"; - } else if (!hasShards) { - // TODO: Add a passive warning about this and allow people to use the wallet in watch-only mode: - nextInitialScreenType = "/add-device"; - } else { - nextInitialScreenType = "default"; - } - - setInitialScreenType(nextInitialScreenType); - - const coverElement = document.getElementById("cover"); - - if (coverElement) { - if (nextInitialScreenType === "cover") { - coverElement.removeAttribute("aria-hidden"); - } else { - coverElement.setAttribute("aria-hidden", "true"); - } - } - } - - ExtensionStorage.watch({ - decryption_key: checkWalletState - }); - - checkWalletState(); - - handleSyncLabelsAlarm(); - - return () => { - ExtensionStorage.unwatch({ - decryption_key: checkWalletState - }); - }; - }, []); - - return initialScreenType; -} diff --git a/tsconfig.json b/tsconfig.json index e756108bf..053296969 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,7 @@ ".plasmo/**/*", "./**/*.ts", "./**/*.tsx", - "./shim.d.ts", - "vite.config.js" + "./shim.d.ts" ], "compilerOptions": { "paths": { From 0c925ba4614c6ad14dc3ac1293a074d8746f5b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 29 Nov 2024 16:39:26 +0100 Subject: [PATCH 085/142] Remove vite.config.js --- vite.config.js | 42 ------------------------------------------ 1 file changed, 42 deletions(-) delete mode 100644 vite.config.js diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index 07f4c8a20..000000000 --- a/vite.config.js +++ /dev/null @@ -1,42 +0,0 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import path from "path"; - -// https://vite.dev/config/ -export default defineConfig({ - // root: "./src/iframe/index.html", - plugins: [react()], - define: { - "process.env": { - ...(process?.env || {}) - } - }, - resolve: { - alias: { - "~": path.resolve(__dirname, "./src"), - "~api": path.resolve(__dirname, "./src/api"), - "~applications": path.resolve(__dirname, "./src/applications"), - "~components": path.resolve(__dirname, "./src/components"), - "~contacts": path.resolve(__dirname, "./src/contacts"), - "~gateways": path.resolve(__dirname, "./src/gateways"), - "~lib": path.resolve(__dirname, "./src/lib"), - "~notifications": path.resolve(__dirname, "./src/notifications"), - "~routes": path.resolve(__dirname, "./src/routes"), - "~settings": path.resolve(__dirname, "./src/settings"), - "~subscriptions": path.resolve(__dirname, "./src/subscriptions"), - "~tokens": path.resolve(__dirname, "./src/tokens"), - "~utils": path.resolve(__dirname, "./src/utils"), - "~wallets": path.resolve(__dirname, "./src/wallets"), - "plasmohq/storage": path.resolve( - __dirname, - "./src/iframe/plasmohq/storage" - ), - "plasmohq/storage/hook": path.resolve( - __dirname, - "./src/iframe/plasmohq/storage/hook" - ), - "url:/assets": path.resolve(__dirname, "./assets"), - "webextension-polyfill": path.resolve(__dirname, "./src/iframe/browser") - } - } -}); From 0ec54a329b27572c8038a9ad410b922c75269574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 29 Nov 2024 19:13:18 +0100 Subject: [PATCH 086/142] Remove HistoryProvider and use a custom useLocation hook everywhere. --- package.json | 3 +- src/components/dashboard/Applications.tsx | 16 ++++---- src/components/dashboard/Contacts.tsx | 20 +++++----- src/components/dashboard/Tokens.tsx | 18 +++++---- src/components/dashboard/Wallets.tsx | 22 +++++----- .../dashboard/list/TokenListItem.tsx | 9 ++--- .../dashboard/subsettings/AddContact.tsx | 15 +++---- .../dashboard/subsettings/AddWallet.tsx | 12 +++--- .../dashboard/subsettings/ContactSettings.tsx | 9 +++-- src/components/popup/Head.tsx | 17 ++++---- src/components/popup/HeadV2.tsx | 18 ++++----- src/components/popup/HistoryProvider.tsx | 36 ----------------- src/components/popup/Navigation.tsx | 9 ++--- src/components/popup/WalletHeader.tsx | 19 ++++----- src/components/popup/WalletSwitcher.tsx | 8 ++-- src/components/popup/home/BuyButton.tsx | 8 ++-- src/components/popup/home/Collectibles.tsx | 11 +++-- src/components/popup/home/NoBalance.tsx | 6 +-- src/components/popup/home/Tokens.tsx | 13 +++--- src/components/popup/home/Transactions.tsx | 8 ++-- src/popup.tsx | 18 +++------ src/routes/dashboard/index.tsx | 12 +++--- src/routes/popup/collectibles.tsx | 9 ++--- src/routes/popup/confirm.tsx | 7 ++-- src/routes/popup/index.tsx | 2 - src/routes/popup/notifications.tsx | 13 +++--- src/routes/popup/pending.tsx | 8 ++-- src/routes/popup/purchase.tsx | 7 ++-- src/routes/popup/receive.tsx | 11 +++-- src/routes/popup/send/auth.tsx | 23 +++++------ src/routes/popup/send/confirm.tsx | 29 +++++++------- src/routes/popup/send/index.tsx | 17 ++++---- src/routes/popup/send/recipient.tsx | 9 ++--- .../popup/settings/apps/[url]/index.tsx | 12 +++--- .../popup/settings/apps/[url]/permissions.tsx | 7 ++-- src/routes/popup/settings/apps/index.tsx | 13 +++--- .../settings/contacts/[address]/index.tsx | 6 +-- src/routes/popup/settings/contacts/index.tsx | 6 +-- src/routes/popup/settings/contacts/new.tsx | 6 +-- .../popup/settings/notifications/index.tsx | 6 +-- src/routes/popup/settings/quickSettings.tsx | 9 ++--- .../popup/settings/tokens/[id]/index.tsx | 10 ++--- src/routes/popup/settings/tokens/index.tsx | 21 +++++----- src/routes/popup/settings/tokens/new.tsx | 6 +-- .../settings/wallets/[address]/export.tsx | 6 +-- .../settings/wallets/[address]/index.tsx | 15 ++++--- .../popup/settings/wallets/[address]/qr.tsx | 9 ++--- src/routes/popup/settings/wallets/index.tsx | 10 ++--- .../subscriptions/subscriptionDetails.tsx | 11 +++-- .../subscriptions/subscriptionPayment.tsx | 8 ++-- .../popup/subscriptions/subscriptions.tsx | 10 +++-- src/routes/popup/token/[id].tsx | 17 ++++---- src/routes/popup/tokens.tsx | 15 ++++--- src/routes/popup/transaction/[id].tsx | 14 +++---- src/routes/popup/transaction/transactions.tsx | 6 +-- src/routes/welcome/generate/backup.tsx | 16 ++++---- src/routes/welcome/generate/confirm.tsx | 14 ++++--- src/routes/welcome/generate/done.tsx | 9 +++-- src/routes/welcome/gettingStarted.tsx | 12 +++--- src/routes/welcome/index.tsx | 12 +++--- src/routes/welcome/load/done.tsx | 8 ++-- src/routes/welcome/load/password.tsx | 13 +++--- src/routes/welcome/load/theme.tsx | 16 ++++---- src/routes/welcome/load/wallets.tsx | 18 +++++---- src/routes/welcome/setup.tsx | 22 ++++++---- src/routes/welcome/start/index.tsx | 21 +++++----- src/tabs/dashboard.tsx | 2 +- src/tabs/welcome.tsx | 14 ++++--- src/wallets/router/auth/auth-router.hook.ts | 2 + src/wallets/router/hash/hash-router.hook.ts | 40 ------------------- src/wallets/router/router.utils.ts | 20 ++++++---- src/wallets/router/routes.component.tsx | 7 ++-- yarn.lock | 22 +++++----- 73 files changed, 432 insertions(+), 501 deletions(-) delete mode 100644 src/components/popup/HistoryProvider.tsx delete mode 100644 src/wallets/router/hash/hash-router.hook.ts diff --git a/package.json b/package.json index 0273b0b56..902b8da08 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "js-confetti": "^0.11.0", "mitt": "^3.0.0", "nanoid": "^4.0.0", - "path-to-regexp": "^6.2.1", "plimit-lit": "^3.0.1", "pretty-bytes": "^6.0.0", "qrcode.react": "^3.1.0", @@ -93,7 +92,7 @@ "uuid": "^9.0.0", "warp-contracts": "^1.2.13", "webextension-polyfill": "^0.10.0", - "wouter": "^2.8.0-alpha.2" + "wouter": "^3.3.5" }, "devDependencies": { "@changesets/cli": "^2.27.1", diff --git a/src/components/dashboard/Applications.tsx b/src/components/dashboard/Applications.tsx index c2efbc0e0..387882e1f 100644 --- a/src/components/dashboard/Applications.tsx +++ b/src/components/dashboard/Applications.tsx @@ -3,14 +3,20 @@ import { useEffect, useMemo, useState } from "react"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { SettingsList } from "./list/BaseElement"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import Application from "~applications/application"; import AppListItem from "./list/AppListItem"; import browser from "webextension-polyfill"; import SearchInput from "./SearchInput"; import styled from "styled-components"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Applications() { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [, params] = useRoute<{ app?: string }>("/apps/:app?"); + // connected apps const [connectedApps] = useStorage( { @@ -43,10 +49,6 @@ export default function Applications() { })(); }, [connectedApps]); - // router - const [, params] = useRoute<{ app?: string }>("/apps/:app?"); - const [, setLocation] = useLocation(); - // active subsetting val const activeApp = useMemo( () => (params?.app ? decodeURIComponent(params.app) : undefined), @@ -60,7 +62,7 @@ export default function Applications() { return; } - setLocation("/apps/" + firstApp); + navigate("/apps/" + firstApp); }, [connectedApps]); // search @@ -96,7 +98,7 @@ export default function Applications() { url={app.url} icon={app.icon} active={activeApp === app.url} - onClick={() => setLocation("/apps/" + encodeURIComponent(app.url))} + onClick={() => navigate("/apps/" + encodeURIComponent(app.url))} key={i} /> ))} diff --git a/src/components/dashboard/Contacts.tsx b/src/components/dashboard/Contacts.tsx index cb6ef838e..0f7ad04aa 100644 --- a/src/components/dashboard/Contacts.tsx +++ b/src/components/dashboard/Contacts.tsx @@ -4,7 +4,7 @@ import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { SettingsList } from "./list/BaseElement"; import ContactListItem from "./list/ContactListItem"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import browser from "webextension-polyfill"; import SearchInput from "./SearchInput"; import styled from "styled-components"; @@ -13,12 +13,20 @@ import { multiSort } from "~utils/multi_sort"; import { enrichContact } from "~contacts/hooks"; import { EventType, trackEvent } from "~utils/analytics"; import type { Contacts } from "~components/Recipient"; +import { useLocation } from "~wallets/router/router.utils"; interface ContactsProps { isQuickSetting?: boolean; } +// TODO: Convert to View export default function Contacts({ isQuickSetting }: ContactsProps) { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [matches, params] = useRoute<{ contact?: string }>( + "/contacts/:contact?" + ); + // contacts const [storedContacts, setStoredContacts] = useStorage( { @@ -91,12 +99,6 @@ export default function Contacts({ isQuickSetting }: ContactsProps) { [contacts] ); - // router - const [matches, params] = useRoute<{ contact?: string }>( - "/contacts/:contact?" - ); - const [, setLocation] = useLocation(); - // active subsetting const activeContact = useMemo( () => (params?.contact ? decodeURIComponent(params.contact) : undefined), @@ -105,7 +107,7 @@ export default function Contacts({ isQuickSetting }: ContactsProps) { // Update the URL when a contact is clicked const handleContactClick = (contactAddress: string) => { - setLocation( + navigate( `/${isQuickSetting ? "quick-settings/" : ""}contacts/${encodeURIComponent( contactAddress )}` @@ -130,7 +132,7 @@ export default function Contacts({ isQuickSetting }: ContactsProps) { const addContact = () => { trackEvent(EventType.ADD_CONTACT, { fromContactSettings: true }); - setLocation(`/${isQuickSetting ? "quick-settings/" : ""}contacts/new`); + navigate(`/${isQuickSetting ? "quick-settings/" : ""}contacts/new`); }; return ( diff --git a/src/components/dashboard/Tokens.tsx b/src/components/dashboard/Tokens.tsx index 5b3d324f8..c20235c8b 100644 --- a/src/components/dashboard/Tokens.tsx +++ b/src/components/dashboard/Tokens.tsx @@ -1,6 +1,6 @@ import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import { useEffect, useMemo, useState } from "react"; import type { Token, TokenType } from "~tokens/token"; import { Reorder } from "framer-motion"; @@ -10,8 +10,14 @@ import PermissionCheckbox from "~components/auth/PermissionCheckbox"; import browser from "webextension-polyfill"; import { ButtonV2, Label, Spacer, Text } from "@arconnect/components"; import { type TokenInfoWithBalance } from "~tokens/aoTokens/ao"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Tokens() { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [matches, params] = useRoute<{ id?: string }>("/tokens/:id?"); + // tokens const [tokens, setTokens] = useStorage( { @@ -56,10 +62,6 @@ export default function Tokens() { await ExtensionStorage.set("setting_ao_support", newSetting); }; - // router - const [matches, params] = useRoute<{ id?: string }>("/tokens/:id?"); - const [, setLocation] = useLocation(); - // active subsetting val const activeTokenSetting = useMemo( () => (params?.id ? params.id : undefined), @@ -84,12 +86,12 @@ export default function Tokens() { } if (allTokens.length > 0) { - setLocation("/tokens/" + allTokens[0].id); + navigate("/tokens/" + allTokens[0].id); } }, [tokens, enhancedAoTokens, activeTokenSetting, matches]); const addToken = () => { - setLocation("/tokens/new"); + navigate("/tokens/new"); }; const handleTokenClick = (token: { @@ -100,7 +102,7 @@ export default function Tokens() { type?: TokenType; name?: string; }) => { - setLocation(`/tokens/${token.id}`); + navigate(`/tokens/${token.id}`); }; return ( diff --git a/src/components/dashboard/Wallets.tsx b/src/components/dashboard/Wallets.tsx index e0b6aec30..56754d0d7 100644 --- a/src/components/dashboard/Wallets.tsx +++ b/src/components/dashboard/Wallets.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from "react"; import { useStorage } from "@plasmohq/storage/hook"; import { type AnsUser, getAnsProfile } from "~lib/ans"; import { ExtensionStorage } from "~utils/storage"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import type { StoredWallet } from "~wallets"; import { Reorder } from "framer-motion"; import WalletListItem from "./list/WalletListItem"; @@ -12,8 +12,16 @@ import browser from "webextension-polyfill"; import SearchInput from "./SearchInput"; import styled from "styled-components"; import { useGateway } from "~gateways/wayfinder"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Wallets() { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [matches, params] = useRoute<{ address?: string }>( + "/wallets/:address?" + ); + // wallets const [wallets, setWallets] = useStorage( { @@ -23,12 +31,6 @@ export default function Wallets() { [] ); - // router - const [matches, params] = useRoute<{ address?: string }>( - "/wallets/:address?" - ); - const [, setLocation] = useLocation(); - // active subsetting val const activeWalletSetting = useMemo( () => (params?.address ? params.address : undefined), @@ -52,7 +54,7 @@ export default function Wallets() { // return if the new wallet page is open if (activeWalletSetting === "new") return; - setLocation("/wallets/" + firstWallet.address); + navigate("/wallets/" + firstWallet.address); }, [wallets, activeWalletSetting]); // ans data @@ -116,7 +118,7 @@ export default function Wallets() { placeholder={browser.i18n.getMessage("search_wallets")} {...searchInput.bindings} /> - setLocation("/wallets/new")}> + navigate("/wallets/new")}> {browser.i18n.getMessage("add_wallet")} @@ -136,7 +138,7 @@ export default function Wallets() { address={wallet.address} avatar={findAvatar(wallet.address)} active={activeWalletSetting === wallet.address} - onClick={() => setLocation("/wallets/" + wallet.address)} + onClick={() => navigate("/wallets/" + wallet.address)} key={wallet.address} /> ))} diff --git a/src/components/dashboard/list/TokenListItem.tsx b/src/components/dashboard/list/TokenListItem.tsx index a9da75487..57829908c 100644 --- a/src/components/dashboard/list/TokenListItem.tsx +++ b/src/components/dashboard/list/TokenListItem.tsx @@ -6,7 +6,6 @@ import { ListItem } from "@arconnect/components"; import { formatAddress } from "~utils/format"; import { getDreForToken } from "~tokens"; import { useTheme } from "~utils/theme"; -import { useLocation } from "wouter"; import * as viewblock from "~lib/viewblock"; import styled from "styled-components"; import { useGateway } from "~gateways/wayfinder"; @@ -14,8 +13,11 @@ import { concatGatewayURL } from "~gateways/utils"; import aoLogo from "url:/assets/ecosystem/ao-logo.svg"; import arLogoDark from "url:/assets/ar/logo_dark.png"; import { getUserAvatar } from "~lib/avatar"; +import { useLocation } from "~wallets/router/router.utils"; export default function TokenListItem({ token, active, ao, onClick }: Props) { + const { navigate } = useLocation(); + // format address const formattedAddress = useMemo( () => formatAddress(token.id, 8), @@ -66,14 +68,11 @@ export default function TokenListItem({ token, active, ao, onClick }: Props) { })(); }, [token, theme, gateway, ao]); - // router - const [, setLocation] = useLocation(); - const handleClick = () => { if (onClick) { onClick(); } else { - setLocation(`/tokens/${token.id}`); + navigate(`/tokens/${token.id}`); } }; diff --git a/src/components/dashboard/subsettings/AddContact.tsx b/src/components/dashboard/subsettings/AddContact.tsx index 84ae69f55..ab449eeea 100644 --- a/src/components/dashboard/subsettings/AddContact.tsx +++ b/src/components/dashboard/subsettings/AddContact.tsx @@ -30,17 +30,22 @@ import { findGateway } from "~gateways/wayfinder"; import { uploadUserAvatar, getUserAvatar } from "~lib/avatar"; // import { getAllArNSNames } from "~lib/arns"; import styled from "styled-components"; -import { useLocation } from "wouter"; import copy from "copy-to-clipboard"; import { gql } from "~gateways/api"; import { useTheme } from "~utils/theme"; +import { useLocation } from "~wallets/router/router.utils"; // import { isAddressFormat } from "~utils/format"; interface AddContactProps { isQuickSetting?: boolean; } +// TODO: Convert to View export default function AddContact({ isQuickSetting }: AddContactProps) { + const { location, navigate } = useLocation(); + // TODO: Get address from params, not by splitting the location: + const address = location.split("=")[1]; + // contacts const [storedContacts, setStoredContacts] = useStorage( { @@ -58,8 +63,6 @@ export default function AddContact({ isQuickSetting }: AddContactProps) { const theme = useTheme(); const { setToast } = useToasts(); - const [location] = useLocation(); - const address = location.split("=")[1]; const [contact, setContact] = useState({ name: "", @@ -191,8 +194,6 @@ export default function AddContact({ isQuickSetting }: AddContactProps) { })(); }, [contact.address, activeAddress]); - const [, setLocation] = useLocation(); - const saveNewContact = async () => { // check if the contact address already exists const addressUsed = storedContacts.some( @@ -230,7 +231,7 @@ export default function AddContact({ isQuickSetting }: AddContactProps) { avatarId: "" }); - setLocation( + navigate( `/${isQuickSetting ? "quick-settings/" : ""}contacts/${contact.address}` ); } catch (error) { @@ -252,7 +253,7 @@ export default function AddContact({ isQuickSetting }: AddContactProps) { }); removeContactModal.setOpen(false); - setLocation(`/${isQuickSetting ? "quick-settings/" : ""}contacts`); + navigate(`/${isQuickSetting ? "quick-settings/" : ""}contacts`); }; const areFieldsEmpty = () => { diff --git a/src/components/dashboard/subsettings/AddWallet.tsx b/src/components/dashboard/subsettings/AddWallet.tsx index a1767f307..cb5964c3a 100644 --- a/src/components/dashboard/subsettings/AddWallet.tsx +++ b/src/components/dashboard/subsettings/AddWallet.tsx @@ -3,7 +3,6 @@ import { PlusIcon, SettingsIcon } from "@iconicicons/react"; import type { JWKInterface } from "arweave/web/lib/wallet"; import { checkPassword } from "~wallets/auth"; import { useEffect, useState } from "react"; -import { useLocation } from "wouter"; import { addWallet, getWalletKeyLength } from "~wallets"; import { Text, @@ -23,8 +22,12 @@ import Arweave from "arweave/web/common"; import styled from "styled-components"; import { defaultGateway } from "~gateways/gateway"; import { WalletKeySizeErrorModal } from "~components/modals/WalletKeySizeErrorModal"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function AddWallet() { + const { navigate } = useLocation(); + // password input const passwordInput = useInput(); @@ -72,9 +75,6 @@ export default function AddWallet() { // the seedphrase component const [providedWallet, setProvidedWallet] = useState(); - // router location - const [, setLocation] = useLocation(); - // add wallet async function loadWallet() { setError(false); @@ -169,7 +169,7 @@ export default function AddWallet() { // redirect to the wallet in settings const arweave = new Arweave(defaultGateway); - setLocation(`/wallets/${await arweave.wallets.jwkToAddress(jwk)}`); + navigate(`/wallets/${await arweave.wallets.jwkToAddress(jwk)}`); } catch (e) { console.log("Failed to load wallet", e); setIncorrectPasswordError(true); @@ -275,7 +275,7 @@ export default function AddWallet() { // redirect to the wallet in settings const arweave = new Arweave(defaultGateway); - setLocation( + navigate( `/wallets/${await arweave.wallets.jwkToAddress(generatedWallet.jwk)}` ); } catch { diff --git a/src/components/dashboard/subsettings/ContactSettings.tsx b/src/components/dashboard/subsettings/ContactSettings.tsx index 8ebecb9a1..8f235d8ab 100644 --- a/src/components/dashboard/subsettings/ContactSettings.tsx +++ b/src/components/dashboard/subsettings/ContactSettings.tsx @@ -23,12 +23,15 @@ import browser from "webextension-polyfill"; import { useTheme } from "~utils/theme"; import styled from "styled-components"; import { svgie } from "~utils/svgies"; -import { useLocation } from "wouter"; import copy from "copy-to-clipboard"; import { formatAddress } from "~utils/format"; +import { useLocation } from "~wallets/router/router.utils"; // import { isAddressFormat } from "~utils/format"; +// TODO: Convert to View export default function ContactSettings({ address, isQuickSetting }: Props) { + const { navigate } = useLocation(); + // contacts const [storedContacts, setStoredContacts] = useStorage( { @@ -253,8 +256,6 @@ export default function ContactSettings({ address, isQuickSetting }: Props) { } }; - const [, setLocation] = useLocation(); - const removeContactModal = useModal(); const confirmRemoveContact = async () => { @@ -271,7 +272,7 @@ export default function ContactSettings({ address, isQuickSetting }: Props) { } removeContactModal.setOpen(false); - setLocation(`/${isQuickSetting ? "quick-settings/" : ""}contacts`); + navigate(`/${isQuickSetting ? "quick-settings/" : ""}contacts`); }; const copyAddress: MouseEventHandler = (e) => { diff --git a/src/components/popup/Head.tsx b/src/components/popup/Head.tsx index e682605a0..85c4627a1 100644 --- a/src/components/popup/Head.tsx +++ b/src/components/popup/Head.tsx @@ -15,20 +15,23 @@ import HardwareWalletIcon, { hwIconAnimateProps } from "~components/hardware/HardwareWalletIcon"; import { useHardwareApi } from "~wallets/hooks"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useEffect, useMemo, useState } from "react"; import keystoneLogo from "url:/assets/hardware/keystone.png"; import WalletSwitcher from "./WalletSwitcher"; import styled from "styled-components"; import { svgie } from "~utils/svgies"; +import { useLocation } from "~wallets/router/router.utils"; export default function Head({ title, showOptions = true, - back, + back: onBack, showBack = true, allowOpen = true }: Props) { + const theme = useTheme(); + const { back } = useLocation(); + // scroll position const [scrollDirection, setScrollDirection] = useState<"up" | "down">("up"); const [scrolled, setScrolled] = useState(false); @@ -61,9 +64,6 @@ export default function Head({ return () => window.removeEventListener("scroll", listener); }, [scrollDirection]); - // ui theme - const theme = useTheme(); - // current address const [activeAddress] = useStorage({ key: "active_address", @@ -88,9 +88,6 @@ export default function Head({ // hardware api type const hardwareApi = useHardwareApi(); - // history back - const [, goBack] = useHistory(); - return ( { - if (back) await back(); - else goBack(); + if (onBack) await onBack(); + else back(); }} /> diff --git a/src/components/popup/HeadV2.tsx b/src/components/popup/HeadV2.tsx index 8455aec48..02736830f 100644 --- a/src/components/popup/HeadV2.tsx +++ b/src/components/popup/HeadV2.tsx @@ -15,15 +15,14 @@ import HardwareWalletIcon, { hwIconAnimateProps } from "~components/hardware/HardwareWalletIcon"; import { useHardwareApi } from "~wallets/hooks"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import React, { useEffect, useMemo, useState } from "react"; import keystoneLogo from "url:/assets/hardware/keystone.png"; import WalletSwitcher from "./WalletSwitcher"; import styled from "styled-components"; import { svgie } from "~utils/svgies"; import type { AppInfo } from "~applications/application"; -import Application from "~applications/application"; import Squircle from "~components/Squircle"; +import { useLocation } from "~wallets/router/router.utils"; export interface HeadV2Props { title: string; @@ -39,12 +38,15 @@ export interface HeadV2Props { export default function HeadV2({ title, showOptions = true, - back, + back: onBack, padding, showBack = true, appInfo, onAppInfoClick }: HeadV2Props) { + const theme = useTheme(); + const { back } = useLocation(); + // scroll position const [scrollDirection, setScrollDirection] = useState<"up" | "down">("up"); const [scrolled, setScrolled] = useState(false); @@ -78,9 +80,6 @@ export default function HeadV2({ return () => window.removeEventListener("scroll", listener); }, [scrollDirection]); - // ui theme - const theme = useTheme(); - // current address const [activeAddress] = useStorage({ key: "active_address", @@ -100,9 +99,6 @@ export default function HeadV2({ // hardware api type const hardwareApi = useHardwareApi(); - // history back - const [, goBack] = useHistory(); - const appName = appInfo?.name; const appIconPlaceholderText = appName?.slice(0, 2).toUpperCase(); @@ -120,8 +116,8 @@ export default function HeadV2({ {showBack ? ( { - if (back) await back(); - else goBack(); + if (onBack) await onBack(); + else back(); }} > diff --git a/src/components/popup/HistoryProvider.tsx b/src/components/popup/HistoryProvider.tsx deleted file mode 100644 index bf110071d..000000000 --- a/src/components/popup/HistoryProvider.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { type PropsWithChildren, useState } from "react"; -import { useLocation } from "wouter"; -import { - type BackAction, - type HistoryAction, - HistoryContext, - type PushAction -} from "~wallets/router/hash/hash-router.hook"; - -// TODO: Do we really need this instead of simply calling history.back()? - -export default function HistoryProvider({ children }: PropsWithChildren<{}>) { - // current history action - const [currentAction, setCurrentAction] = useState("push"); - - // location - const [, setLocation] = useLocation(); - - // push action implementation - const push: PushAction = (to, options) => { - setCurrentAction("push"); - setLocation(to, options); - }; - - // back action implementation - const back: BackAction = () => { - setCurrentAction("pop"); - history.back(); - }; - - return ( - - {children} - - ); -} diff --git a/src/components/popup/Navigation.tsx b/src/components/popup/Navigation.tsx index 758d846a0..a44850db4 100644 --- a/src/components/popup/Navigation.tsx +++ b/src/components/popup/Navigation.tsx @@ -3,14 +3,12 @@ import { ArrowDownLeft, ArrowUpRight, Compass03, - Home01, Home02 } from "@untitled-ui/icons-react"; import browser from "webextension-polyfill"; import styled from "styled-components"; -import { useLocation } from "wouter"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useTheme } from "~utils/theme"; +import { useLocation } from "~wallets/router/router.utils"; const buttons = [ { @@ -46,8 +44,7 @@ const buttons = [ export const NavigationBar = () => { const theme = useTheme(); - const [push] = useHistory(); - const [location] = useLocation(); + const { location, navigate } = useLocation(); const shouldShowNavigationBar = buttons.some((button) => { if (button.title === "Send") { @@ -70,7 +67,7 @@ export const NavigationBar = () => { displayTheme={theme} active={active} key={index} - onClick={() => push(button.route)} + onClick={() => navigate(button.route)} > {button.icon} diff --git a/src/components/popup/WalletHeader.tsx b/src/components/popup/WalletHeader.tsx index ceb5b5dbd..603786e88 100644 --- a/src/components/popup/WalletHeader.tsx +++ b/src/components/popup/WalletHeader.tsx @@ -50,10 +50,13 @@ import { Users01 } from "@untitled-ui/icons-react"; import { svgie } from "~utils/svgies"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import WalletMenu from "./WalletMenu"; +import { useLocation } from "~wallets/router/router.utils"; export default function WalletHeader() { + const theme = useTheme(); + const { navigate } = useLocation(); + // current address const [activeAddress] = useStorage({ key: "active_address", @@ -131,12 +134,6 @@ export default function WalletHeader() { // hardware wallet type const hardwareApi = useHardwareApi(); - // ui theme - const theme = useTheme(); - - // router push - const [push] = useHistory(); - // has notifications const [newNotifications, setNewNotifications] = useStorage( { @@ -207,7 +204,7 @@ export default function WalletHeader() { icon: , title: "setting_contacts", route: () => { - push("/quick-settings/contacts"); + navigate("/quick-settings/contacts"); } }, { @@ -222,14 +219,14 @@ export default function WalletHeader() { icon: , title: "subscriptions", route: () => { - push("/subscriptions"); + navigate("/subscriptions"); } }, { icon: , title: "Settings", route: () => { - push("/quick-settings"); + navigate("/quick-settings"); } } ]; @@ -302,7 +299,7 @@ export default function WalletHeader() { as={Bell03} onClick={() => { setNewNotifications(false); - push("/notifications"); + navigate("/notifications"); }} style={{ width: "20px", diff --git a/src/components/popup/WalletSwitcher.tsx b/src/components/popup/WalletSwitcher.tsx index 469a1ed9c..1c288d8ef 100644 --- a/src/components/popup/WalletSwitcher.tsx +++ b/src/components/popup/WalletSwitcher.tsx @@ -27,7 +27,7 @@ import Arweave from "arweave"; import { svgie } from "~utils/svgies"; import { Action } from "./WalletHeader"; import copy from "copy-to-clipboard"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; export default function WalletSwitcher({ open, @@ -36,6 +36,8 @@ export default function WalletSwitcher({ exactTop = false, noPadding = false }: Props) { + const { navigate } = useLocation(); + // current address const [activeAddress, setActiveAddress] = useStorage({ key: "active_address", @@ -166,8 +168,6 @@ export default function WalletSwitcher({ // toasts const { setToast } = useToasts(); - const [push] = useHistory(); - return ( {open && ( @@ -265,7 +265,7 @@ export default function WalletSwitcher({ onClick={(e) => { e.preventDefault(); - push(`/quick-settings/wallets/${activeAddress}`); + navigate(`/quick-settings/wallets/${activeAddress}`); }} /> diff --git a/src/components/popup/home/BuyButton.tsx b/src/components/popup/home/BuyButton.tsx index 336ceed9c..2811b87dc 100644 --- a/src/components/popup/home/BuyButton.tsx +++ b/src/components/popup/home/BuyButton.tsx @@ -1,8 +1,8 @@ -import { useHistory } from "~wallets/router/hash/hash-router.hook"; -import { Button, ButtonV2 } from "@arconnect/components"; +import { ButtonV2 } from "@arconnect/components"; import browser from "webextension-polyfill"; import styled from "styled-components"; import arLogoDark from "url:/assets/ar/logo_dark.png"; +import { useLocation } from "~wallets/router/router.utils"; export default function BuyButton() { return ( @@ -13,12 +13,12 @@ export default function BuyButton() { } export const PureBuyButton = () => { - const [push] = useHistory(); + const { navigate } = useLocation(); return ( push("/purchase")} + onClick={() => navigate("/purchase")} style={{ display: "flex", gap: "5px" }} > {browser.i18n.getMessage("buy_ar_button")} diff --git a/src/components/popup/home/Collectibles.tsx b/src/components/popup/home/Collectibles.tsx index b0b15ed6c..a8f9f6a14 100644 --- a/src/components/popup/home/Collectibles.tsx +++ b/src/components/popup/home/Collectibles.tsx @@ -1,13 +1,15 @@ import { Heading, TokenCount, ViewAll } from "../Title"; import { Spacer, Text } from "@arconnect/components"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useTokens } from "~tokens"; import { useMemo } from "react"; import browser from "webextension-polyfill"; import Collectible from "../Collectible"; import styled from "styled-components"; +import { useLocation } from "~wallets/router/router.utils"; export default function Collectibles() { + const { navigate } = useLocation(); + // all tokens const tokens = useTokens(); @@ -17,16 +19,13 @@ export default function Collectibles() { [tokens] ); - // router location - const [push] = useHistory(); - return ( <> { if (collectibles.length === 0) return; - push("/collectibles"); + navigate("/collectibles"); }} > {browser.i18n.getMessage("view_all")} @@ -45,7 +44,7 @@ export default function Collectibles() { balance={collectible.balance} divisibility={collectible.divisibility} decimals={collectible.decimals} - onClick={() => push(`/collectible/${collectible.id}`)} + onClick={() => navigate(`/collectible/${collectible.id}`)} key={i} /> ))} diff --git a/src/components/popup/home/NoBalance.tsx b/src/components/popup/home/NoBalance.tsx index c70b38149..74ba5bc6d 100644 --- a/src/components/popup/home/NoBalance.tsx +++ b/src/components/popup/home/NoBalance.tsx @@ -1,13 +1,13 @@ import { ButtonV2, Section, Text } from "@arconnect/components"; import { ArrowRightIcon } from "@iconicicons/react"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import noBalanceArt from "url:/assets/ar/no_funds.png"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { PureBuyButton } from "./BuyButton"; +import { useLocation } from "~wallets/router/router.utils"; export default function NoBalance() { - const [push] = useHistory(); + const { navigate } = useLocation(); return ( @@ -18,7 +18,7 @@ export default function NoBalance() { push("/tokens")} + onClick={() => navigate("/tokens")} secondary fullWidth className="normal-font-weight" diff --git a/src/components/popup/home/Tokens.tsx b/src/components/popup/home/Tokens.tsx index 7260b648a..5ac4f0ef6 100644 --- a/src/components/popup/home/Tokens.tsx +++ b/src/components/popup/home/Tokens.tsx @@ -1,14 +1,16 @@ import { Heading, TokenCount, ViewAll } from "../Title"; import { Spacer, Text } from "@arconnect/components"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { useTokens } from "~tokens"; import { useMemo } from "react"; import browser from "webextension-polyfill"; import styled from "styled-components"; import Token from "../Token"; import { useAoTokens } from "~tokens/aoTokens/ao"; +import { useLocation } from "~wallets/router/router.utils"; export default function Tokens() { + const { navigate } = useLocation(); + // all tokens const tokens = useTokens(); @@ -21,18 +23,15 @@ export default function Tokens() { [tokens] ); - // router push - const [push] = useHistory(); - // handle aoClick function handleTokenClick(tokenId) { - push(`/send/transfer/${tokenId}`); + navigate(`/send/transfer/${tokenId}`); } return ( <> - push("/tokens")}> + navigate("/tokens")}> {browser.i18n.getMessage("view_all")} ({assets.length + aoTokens.length}) @@ -61,7 +60,7 @@ export default function Tokens() { {assets.slice(0, 8).map((token, i) => ( push(`/token/${token.id}`)} + onClick={() => navigate(`/token/${token.id}`)} key={i} /> ))} diff --git a/src/components/popup/home/Transactions.tsx b/src/components/popup/home/Transactions.tsx index cae4651db..73bb7ff21 100644 --- a/src/components/popup/home/Transactions.tsx +++ b/src/components/popup/home/Transactions.tsx @@ -13,7 +13,6 @@ import { AR_SENT_QUERY, PRINT_ARWEAVE_QUERY } from "~notifications/utils"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { getArPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import { printTxWorkingGateways, txHistoryGateways } from "~gateways/gateway"; @@ -30,11 +29,12 @@ import { } from "~lib/transactions"; import BigNumber from "bignumber.js"; import { retryWithDelay } from "~utils/promises/retry"; +import { useLocation } from "~wallets/router/router.utils"; export default function Transactions() { + const { navigate } = useLocation(); const [transactions, fetchTransactions] = useState([]); const [arPrice, setArPrice] = useState(0); - const [push] = useHistory(); const [loading, setLoading] = useState(false); const [currency] = useSetting("currency"); const [activeAddress] = useStorage({ @@ -162,13 +162,13 @@ export default function Transactions() { }, [activeAddress]); const handleClick = (id: string) => { - push(`/transaction/${id}?back=${encodeURIComponent("/transactions")}`); + navigate(`/transaction/${id}?back=${encodeURIComponent("/transactions")}`); }; return ( <> - push("/transactions")}> + navigate("/transactions")}> {browser.i18n.getMessage("view_all")} ({transactions.length}) diff --git a/src/popup.tsx b/src/popup.tsx index f87d749a0..9b6d788d0 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -5,11 +5,10 @@ import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; import { Routes } from "~wallets/router/routes.component"; import { POPUP_ROUTES } from "~wallets/router/popup/popup.routes"; -import HistoryProvider from "~components/popup/HistoryProvider"; -import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; import { Router as Wouter } from "wouter"; -import { BodyScroller, HistoryObserver } from "~wallets/router/router.utils"; +import { BodyScroller } from "~wallets/router/router.utils"; import { AnimatePresence } from "framer-motion"; +import { useHashLocation } from "wouter/use-hash-location"; interface ArConnectBrowserExtensionAppProps { initialScreenType: InitialScreenType; @@ -41,15 +40,10 @@ export function ArConnectBrowserExtensionAppRoot() { - - - - - - - + + + + ); diff --git a/src/routes/dashboard/index.tsx b/src/routes/dashboard/index.tsx index 2cea2464e..6bdcaea32 100644 --- a/src/routes/dashboard/index.tsx +++ b/src/routes/dashboard/index.tsx @@ -4,7 +4,6 @@ import SettingListItem, { } from "~components/dashboard/list/SettingListItem"; import { SettingsList } from "~components/dashboard/list/BaseElement"; import { useEffect, useMemo, useState } from "react"; -import { useLocation } from "wouter"; import { GridIcon, InformationIcon, @@ -40,10 +39,11 @@ import SignSettings from "~components/dashboard/SignSettings"; import AddToken from "~components/dashboard/subsettings/AddToken"; import NotificationSettings from "~components/dashboard/NotificationSettings"; import SearchInput from "~components/dashboard/SearchInput"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Settings({ params }: Props) { - // router location - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); const [showAdvanced, setShowAdvanced] = useState(false); @@ -100,7 +100,7 @@ export default function Settings({ params }: Props) { // if none is selected useEffect(() => { if (!!activeSetting) return; - setLocation("/" + allSettings[0].name); + navigate("/" + allSettings[0].name); }, [activeSetting]); // Segment @@ -128,7 +128,7 @@ export default function Settings({ params }: Props) { description={setting.description} icon={setting.icon} active={activeSetting === setting.name} - onClick={() => setLocation("/" + setting.name)} + onClick={() => navigate("/" + setting.name)} key={`basic-settings-${i}`} /> ))} @@ -153,7 +153,7 @@ export default function Settings({ params }: Props) { description={setting.description} icon={setting.icon} active={activeSetting === setting.name} - onClick={() => setLocation("/" + setting.name)} + onClick={() => navigate("/" + setting.name)} key={`advanced-settings-${i}`} /> ))} diff --git a/src/routes/popup/collectibles.tsx b/src/routes/popup/collectibles.tsx index b962bfb11..83797bb4c 100644 --- a/src/routes/popup/collectibles.tsx +++ b/src/routes/popup/collectibles.tsx @@ -1,4 +1,4 @@ -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { Section } from "@arconnect/components"; import { useTokens } from "~tokens"; import { useMemo } from "react"; @@ -8,6 +8,8 @@ import styled from "styled-components"; import HeadV2 from "~components/popup/HeadV2"; export function CollectiblesView() { + const { navigate } = useLocation(); + // all tokens const tokens = useTokens(); @@ -17,9 +19,6 @@ export function CollectiblesView() { [tokens] ); - // router push - const [push] = useHistory(); - return ( <> @@ -31,7 +30,7 @@ export function CollectiblesView() { balance={collectible.balance} divisibility={collectible.divisibility} decimals={collectible.decimals} - onClick={() => push(`/collectible/${collectible.id}`)} + onClick={() => navigate(`/collectible/${collectible.id}`)} key={i} /> ))} diff --git a/src/routes/popup/confirm.tsx b/src/routes/popup/confirm.tsx index e5d533900..1f9ec8f2c 100644 --- a/src/routes/popup/confirm.tsx +++ b/src/routes/popup/confirm.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { ExtensionStorage } from "~utils/storage"; @@ -19,10 +19,11 @@ export interface ConfirmPurchaseViewParams { export type ConfirmPurchaseViewProps = CommonRouteProps; +// TODO: Convert to View (remove unused params) export function ConfirmPurchaseView({ params: { quoteId: id } }: ConfirmPurchaseViewProps) { - const [push] = useHistory(); + const { navigate } = useLocation(); const [activeAddress] = useStorage({ key: "active_address", @@ -54,7 +55,7 @@ export function ConfirmPurchaseView({ browser.tabs.create({ url: url }); - push("/purchase-pending"); + navigate("/purchase-pending"); } catch (error) { console.error("Error buying AR:", error); } diff --git a/src/routes/popup/index.tsx b/src/routes/popup/index.tsx index 697daccb8..538e76f84 100644 --- a/src/routes/popup/index.tsx +++ b/src/routes/popup/index.tsx @@ -6,7 +6,6 @@ import NoBalance from "~components/popup/home/NoBalance"; import Balance from "~components/popup/home/Balance"; import { AnnouncementPopup } from "./announcement"; import { getDecryptionKey } from "~wallets/auth"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; import { trackEvent, EventType, @@ -29,7 +28,6 @@ export function HomeView() { const [noBalance, setNoBalance] = useState(false); const [loggedIn, setLoggedIn] = useState(false); const [isOpen, setOpen] = useState(false); - const [push] = useHistory(); const [activeAddress] = useStorage({ key: "active_address", instance: ExtensionStorage diff --git a/src/routes/popup/notifications.tsx b/src/routes/popup/notifications.tsx index 898ebde6b..3e3860eaa 100644 --- a/src/routes/popup/notifications.tsx +++ b/src/routes/popup/notifications.tsx @@ -6,7 +6,7 @@ import { mergeAndSortNotifications } from "~utils/notifications"; import aoLogo from "url:/assets/ecosystem/ao-logo.svg"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { Loading } from "@arconnect/components"; import { formatAddress } from "~utils/format"; import HeadV2 from "~components/popup/HeadV2"; @@ -15,7 +15,6 @@ import { useEffect, useState } from "react"; import { useAo } from "~tokens/aoTokens/ao"; import styled from "styled-components"; import { balanceToFractioned, formatTokenBalance } from "~tokens/currency"; -import type { Transaction } from "~notifications/api"; import { ExtensionStorage } from "~utils/storage"; import { getActiveAddress } from "~wallets"; import { @@ -23,8 +22,11 @@ import { type SubscriptionData } from "~subscriptions/subscription"; import { checkTransactionError } from "~lib/transactions"; +import type { Transaction } from "~api/background/handlers/alarms/notifications/notifications-alarm.utils"; export function NotificationsView() { + const { navigate } = useLocation(); + const [notifications, setNotifications] = useState([]); const [formattedTxMsgs, setFormattedTxMsgs] = useState([]); const [subscriptions, setSubscriptions] = useState([]); @@ -33,7 +35,6 @@ export function NotificationsView() { const [empty, setEmpty] = useState(false); const ao = useAo(); - const [push] = useHistory(); useEffect(() => { (async () => { @@ -226,12 +227,12 @@ export function NotificationsView() { const handleLink = (n) => { n.transactionType === "Message" - ? push( + ? navigate( `/notification/${n.node.id}?back=${encodeURIComponent( "/notifications" )}` ) - : push( + : navigate( `/transaction/${n.node.id}?back=${encodeURIComponent( "/notifications" )}` @@ -263,7 +264,7 @@ export function NotificationsView() { {`${subscription.applicationName} Awaiting Payment`} - push(`/subscriptions/${subscription.arweaveAccountAddress}`) + navigate(`/subscriptions/${subscription.arweaveAccountAddress}`) } > Pay Subscription diff --git a/src/routes/popup/pending.tsx b/src/routes/popup/pending.tsx index 226224d38..091ce2472 100644 --- a/src/routes/popup/pending.tsx +++ b/src/routes/popup/pending.tsx @@ -5,15 +5,15 @@ import { ExtensionStorage } from "~utils/storage"; import { useTheme } from "~utils/theme"; import { ButtonV2, Section } from "@arconnect/components"; import type { DisplayTheme } from "@arconnect/components"; -import BuyButton from "~components/popup/home/BuyButton"; import { PageType, trackPage } from "~utils/analytics"; import { useStorage } from "@plasmohq/storage/hook"; import type { Quote } from "~lib/onramper"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export function PendingPurchase() { const theme = useTheme(); - const [push] = useHistory(); + const { navigate } = useLocation(); const [quote] = useStorage({ key: "transak_quote", @@ -45,7 +45,7 @@ export function PendingPurchase() { )}
- push("/")}> + navigate("/")}> Home
diff --git a/src/routes/popup/purchase.tsx b/src/routes/popup/purchase.tsx index 812b944da..eaf71ff71 100644 --- a/src/routes/popup/purchase.tsx +++ b/src/routes/popup/purchase.tsx @@ -16,14 +16,15 @@ import HeadV2 from "~components/popup/HeadV2"; import { useEffect, useMemo, useState } from "react"; import { PageType, trackPage } from "~utils/analytics"; import type { PaymentType, Quote } from "~lib/onramper"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { ExtensionStorage } from "~utils/storage"; import { useDebounce } from "~wallets/hooks"; import { retryWithDelay } from "~utils/promises/retry"; import SliderMenu from "~components/SliderMenu"; export function PurchaseView() { - const [push] = useHistory(); + const { navigate } = useLocation(); + const youPayInput = useInput(); const debouncedYouPayInput = useDebounce(youPayInput.state, 300); const [arConversion, setArConversion] = useState(false); @@ -281,7 +282,7 @@ export function PurchaseView() { fullWidth onClick={async () => { await ExtensionStorage.set("transak_quote", quote); - push(`/confirm-purchase/${quote.quoteId}`); + navigate(`/confirm-purchase/${quote.quoteId}`); }} > Next diff --git a/src/routes/popup/receive.tsx b/src/routes/popup/receive.tsx index d898ab8f2..546cbd9ab 100644 --- a/src/routes/popup/receive.tsx +++ b/src/routes/popup/receive.tsx @@ -10,8 +10,8 @@ import copy from "copy-to-clipboard"; import { useEffect, type MouseEventHandler, useState, useMemo } from "react"; import { PageType, trackPage } from "~utils/analytics"; import HeadV2 from "~components/popup/HeadV2"; -import { useLocation } from "wouter"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; interface ReceiveViewProps extends CommonRouteProps { walletName?: string; @@ -19,6 +19,8 @@ interface ReceiveViewProps extends CommonRouteProps { } export function ReceiveView({ walletName, walletAddress }: ReceiveViewProps) { + const { navigate } = useLocation(); + // active address const [activeAddress] = useStorage({ key: "active_address", @@ -40,9 +42,6 @@ export function ReceiveView({ walletName, walletAddress }: ReceiveViewProps) { const { setToast } = useToasts(); - // location - const [, setLocation] = useLocation(); - const copyAddress: MouseEventHandler = (e) => { e.stopPropagation(); copy(effectiveAddress); @@ -64,9 +63,9 @@ export function ReceiveView({ walletName, walletAddress }: ReceiveViewProps) { title={walletName || browser.i18n.getMessage("receive")} back={() => { if (walletName && walletAddress) { - setLocation(`/quick-settings/wallets/${walletAddress}`); + navigate(`/quick-settings/wallets/${walletAddress}`); } else { - setLocation("/"); + navigate("/"); } }} /> diff --git a/src/routes/popup/send/auth.tsx b/src/routes/popup/send/auth.tsx index 948baf10c..78f503ce7 100644 --- a/src/routes/popup/send/auth.tsx +++ b/src/routes/popup/send/auth.tsx @@ -12,7 +12,7 @@ import type { JWKInterface } from "arweave/web/lib/wallet"; import type { Tag } from "arweave/web/lib/transaction"; import { useScanner } from "@arconnect/keystone-sdk"; import { useActiveWallet } from "~wallets/hooks"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { useEffect, useState } from "react"; import { getActiveKeyfile, getActiveWallet } from "~wallets"; import type { UR } from "@ngraveio/bc-ur"; @@ -56,6 +56,8 @@ export interface SendAuthViewParams { export type SendAuthViewProps = CommonRouteProps; export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { + const { navigate } = useLocation(); + // loading const [loading, setLoading] = useState(false); @@ -178,9 +180,6 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { // toasts const { setToast } = useToasts(); - // router push - const [push] = useHistory(); - /** * Local wallet functionalities */ @@ -256,8 +255,8 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { // Redirect uToken - ? push("/") - : push( + ? navigate("/") + : navigate( `/transaction/${transaction.id}?back=${encodeURIComponent("/")}` ); @@ -322,8 +321,8 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { duration: 2000 }); uToken - ? push("/") - : push( + ? navigate("/") + : navigate( `/transaction/${transaction.id}?back=${encodeURIComponent("/")}` ); @@ -360,7 +359,7 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { // redirect to transfer if the // transaction was not found if (!transaction) { - return push("/send/transfer"); + return navigate("/send/transfer"); } // check if the current wallet @@ -378,7 +377,7 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { duration: 2300, content: browser.i18n.getMessage("transaction_auth_ur_fail") }); - push("/send/transfer"); + navigate("/send/transfer"); } })(); }, [wallet]); @@ -425,8 +424,8 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { duration: 2000 }); uToken - ? push("/") - : push( + ? navigate("/") + : navigate( `/transaction/${transaction.id}?back=${encodeURIComponent("/")}` ); } catch (e) { diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index 17d3373e8..974671eaf 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -21,7 +21,7 @@ import { import { useEffect, useMemo, useState } from "react"; import { findGateway } from "~gateways/wayfinder"; import Arweave from "arweave"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { fallbackGateway, type Gateway } from "~gateways/gateway"; import AnimatedQRScanner from "~components/hardware/AnimatedQRScanner"; import AnimatedQRPlayer from "~components/hardware/AnimatedQRPlayer"; @@ -80,9 +80,12 @@ export interface ConfirmViewParams { export type ConfirmViewProps = CommonRouteProps; +// TODO: Convert to View (fix param parsing) export function ConfirmView({ params: { tokenID, qty: qtyParam, subscription } }: ConfirmViewProps) { + const { navigate } = useLocation(); + // TODO: Add generic utils to parse params: const qty = Number(qtyParam || "0"); // TODO: Need to get Token information @@ -121,8 +124,6 @@ export function ConfirmView({ 10 ); - const [push] = useHistory(); - useEffect(() => { const fetchData = async () => { try { @@ -151,7 +152,7 @@ export function ConfirmView({ setMessage(data.message); } } else { - push("/send/transfer"); + navigate("/send/transfer"); } } catch (error) { console.error("Error fetching data:", error); @@ -365,7 +366,7 @@ export function ConfirmView({ content: browser.i18n.getMessage("sent_tx"), duration: 2000 }); - push(`/transaction/${res}`); + navigate(`/transaction/${res}`); setIsLoading(false); } return res; @@ -425,8 +426,8 @@ export function ConfirmView({ }); // Redirect uToken - ? push("/") - : push( + ? navigate("/") + : navigate( `/transaction/${ convertedTransaction.id }?back=${encodeURIComponent("/")}` @@ -493,8 +494,8 @@ export function ConfirmView({ fee: networkFee }); uToken - ? push("/") - : push( + ? navigate("/") + : navigate( `/transaction/${ convertedTransaction.id }?back=${encodeURIComponent("/")}` @@ -559,7 +560,7 @@ export function ConfirmView({ // redirect to transfer if the // transaction was not found if (!prepared || !prepared.transaction) { - return push("/send/transfer"); + return navigate("/send/transfer"); } // check if the current wallet @@ -585,7 +586,7 @@ export function ConfirmView({ content: browser.i18n.getMessage("sent_tx"), duration: 2000 }); - push(`/transaction/${res}`); + navigate(`/transaction/${res}`); setIsLoading(false); } return res; @@ -617,7 +618,7 @@ export function ConfirmView({ duration: 2300, content: browser.i18n.getMessage("transaction_auth_ur_fail") }); - push("/send/transfer"); + navigate("/send/transfer"); } })(); }, [wallet, recipient, keystoneSigner, setIsLoading]); @@ -684,8 +685,8 @@ export function ConfirmView({ fee: networkFee }); uToken - ? push("/") - : push( + ? navigate("/") + : navigate( `/transaction/${transaction.id}?back=${encodeURIComponent("/")}` ); } catch (e) { diff --git a/src/routes/popup/send/index.tsx b/src/routes/popup/send/index.tsx index 5d155efd1..81631ff43 100644 --- a/src/routes/popup/send/index.tsx +++ b/src/routes/popup/send/index.tsx @@ -44,13 +44,12 @@ import { useTheme } from "~utils/theme"; import arLogoLight from "url:/assets/ar/logo_light.png"; import arLogoDark from "url:/assets/ar/logo_dark.png"; import Arweave from "arweave"; -import { useActiveWallet, useBalance } from "~wallets/hooks"; +import { useBalance } from "~wallets/hooks"; import { getArPrice, getPrice } from "~lib/coingecko"; import redstone from "redstone-api"; -import { AnimatePresence, motion, type Variants } from "framer-motion"; import Collectible from "~components/popup/Collectible"; import { findGateway } from "~gateways/wayfinder"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { DREContract, DRENode } from "@arconnect/warp-dre"; import { isUToken } from "~utils/send"; import HeadV2 from "~components/popup/HeadV2"; @@ -105,6 +104,9 @@ export interface SendViewParams { export type SendViewProps = CommonRouteProps; export function SendView({ params: { id } }: SendViewProps) { + const { navigate, back } = useLocation(); + const theme = useTheme(); + // Segment useEffect(() => { trackPage(PageType.SEND); @@ -208,7 +210,6 @@ export function SendView({ params: { id } }: SendViewProps) { // token logo const [logo, setLogo] = useState(); - const theme = useTheme(); useEffect(() => { (async () => { @@ -313,7 +314,6 @@ export function SendView({ params: { id } }: SendViewProps) { // network fee const [networkFee, setNetworkFee] = useState("0"); - const [, goBack] = useHistory(); useEffect(() => { (async () => { @@ -402,9 +402,6 @@ export function SendView({ params: { id } }: SendViewProps) { return defaulQtytSize / (qtyLengthWithSymbol / maxLengthDef); }, [qty, qtyMode, currency, token]); - // router push - const [push] = useHistory(); - // prepare tx to send async function send() { // check qty @@ -433,7 +430,7 @@ export function SendView({ params: { id } }: SendViewProps) { }); // continue to confirmation page - push(`/send/confirm/${tokenID}/${finalQty}/${recipient.address}`); + navigate(`/send/confirm/${tokenID}/${finalQty}/${recipient.address}`); } return ( @@ -442,7 +439,7 @@ export function SendView({ params: { id } }: SendViewProps) { back={() => { TempTransactionStorage.removeItem("send"); setQty(""); - goBack(); + back(); }} title={browser.i18n.getMessage("send")} /> diff --git a/src/routes/popup/send/recipient.tsx b/src/routes/popup/send/recipient.tsx index f572a2e53..27da74f3e 100644 --- a/src/routes/popup/send/recipient.tsx +++ b/src/routes/popup/send/recipient.tsx @@ -18,7 +18,7 @@ import type Transaction from "arweave/web/lib/transaction"; import { useEffect, useMemo, useState } from "react"; import { useStorage } from "@plasmohq/storage/hook"; import { findGateway } from "~gateways/wayfinder"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import browser from "webextension-polyfill"; import Head from "~components/popup/Head"; import styled from "styled-components"; @@ -36,6 +36,8 @@ export type RecipientViewProps = CommonRouteProps; export function RecipientView({ params: { tokenID, qty, message } }: RecipientViewProps) { + const { navigate } = useLocation(); + // transaction target input const targetInput = useInput(); @@ -99,9 +101,6 @@ export function RecipientView({ ); }, [lastRecipients, targetInput]); - // router push - const [push] = useHistory(); - // toasts const { setToast } = useToasts(); @@ -155,7 +154,7 @@ export function RecipientView({ await TempTransactionStorage.set(TRANSFER_TX_STORAGE, storedTx); // push to auth & signature - push(`/send/auth/${tokenID}`); + navigate(`/send/auth/${tokenID}`); } catch { return setToast({ type: "error", diff --git a/src/routes/popup/settings/apps/[url]/index.tsx b/src/routes/popup/settings/apps/[url]/index.tsx index e797d118f..001baaefa 100644 --- a/src/routes/popup/settings/apps/[url]/index.tsx +++ b/src/routes/popup/settings/apps/[url]/index.tsx @@ -23,9 +23,9 @@ import styled from "styled-components"; import Arweave from "arweave"; import { defaultGateway, suggestedGateways, testnets } from "~gateways/gateway"; import HeadV2 from "~components/popup/HeadV2"; -import { useLocation } from "wouter"; import { ToggleSwitch } from "~routes/popup/subscriptions/subscriptionDetails"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface AppSettingsViewParams { url: string; @@ -34,13 +34,13 @@ export interface AppSettingsViewParams { export type AppSettingsViewProps = CommonRouteProps; export function AppSettingsView({ params: { url } }: AppSettingsViewProps) { + const { navigate } = useLocation(); + // app settings const app = new Application(decodeURIComponent(url)); const [settings, updateSettings] = app.hook(); const arweave = new Arweave(defaultGateway); - const [, setLocation] = useLocation(); - // allowance spent qty const spent = useMemo(() => { const val = settings?.allowance?.spent; @@ -108,7 +108,7 @@ export function AppSettingsView({ params: { url } }: AppSettingsViewProps) { <> setLocation("/quick-settings/apps")} + back={() => navigate("/quick-settings/apps")} />
@@ -116,7 +116,7 @@ export function AppSettingsView({ params: { url } }: AppSettingsViewProps) { {browser.i18n.getMessage("permissions")} - setLocation(`/quick-settings/apps/${url}/permissions`) + navigate(`/quick-settings/apps/${url}/permissions`) } > { await removeApp(app.url); - setLocation(`/quick-settings/apps`); + navigate(`/quick-settings/apps`); }} > {browser.i18n.getMessage("remove")} diff --git a/src/routes/popup/settings/apps/[url]/permissions.tsx b/src/routes/popup/settings/apps/[url]/permissions.tsx index 33d8a7125..9e91f4deb 100644 --- a/src/routes/popup/settings/apps/[url]/permissions.tsx +++ b/src/routes/popup/settings/apps/[url]/permissions.tsx @@ -5,8 +5,8 @@ import styled from "styled-components"; import HeadV2 from "~components/popup/HeadV2"; import { permissionData, type PermissionType } from "~applications/permissions"; import Checkbox from "~components/Checkbox"; -import { useLocation } from "wouter"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface AppPermissionsViewParams { url: string; @@ -18,10 +18,11 @@ export type AppPermissionsViewProps = export function AppPermissionsView({ params: { url } }: AppPermissionsViewProps) { + const { navigate } = useLocation(); + // app settings const app = new Application(decodeURIComponent(url)); const [settings, updateSettings] = app.hook(); - const [, setLocation] = useLocation(); if (!settings) return <>; @@ -29,7 +30,7 @@ export function AppPermissionsView({ <> setLocation(`/quick-settings/apps/${url}`)} + back={() => navigate(`/quick-settings/apps/${url}`)} /> {browser.i18n.getMessage("permissions")} diff --git a/src/routes/popup/settings/apps/index.tsx b/src/routes/popup/settings/apps/index.tsx index e608a08f4..4c953ee4e 100644 --- a/src/routes/popup/settings/apps/index.tsx +++ b/src/routes/popup/settings/apps/index.tsx @@ -2,7 +2,6 @@ import { Spacer, Text, useInput } from "@arconnect/components"; import { useEffect, useMemo, useState } from "react"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; -import { useLocation } from "wouter"; import Application from "~applications/application"; import browser from "webextension-polyfill"; import styled from "styled-components"; @@ -12,8 +11,11 @@ import SearchInput from "~components/dashboard/SearchInput"; import HeadV2 from "~components/popup/HeadV2"; import useActiveTab from "~applications/useActiveTab"; import { getAppURL } from "~utils/format"; +import { useLocation } from "~wallets/router/router.utils"; export function ApplicationsView() { + const { navigate } = useLocation(); + // connected apps const [connectedApps] = useStorage( { @@ -46,9 +48,6 @@ export function ApplicationsView() { })(); }, [connectedApps]); - // router - const [, setLocation] = useLocation(); - // active app const activeTab = useActiveTab(); const activeApp = useMemo(() => { @@ -80,7 +79,7 @@ export function ApplicationsView() { <> setLocation("/quick-settings")} + back={() => navigate("/quick-settings")} /> @@ -111,9 +110,7 @@ export function ApplicationsView() { icon={app.icon} active={false} onClick={() => - setLocation( - "/quick-settings/apps/" + encodeURIComponent(app.url) - ) + navigate("/quick-settings/apps/" + encodeURIComponent(app.url)) } key={i} /> diff --git a/src/routes/popup/settings/contacts/[address]/index.tsx b/src/routes/popup/settings/contacts/[address]/index.tsx index fe882c0b5..0c5a62f5d 100644 --- a/src/routes/popup/settings/contacts/[address]/index.tsx +++ b/src/routes/popup/settings/contacts/[address]/index.tsx @@ -2,8 +2,8 @@ import HeadV2 from "~components/popup/HeadV2"; import browser from "webextension-polyfill"; import { default as ContactSettingsComponent } from "~components/dashboard/subsettings/ContactSettings"; import styled from "styled-components"; -import { useLocation } from "wouter"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface ContactSettingsViewParams { address: string; @@ -15,13 +15,13 @@ export type ContactSettingsViewProps = export function ContactSettingsView({ params: { address } }: ContactSettingsViewProps) { - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); return ( <> setLocation("/quick-settings/contacts")} + back={() => navigate("/quick-settings/contacts")} /> diff --git a/src/routes/popup/settings/contacts/index.tsx b/src/routes/popup/settings/contacts/index.tsx index 3448e5b94..7c281b582 100644 --- a/src/routes/popup/settings/contacts/index.tsx +++ b/src/routes/popup/settings/contacts/index.tsx @@ -2,16 +2,16 @@ import HeadV2 from "~components/popup/HeadV2"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { default as ContactsComponent } from "~components/dashboard/Contacts"; -import { useLocation } from "wouter"; +import { useLocation } from "~wallets/router/router.utils"; export function ContactsView() { - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); return ( <> setLocation("/quick-settings")} + back={() => navigate("/quick-settings")} /> diff --git a/src/routes/popup/settings/contacts/new.tsx b/src/routes/popup/settings/contacts/new.tsx index 4579c5d16..ab6b1a11a 100644 --- a/src/routes/popup/settings/contacts/new.tsx +++ b/src/routes/popup/settings/contacts/new.tsx @@ -2,16 +2,16 @@ import HeadV2 from "~components/popup/HeadV2"; import browser from "webextension-polyfill"; import AddContact from "~components/dashboard/subsettings/AddContact"; import styled from "styled-components"; -import { useLocation } from "wouter"; +import { useLocation } from "~wallets/router/router.utils"; export function NewContactView() { - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); return ( <> setLocation("/quick-settings/contacts")} + back={() => navigate("/quick-settings/contacts")} /> diff --git a/src/routes/popup/settings/notifications/index.tsx b/src/routes/popup/settings/notifications/index.tsx index 57999dcf4..c742043a2 100644 --- a/src/routes/popup/settings/notifications/index.tsx +++ b/src/routes/popup/settings/notifications/index.tsx @@ -8,10 +8,10 @@ import HeadV2 from "~components/popup/HeadV2"; import { ToggleSwitch } from "~routes/popup/subscriptions/subscriptionDetails"; import { InformationIcon } from "@iconicicons/react"; import Checkbox from "~components/Checkbox"; -import { useLocation } from "wouter"; +import { useLocation } from "~wallets/router/router.utils"; export function NotificationSettingsView() { - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); const [notificationSettings, setNotificationSettings] = useStorage( { @@ -41,7 +41,7 @@ export function NotificationSettingsView() { <> setLocation("/quick-settings")} + back={() => navigate("/quick-settings")} /> diff --git a/src/routes/popup/settings/quickSettings.tsx b/src/routes/popup/settings/quickSettings.tsx index 6d6e89999..6c854aaaa 100644 --- a/src/routes/popup/settings/quickSettings.tsx +++ b/src/routes/popup/settings/quickSettings.tsx @@ -7,13 +7,13 @@ import { LinkExternal02, Coins04 } from "@untitled-ui/icons-react"; -import { useLocation } from "wouter"; import { useMemo } from "react"; import { ListItem, ListItemIcon } from "@arconnect/components"; import type { Icon } from "~settings/setting"; import type { HTMLProps, ReactNode } from "react"; import styled from "styled-components"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface QuickSettingsViewParams { setting?: string; @@ -23,8 +23,7 @@ export interface QuickSettingsViewParams { export type QuickSettingsViewProps = CommonRouteProps; export function QuickSettingsView({ params }: QuickSettingsViewProps) { - // router location - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); // active setting val const activeSetting = useMemo(() => params.setting, [params.setting]); @@ -33,7 +32,7 @@ export function QuickSettingsView({ params }: QuickSettingsViewProps) { <> setLocation("/")} + back={() => navigate("/")} /> {allSettings.map((setting, i) => ( @@ -49,7 +48,7 @@ export function QuickSettingsView({ params }: QuickSettingsViewProps) { url: browser.runtime.getURL(setting.externalLink) }); } else { - setLocation("/quick-settings/" + setting.name); + navigate("/quick-settings/" + setting.name); } }} key={i} diff --git a/src/routes/popup/settings/tokens/[id]/index.tsx b/src/routes/popup/settings/tokens/[id]/index.tsx index 0b3b98163..d956e1a3a 100644 --- a/src/routes/popup/settings/tokens/[id]/index.tsx +++ b/src/routes/popup/settings/tokens/[id]/index.tsx @@ -18,8 +18,8 @@ import copy from "copy-to-clipboard"; import { formatAddress } from "~utils/format"; import { CopyButton } from "~components/dashboard/subsettings/WalletSettings"; import HeadV2 from "~components/popup/HeadV2"; -import { useLocation } from "wouter"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface TokenSettingsParams { id: string; @@ -28,6 +28,8 @@ export interface TokenSettingsParams { export type TokenSettingsProps = CommonRouteProps; export function TokenSettingsView({ params: { id } }: TokenSettingsProps) { + const { navigate } = useLocation(); + // tokens const [tokens, setTokens] = useStorage( { @@ -48,8 +50,6 @@ export function TokenSettingsView({ params: { id } }: TokenSettingsProps) { const { setToast } = useToasts(); - const [, setLocation] = useLocation(); - const { token, isAoToken } = useMemo(() => { const aoToken = aoTokens.find((ao) => ao.processId === id); if (aoToken) { @@ -88,7 +88,7 @@ export function TokenSettingsView({ params: { id } }: TokenSettingsProps) { <> setLocation("/quick-settings/tokens")} + back={() => navigate("/quick-settings/tokens")} />
@@ -143,7 +143,7 @@ export function TokenSettingsView({ params: { id } }: TokenSettingsProps) { fullWidth onClick={async () => { await removeToken(id); - setLocation(`/quick-settings/tokens`); + navigate(`/quick-settings/tokens`); }} style={{ backgroundColor: "#8C1A1A" }} > diff --git a/src/routes/popup/settings/tokens/index.tsx b/src/routes/popup/settings/tokens/index.tsx index d1a0a43ff..d7611732f 100644 --- a/src/routes/popup/settings/tokens/index.tsx +++ b/src/routes/popup/settings/tokens/index.tsx @@ -1,6 +1,5 @@ import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; -import { useLocation } from "wouter"; import { useEffect, useMemo, useState } from "react"; import type { Token, TokenType } from "~tokens/token"; import styled from "styled-components"; @@ -20,8 +19,11 @@ import aoLogo from "url:/assets/ecosystem/ao-logo.svg"; import arLogoDark from "url:/assets/ar/logo_dark.png"; import { getUserAvatar } from "~lib/avatar"; import SearchInput from "~components/dashboard/SearchInput"; +import { useLocation } from "~wallets/router/router.utils"; export function TokensSettingsView() { + const { navigate } = useLocation(); + // tokens const [tokens] = useStorage( { @@ -61,9 +63,6 @@ export function TokensSettingsView() { })(); }, []); - // router - const [, setLocation] = useLocation(); - // search const searchInput = useInput(); @@ -82,7 +81,7 @@ export function TokensSettingsView() { } const addToken = () => { - setLocation("/quick-settings/tokens/new"); + navigate("/quick-settings/tokens/new"); }; const handleTokenClick = (token: { @@ -93,14 +92,14 @@ export function TokensSettingsView() { type?: TokenType; name?: string; }) => { - setLocation(`/quick-settings/tokens/${token.id}`); + navigate(`/quick-settings/tokens/${token.id}`); }; return ( <> setLocation("/quick-settings")} + back={() => navigate("/quick-settings")} />
@@ -149,7 +148,10 @@ export function TokensSettingsView() { ); } +// TODO: Convert to View (extract to its own file) function TokenListItem({ token, ao, onClick }: Props) { + const { navigate } = useLocation(); + // format address const formattedAddress = useMemo( () => formatAddress(token.id, 8), @@ -197,14 +199,11 @@ function TokenListItem({ token, ao, onClick }: Props) { })(); }, [token, theme, gateway, ao]); - // router - const [, setLocation] = useLocation(); - const handleClick = () => { if (onClick) { onClick(); } else { - setLocation(`/quick-settings/tokens/${token.id}`); + navigate(`/quick-settings/tokens/${token.id}`); } }; diff --git a/src/routes/popup/settings/tokens/new.tsx b/src/routes/popup/settings/tokens/new.tsx index 6b7f7e92a..c4e4445f9 100644 --- a/src/routes/popup/settings/tokens/new.tsx +++ b/src/routes/popup/settings/tokens/new.tsx @@ -2,16 +2,16 @@ import HeadV2 from "~components/popup/HeadV2"; import browser from "webextension-polyfill"; import AddToken from "~components/dashboard/subsettings/AddToken"; import styled from "styled-components"; -import { useLocation } from "wouter"; +import { useLocation } from "~wallets/router/router.utils"; export function NewTokenSettingsView() { - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); return ( <> setLocation("/quick-settings/tokens")} + back={() => navigate("/quick-settings/tokens")} /> diff --git a/src/routes/popup/settings/wallets/[address]/export.tsx b/src/routes/popup/settings/wallets/[address]/export.tsx index 5d93f3233..19d96f91d 100644 --- a/src/routes/popup/settings/wallets/[address]/export.tsx +++ b/src/routes/popup/settings/wallets/[address]/export.tsx @@ -15,8 +15,8 @@ import { downloadFile } from "~utils/file"; import browser from "webextension-polyfill"; import styled from "styled-components"; import HeadV2 from "~components/popup/HeadV2"; -import { useLocation } from "wouter"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface ExportWalletViewParams { address: string; @@ -27,7 +27,7 @@ export type ExportWalletViewProps = CommonRouteProps; export function ExportWalletView({ params: { address } }: ExportWalletViewProps) { - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); // wallets const [wallets] = useStorage( @@ -93,7 +93,7 @@ export function ExportWalletView({ <> setLocation(`/quick-settings/wallets/${address}`)} + back={() => navigate(`/quick-settings/wallets/${address}`)} /> diff --git a/src/routes/popup/settings/wallets/[address]/index.tsx b/src/routes/popup/settings/wallets/[address]/index.tsx index 4386d0096..7c282f950 100644 --- a/src/routes/popup/settings/wallets/[address]/index.tsx +++ b/src/routes/popup/settings/wallets/[address]/index.tsx @@ -24,8 +24,8 @@ import styled from "styled-components"; import copy from "copy-to-clipboard"; import { formatAddress } from "~utils/format"; import HeadV2 from "~components/popup/HeadV2"; -import { useLocation } from "wouter"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface WalletViewParams { address: string; @@ -34,6 +34,8 @@ export interface WalletViewParams { export type WalletViewProps = CommonRouteProps; export function WalletView({ params: { address } }: WalletViewProps) { + const { navigate } = useLocation(); + // wallets const [wallets, setWallets] = useStorage( { @@ -52,9 +54,6 @@ export function WalletView({ params: { address } }: WalletViewProps) { // toasts const { setToast } = useToasts(); - // location - const [, setLocation] = useLocation(); - // ans const [ansLabel, setAnsLabel] = useState(); @@ -133,7 +132,7 @@ export function WalletView({ params: { address } }: WalletViewProps) { <> setLocation("/quick-settings/wallets")} + back={() => navigate("/quick-settings/wallets")} />
@@ -203,7 +202,7 @@ export function WalletView({ params: { address } }: WalletViewProps) {
setLocation(`/quick-settings/wallets/${address}/qr`)} + onClick={() => navigate(`/quick-settings/wallets/${address}/qr`)} > {browser.i18n.getMessage("generate_qr_code")} @@ -213,7 +212,7 @@ export function WalletView({ params: { address } }: WalletViewProps) { fullWidth secondary onClick={() => - setLocation(`/quick-settings/wallets/${address}/export`) + navigate(`/quick-settings/wallets/${address}/export`) } disabled={wallet.type === "hardware"} > @@ -254,7 +253,7 @@ export function WalletView({ params: { address } }: WalletViewProps) { ), duration: 2000 }); - setLocation("/quick-settings/wallets"); + navigate("/quick-settings/wallets"); } catch (e) { console.log("Error removing wallet", e); setToast({ diff --git a/src/routes/popup/settings/wallets/[address]/qr.tsx b/src/routes/popup/settings/wallets/[address]/qr.tsx index b55537904..b3b8c6df7 100644 --- a/src/routes/popup/settings/wallets/[address]/qr.tsx +++ b/src/routes/popup/settings/wallets/[address]/qr.tsx @@ -18,7 +18,6 @@ import { type Key, type MouseEventHandler } from "react"; -import { useLocation } from "wouter"; import HeadV2 from "~components/popup/HeadV2"; import { WarningIcon } from "~components/popup/Token"; import browser from "webextension-polyfill"; @@ -36,6 +35,7 @@ import { import { dataToFrames } from "qrloop"; import { checkPassword } from "~wallets/auth"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface GenerateQRViewParams { address: string; @@ -44,12 +44,11 @@ export interface GenerateQRViewParams { export type GenerateQRViewProps = CommonRouteProps; export function GenerateQRView({ params: { address } }: GenerateQRViewProps) { + const { navigate } = useLocation(); const [wallet, setWallet] = useState(null); const [copied, setCopied] = useState(false); const [loading, setLoading] = useState(false); const [frames, setFrames] = useState([]); - - const [, setLocation] = useLocation(); const { setToast } = useToasts(); const passwordInput = useInput(); @@ -112,9 +111,9 @@ export function GenerateQRView({ params: { address } }: GenerateQRViewProps) { } back={() => { if (address) { - setLocation(`/quick-settings/wallets/${address}`); + navigate(`/quick-settings/wallets/${address}`); } else { - setLocation("/"); + navigate("/"); } }} /> diff --git a/src/routes/popup/settings/wallets/index.tsx b/src/routes/popup/settings/wallets/index.tsx index 203404262..d8cc716f0 100644 --- a/src/routes/popup/settings/wallets/index.tsx +++ b/src/routes/popup/settings/wallets/index.tsx @@ -4,7 +4,6 @@ import { useEffect, useState } from "react"; import { useStorage } from "@plasmohq/storage/hook"; import { type AnsUser, getAnsProfile } from "~lib/ans"; import { ExtensionStorage } from "~utils/storage"; -import { useLocation } from "wouter"; import type { StoredWallet } from "~wallets"; import { Reorder } from "framer-motion"; import browser from "webextension-polyfill"; @@ -13,8 +12,11 @@ import { useGateway } from "~gateways/wayfinder"; import WalletListItem from "~components/dashboard/list/WalletListItem"; import SearchInput from "~components/dashboard/SearchInput"; import HeadV2 from "~components/popup/HeadV2"; +import { useLocation } from "~wallets/router/router.utils"; export function WalletsView() { + const { navigate } = useLocation(); + // wallets const [wallets, setWallets] = useStorage( { @@ -24,8 +26,6 @@ export function WalletsView() { [] ); - const [, setLocation] = useLocation(); - // ans data const [ansProfiles, setAnsProfiles] = useState([]); @@ -83,7 +83,7 @@ export function WalletsView() { <> setLocation("/quick-settings")} + back={() => navigate("/quick-settings")} />
@@ -115,7 +115,7 @@ export function WalletsView() { avatar={findAvatar(wallet.address)} active={false} onClick={() => - setLocation("/quick-settings/wallets/" + wallet.address) + navigate("/quick-settings/wallets/" + wallet.address) } key={wallet.address} /> diff --git a/src/routes/popup/subscriptions/subscriptionDetails.tsx b/src/routes/popup/subscriptions/subscriptionDetails.tsx index fe4cf9b4f..f6e38e07f 100644 --- a/src/routes/popup/subscriptions/subscriptionDetails.tsx +++ b/src/routes/popup/subscriptions/subscriptionDetails.tsx @@ -32,7 +32,7 @@ import { } from "~components/dashboard/list/BaseElement"; import { formatAddress } from "~utils/format"; import { useTheme } from "~utils/theme"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { getPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import { PageType, trackPage } from "~utils/analytics"; @@ -49,12 +49,11 @@ export type SubscriptionDetailsViewProps = export function SubscriptionDetailsView({ params: { id } }: SubscriptionDetailsViewProps) { + const { navigate, back } = useLocation(); const theme = useTheme(); const [subData, setSubData] = useState(null); const [checked, setChecked] = useState(false); const [autopayChecked, setAutopayChecked] = useState(false); - - const [push, goBack] = useHistory(); const { setToast } = useToasts(); const [price, setPrice] = useState(); const [currency] = useSetting("currency"); @@ -102,7 +101,7 @@ export function SubscriptionDetailsView({ } browser.alarms.clear(`subscription-alarm-${subData.arweaveAccountAddress}`); - goBack(); + back(); // redirect to subscription page }; @@ -207,7 +206,7 @@ export function SubscriptionDetailsView({ SubscriptionStatus.AWAITING_PAYMENT && ( - push( + navigate( `/subscriptions/${subData.arweaveAccountAddress}/payment` ) } @@ -324,7 +323,7 @@ export function SubscriptionDetailsView({ push(`/subscriptions/${id}/manage`)} + onClick={() => navigate(`/subscriptions/${id}/manage`)} > Manage Subscription diff --git a/src/routes/popup/subscriptions/subscriptionPayment.tsx b/src/routes/popup/subscriptions/subscriptionPayment.tsx index 0765fe028..cf1a5649d 100644 --- a/src/routes/popup/subscriptions/subscriptionPayment.tsx +++ b/src/routes/popup/subscriptions/subscriptionPayment.tsx @@ -20,7 +20,7 @@ import { ArrowRightIcon } from "@iconicicons/react"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { ButtonV2, useToasts } from "@arconnect/components"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { getPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import BigNumber from "bignumber.js"; @@ -36,10 +36,10 @@ export type SubscriptionPaymentViewProps = export function SubscriptionPaymentView({ params: { id } }: SubscriptionPaymentViewProps) { + const { back } = useLocation(); const [subData, setSubData] = useState(null); const [price, setPrice] = useState("--"); const [currency] = useSetting("currency"); - const [push, goBack] = useHistory(); const { setToast } = useToasts(); const [activeAddress] = useStorage({ key: "active_address", @@ -59,7 +59,7 @@ export function SubscriptionPaymentView({ content: browser.i18n.getMessage("subscription_cancelled"), duration: 5000 }); - goBack(); + back(); } catch { setToast({ type: "error", @@ -98,7 +98,7 @@ export function SubscriptionPaymentView({ content: "Subscription paid", duration: 5000 }); - goBack(); + back(); } catch (e) { console.log("e", e); setToast({ diff --git a/src/routes/popup/subscriptions/subscriptions.tsx b/src/routes/popup/subscriptions/subscriptions.tsx index ab13548f1..64b7b07b2 100644 --- a/src/routes/popup/subscriptions/subscriptions.tsx +++ b/src/routes/popup/subscriptions/subscriptions.tsx @@ -10,7 +10,7 @@ import Squircle from "~components/Squircle"; import browser from "webextension-polyfill"; import { getSubscriptionData, updateSubscription } from "~subscriptions"; import dayjs from "dayjs"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { SettingIconWrapper, SettingImage @@ -111,6 +111,9 @@ const SubscriptionListItem = ({ icon }) => { + const { navigate } = useLocation(); + const theme = useTheme(); + let period: string = ""; const color: string = getColorByStatus(status as SubscriptionStatus); @@ -130,10 +133,9 @@ const SubscriptionListItem = ({ default: period = ""; } - const theme = useTheme(); - const [push] = useHistory(); + return ( - push(`/subscriptions/${id}`)}> + navigate(`/subscriptions/${id}`)}> ; export function AssetView({ params: { id } }: AssetViewProps) { + const { navigate } = useLocation(); + // load state const [state, setState] = useState(); const [validity, setValidity] = useState<{ [id: string]: boolean }>(); @@ -115,9 +117,6 @@ export function AssetView({ params: { id } }: AssetViewProps) { return formatTokenBalance(val); }, [id, state, activeAddress]); - // router push - const [push] = useHistory(); - // token gateway const tokens = useTokens(); const defaultGateway = useGateway({ @@ -290,11 +289,13 @@ export function AssetView({ params: { id } }: AssetViewProps) { exit="hidden" > - push(`/send/transfer/${id}`)} /> + navigate(`/send/transfer/${id}`)} + /> push("/receive")} + onClick={() => navigate("/receive")} /> @@ -421,13 +422,13 @@ export function AssetView({ params: { id } }: AssetViewProps) { {...interaction} onClick={() => { if (gateway.host !== "arweave.net") { - push( + navigate( `/transaction/${interaction.id}/${encodeURIComponent( concatGatewayURL(gateway) )}` ); } else { - push(`/transaction/${interaction.id}`); + navigate(`/transaction/${interaction.id}`); } }} /> diff --git a/src/routes/popup/tokens.tsx b/src/routes/popup/tokens.tsx index 0ec9efb96..9d8537eed 100644 --- a/src/routes/popup/tokens.tsx +++ b/src/routes/popup/tokens.tsx @@ -1,4 +1,4 @@ -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { ButtonV2, Section, useToasts, Loading } from "@arconnect/components"; import { EditIcon } from "@iconicicons/react"; import { @@ -21,6 +21,8 @@ import { syncAoTokens } from "~tokens/aoTokens/sync"; import { useStorage } from "@plasmohq/storage/hook"; export function TokensView() { + const { navigate } = useLocation(); + const [isLoading, setIsLoading] = useState(false); const [hasNextPage, setHasNextPage] = useState( undefined @@ -50,12 +52,9 @@ export function TokensView() { ); function handleTokenClick(tokenId: string) { - push(`/send/transfer/${tokenId}`); + navigate(`/send/transfer/${tokenId}`); } - // router push - const [push] = useHistory(); - const addAoToken = async (token: TokenInfoWithBalance) => { try { const aoTokens = await getAoTokens(); @@ -165,10 +164,10 @@ export function TokensView() { {assets.map((token, i) => ( push(`/token/${token.id}`)} + onClick={() => navigate(`/token/${token.id}`)} onSettingsClick={(e) => { e.preventDefault(); - push(`/quick-settings/tokens/${token.id}`); + navigate(`/quick-settings/tokens/${token.id}`); }} key={i} /> @@ -214,7 +213,7 @@ export function TokensView() { href="#/quick-settings/tokens" onClick={(e) => { e.preventDefault(); - push("/quick-settings/tokens"); + navigate("/quick-settings/tokens"); }} > diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 7df7efcc3..7ad0b98d0 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -15,7 +15,7 @@ import { type MutableRefObject } from "react"; import { useGateway } from "~gateways/wayfinder"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { ChevronDownIcon, ChevronUpIcon, @@ -71,9 +71,12 @@ export type TransactionViewProps = CommonRouteProps; export function TransactionView({ params: { id: rawId, gw, message } }: TransactionViewProps) { + const { navigate, back } = useLocation(); + // fixup id const id = useMemo(() => rawId.split("?")[0], [rawId]); + // TODO: This should be a redirect... if (!id) return <>; // fetch tx data @@ -321,9 +324,6 @@ export function TransactionView({ })(); }, []); - // router push - const [push, back] = useHistory(); - // interaction input const input = useMemo(() => { const value = transaction?.tags?.find((tag) => tag.name === "Input")?.value; @@ -344,7 +344,7 @@ export function TransactionView({ if (backPath === "/notifications" || backPath === "/transactions") { back(); } else { - push("/"); + navigate("/"); } }} /> @@ -406,7 +406,7 @@ export function TransactionView({ fromSendFlow: true }); - push( + navigate( `/quick-settings/contacts/new?address=${fromAddress}` ); }} @@ -458,7 +458,7 @@ export function TransactionView({ fromSendFlow: true }); - push( + navigate( `/quick-settings/contacts/new?address=${toAddress}` ); }} diff --git a/src/routes/popup/transaction/transactions.tsx b/src/routes/popup/transaction/transactions.tsx index 5b8078ec2..3f511d748 100644 --- a/src/routes/popup/transaction/transactions.tsx +++ b/src/routes/popup/transaction/transactions.tsx @@ -14,7 +14,7 @@ import { AR_SENT_QUERY_WITH_CURSOR, PRINT_ARWEAVE_QUERY_WITH_CURSOR } from "~notifications/utils"; -import { useHistory } from "~wallets/router/hash/hash-router.hook"; +import { useLocation } from "~wallets/router/router.utils"; import { getArPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; import { printTxWorkingGateways, txHistoryGateways } from "~gateways/gateway"; @@ -37,11 +37,11 @@ const defaultCursors = ["", "", "", "", ""]; const defaultHasNextPages = [true, true, true, true, true]; export function TransactionsView() { + const { navigate } = useLocation(); const [cursors, setCursors] = useState(defaultCursors); const [hasNextPages, setHasNextPages] = useState(defaultHasNextPages); const [transactions, setTransactions] = useState({}); const [arPrice, setArPrice] = useState(0); - const [push] = useHistory(); const [loading, setLoading] = useState(false); const [currency] = useSetting("currency"); @@ -217,7 +217,7 @@ export function TransactionsView() { }, [activeAddress]); const handleClick = (id: string) => { - push(`/transaction/${id}?back=${encodeURIComponent("/transactions")}`); + navigate(`/transaction/${id}?back=${encodeURIComponent("/transactions")}`); }; return ( diff --git a/src/routes/welcome/generate/backup.tsx b/src/routes/welcome/generate/backup.tsx index 38976d24f..bef00011d 100644 --- a/src/routes/welcome/generate/backup.tsx +++ b/src/routes/welcome/generate/backup.tsx @@ -1,5 +1,5 @@ import { ButtonV2, Spacer, Text } from "@arconnect/components"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import { useContext, useEffect, useRef, useState } from "react"; import { WalletContext } from "../setup"; import Paragraph from "~components/Paragraph"; @@ -14,8 +14,14 @@ import { EyeOffIcon } from "@iconicicons/react"; import { PageType, trackPage } from "~utils/analytics"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Backup() { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); + // seed blur status const [shown, setShown] = useState(false); @@ -25,10 +31,6 @@ export default function Backup() { // ref to track the latest generated wallet const walletRef = useRef(generatedWallet); - // route - const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); - const [, setLocation] = useLocation(); - // icon displayed for "copy seedphrase" const [copyDisplay, setCopyDisplay] = useState(true); @@ -64,9 +66,7 @@ export default function Backup() { - setLocation(`/${params.setup}/${Number(params.page) + 1}`) - } + onClick={() => navigate(`/${params.setup}/${Number(params.page) + 1}`)} > {browser.i18n.getMessage("next")} diff --git a/src/routes/welcome/generate/confirm.tsx b/src/routes/welcome/generate/confirm.tsx index 6e2430106..a468acc42 100644 --- a/src/routes/welcome/generate/confirm.tsx +++ b/src/routes/welcome/generate/confirm.tsx @@ -1,14 +1,20 @@ import { ButtonV2, Spacer, Text, useToasts } from "@arconnect/components"; import { ArrowRightIcon } from "@iconicicons/react"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import { useContext, useEffect, useState } from "react"; import { WalletContext } from "../setup"; import SeedInput from "~components/SeedInput"; import Paragraph from "~components/Paragraph"; import browser from "webextension-polyfill"; import { PageType, trackPage } from "~utils/analytics"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Confirm() { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); + // wallet context const { wallet: generatedWallet } = useContext(WalletContext); @@ -18,10 +24,6 @@ export default function Confirm() { // confirm seedphrase input state const [seedInputState, setSeedInputState] = useState(); - // route - const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); - const [, setLocation] = useLocation(); - // validate entered seedphrase function validateSeedphrase() { // check if the entered seedphrase is @@ -35,7 +37,7 @@ export default function Confirm() { } // continue - setLocation(`/${params.setup}/${Number(params.page) + 1}`); + navigate(`/${params.setup}/${Number(params.page) + 1}`); } // Segment diff --git a/src/routes/welcome/generate/done.tsx b/src/routes/welcome/generate/done.tsx index ef3298711..397a217dd 100644 --- a/src/routes/welcome/generate/done.tsx +++ b/src/routes/welcome/generate/done.tsx @@ -17,9 +17,12 @@ import useSetting from "~settings/hook"; import { ExtensionStorage } from "~utils/storage"; import { useStorage } from "@plasmohq/storage/hook"; import JSConfetti from "js-confetti"; -import { useLocation } from "wouter"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Done() { + const { navigate } = useLocation(); + // wallet context const { wallet } = useContext(WalletContext); const walletRef = useRef(wallet); @@ -30,8 +33,6 @@ export default function Done() { // wallet generation taking longer const [showLongWaitMessage, setShowLongWaitMessage] = useState(false); - const [, setLocation] = useLocation(); - // password const { password } = useContext(PasswordContext); const [analytics, setAnalytics] = useSetting("analytics"); @@ -91,7 +92,7 @@ export default function Done() { } // redirect to getting started pages - setLocation("/getting-started/1"); + navigate("/getting-started/1"); setShowLongWaitMessage(false); setLoading(false); diff --git a/src/routes/welcome/gettingStarted.tsx b/src/routes/welcome/gettingStarted.tsx index a79d7a20f..b0cc3cf03 100644 --- a/src/routes/welcome/gettingStarted.tsx +++ b/src/routes/welcome/gettingStarted.tsx @@ -9,8 +9,8 @@ import styled from "styled-components"; import Completed from "./gettingStarted/completed"; import Enabled from "./gettingStarted/enableNotifications"; import Connect from "./gettingStarted/connect"; -import { useLocation } from "wouter"; import Explore from "./gettingStarted/explore"; +import { useLocation } from "~wallets/router/router.utils"; const gettingStartedPages = [ , @@ -19,10 +19,12 @@ const gettingStartedPages = [ ]; +// TODO: Convert to View export default function GettingStarted({ page }) { + const { navigate } = useLocation(); + // animate content sice const [contentSize, setContentSize] = useState(0); - const [, setLocation] = useLocation(); const contentRef = useCallback<(el: HTMLDivElement) => void>((el) => { if (!el) return; @@ -34,9 +36,9 @@ export default function GettingStarted({ page }) { obs.observe(el); }, []); - const navigate = (pageNum: number) => { + const navigateToPage = (pageNum: number) => { if (pageNum < 5) { - setLocation(`/getting-started/${pageNum}`); + navigate(`/getting-started/${pageNum}`); } else { // reset before unload window.onbeforeunload = null; @@ -68,7 +70,7 @@ export default function GettingStarted({ page }) { {gettingStartedPages.map((_, i) => ( navigate(i + 1)} + onClick={() => navigateToPage(i + 1)} active={page === i + 1} /> ))} diff --git a/src/routes/welcome/index.tsx b/src/routes/welcome/index.tsx index 0f46518c0..6dcd080f3 100644 --- a/src/routes/welcome/index.tsx +++ b/src/routes/welcome/index.tsx @@ -6,7 +6,6 @@ import { AnimatePresence, motion } from "framer-motion"; import styled, { keyframes } from "styled-components"; import { useStorage } from "@plasmohq/storage/hook"; import browser from "webextension-polyfill"; -import { useLocation } from "wouter"; import { type MutableRefObject, useEffect, @@ -16,8 +15,12 @@ import { } from "react"; import { popoverAnimation } from "~components/popup/WalletSwitcher"; import { PageType, trackPage } from "~utils/analytics"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Home() { + const { navigate } = useLocation(); + // button refs const startButton = useRef(); const walletButton = useRef(); @@ -52,9 +55,6 @@ export default function Home() { [windowDimensions] ); - // router - const [, setLocation] = useLocation(); - // migration available const [oldState] = useStorage({ key: OLD_STORAGE_NAME, @@ -82,7 +82,7 @@ export default function Home() { ref={startButton} onClick={async () => { await animate(startButton); - setLocation("/start/1"); + navigate("/start/1"); }} > {browser.i18n.getMessage("get_me_started")} @@ -93,7 +93,7 @@ export default function Home() { ref={walletButton} onClick={async () => { await animate(walletButton); - setLocation("/load/1"); + navigate("/load/1"); }} > {browser.i18n.getMessage("have_wallet")} diff --git a/src/routes/welcome/load/done.tsx b/src/routes/welcome/load/done.tsx index fdbc45368..f172d2952 100644 --- a/src/routes/welcome/load/done.tsx +++ b/src/routes/welcome/load/done.tsx @@ -7,16 +7,18 @@ import browser from "webextension-polyfill"; import useSetting from "~settings/hook"; import JSConfetti from "js-confetti"; import { useEffect } from "react"; -import { useLocation } from "wouter"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Done() { + const { navigate } = useLocation(); + // analytics opt-in const [analytics, setAnalytics] = useSetting("analytics"); const [answered, setAnswered] = useStorage({ key: "analytics_consent_answered", instance: ExtensionStorage }); - const [, setLocation] = useLocation(); // finalize async function done() { @@ -29,7 +31,7 @@ export default function Done() { window.onbeforeunload = null; // redirect to getting started pages - setLocation("/getting-started/1"); + navigate("/getting-started/1"); } // determine location diff --git a/src/routes/welcome/load/password.tsx b/src/routes/welcome/load/password.tsx index 16e6b9bb5..4fe34ea67 100644 --- a/src/routes/welcome/load/password.tsx +++ b/src/routes/welcome/load/password.tsx @@ -2,7 +2,7 @@ import PasswordStrength from "../../../components/welcome/PasswordStrength"; import PasswordMatch from "~components/welcome/PasswordMatch"; import { checkPasswordValid } from "~wallets/generator"; import { ArrowRightIcon } from "@iconicicons/react"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import Paragraph from "~components/Paragraph"; import { useContext, useMemo, useEffect } from "react"; import browser from "webextension-polyfill"; @@ -19,8 +19,14 @@ import { import { PageType, trackPage } from "~utils/analytics"; import { PasswordWarningModal } from "~routes/popup/passwordPopup"; import { passwordStrength } from "check-password-strength"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Password() { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); + // input controls const passwordInput = useInput(); const validPasswordInput = useInput(); @@ -33,9 +39,6 @@ export default function Password() { const passwordModal = useModal(); - // route - const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); - const [, setLocation] = useLocation(); const passwordStatus = passwordStrength(passwordInput.state); // handle done button @@ -67,7 +70,7 @@ export default function Password() { setPassword(passwordInput.state); // next page - setLocation(`/${params.setup}/${Number(params.page) + 1}`); + navigate(`/${params.setup}/${Number(params.page) + 1}`); } // passwords match diff --git a/src/routes/welcome/load/theme.tsx b/src/routes/welcome/load/theme.tsx index e1492747b..741e666bb 100644 --- a/src/routes/welcome/load/theme.tsx +++ b/src/routes/welcome/load/theme.tsx @@ -1,5 +1,5 @@ import { ButtonV2, Spacer, Text } from "@arconnect/components"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import { ArrowRightIcon, DashboardIcon, @@ -11,15 +11,17 @@ import useSetting from "~settings/hook"; import styled from "styled-components"; import { useEffect } from "react"; import { PageType, trackPage } from "~utils/analytics"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Theme() { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); + // theme const [theme, setTheme] = useSetting("display_theme"); - // route - const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); - const [, setLocation] = useLocation(); - // Segment // TODO: specify if this is an imported or new wallet useEffect(() => { @@ -49,9 +51,7 @@ export default function Theme() { - setLocation(`/${params.setup}/${Number(params.page) + 1}`) - } + onClick={() => navigate(`/${params.setup}/${Number(params.page) + 1}`)} > {browser.i18n.getMessage("next")} diff --git a/src/routes/welcome/load/wallets.tsx b/src/routes/welcome/load/wallets.tsx index dab1e310b..5954bf16c 100644 --- a/src/routes/welcome/load/wallets.tsx +++ b/src/routes/welcome/load/wallets.tsx @@ -11,7 +11,7 @@ import type { JWKInterface } from "arweave/web/lib/wallet"; import { useContext, useEffect, useMemo, useState } from "react"; import { ArrowRightIcon } from "@iconicicons/react"; import { useStorage } from "@plasmohq/storage/hook"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import { PasswordContext } from "../setup"; import { ButtonV2, @@ -28,8 +28,14 @@ import Paragraph from "~components/Paragraph"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { WalletKeySizeErrorModal } from "~components/modals/WalletKeySizeErrorModal"; +import { useLocation } from "~wallets/router/router.utils"; +// TODO: Convert to View export default function Wallets() { + const { navigate } = useLocation(); + // TODO: Replace with useParams: + const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); + // password context const { password } = useContext(PasswordContext); @@ -89,10 +95,6 @@ export default function Wallets() { // toasts const { setToast } = useToasts(); - // route - const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); - const [, setLocation] = useLocation(); - // loading const [loading, setLoading] = useState(false); @@ -170,7 +172,7 @@ export default function Wallets() { } // continue to the next page - setLocation(`/${params.setup}/${Number(params.page) + 1}`); + navigate(`/${params.setup}/${Number(params.page) + 1}`); } catch (e) { console.log("Failed to load wallet", e); setToast({ @@ -191,7 +193,7 @@ export default function Wallets() { await setActiveWallet(account.address); // redirect - setLocation(`/${params.setup}/${Number(params.page) + 1}`); + navigate(`/${params.setup}/${Number(params.page) + 1}`); } // migration available @@ -282,7 +284,7 @@ export default function Wallets() { - setLocation(`/`)} /> + navigate(`/`)} /> ); } diff --git a/src/routes/welcome/setup.tsx b/src/routes/welcome/setup.tsx index 6162d6301..3f46497ab 100644 --- a/src/routes/welcome/setup.tsx +++ b/src/routes/welcome/setup.tsx @@ -9,7 +9,7 @@ import { import { Card, Spacer, useToasts } from "@arconnect/components"; import type { JWKInterface } from "arweave/web/lib/wallet"; import { jwkFromMnemonic } from "~wallets/generator"; -import { useLocation, useRoute } from "wouter"; +import { useRoute } from "wouter"; import { ArrowLeftIcon } from "@iconicicons/react"; import browser from "webextension-polyfill"; import * as bip39 from "bip39-web-crypto"; @@ -27,6 +27,7 @@ import Theme from "./load/theme"; import { defaultGateway } from "~gateways/gateway"; import Pagination, { Status } from "~components/Pagination"; import { getWalletKeyLength } from "~wallets"; +import { useLocation } from "~wallets/router/router.utils"; /** Wallet generate pages */ const generatePages = [ @@ -53,9 +54,10 @@ const loadTitles = [ "done" ]; +// TODO: Convert to View export default function Setup({ setupMode, page }: Props) { - // location - const [, setLocation] = useLocation(); + const { navigate } = useLocation(); + // TODO: Replace with useParams: const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); // total page count @@ -72,7 +74,7 @@ export default function Setup({ setupMode, page }: Props) { useEffect(() => { // wrong setup mode if (Number.isNaN(page) || page < 1 || page > pageCount) { - setLocation(`/${setupMode}/1`); + navigate(`/${setupMode}/1`); } }, [setupMode, page]); @@ -82,7 +84,7 @@ export default function Setup({ setupMode, page }: Props) { // check if the user is on the wrong page without a password useEffect(() => { if (page !== 1 && password === "") { - setLocation(`/${setupMode}/1`); + navigate(`/${setupMode}/1`); } }, [page, password]); @@ -95,8 +97,8 @@ export default function Setup({ setupMode, page }: Props) { // generate wallet in the background const [generatedWallet, setGeneratedWallet] = useState({}); - const navigate = () => { - setLocation(`/${params.setup}/${page - 1}`); + const navigateToPreviousPage = () => { + navigate(`/${params.setup}/${page - 1}`); }; async function generateWallet() { @@ -173,7 +175,11 @@ export default function Setup({ setupMode, page }: Props) { - {page === 1 ? : } + {page === 1 ? ( + + ) : ( + + )} {pageTitles.map((title, i) => ( ("/start/:page"); // page of the setup const page = useMemo(() => { const page = Number(params?.page || "1"); + // TODO: This should be a redirect: if (![1, 2, 3].includes(page)) return 1; return page; @@ -58,17 +59,13 @@ export default function Start() { - setLocation(page === 3 ? "/generate/1" : `/start/${page + 1}`) + navigate(page === 3 ? "/generate/1" : `/start/${page + 1}`) } > {browser.i18n.getMessage("next")} - setLocation("/generate/1")} - > + navigate("/generate/1")}> {browser.i18n.getMessage("skip")} @@ -78,7 +75,7 @@ export default function Start() { .fill("") .map((_, i) => ( setLocation(`/start/${i + 1}`)} + onClick={() => navigate(`/start/${i + 1}`)} key={i} active={page === i + 1} /> diff --git a/src/tabs/dashboard.tsx b/src/tabs/dashboard.tsx index 4a43d1b4c..4e573c78c 100644 --- a/src/tabs/dashboard.tsx +++ b/src/tabs/dashboard.tsx @@ -1,4 +1,4 @@ -import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; +import { useHashLocation } from "wouter/use-hash-location"; import { Router, Route } from "wouter"; import Settings from "~routes/dashboard"; diff --git a/src/tabs/welcome.tsx b/src/tabs/welcome.tsx index 9f3f7d3bf..e199f877f 100644 --- a/src/tabs/welcome.tsx +++ b/src/tabs/welcome.tsx @@ -1,22 +1,22 @@ -import { type Path, pathToRegexp } from "path-to-regexp"; -import { useHashLocation } from "~wallets/router/hash/hash-router.hook"; +import { useHashLocation } from "wouter/use-hash-location"; import { Router, Route } from "wouter"; import Home from "~routes/welcome"; import Start from "~routes/welcome/start"; import Setup from "~routes/welcome/setup"; - -import makeCachedMatcher from "wouter/matcher"; import GettingStarted from "~routes/welcome/gettingStarted"; + import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; export default function Welcome() { useRemoveCover(); + // TODO: Make sure the router still works without the custom matcher: + return ( - + @@ -35,6 +35,9 @@ export default function Welcome() { ); } +/* +import makeCachedMatcher from "wouter/matcher"; + const convertPathToRegexp = (path: Path) => { let keys = []; @@ -44,3 +47,4 @@ const convertPathToRegexp = (path: Path) => { }; const customMatcher = makeCachedMatcher(convertPathToRegexp); +*/ diff --git a/src/wallets/router/auth/auth-router.hook.ts b/src/wallets/router/auth/auth-router.hook.ts index 588df77e4..dd6e7f5df 100644 --- a/src/wallets/router/auth/auth-router.hook.ts +++ b/src/wallets/router/auth/auth-router.hook.ts @@ -10,5 +10,7 @@ export const useAuthRequestsLocation: BaseLocationHook = () => { ? `/${currentAuthRequest.type}/${currentAuthRequest.authID}` : ""; + // TODO: Implement a navigate function that just selects a different route... + // TODO: Also use when possible. return [currentAuthRequestType, (path: string) => ""]; }; diff --git a/src/wallets/router/hash/hash-router.hook.ts b/src/wallets/router/hash/hash-router.hook.ts deleted file mode 100644 index 9cdb315f9..000000000 --- a/src/wallets/router/hash/hash-router.hook.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createContext, useContext, useEffect, useState } from "react"; -import type { BaseLocationHook } from "wouter/types/use-location"; - -const navigate = (path: string) => (window.location.hash = path); - -const currentLocation = () => window.location.hash.replace(/^#/, "") || "/"; - -export const useHashLocation: BaseLocationHook = () => { - const [loc, setLoc] = useState(currentLocation()); - - useEffect(() => { - // this function is called whenever the hash changes - const handler = () => setLoc(currentLocation()); - - // subscribe to hash changes - window.addEventListener("hashchange", handler); - return () => window.removeEventListener("hashchange", handler); - }, []); - - return [loc, navigate]; -}; - -/** - * History push/pop context - */ -export type HistoryAction = "push" | "pop"; -export type PushAction = ( - path: string, - options?: { - replace?: boolean; - } -) => any; -export type BackAction = () => any; - -/** Push function, back function, action status */ -export const HistoryContext = createContext< - [PushAction, BackAction, HistoryAction] ->([() => {}, () => {}, "push"]); - -export const useHistory = () => useContext(HistoryContext); diff --git a/src/wallets/router/router.utils.ts b/src/wallets/router/router.utils.ts index 2edcbb086..fa6ff7c4a 100644 --- a/src/wallets/router/router.utils.ts +++ b/src/wallets/router/router.utils.ts @@ -1,5 +1,5 @@ -import { useEffect } from "react"; -import { useLocation } from "wouter"; +import { useCallback, useEffect } from "react"; +import { useLocation as useWouterLocation } from "wouter"; import type { RouteConfig, RouteString } from "~wallets/router/router.types"; export function prefixRoutes( @@ -13,7 +13,7 @@ export function prefixRoutes( } export function BodyScroller() { - const [location] = useLocation(); + const { location } = useLocation(); useEffect(() => { window.scrollTo(0, 0); @@ -22,10 +22,16 @@ export function BodyScroller() { return null; } -export function HistoryObserver() { - const [location] = useLocation(); +export function useLocation() { + const [location, navigate] = useWouterLocation(); - // TODO: To be implemented... + const back = useCallback(() => { + history.back(); + }, []); - return null; + return { + location, + navigate, + back + }; } diff --git a/src/wallets/router/routes.component.tsx b/src/wallets/router/routes.component.tsx index e537dba61..967f29e86 100644 --- a/src/wallets/router/routes.component.tsx +++ b/src/wallets/router/routes.component.tsx @@ -1,10 +1,11 @@ import React, { useEffect, useMemo, type PropsWithChildren } from "react"; -import { Switch, useLocation, Route as Woute } from "wouter"; +import { Switch, Route as Woute } from "wouter"; import { Page } from "~components/page/page.component"; import type { CommonRouteProps, RouteConfig } from "~wallets/router/router.types"; +import { useLocation } from "~wallets/router/router.utils"; export interface RoutesProps { routes: RouteConfig[]; @@ -15,6 +16,8 @@ export function Routes({ routes, pageComponent: PageComponent = Page }: RoutesProps) { + const { location } = useLocation(); + // In development, check there are no duplicate routes (paths): if (process.env.NODE_ENV === "development") { @@ -30,8 +33,6 @@ export function Routes({ }, [routes]); } - const [location] = useLocation(); - const memoizedRoutes = useMemo(() => { return ( diff --git a/yarn.lock b/yarn.lock index 8c6049065..2ca17a751 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8836,7 +8836,7 @@ minizlib@^2.1.1, minizlib@^2.1.2: minipass "^3.0.0" yallist "^4.0.0" -mitt@^3.0.0: +mitt@^3.0.0, mitt@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== @@ -9671,11 +9671,6 @@ path-scurry@^1.11.0: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-to-regexp@^6.2.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" - integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -10340,6 +10335,11 @@ regexp.prototype.flags@^1.5.2: es-errors "^1.3.0" set-function-name "^2.0.1" +regexparam@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-3.0.0.tgz#1673e09d41cb7fd41eaafd4040a6aa90daa0a21a" + integrity sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q== + registry-auth-token@^5.0.0, registry-auth-token@^5.0.1: version "5.0.2" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.0.2.tgz#8b026cc507c8552ebbe06724136267e63302f756" @@ -11982,11 +11982,13 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -wouter@^2.8.0-alpha.2: - version "2.12.1" - resolved "https://registry.yarnpkg.com/wouter/-/wouter-2.12.1.tgz#11d913324c6320b679873783acb15ea3523b8521" - integrity sha512-G7a6JMSLSNcu6o8gdOfIzqxuo8Qx1qs+9rpVnlurH69angsSFPZP5gESNuVNeJct/MGpQg191pDo4HUjTx7IIQ== +wouter@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/wouter/-/wouter-3.3.5.tgz#3298e29aa0c2f975a955339cfc378b869219340f" + integrity sha512-bx3fLQAMn+EhYbBdY3W1gw9ZfO/uchudxYMwOIBzF3HVgqNEEIT199vEoh7FLTC0Vz5+rpMO6NdFsOkGX1QQCw== dependencies: + mitt "^3.0.1" + regexparam "^3.0.0" use-sync-external-store "^1.0.0" "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: From b0993efafabd817a73caf38d95473b4b2dfe9979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Mon, 2 Dec 2024 11:54:44 +0100 Subject: [PATCH 087/142] Update Welcome pages to get params from props (WIP). --- .../popup/subscriptions/subscriptions.tsx | 1 + src/routes/welcome/gettingStarted.tsx | 16 +++++-- src/routes/welcome/index.tsx | 3 +- src/routes/welcome/load/password.tsx | 3 ++ src/routes/welcome/setup.tsx | 44 ++++++++++-------- src/routes/welcome/start/index.tsx | 19 +++++--- src/tabs/welcome.tsx | 45 +++++++++---------- src/wallets/router/welcome/welcome.routes.ts | 33 ++++++++++++++ 8 files changed, 109 insertions(+), 55 deletions(-) create mode 100644 src/wallets/router/welcome/welcome.routes.ts diff --git a/src/routes/popup/subscriptions/subscriptions.tsx b/src/routes/popup/subscriptions/subscriptions.tsx index 64b7b07b2..960683a0c 100644 --- a/src/routes/popup/subscriptions/subscriptions.tsx +++ b/src/routes/popup/subscriptions/subscriptions.tsx @@ -101,6 +101,7 @@ export function SubscriptionsView() { ); } +// TODO: Convert to View (extract to its own file) const SubscriptionListItem = ({ title, expiration, diff --git a/src/routes/welcome/gettingStarted.tsx b/src/routes/welcome/gettingStarted.tsx index b0cc3cf03..155a6806b 100644 --- a/src/routes/welcome/gettingStarted.tsx +++ b/src/routes/welcome/gettingStarted.tsx @@ -11,6 +11,7 @@ import Enabled from "./gettingStarted/enableNotifications"; import Connect from "./gettingStarted/connect"; import Explore from "./gettingStarted/explore"; import { useLocation } from "~wallets/router/router.utils"; +import type { CommonRouteProps } from "~wallets/router/router.types"; const gettingStartedPages = [ , @@ -19,9 +20,18 @@ const gettingStartedPages = [ ]; -// TODO: Convert to View -export default function GettingStarted({ page }) { +export interface GettingStartedWelcomeViewParams { + page: string; +} + +export type GettingStartedWelcomeViewProps = + CommonRouteProps; + +export function GettingStartedWelcomeView({ + params: { page: pageParam } +}: GettingStartedWelcomeViewProps) { const { navigate } = useLocation(); + const page = Number(pageParam); // animate content sice const [contentSize, setContentSize] = useState(0); @@ -75,7 +85,7 @@ export default function GettingStarted({ page }) { /> ))} - navigate(page + 1)}> + navigateToPage(page + 1)}> {browser.i18n.getMessage(page + 1 < 4 ? "next" : "done")} diff --git a/src/routes/welcome/index.tsx b/src/routes/welcome/index.tsx index 6dcd080f3..cfc47ff28 100644 --- a/src/routes/welcome/index.tsx +++ b/src/routes/welcome/index.tsx @@ -17,8 +17,7 @@ import { popoverAnimation } from "~components/popup/WalletSwitcher"; import { PageType, trackPage } from "~utils/analytics"; import { useLocation } from "~wallets/router/router.utils"; -// TODO: Convert to View -export default function Home() { +export function HomeWelcomeView() { const { navigate } = useLocation(); // button refs diff --git a/src/routes/welcome/load/password.tsx b/src/routes/welcome/load/password.tsx index 4fe34ea67..afbeb1d3b 100644 --- a/src/routes/welcome/load/password.tsx +++ b/src/routes/welcome/load/password.tsx @@ -69,6 +69,9 @@ export default function Password() { // set password in global context setPassword(passwordInput.state); + console.log(params); + console.log(`/${params.setup}/${Number(params.page) + 1}`); + // next page navigate(`/${params.setup}/${Number(params.page) + 1}`); } diff --git a/src/routes/welcome/setup.tsx b/src/routes/welcome/setup.tsx index 3f46497ab..a9dd72266 100644 --- a/src/routes/welcome/setup.tsx +++ b/src/routes/welcome/setup.tsx @@ -28,6 +28,7 @@ import { defaultGateway } from "~gateways/gateway"; import Pagination, { Status } from "~components/Pagination"; import { getWalletKeyLength } from "~wallets"; import { useLocation } from "~wallets/router/router.utils"; +import type { CommonRouteProps } from "~wallets/router/router.types"; /** Wallet generate pages */ const generatePages = [ @@ -54,11 +55,27 @@ const loadTitles = [ "done" ]; -// TODO: Convert to View -export default function Setup({ setupMode, page }: Props) { +export interface SetupWelcomeViewParams { + setupMode: "generate" | "load"; + page: string; +} + +export type SetupWelcomeViewProps = CommonRouteProps; + +export function SetupWelcomeView({ + params: { setupMode, page: pageParam } +}: SetupWelcomeViewProps) { const { navigate } = useLocation(); - // TODO: Replace with useParams: - const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); + const page = Number(pageParam); + + // redirect if not on a page + useEffect(() => { + // wrong setup mode + if (Number.isNaN(page) || page < 1 || page > pageCount) { + console.log("REDIRECT 1"); + navigate(`/${setupMode}/1`); + } + }, [setupMode, page]); // total page count const pageCount = useMemo( @@ -70,23 +87,16 @@ export default function Setup({ setupMode, page }: Props) { [setupMode] ); - // redirect if not on a page - useEffect(() => { - // wrong setup mode - if (Number.isNaN(page) || page < 1 || page > pageCount) { - navigate(`/${setupMode}/1`); - } - }, [setupMode, page]); - // temporarily stored password const [password, setPassword] = useState(""); // check if the user is on the wrong page without a password useEffect(() => { if (page !== 1 && password === "") { + console.log("REDIRECT 2", setupMode); navigate(`/${setupMode}/1`); } - }, [page, password]); + }, [page, password, setupMode]); // is the setup mode "wallet generation" const [isGenerateWallet] = useRoute("/generate/:page"); @@ -98,7 +108,8 @@ export default function Setup({ setupMode, page }: Props) { const [generatedWallet, setGeneratedWallet] = useState({}); const navigateToPreviousPage = () => { - navigate(`/${params.setup}/${page - 1}`); + console.log("REDIRECT 3"); + navigate(`/${setupMode}/${page - 1}`); }; async function generateWallet() { @@ -325,8 +336,3 @@ interface GeneratedWallet { mnemonic?: string; jwk?: JWKInterface; } - -interface Props { - setupMode: "generate" | "load"; - page: number; -} diff --git a/src/routes/welcome/start/index.tsx b/src/routes/welcome/start/index.tsx index 8750a28e2..c3069ad2a 100644 --- a/src/routes/welcome/start/index.tsx +++ b/src/routes/welcome/start/index.tsx @@ -1,6 +1,5 @@ import { ButtonV2, Spacer, Text } from "@arconnect/components"; import { ArrowRightIcon } from "@iconicicons/react"; -import { useRoute } from "wouter"; import { motion } from "framer-motion"; import { useMemo } from "react"; import Screenshots from "~components/welcome/Screenshots"; @@ -9,22 +8,28 @@ import styled from "styled-components"; import Ecosystem from "./ecosystem"; import Arweave from "./arweave"; import { useLocation } from "~wallets/router/router.utils"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -// TODO: Convert to View -export default function Start() { +export interface StartWelcomeViewParams { + page: string; +} + +export type StartWelcomeViewProps = CommonRouteProps; + +export function StartWelcomeView({ + params: { page: pageParam } +}: StartWelcomeViewProps) { const { navigate } = useLocation(); - // TODO: Replace with useParams: - const [, params] = useRoute<{ page: string }>("/start/:page"); // page of the setup const page = useMemo(() => { - const page = Number(params?.page || "1"); + const page = Number(pageParam || "1"); // TODO: This should be a redirect: if (![1, 2, 3].includes(page)) return 1; return page; - }, [params]); + }, [pageParam]); // active page const activePage = useMemo( diff --git a/src/tabs/welcome.tsx b/src/tabs/welcome.tsx index e199f877f..e0f849509 100644 --- a/src/tabs/welcome.tsx +++ b/src/tabs/welcome.tsx @@ -1,40 +1,37 @@ import { useHashLocation } from "wouter/use-hash-location"; -import { Router, Route } from "wouter"; - -import Home from "~routes/welcome"; -import Start from "~routes/welcome/start"; -import Setup from "~routes/welcome/setup"; -import GettingStarted from "~routes/welcome/gettingStarted"; +import { Route, Router as Wouter } from "wouter"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; +import { BodyScroller } from "~wallets/router/router.utils"; +import { AnimatePresence } from "framer-motion"; +import { Routes } from "~wallets/router/routes.component"; +import { WELCOME_ROUTES } from "~wallets/router/welcome/welcome.routes"; -export default function Welcome() { - useRemoveCover(); +export function ArConnectWelcomeApp() { + return ; +} - // TODO: Make sure the router still works without the custom matcher: +export function ArConnectWelcomeAppRoot() { + useRemoveCover(); return ( - - - - - {(params: { page: string }) => ( - - )} - - - - {(params: { setupMode: "generate" | "load"; page: string }) => ( - - )} - - + + + + + + + ); } +export default ArConnectWelcomeAppRoot; + +// TODO: Make sure the router still works without the custom matcher: + /* import makeCachedMatcher from "wouter/matcher"; diff --git a/src/wallets/router/welcome/welcome.routes.ts b/src/wallets/router/welcome/welcome.routes.ts new file mode 100644 index 000000000..32f0fceb0 --- /dev/null +++ b/src/wallets/router/welcome/welcome.routes.ts @@ -0,0 +1,33 @@ +import { HomeWelcomeView } from "~routes/welcome"; +import { GettingStartedWelcomeView } from "~routes/welcome/gettingStarted"; +import { SetupWelcomeView } from "~routes/welcome/setup"; +import { StartWelcomeView } from "~routes/welcome/start"; +import type { RouteConfig } from "~wallets/router/router.types"; + +// TODO: Update with functions to pass params and replace in all usages: + +export const WelcomePaths = { + Home: "/", + Start: "/start/:page", + GettingStarted: "/getting-started/:page", + Setup: "/:setupMode/:page" +} as const; + +export const WELCOME_ROUTES = [ + { + path: WelcomePaths.Home, + component: HomeWelcomeView + }, + { + path: WelcomePaths.Start, + component: StartWelcomeView + }, + { + path: WelcomePaths.GettingStarted, + component: GettingStartedWelcomeView + }, + { + path: WelcomePaths.Setup, + component: SetupWelcomeView + } +] as const satisfies RouteConfig[]; From fd9dd956e1d4267514fd781dfda63f50e3936811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 3 Dec 2024 15:16:56 +0100 Subject: [PATCH 088/142] Add types for routes. --- src/routes/welcome/generate/backup.tsx | 14 +- src/routes/welcome/generate/confirm.tsx | 12 +- src/routes/welcome/generate/done.tsx | 3 +- src/routes/welcome/gettingStarted.tsx | 32 +++-- .../welcome/gettingStarted/completed.tsx | 2 +- src/routes/welcome/gettingStarted/connect.tsx | 2 +- .../gettingStarted/enableNotifications.tsx | 2 +- src/routes/welcome/gettingStarted/explore.tsx | 2 +- src/routes/welcome/load/done.tsx | 3 +- src/routes/welcome/load/password.tsx | 15 +- src/routes/welcome/load/theme.tsx | 13 +- src/routes/welcome/load/wallets.tsx | 14 +- src/routes/welcome/setup.tsx | 130 ++++++++---------- src/routes/welcome/start/arweave.tsx | 2 +- src/routes/welcome/start/ecosystem.tsx | 2 +- src/routes/welcome/start/index.tsx | 116 ++++++++-------- src/routes/welcome/start/screenshots.tsx | 5 + src/tabs/auth.tsx | 2 +- src/tabs/welcome.tsx | 16 --- src/wallets/router/auth/auth.routes.ts | 14 +- src/wallets/router/components/link/Link.tsx | 11 ++ .../router/components/redirect/Redirect.tsx | 11 ++ src/wallets/router/popup/popup.routes.ts | 41 +++++- src/wallets/router/router.types.ts | 14 +- src/wallets/router/router.utils.ts | 18 ++- src/wallets/router/routes.component.tsx | 10 +- src/wallets/router/welcome/welcome.routes.ts | 8 +- 27 files changed, 306 insertions(+), 208 deletions(-) create mode 100644 src/routes/welcome/start/screenshots.tsx create mode 100644 src/wallets/router/components/link/Link.tsx create mode 100644 src/wallets/router/components/redirect/Redirect.tsx diff --git a/src/routes/welcome/generate/backup.tsx b/src/routes/welcome/generate/backup.tsx index bef00011d..eaf706697 100644 --- a/src/routes/welcome/generate/backup.tsx +++ b/src/routes/welcome/generate/backup.tsx @@ -1,7 +1,7 @@ import { ButtonV2, Spacer, Text } from "@arconnect/components"; import { useRoute } from "wouter"; import { useContext, useEffect, useRef, useState } from "react"; -import { WalletContext } from "../setup"; +import { WalletContext, type SetupWelcomeViewParams } from "../setup"; import Paragraph from "~components/Paragraph"; import browser from "webextension-polyfill"; import styled from "styled-components"; @@ -15,12 +15,14 @@ import { } from "@iconicicons/react"; import { PageType, trackPage } from "~utils/analytics"; import { useLocation } from "~wallets/router/router.utils"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -// TODO: Convert to View -export default function Backup() { +export type BackupWelcomeViewProps = CommonRouteProps; + +export function BackupWelcomeView({ params }: BackupWelcomeViewProps) { const { navigate } = useLocation(); // TODO: Replace with useParams: - const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); + // const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); // seed blur status const [shown, setShown] = useState(false); @@ -66,7 +68,9 @@ export default function Backup() { navigate(`/${params.setup}/${Number(params.page) + 1}`)} + onClick={() => + navigate(`/${params.setupMode}/${Number(params.page) + 1}`) + } > {browser.i18n.getMessage("next")} diff --git a/src/routes/welcome/generate/confirm.tsx b/src/routes/welcome/generate/confirm.tsx index a468acc42..2e3258b0d 100644 --- a/src/routes/welcome/generate/confirm.tsx +++ b/src/routes/welcome/generate/confirm.tsx @@ -2,18 +2,20 @@ import { ButtonV2, Spacer, Text, useToasts } from "@arconnect/components"; import { ArrowRightIcon } from "@iconicicons/react"; import { useRoute } from "wouter"; import { useContext, useEffect, useState } from "react"; -import { WalletContext } from "../setup"; +import { WalletContext, type SetupWelcomeViewParams } from "../setup"; import SeedInput from "~components/SeedInput"; import Paragraph from "~components/Paragraph"; import browser from "webextension-polyfill"; import { PageType, trackPage } from "~utils/analytics"; import { useLocation } from "~wallets/router/router.utils"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -// TODO: Convert to View -export default function Confirm() { +export type ConfirmWelcomeViewProps = CommonRouteProps; + +export function ConfirmWelcomeView({ params }: ConfirmWelcomeViewProps) { const { navigate } = useLocation(); // TODO: Replace with useParams: - const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); + // const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); // wallet context const { wallet: generatedWallet } = useContext(WalletContext); @@ -37,7 +39,7 @@ export default function Confirm() { } // continue - navigate(`/${params.setup}/${Number(params.page) + 1}`); + navigate(`/${params.setupMode}/${Number(params.page) + 1}`); } // Segment diff --git a/src/routes/welcome/generate/done.tsx b/src/routes/welcome/generate/done.tsx index 397a217dd..1094e0759 100644 --- a/src/routes/welcome/generate/done.tsx +++ b/src/routes/welcome/generate/done.tsx @@ -19,8 +19,7 @@ import { useStorage } from "@plasmohq/storage/hook"; import JSConfetti from "js-confetti"; import { useLocation } from "~wallets/router/router.utils"; -// TODO: Convert to View -export default function Done() { +export function GenerateDoneWelcomeView() { const { navigate } = useLocation(); // wallet context diff --git a/src/routes/welcome/gettingStarted.tsx b/src/routes/welcome/gettingStarted.tsx index 155a6806b..7ffd124ff 100644 --- a/src/routes/welcome/gettingStarted.tsx +++ b/src/routes/welcome/gettingStarted.tsx @@ -6,21 +6,23 @@ import browser from "webextension-polyfill"; import styled from "styled-components"; -import Completed from "./gettingStarted/completed"; -import Enabled from "./gettingStarted/enableNotifications"; -import Connect from "./gettingStarted/connect"; -import Explore from "./gettingStarted/explore"; +import { CompletedWelcomeView } from "./gettingStarted/completed"; +import { EnableNotificationsWelcomeView } from "./gettingStarted/enableNotifications"; +import { ConnectWelcomeView } from "./gettingStarted/connect"; +import { ExploreWelcomeView } from "./gettingStarted/explore"; import { useLocation } from "~wallets/router/router.utils"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { Redirect } from "~wallets/router/components/redirect/Redirect"; -const gettingStartedPages = [ - , - , - , - +const Views = [ + CompletedWelcomeView, + EnableNotificationsWelcomeView, + ExploreWelcomeView, + ConnectWelcomeView ]; export interface GettingStartedWelcomeViewParams { + // TODO: Use a nested router instead: page: string; } @@ -33,7 +35,7 @@ export function GettingStartedWelcomeView({ const { navigate } = useLocation(); const page = Number(pageParam); - // animate content sice + // animate content size const [contentSize, setContentSize] = useState(0); const contentRef = useCallback<(el: HTMLDivElement) => void>((el) => { if (!el) return; @@ -46,6 +48,10 @@ export function GettingStartedWelcomeView({ obs.observe(el); }, []); + if (isNaN(page) || page < 1 || page > 5) { + return ; + } + const navigateToPage = (pageNum: number) => { if (pageNum < 5) { navigate(`/getting-started/${pageNum}`); @@ -56,6 +62,8 @@ export function GettingStartedWelcomeView({ } }; + const View = Views[page - 1]; + return ( @@ -71,14 +79,14 @@ export function GettingStartedWelcomeView({ - {gettingStartedPages[page - 1]} +
{ e.preventDefault(); diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 79349fe7f..9830c92ee 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -183,7 +183,12 @@ export default function Transaction({ id: rawId, gw, message }: Props) { id: data.transaction.recipient, decimals: Number(tokenInfo.Denomination) }); - setTicker(tokenInfo.Ticker); + setTicker( + tokenInfo?.type === "collectible" || + tokenInfo?.Ticker === "ATOMIC" + ? tokenInfo.Name! + : tokenInfo.Ticker! + ); data.transaction.quantity = { ar: amount.toFixed(), winston: "" }; data.transaction.recipient = aoRecipient.value; } diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index 4ae02a656..bb7e70dc8 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -17,7 +17,7 @@ import type { Alarms } from "webextension-polyfill"; import type { KeystoneSigner } from "~wallets/hardware/keystone"; import browser from "webextension-polyfill"; import { fetchTokenByProcessId } from "~lib/transactions"; -import { tokenTypeRegistry } from "~tokens/token"; +import { tokenTypeRegistry, type TokenType } from "~tokens/token"; export type AoInstance = ReturnType; @@ -78,9 +78,13 @@ export function useAo() { return ao; } -export function useAoTokens( - refresh?: boolean -): [TokenInfoWithBalance[], boolean] { +export function useAoTokens({ + refresh, + type +}: { refresh?: boolean; type?: "asset" | "collectible" } = {}): [ + TokenInfoWithBalance[], + boolean +] { const [tokens, setTokens] = useState([]); const [balances, setBalances] = useState<{ id: string; balance: string }[]>( [] @@ -106,7 +110,7 @@ export function useAoTokens( instance: ExtensionStorage }); - const [aoTokens] = useStorage( + const [aoTokens] = useStorage( { key: "ao_tokens", instance: ExtensionStorage @@ -124,14 +128,22 @@ export function useAoTokens( } setTokens( - aoTokens.map((aoToken) => ({ - id: aoToken.processId, - balance: "0", - Ticker: aoToken.Ticker, - Name: aoToken.Name, - Denomination: Number(aoToken.Denomination || 0), - Logo: aoToken?.Logo - })) + aoTokens + .filter((t) => { + if (!type) return true; + else if (type === "asset") return t.type === "asset" || !t.type; + else if (type === "collectible") return t.type === "collectible"; + return false; + }) + .map((aoToken) => ({ + id: aoToken.processId, + balance: "0", + Ticker: aoToken.Ticker, + Name: aoToken.Name, + Denomination: Number(aoToken.Denomination || 0), + Logo: aoToken?.Logo, + type: aoToken.type || "asset" + })) ); } catch {} })(); @@ -147,24 +159,34 @@ export function useAoTokens( setLoading(true); try { const balances = await Promise.all( - tokens.map(async ({ id }) => { + tokens.map(async (token) => { try { const balance = await timeoutPromise( (async () => { - if (id === AO_NATIVE_TOKEN) { + if (token.id === AO_NATIVE_TOKEN) { const res = await getNativeTokenBalance(activeAddress); return res; } else { let balance: string; - if (refresh) { - const aoToken = await Token(id); + if (token.type === "collectible") { balance = ( - await aoToken.getBalance(activeAddress) + await getAoCollectibleBalance(token, activeAddress) ).toString(); } else { - balance = (await getAoTokenBalance(activeAddress, id)) - .toString() - .toString(); + if (refresh) { + const aoToken = await Token(token.id); + balance = ( + await aoToken.getBalance(activeAddress) + ).toString(); + } else { + balance = ( + await getAoTokenBalance( + activeAddress, + token.id, + token + ) + ).toString(); + } } if (balance) { return balance; @@ -178,7 +200,7 @@ export function useAoTokens( ); return { - id, + id: token.id, balance }; } catch (error) { @@ -187,9 +209,9 @@ export function useAoTokens( error?.message.includes("ERR_SSL_PROTOCOL_ERROR") || error?.message.includes("ERR_CONNECTION_CLOSED") ) { - return { id, balance: "" }; + return { id: token.id, balance: "" }; } - return { id, balance: null }; + return { id: token.id, balance: null }; } }) ); @@ -207,11 +229,15 @@ export function useAoTokens( export async function getAoTokenBalance( address: string, - process: string + process: string, + aoToken?: TokenInfo ): Promise { - const aoTokens = (await ExtensionStorage.get("ao_tokens")) || []; + if (!aoToken) { + const aoTokens = + (await ExtensionStorage.get("ao_tokens")) || []; - let aoToken = aoTokens.find((token) => token.processId === process); + aoToken = aoTokens.find((token) => token.processId === process); + } const res = await dryrun({ Id, @@ -251,6 +277,24 @@ export async function getAoTokenBalance( return new Quantity(0n, 12n); } +export async function getAoCollectibleBalance( + collectible: TokenInfoWithBalance, + address: string +): Promise { + const res = await dryrun({ + Id, + Owner: address, + process: collectible.processId || collectible.id, + tags: [{ name: "Action", value: "Balance" }], + data: JSON.stringify({ Target: address }) + }); + + const balance = res.Messages[0].Data; + return balance + ? new Quantity(BigInt(balance), BigInt(collectible.Denomination)) + : new Quantity(0, BigInt(collectible.Denomination)); +} + export async function getNativeTokenBalance(address: string): Promise { const res = await dryrun({ Id, @@ -468,20 +512,63 @@ export const aoTokensCacheHandler = async (alarmInfo?: Alarms.Alarm) => { ); if (res.Messages && Array.isArray(res.Messages)) { + let updatedToken: TokenInfo | undefined; + for (const msg of res.Messages as Message[]) { + // If we already found token info from Data, don't process Tags + if (updatedToken) break; + + // Try to parse from Data first + if (msg?.Data) { + try { + const data = JSON.parse(msg.Data); + const Ticker = data.Ticker || data.ticker; + const Name = data.Name || data.name; + const Denomination = data.Denomination || data.denomination; + const Logo = data.Logo || data.logo || token.processId; + const type = + typeof data?.transferable === "boolean" || + typeof data?.Transferable === "boolean" || + Ticker === "ATOMIC" + ? "collectible" + : "asset"; + + if (Ticker && Name) { + updatedToken = { + processId: token.processId, + Ticker, + Name, + Denomination: Number(Denomination || 0), + Logo, + lastUpdated: new Date().toISOString(), + type + } as TokenInfo; + break; + } + } catch {} + } + const Ticker = getTagValue("Ticker", msg.Tags); const Name = getTagValue("Name", msg.Tags); const Denomination = getTagValue("Denomination", msg.Tags); const Logo = getTagValue("Logo", msg.Tags); - const updatedToken = { + const Transferable = getTagValue("Transferable", msg.Tags); + + if (!Ticker && !Name) continue; + + updatedToken = { Name, Ticker, - Denomination: Number(Denomination), + Denomination: Number(Denomination || 0), processId: token.processId, Logo, - lastUpdated: new Date().toISOString() + lastUpdated: new Date().toISOString(), + type: Transferable || Ticker === "ATOMIC" ? "collectible" : "asset" }; + break; + } + if (updatedToken) { const index = updatedTokens.findIndex( (t) => t.processId === token.processId ); @@ -555,6 +642,7 @@ export interface TokenInfo { Denomination: number; processId?: string; lastUpdated?: string | null; + type?: "asset" | "collectible"; } export type TokenInfoWithProcessId = TokenInfo & { processId: string }; diff --git a/src/tokens/aoTokens/router.ts b/src/tokens/aoTokens/router.ts index 4bf9cfb4a..466c2de72 100644 --- a/src/tokens/aoTokens/router.ts +++ b/src/tokens/aoTokens/router.ts @@ -1,15 +1,66 @@ import { useEffect, useState } from "react"; -import { type TokenInfo } from "./ao"; +import { getTagValue, Id, Owner, type Message, type TokenInfo } from "./ao"; import { getAoTokens } from "~tokens"; -import { Token } from "ao-tokens"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; +import { dryrun } from "@permaweb/aoconnect/browser"; export async function getTokenInfo(id: string): Promise { - const token = (await Token(id)).info; - const denomination = Number(token.Denomination.toString()); - const tokenInfo: TokenInfo = { ...token, Denomination: denomination }; - return tokenInfo; + // query ao + const res = await dryrun({ + Id, + Owner, + process: id, + tags: [{ name: "Action", value: "Info" }] + }); + + // find message with token info + for (const msg of res.Messages as Message[]) { + if (msg?.Data) { + try { + const data = JSON.parse(msg.Data); + const Ticker = data.Ticker || data.ticker; + const Name = data.Name || data.name; + const Denomination = data.Denomination || data.denomination; + const Logo = data.Logo || data.logo || id; + const type = + typeof data?.transferable === "boolean" || + typeof data?.Transferable === "boolean" || + Ticker === "ATOMIC" + ? "collectible" + : "asset"; + + if (Ticker && Name) { + return { + processId: id, + Ticker, + Name, + Denomination: Number(Denomination || 0), + Logo, + type + } as TokenInfo; + } + } catch {} + } + const Ticker = getTagValue("Ticker", msg.Tags); + const Name = getTagValue("Name", msg.Tags); + const Denomination = getTagValue("Denomination", msg.Tags); + const Logo = getTagValue("Logo", msg.Tags); + const Transferable = getTagValue("Transferable", msg.Tags); + + if (!Ticker && !Name) continue; + + return { + processId: id, + Name, + Ticker, + Denomination: Number(Denomination || 0), + Logo, + type: Transferable || Ticker === "ATOMIC" ? "collectible" : "asset" + }; + } + + throw new Error("Could not load token info."); } export function useTokenIDs(): [string[], boolean] { diff --git a/src/tokens/aoTokens/sync.ts b/src/tokens/aoTokens/sync.ts index 11d0a5dca..8e65094e7 100644 --- a/src/tokens/aoTokens/sync.ts +++ b/src/tokens/aoTokens/sync.ts @@ -63,19 +63,48 @@ async function getTokenInfo(id: string): Promise { // find message with token info for (const msg of res.Messages as Message[]) { + if (msg?.Data) { + try { + const data = JSON.parse(msg.Data); + const Ticker = data.Ticker || data.ticker; + const Name = data.Name || data.name; + const Denomination = data.Denomination || data.denomination; + const Logo = data.Logo || data.logo || id; + const type = + typeof data?.transferable === "boolean" || + typeof data?.Transferable === "boolean" || + Ticker === "ATOMIC" + ? "collectible" + : "asset"; + + if (Ticker && Name) { + return { + processId: id, + Ticker, + Name, + Denomination: Number(Denomination || 0), + Logo, + type + } as TokenInfo; + } + } catch {} + } const Ticker = getTagValue("Ticker", msg.Tags); const Name = getTagValue("Name", msg.Tags); const Denomination = getTagValue("Denomination", msg.Tags); const Logo = getTagValue("Logo", msg.Tags); + const Transferable = getTagValue("Transferable", msg.Tags); if (!Ticker && !Name) continue; // if the message was found, return the token details return { + processId: id, Name, Ticker, Denomination: Number(Denomination || 0), - Logo + Logo, + type: Transferable || Ticker === "ATOMIC" ? "collectible" : "asset" }; } From c3a02681aff14347ec80d4464126d13e309c5598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 4 Dec 2024 19:24:18 +0100 Subject: [PATCH 103/142] Add special prev/next/up values to navigate(). --- src/wallets/router/router.utils.ts | 43 ++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/wallets/router/router.utils.ts b/src/wallets/router/router.utils.ts index 5673d0bf2..511aff7ed 100644 --- a/src/wallets/router/router.utils.ts +++ b/src/wallets/router/router.utils.ts @@ -29,19 +29,52 @@ export function BodyScroller() { return null; } +export type NavigateAction = "prev" | "next" | "up"; + +function isNavigateAction( + to: ArConnectRoutePath | NavigateAction +): to is NavigateAction { + return !to.startsWith("/"); +} + export function useLocation() { - const [location, wavigate] = useWouterLocation(); + const [wocation, wavigate] = useWouterLocation(); const navigate = useCallback( ( - to: ArConnectRoutePath, + to: ArConnectRoutePath | NavigateAction, options?: { replace?: boolean; state?: S; search?: Record; } ) => { - let toPath = to; + let toPath = to as ArConnectRoutePath; + + if (isNavigateAction(to)) { + const toParts = to.split("/"); + const lastPart = toParts.pop(); + const parentPath = `/${toParts.join("/")}` as ArConnectRoutePath; + + if (to === "up") { + toPath = parentPath; + } else { + const index = parseInt(lastPart); + + if (isNaN(index)) + throw new Error( + `The current location "${location}" doesn't end with an index` + ); + + if (to === "prev") { + if (index === 0) throw new Error(`Index -1 out of bounds`); + + toPath = `${parentPath}/${index - 1}` as ArConnectRoutePath; + } else if (to === "next") { + toPath = `/${parentPath}/${index + 1}` as ArConnectRoutePath; + } + } + } if (options?.search) { const searchParams = new URLSearchParams(); @@ -57,7 +90,7 @@ export function useLocation() { return wavigate(toPath, options); }, - [wavigate] + [wocation, wavigate] ); const back = useCallback(() => { @@ -65,7 +98,7 @@ export function useLocation() { }, []); return { - location, + location: wocation, navigate, back } as { From c61f654dbfb289a096abb383397c391b0761f804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 4 Dec 2024 19:25:43 +0100 Subject: [PATCH 104/142] Add special prev/next/up values to navigate(). --- src/wallets/router/router.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallets/router/router.utils.ts b/src/wallets/router/router.utils.ts index 511aff7ed..e4b72ebb2 100644 --- a/src/wallets/router/router.utils.ts +++ b/src/wallets/router/router.utils.ts @@ -52,7 +52,7 @@ export function useLocation() { let toPath = to as ArConnectRoutePath; if (isNavigateAction(to)) { - const toParts = to.split("/"); + const toParts = wocation.split("/"); const lastPart = toParts.pop(); const parentPath = `/${toParts.join("/")}` as ArConnectRoutePath; From db46bbee2e8775dcf072d1eca543b9d62adf1fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 4 Dec 2024 19:46:41 +0100 Subject: [PATCH 105/142] Replace Promise.all with Promise.allSettled. --- src/components/dashboard/Reset.tsx | 2 +- src/wallets/index.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/dashboard/Reset.tsx b/src/components/dashboard/Reset.tsx index 0e2bbac69..9b6ebfb0f 100644 --- a/src/components/dashboard/Reset.tsx +++ b/src/components/dashboard/Reset.tsx @@ -31,7 +31,7 @@ export default function Reset() { ); // remove all keys - await Promise.all( + await Promise.allSettled( allStoredKeys.map((key) => ExtensionStorage.remove(key)) ); diff --git a/src/wallets/index.ts b/src/wallets/index.ts index 082753eda..7e77eeca9 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -243,7 +243,9 @@ export async function openOrSelectWelcomePage(force = false) { ); // remove all keys - await Promise.all(allStoredKeys.map((key) => ExtensionStorage.remove(key))); + await Promise.allSettled( + allStoredKeys.map((key) => ExtensionStorage.remove(key)) + ); const url = browser.runtime.getURL("tabs/welcome.html"); const welcomePageTabs = await browser.tabs.query({ url }); From fccb2f72c66031a387ddf828dffa9027ff4448d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 4 Dec 2024 21:19:22 +0100 Subject: [PATCH 106/142] Remove value from binary props (isQuickSetting). --- src/routes/popup/settings/contacts/[address]/index.tsx | 2 +- src/routes/popup/settings/contacts/index.tsx | 2 +- src/routes/popup/settings/contacts/new.tsx | 2 +- src/routes/popup/settings/tokens/new.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/popup/settings/contacts/[address]/index.tsx b/src/routes/popup/settings/contacts/[address]/index.tsx index ae848290d..9d2de34da 100644 --- a/src/routes/popup/settings/contacts/[address]/index.tsx +++ b/src/routes/popup/settings/contacts/[address]/index.tsx @@ -22,7 +22,7 @@ export function ContactSettingsView({ params }: ContactSettingsViewProps) { back={() => navigate("/quick-settings/contacts")} /> - + ); diff --git a/src/routes/popup/settings/contacts/index.tsx b/src/routes/popup/settings/contacts/index.tsx index f53738ff8..5b97efbef 100644 --- a/src/routes/popup/settings/contacts/index.tsx +++ b/src/routes/popup/settings/contacts/index.tsx @@ -14,7 +14,7 @@ export function ContactsView() { back={() => navigate("/quick-settings")} /> - + ); diff --git a/src/routes/popup/settings/contacts/new.tsx b/src/routes/popup/settings/contacts/new.tsx index 128cd1ad8..e00a32c57 100644 --- a/src/routes/popup/settings/contacts/new.tsx +++ b/src/routes/popup/settings/contacts/new.tsx @@ -14,7 +14,7 @@ export function NewContactView() { back={() => navigate("/quick-settings/contacts")} /> - + ); diff --git a/src/routes/popup/settings/tokens/new.tsx b/src/routes/popup/settings/tokens/new.tsx index 1a2ab7253..0fed2537d 100644 --- a/src/routes/popup/settings/tokens/new.tsx +++ b/src/routes/popup/settings/tokens/new.tsx @@ -14,7 +14,7 @@ export function NewTokenSettingsView() { back={() => navigate("/quick-settings/tokens")} /> - + ); From bd0865272c67f285bc25e2139e944b1104bd332a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 4 Dec 2024 21:30:37 +0100 Subject: [PATCH 107/142] Update useLocation() / navigate() to accept number paths. --- src/wallets/router/router.utils.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/wallets/router/router.utils.ts b/src/wallets/router/router.utils.ts index e4b72ebb2..37621aa6a 100644 --- a/src/wallets/router/router.utils.ts +++ b/src/wallets/router/router.utils.ts @@ -29,12 +29,12 @@ export function BodyScroller() { return null; } -export type NavigateAction = "prev" | "next" | "up"; +export type NavigateAction = "prev" | "next" | "up" | number; function isNavigateAction( to: ArConnectRoutePath | NavigateAction ): to is NavigateAction { - return !to.startsWith("/"); + return typeof to === "number" || !to.startsWith("/"); } export function useLocation() { @@ -58,21 +58,25 @@ export function useLocation() { if (to === "up") { toPath = parentPath; - } else { - const index = parseInt(lastPart); + } else if (typeof to === "string") { + const page = parseInt(lastPart); - if (isNaN(index)) + if (isNaN(page)) throw new Error( `The current location "${location}" doesn't end with an index` ); if (to === "prev") { - if (index === 0) throw new Error(`Index -1 out of bounds`); + if (page === 1) throw new Error(`Page 0 out of bounds`); - toPath = `${parentPath}/${index - 1}` as ArConnectRoutePath; + toPath = `${parentPath}/${page - 1}` as ArConnectRoutePath; } else if (to === "next") { - toPath = `/${parentPath}/${index + 1}` as ArConnectRoutePath; + toPath = `/${parentPath}/${page + 1}` as ArConnectRoutePath; } + } else { + if (to <= 0) throw new Error(`Page ${to} out of bounds`); + + toPath = `${parentPath}/${to}` as ArConnectRoutePath; } } From 2c67339cb48247f0fa81d902269dad4b2d218d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Wed, 4 Dec 2024 21:34:12 +0100 Subject: [PATCH 108/142] Remove console.log()s. --- src/routes/welcome/setup.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/routes/welcome/setup.tsx b/src/routes/welcome/setup.tsx index 9fddf125a..143e3eccd 100644 --- a/src/routes/welcome/setup.tsx +++ b/src/routes/welcome/setup.tsx @@ -163,15 +163,12 @@ export function SetupWelcomeView({ params }: SetupWelcomeViewProps) { page > pageCount || (page !== 1 && password === "") ) { - console.log("REDIRECT 1"); return ; } if (setupMode !== "generate" && setupMode !== "load") { - console.log("REDIRECT 2"); return ; } - console.log("CurrentView"); const CurrentView = ViewsBySetupMode[setupMode][page - 1]; From 8176cffafc7a20e57b24d910c45f8635963ad9fc Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 5 Dec 2024 13:18:30 +0545 Subject: [PATCH 109/142] refactor: use type to show name or ticker --- src/lib/transactions.ts | 3 +- src/routes/popup/notifications.tsx | 2 +- src/routes/popup/transaction/[id].tsx | 3 +- src/tokens/aoTokens/ao.ts | 62 +++------------------------ src/tokens/aoTokens/router.ts | 22 ++++++---- src/tokens/aoTokens/sync.ts | 59 ++----------------------- 6 files changed, 25 insertions(+), 126 deletions(-) diff --git a/src/lib/transactions.ts b/src/lib/transactions.ts index 13a92812c..2e01d6fde 100644 --- a/src/lib/transactions.ts +++ b/src/lib/transactions.ts @@ -131,8 +131,7 @@ const processAoTransaction = async ( const quantityTag = transaction.node.tags.find( (tag) => tag.name === "Quantity" ); - const isCollectible = - tokenData?.type === "collectible" || tokenData?.Ticker === "ATOMIC"; + const isCollectible = tokenData?.type === "collectible"; return { ...transaction, diff --git a/src/routes/popup/notifications.tsx b/src/routes/popup/notifications.tsx index 455cc8c96..7bb5fa266 100644 --- a/src/routes/popup/notifications.tsx +++ b/src/routes/popup/notifications.tsx @@ -110,7 +110,7 @@ export default function Notifications() { quantityTransfered = notification.quantity; } else { ticker = - token?.type === "collectible" || token?.Ticker === "ATOMIC" + token?.type === "collectible" ? token.Name! || token.Ticker! : token.Ticker! || token.Name!; quantityTransfered = balanceToFractioned( diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 9830c92ee..f0bf2bfe1 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -184,8 +184,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) { decimals: Number(tokenInfo.Denomination) }); setTicker( - tokenInfo?.type === "collectible" || - tokenInfo?.Ticker === "ATOMIC" + tokenInfo?.type === "collectible" ? tokenInfo.Name! : tokenInfo.Ticker! ); diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index bb7e70dc8..39c5f95cb 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -17,7 +17,7 @@ import type { Alarms } from "webextension-polyfill"; import type { KeystoneSigner } from "~wallets/hardware/keystone"; import browser from "webextension-polyfill"; import { fetchTokenByProcessId } from "~lib/transactions"; -import { tokenTypeRegistry, type TokenType } from "~tokens/token"; +import { getTokenInfoFromData } from "./router"; export type AoInstance = ReturnType; @@ -512,61 +512,11 @@ export const aoTokensCacheHandler = async (alarmInfo?: Alarms.Alarm) => { ); if (res.Messages && Array.isArray(res.Messages)) { - let updatedToken: TokenInfo | undefined; - - for (const msg of res.Messages as Message[]) { - // If we already found token info from Data, don't process Tags - if (updatedToken) break; - - // Try to parse from Data first - if (msg?.Data) { - try { - const data = JSON.parse(msg.Data); - const Ticker = data.Ticker || data.ticker; - const Name = data.Name || data.name; - const Denomination = data.Denomination || data.denomination; - const Logo = data.Logo || data.logo || token.processId; - const type = - typeof data?.transferable === "boolean" || - typeof data?.Transferable === "boolean" || - Ticker === "ATOMIC" - ? "collectible" - : "asset"; - - if (Ticker && Name) { - updatedToken = { - processId: token.processId, - Ticker, - Name, - Denomination: Number(Denomination || 0), - Logo, - lastUpdated: new Date().toISOString(), - type - } as TokenInfo; - break; - } - } catch {} - } - - const Ticker = getTagValue("Ticker", msg.Tags); - const Name = getTagValue("Name", msg.Tags); - const Denomination = getTagValue("Denomination", msg.Tags); - const Logo = getTagValue("Logo", msg.Tags); - const Transferable = getTagValue("Transferable", msg.Tags); - - if (!Ticker && !Name) continue; - - updatedToken = { - Name, - Ticker, - Denomination: Number(Denomination || 0), - processId: token.processId, - Logo, - lastUpdated: new Date().toISOString(), - type: Transferable || Ticker === "ATOMIC" ? "collectible" : "asset" - }; - break; - } + const tokenInfo = getTokenInfoFromData(res, token.processId); + const updatedToken = { + ...tokenInfo, + lastUpdated: new Date().toISOString() + }; if (updatedToken) { const index = updatedTokens.findIndex( diff --git a/src/tokens/aoTokens/router.ts b/src/tokens/aoTokens/router.ts index 466c2de72..82ab75ecc 100644 --- a/src/tokens/aoTokens/router.ts +++ b/src/tokens/aoTokens/router.ts @@ -5,15 +5,7 @@ import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { dryrun } from "@permaweb/aoconnect/browser"; -export async function getTokenInfo(id: string): Promise { - // query ao - const res = await dryrun({ - Id, - Owner, - process: id, - tags: [{ name: "Action", value: "Info" }] - }); - +export function getTokenInfoFromData(res: any, id: string): TokenInfo { // find message with token info for (const msg of res.Messages as Message[]) { if (msg?.Data) { @@ -63,6 +55,18 @@ export async function getTokenInfo(id: string): Promise { throw new Error("Could not load token info."); } +export async function getTokenInfo(id: string): Promise { + // query ao + const res = await dryrun({ + Id, + Owner, + process: id, + tags: [{ name: "Action", value: "Info" }] + }); + + return getTokenInfoFromData(res, id); +} + export function useTokenIDs(): [string[], boolean] { // all token ids const [tokenIDs, setTokenIDs] = useState([]); diff --git a/src/tokens/aoTokens/sync.ts b/src/tokens/aoTokens/sync.ts index 8e65094e7..f63c7bd7a 100644 --- a/src/tokens/aoTokens/sync.ts +++ b/src/tokens/aoTokens/sync.ts @@ -9,15 +9,9 @@ import { import type { GQLTransactionsResultInterface } from "ar-gql/dist/faces"; import { ExtensionStorage } from "~utils/storage"; import { getActiveAddress } from "~wallets"; -import { - getTagValue, - timeoutPromise, - type Message, - type TokenInfo, - Id, - Owner -} from "./ao"; +import { timeoutPromise, type TokenInfo, Id, Owner } from "./ao"; import { withRetry } from "~utils/retry"; +import { getTokenInfoFromData } from "./router"; /** Tokens storage name */ const AO_TOKENS = "ao_tokens"; @@ -61,54 +55,7 @@ async function getTokenInfo(id: string): Promise { }) ).json(); - // find message with token info - for (const msg of res.Messages as Message[]) { - if (msg?.Data) { - try { - const data = JSON.parse(msg.Data); - const Ticker = data.Ticker || data.ticker; - const Name = data.Name || data.name; - const Denomination = data.Denomination || data.denomination; - const Logo = data.Logo || data.logo || id; - const type = - typeof data?.transferable === "boolean" || - typeof data?.Transferable === "boolean" || - Ticker === "ATOMIC" - ? "collectible" - : "asset"; - - if (Ticker && Name) { - return { - processId: id, - Ticker, - Name, - Denomination: Number(Denomination || 0), - Logo, - type - } as TokenInfo; - } - } catch {} - } - const Ticker = getTagValue("Ticker", msg.Tags); - const Name = getTagValue("Name", msg.Tags); - const Denomination = getTagValue("Denomination", msg.Tags); - const Logo = getTagValue("Logo", msg.Tags); - const Transferable = getTagValue("Transferable", msg.Tags); - - if (!Ticker && !Name) continue; - - // if the message was found, return the token details - return { - processId: id, - Name, - Ticker, - Denomination: Number(Denomination || 0), - Logo, - type: Transferable || Ticker === "ATOMIC" ? "collectible" : "asset" - }; - } - - throw new Error("Could not load token info."); + return getTokenInfoFromData(res, id); } function getNoticeTransactionsQuery( From 67269670fa0c671828ee59af12821703d32969c7 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 5 Dec 2024 13:25:38 +0545 Subject: [PATCH 110/142] refactor: recheck if collectibles are actually collectibles --- src/tokens/aoTokens/sync.ts | 95 ++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/tokens/aoTokens/sync.ts b/src/tokens/aoTokens/sync.ts index f63c7bd7a..ff0724b64 100644 --- a/src/tokens/aoTokens/sync.ts +++ b/src/tokens/aoTokens/sync.ts @@ -94,6 +94,96 @@ function getNoticeTransactionsQuery( }`; } +function getCollectiblesQuery() { + return `query ($ids: [ID!]!) { + transactions( + ids: $ids + tags: [ + { name: "Data-Protocol", values: ["ao"] }, + { name: "Type", values: ["Process"] }, + { name: "Implements", values: ["ANS-110"] }, + { name: "Content-Type" } + ] + first: 100 + ) { + pageInfo { + hasNextPage + } + edges { + node { + id + tags { + name, + value + } + } + } + } + }`; +} + +async function verifyCollectiblesType(tokens: TokenInfo[], arweave: Arweave) { + const batchSize = 100; + + // Get IDs of tokens that are already marked as collectibles + const idsToCheck = tokens + .filter((token) => token.type === "collectible") + .map((token) => token.processId); + + const collectibleIds = new Set(); + const verifiedIds = new Set(); + + if (idsToCheck.length === 0) return tokens; + + const totalBatches = Math.ceil(idsToCheck.length / batchSize); + + // Process IDs in batches + for (let batch = 0; batch < totalBatches; batch++) { + try { + const startIndex = batch * batchSize; + const currentBatch = idsToCheck.slice(startIndex, startIndex + batchSize); + + const query = getCollectiblesQuery(); + const transactions = await withRetry(async () => { + const response = await arweave.api.post("/graphql", { + query, + variables: { ids: currentBatch } + }); + + return response.data.data + .transactions as GQLTransactionsResultInterface; + }, 2); + + // Mark all IDs in this batch as verified + currentBatch.forEach((id) => verifiedIds.add(id)); + + if (transactions.edges.length > 0) { + const processIds = transactions.edges.map((edge) => edge.node.id); + processIds.forEach((processId) => collectibleIds.add(processId)); + } + } catch (error) { + console.error( + `Failed to get transactions for batch ${batch}, error:`, + error + ); + continue; + } + } + + tokens = tokens.map((token) => { + // Only modify tokens we've successfully verified + if (token.type === "collectible" && verifiedIds.has(token.processId)) { + if (collectibleIds.has(token.processId)) { + return { ...token, type: "collectible" }; + } + return { ...token, type: "asset" }; + } + return token; + }); + + return tokens; +} + async function getNoticeTransactions( arweave: Arweave, address: string, @@ -329,11 +419,14 @@ export async function importAoTokens(alarm: Alarms.Alarm) { ); } - const newTokens = updatedTokens.filter( + let newTokens = updatedTokens.filter( (token) => !tokenIdstoExclude.has(token.processId) ); if (newTokens.length === 0) return; + // verify collectible type + newTokens = await verifyCollectiblesType(newTokens, arweave); + newTokens.forEach((token) => aoTokens.push(token)); await ExtensionStorage.set(AO_TOKENS, aoTokens); From 71af4ee1812a52238ad3b5a878772e7f7e0e6b99 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 5 Dec 2024 14:21:47 +0545 Subject: [PATCH 111/142] refactor: update collectible links & icons --- src/routes/popup/collectible/[id].tsx | 121 +++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 4 deletions(-) diff --git a/src/routes/popup/collectible/[id].tsx b/src/routes/popup/collectible/[id].tsx index b7b83c891..a43be6e9e 100644 --- a/src/routes/popup/collectible/[id].tsx +++ b/src/routes/popup/collectible/[id].tsx @@ -1,4 +1,3 @@ -import { EyeIcon, GlobeIcon } from "@iconicicons/react"; import { concatGatewayURL } from "~gateways/utils"; import { Section, Spacer, Text } from "@arconnect/components"; import { useEffect, useMemo, useState } from "react"; @@ -114,12 +113,12 @@ export default function Collectible({ id }: Props) { - + AO Link - - + + Viewblock @@ -195,3 +194,117 @@ const BazarIcon = () => ( ); + +const ViewBlockIcon = () => ( + + + + + + + + + + + + + + + + + + + +); + +const AoLinkIcon = () => ( + + + + + + + + + + + + + + + + + +); From 3b225de4b3a6b5228f52b9847ef44de589059910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 6 Dec 2024 15:42:44 +0100 Subject: [PATCH 112/142] - Fix casing issue in subscriptionDetails.tsx SVG. - Move and into . - Fix rendering issue in making it re-render constantly. - Implement routing overrides. - Use routing overrides for the cover, loading, unlock and unlocked states of the Popup and Auth apps. - Get rid of useNowallets() and useDecryptionKey() in favour of the new useWallets() hook and . --- src/components/HeadAuth.tsx | 15 ++- src/components/arlocal/Transaction.tsx | 13 +- .../page/common/loading/loading.view.tsx | 6 +- src/components/page/page.utils.tsx | 5 +- src/popup.tsx | 54 +++----- src/routes/auth/loading.tsx | 18 +++ src/routes/auth/unlock.tsx | 2 - .../subscriptions/subscriptionDetails.tsx | 6 +- src/routes/popup/unlock.tsx | 2 - src/tabs/arlocal.tsx | 11 +- src/tabs/auth.tsx | 62 +++------- src/tabs/dashboard.tsx | 29 +++-- src/tabs/devtools.tsx | 7 +- src/utils/auth/auth.provider.tsx | 40 +++--- src/utils/misc.ts | 1 + src/utils/wallets/wallets.hooks.ts | 6 + src/utils/wallets/wallets.provider.tsx | 117 ++++++++++++++++++ src/wallets/index.ts | 32 ----- src/wallets/router/auth/auth-router.hook.ts | 16 ++- src/wallets/router/auth/auth.routes.ts | 7 ++ .../router/extension/extension-router.hook.ts | 31 +++++ .../router/extension/extension.routes.tsx | 42 +++++++ src/wallets/router/popup/popup.routes.ts | 7 ++ src/wallets/router/router.types.ts | 8 +- src/wallets/router/router.utils.ts | 17 ++- src/wallets/router/routes.component.tsx | 13 +- .../browser-extension-wallet-setup.hook.ts | 73 ----------- src/wallets/setup/wallet-setup.types.ts | 1 - 28 files changed, 373 insertions(+), 268 deletions(-) create mode 100644 src/routes/auth/loading.tsx create mode 100644 src/utils/misc.ts create mode 100644 src/utils/wallets/wallets.hooks.ts create mode 100644 src/utils/wallets/wallets.provider.tsx create mode 100644 src/wallets/router/extension/extension-router.hook.ts create mode 100644 src/wallets/router/extension/extension.routes.tsx delete mode 100644 src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts delete mode 100644 src/wallets/setup/wallet-setup.types.ts diff --git a/src/components/HeadAuth.tsx b/src/components/HeadAuth.tsx index 9bada4a05..46b635aff 100644 --- a/src/components/HeadAuth.tsx +++ b/src/components/HeadAuth.tsx @@ -17,7 +17,7 @@ export interface HeadAuthProps { export const HeadAuth: React.FC = ({ title, back, - appInfo: appInfoProp = {} + appInfo: appInfoProp = { name: "ArConnect" } }) => { const [areLogsExpanded, setAreLogsExpanded] = useState(false); const { authRequests, currentAuthRequestIndex, setCurrentAuthRequestIndex } = @@ -27,12 +27,11 @@ export const HeadAuth: React.FC = ({ // `/src/routes/auth/connect.tsx` calls `addApp()`, so the `appInfo` prop (`appInfoProp`) is used as initial / // fallback value: + const { name: fallbackName, logo: fallbackLogo } = appInfoProp; + const { tabID = null, url = "" } = + authRequests[currentAuthRequestIndex] || {}; const [appInfo, setAppInfo] = useState(appInfoProp); - const authRequest = authRequests[currentAuthRequestIndex]; - - const { tabID = null, url = "" } = authRequest; - useEffect(() => { async function loadAppInfo() { if (!url) return; @@ -47,14 +46,14 @@ export const HeadAuth: React.FC = ({ setAppInfo({ name: appInfo.name || - appInfoProp.name || + fallbackName || new URL(url).hostname.split(".").slice(-2).join("."), - logo: appInfo.logo || appInfoProp.logo + logo: appInfo.logo || fallbackLogo }); } loadAppInfo(); - }, [url, appInfoProp]); + }, [url, fallbackName, fallbackLogo]); const handleAppInfoClicked = tabID ? () => { diff --git a/src/components/arlocal/Transaction.tsx b/src/components/arlocal/Transaction.tsx index e80d23a21..9bd575008 100644 --- a/src/components/arlocal/Transaction.tsx +++ b/src/components/arlocal/Transaction.tsx @@ -1,5 +1,5 @@ import type { CreateTransactionInterface } from "arweave/web/common"; -import { getActiveKeyfile, useDecryptionKey } from "~wallets"; +import { getActiveKeyfile } from "~wallets"; import { InputWithBtn, InputWrapper } from "./InputWrapper"; import { freeDecryptedWallet } from "~wallets/encryption"; import { PlusIcon, TrashIcon } from "@iconicicons/react"; @@ -21,10 +21,11 @@ import type Arweave from "arweave"; import browser from "webextension-polyfill"; import styled from "styled-components"; import copy from "copy-to-clipboard"; +import { useWallets } from "~utils/wallets/wallets.hooks"; -export default function Transaction({ arweave }: Props) { - // decryption key to check if a password is required - const [decryptionKey] = useDecryptionKey(); +export function ArLocalTransaction({ arweave }: Props) { + // used to check if a password is required + const { walletStatus } = useWallets(); // toast const { setToast } = useToasts(); @@ -99,7 +100,7 @@ export default function Transaction({ arweave }: Props) { } // unlock if there isn't a decryption key - if (!decryptionKey) { + if (walletStatus === "locked") { const unlockResult = await unlock(passwordInput.state); if (!unlockResult) { @@ -308,7 +309,7 @@ export default function Transaction({ arweave }: Props) { {browser.i18n.getMessage("dragAndDropFile")} - {!decryptionKey && ( + {walletStatus === "locked" && ( <> { ); }; -export const LoadingPage = withPage(LoadingView); - const DivWrapper = styled.div` display: flex; flex-direction: column; diff --git a/src/components/page/page.utils.tsx b/src/components/page/page.utils.tsx index 7fff31946..62b4bde37 100644 --- a/src/components/page/page.utils.tsx +++ b/src/components/page/page.utils.tsx @@ -1,6 +1,9 @@ import { Page } from "~components/page/page.component"; +import type { CommonRouteProps } from "~wallets/router/router.types"; -export function withPage

(Component: React.ComponentType

) { +export function withPage

( + Component: React.ComponentType

+) { const PageComponent = (props: P) => { return ( diff --git a/src/popup.tsx b/src/popup.tsx index 9b6d788d0..5b409c886 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,50 +1,34 @@ -import { UnlockPage } from "~routes/popup/unlock"; import { NavigationBar } from "~components/popup/Navigation"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; -import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; -import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; import { Routes } from "~wallets/router/routes.component"; import { POPUP_ROUTES } from "~wallets/router/popup/popup.routes"; import { Router as Wouter } from "wouter"; -import { BodyScroller } from "~wallets/router/router.utils"; -import { AnimatePresence } from "framer-motion"; -import { useHashLocation } from "wouter/use-hash-location"; +import { useExtensionLocation } from "~wallets/router/extension/extension-router.hook"; +import { WalletsProvider } from "~utils/wallets/wallets.provider"; +import { useEffect } from "react"; +import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; -interface ArConnectBrowserExtensionAppProps { - initialScreenType: InitialScreenType; -} - -export function ArConnectBrowserExtensionApp({ - initialScreenType -}: ArConnectBrowserExtensionAppProps) { - let content: React.ReactElement = null; +export function ArConnectBrowserExtensionApp() { + useEffect(() => { + handleSyncLabelsAlarm(); + }, []); - if (initialScreenType === "locked") { - content = ; - } else if (initialScreenType === "default") { - content = ( - <> - - - - ); - } - - return <>{content}; + return ( + <> + + + + ); } export function ArConnectBrowserExtensionAppRoot() { - const initialScreenType = useBrowserExtensionWalletSetUp(); - return ( - - - - - - - + + + + + ); } diff --git a/src/routes/auth/loading.tsx b/src/routes/auth/loading.tsx new file mode 100644 index 000000000..2f629b599 --- /dev/null +++ b/src/routes/auth/loading.tsx @@ -0,0 +1,18 @@ +import { LoadingView } from "~components/page/common/loading/loading.view"; +import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; +import browser from "webextension-polyfill"; + +export function LoadingAuthRequestView() { + const { lastCompletedAuthRequest } = useCurrentAuthRequest("any"); + + return ( + + ); +} diff --git a/src/routes/auth/unlock.tsx b/src/routes/auth/unlock.tsx index 4959e2247..943c12422 100644 --- a/src/routes/auth/unlock.tsx +++ b/src/routes/auth/unlock.tsx @@ -74,5 +74,3 @@ export function UnlockAuthRequestView() { ); } - -export const UnlockAuthRequestPage = withPage(UnlockAuthRequestView); diff --git a/src/routes/popup/subscriptions/subscriptionDetails.tsx b/src/routes/popup/subscriptions/subscriptionDetails.tsx index a55009b55..3d24a37f2 100644 --- a/src/routes/popup/subscriptions/subscriptionDetails.tsx +++ b/src/routes/popup/subscriptions/subscriptionDetails.tsx @@ -521,9 +521,9 @@ export const InfoCircle = () => ( diff --git a/src/routes/popup/unlock.tsx b/src/routes/popup/unlock.tsx index b71cc0b31..912bd7689 100644 --- a/src/routes/popup/unlock.tsx +++ b/src/routes/popup/unlock.tsx @@ -76,5 +76,3 @@ export function UnlockView() { ); } - -export const UnlockPage = withPage(UnlockView); diff --git a/src/tabs/arlocal.tsx b/src/tabs/arlocal.tsx index bbf7d3e3e..bbe18bf5f 100644 --- a/src/tabs/arlocal.tsx +++ b/src/tabs/arlocal.tsx @@ -5,7 +5,6 @@ import { urlToGateway } from "~gateways/utils"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { RefreshIcon } from "@iconicicons/react"; -import { useNoWallets } from "~wallets"; import { ButtonV2 as Button, InputV2 as Input, @@ -22,7 +21,7 @@ import { Title, Wrapper } from "./devtools"; -import Transaction from "~components/arlocal/Transaction"; +import { ArLocalTransaction } from "~components/arlocal/Transaction"; import NoWallets from "~components/devtools/NoWallets"; import Tutorial from "~components/arlocal/Tutorial"; import Mint from "~components/arlocal/Mint"; @@ -31,6 +30,7 @@ import Arweave from "arweave"; import axios from "axios"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; +import { useWallets } from "~utils/wallets/wallets.hooks"; export default function ArLocal() { useRemoveCover(); @@ -155,13 +155,12 @@ export default function ArLocal() { setMining(false); } - // no wallets - const noWallets = useNoWallets(); + const { walletStatus } = useWallets(); return ( - {noWallets && } + {walletStatus === "noWallets" && } ArLocal {browser.i18n.getMessage("devtools")} @@ -196,7 +195,7 @@ export default function ArLocal() { <> <Mint arweave={arweave} /> <Spacer y={1} /> - <Transaction arweave={arweave} /> + <ArLocalTransaction arweave={arweave} /> <Spacer y={1} /> <Button fullWidth secondary loading={mining} onClick={mine}> {browser.i18n.getMessage("mine")} diff --git a/src/tabs/auth.tsx b/src/tabs/auth.tsx index fbd91623e..a85e29a08 100644 --- a/src/tabs/auth.tsx +++ b/src/tabs/auth.tsx @@ -1,62 +1,32 @@ import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; -import { UnlockAuthRequestPage } from "~routes/auth/unlock"; import { AuthRequestsProvider } from "~utils/auth/auth.provider"; -import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; -import browser from "webextension-polyfill"; -import { LoadingPage } from "~components/page/common/loading/loading.view"; -import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; -import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; import { Routes } from "~wallets/router/routes.component"; import { useAuthRequestsLocation } from "~wallets/router/auth/auth-router.hook"; import { AUTH_ROUTES } from "~wallets/router/auth/auth.routes"; -import { BodyScroller } from "~wallets/router/router.utils"; -import { AnimatePresence } from "framer-motion"; import { Router as Wouter } from "wouter"; +import { WalletsProvider } from "~utils/wallets/wallets.provider"; +import { useExtensionStatusOverride } from "~wallets/router/extension/extension-router.hook"; +import { useEffect } from "react"; +import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; -interface AuthAppProps { - initialScreenType: InitialScreenType; -} - -export function AuthApp({ initialScreenType }: AuthAppProps) { - const { authRequest, lastCompletedAuthRequest } = - useCurrentAuthRequest("any"); - - let content: React.ReactElement = null; +export function AuthApp() { + useEffect(() => { + handleSyncLabelsAlarm(); + }, []); - if (initialScreenType === "locked") { - content = <UnlockAuthRequestPage />; - } else if (!authRequest) { - content = ( - <LoadingPage - label={browser.i18n.getMessage( - !lastCompletedAuthRequest || - lastCompletedAuthRequest.status === "accepted" - ? `${lastCompletedAuthRequest?.type || "default"}RequestLoading` - : `abortingRequestLoading` - )} - /> - ); - } else if (initialScreenType === "default") { - content = <Routes routes={AUTH_ROUTES} diffLocation />; - } - - return <>{content}</>; + return <Routes routes={AUTH_ROUTES} diffLocation />; } export function AuthAppRoot() { - const initialScreenType = useBrowserExtensionWalletSetUp(); - return ( <ArConnectThemeProvider> - <AuthRequestsProvider initialScreenType={initialScreenType}> - <Wouter hook={useAuthRequestsLocation}> - <BodyScroller /> - - <AnimatePresence initial={false}> - <AuthApp initialScreenType={initialScreenType} /> - </AnimatePresence> - </Wouter> - </AuthRequestsProvider> + <WalletsProvider redirectToWelcome> + <AuthRequestsProvider useStatusOverride={useExtensionStatusOverride}> + <Wouter hook={useAuthRequestsLocation}> + <AuthApp /> + </Wouter> + </AuthRequestsProvider> + </WalletsProvider> </ArConnectThemeProvider> ); } diff --git a/src/tabs/dashboard.tsx b/src/tabs/dashboard.tsx index 5785526ae..83a2b8905 100644 --- a/src/tabs/dashboard.tsx +++ b/src/tabs/dashboard.tsx @@ -3,19 +3,30 @@ import { Router as Wouter, Route as Woute } from "wouter"; import { SettingsDashboardView } from "~routes/dashboard"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; -import { useBrowserExtensionWalletSetUp } from "~wallets/setup/browser-extension/browser-extension-wallet-setup.hook"; +import { useEffect } from "react"; +import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; +import { WalletsProvider } from "~utils/wallets/wallets.provider"; -export default function Dashboard() { - useBrowserExtensionWalletSetUp(); +export function DashboardApp() { + useEffect(() => { + handleSyncLabelsAlarm(); + }, []); + return ( + <Woute path="/:setting?/:subsetting?" component={SettingsDashboardView} /> + ); +} + +export function DashboardAppRoot() { return ( <ArConnectThemeProvider> - <Wouter hook={useHashLocation}> - <Woute - path="/:setting?/:subsetting?" - component={SettingsDashboardView} - /> - </Wouter> + <WalletsProvider> + <Wouter hook={useHashLocation}> + <DashboardApp /> + </Wouter> + </WalletsProvider> </ArConnectThemeProvider> ); } + +export default DashboardAppRoot; diff --git a/src/tabs/devtools.tsx b/src/tabs/devtools.tsx index 2f1defa38..31c78ace5 100644 --- a/src/tabs/devtools.tsx +++ b/src/tabs/devtools.tsx @@ -4,7 +4,6 @@ import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; import { getTab } from "~applications/tab"; import { getAppURL } from "~utils/format"; -import { useNoWallets } from "~wallets"; import { AppSettingsDashboardView } from "~components/dashboard/subsettings/AppSettings"; import Connector from "~components/devtools/Connector"; import NoWallets from "~components/devtools/NoWallets"; @@ -13,6 +12,7 @@ import browser from "webextension-polyfill"; import styled from "styled-components"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; +import { useWallets } from "~utils/wallets/wallets.hooks"; export default function DevTools() { useRemoveCover(); @@ -47,13 +47,12 @@ export default function DevTools() { return connectedApps.includes(app.url); }, [app, connectedApps]); - // no wallets - const noWallets = useNoWallets(); + const { walletStatus } = useWallets(); return ( <ArConnectThemeProvider> <Wrapper> - {noWallets && <NoWallets />} + {walletStatus === "noWallets" && <NoWallets />} <CardBody> <Title>ArConnect {browser.i18n.getMessage("devtools")} diff --git a/src/utils/auth/auth.provider.tsx b/src/utils/auth/auth.provider.tsx index 0480c3a47..beb501309 100644 --- a/src/utils/auth/auth.provider.tsx +++ b/src/utils/auth/auth.provider.tsx @@ -34,7 +34,7 @@ import { isomorphicOnMessage } from "~utils/messaging/messaging.utils"; import type { IBridgeMessage } from "@arconnect/webext-bridge"; import { log, LOG_GROUP } from "~utils/log/log.utils"; import { isError } from "~utils/error/error.utils"; -import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; +import type { RouteOverride } from "~wallets/router/router.types"; interface AuthRequestsContextState { authRequests: AuthRequest[]; @@ -42,33 +42,31 @@ interface AuthRequestsContextState { lastCompletedAuthRequest: null | AuthRequest; } -interface AuthRequestContextData extends AuthRequestsContextState { +interface AuthRequestsContextData extends AuthRequestsContextState { setCurrentAuthRequestIndex: (currentAuthRequestIndex: number) => void; completeAuthRequest: (authID: string, data: any) => Promise; } -export const AuthRequestsContext = createContext({ +const AUTH_REQUESTS_CONTEXT_INITIAL_STATE: AuthRequestsContextState = { authRequests: [], currentAuthRequestIndex: 0, - lastCompletedAuthRequest: null, + lastCompletedAuthRequest: null +}; + +export const AuthRequestsContext = createContext({ + ...AUTH_REQUESTS_CONTEXT_INITIAL_STATE, setCurrentAuthRequestIndex: () => {}, completeAuthRequest: async () => {} }); -interface AuthRequestProviderPRops extends PropsWithChildren { - initialScreenType: InitialScreenType; +interface AuthRequestProviderProps extends PropsWithChildren { + useStatusOverride: () => RouteOverride; } -const AUTH_REQUESTS_CONTEXT_INITIAL_STATE: AuthRequestsContextState = { - authRequests: [], - currentAuthRequestIndex: 0, - lastCompletedAuthRequest: null -}; - export function AuthRequestsProvider({ children, - initialScreenType -}: AuthRequestProviderPRops) { + useStatusOverride +}: AuthRequestProviderProps) { const [ { authRequests, currentAuthRequestIndex, lastCompletedAuthRequest }, setAuthRequestContextState @@ -435,6 +433,8 @@ export function AuthRequestsProvider({ }); }, []); + const statusOverride = useStatusOverride(); + useEffect(() => { let clearCloseAuthPopupTimeout = () => {}; @@ -442,11 +442,12 @@ export function AuthRequestsProvider({ authRequests.length > 0 && authRequests.every((authRequest) => authRequest.status !== "pending"); - if (initialScreenType === "default" && authRequests.length === 0) { + if (statusOverride === null && authRequests.length === 0) { + // TODO: Maybe move to the app entry point? // Close the popup if an AuthRequest doesn't arrive in less than `AUTH_POPUP_REQUEST_WAIT_MS` (1s), unless the // wallet is locked (no timeout in that case): clearCloseAuthPopupTimeout = closeAuthPopup(AUTH_POPUP_REQUEST_WAIT_MS); - } else if (initialScreenType !== "default") { + } else if (statusOverride) { // If the user doesn't unlock the wallet in 15 minutes, or somehow the popup gets stuck into any other state for // more than that, we close it: clearCloseAuthPopupTimeout = closeAuthPopup( @@ -481,12 +482,7 @@ export function AuthRequestsProvider({ window.removeEventListener("beforeunload", handleBeforeUnload); }; - }, [ - initialScreenType, - authRequests, - currentAuthRequestIndex, - closeAuthPopup - ]); + }, [statusOverride, authRequests, currentAuthRequestIndex, closeAuthPopup]); return ( {}; diff --git a/src/utils/wallets/wallets.hooks.ts b/src/utils/wallets/wallets.hooks.ts new file mode 100644 index 000000000..adb8072cd --- /dev/null +++ b/src/utils/wallets/wallets.hooks.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { WalletsContext } from "~utils/wallets/wallets.provider"; + +export function useWallets() { + return useContext(WalletsContext); +} diff --git a/src/utils/wallets/wallets.provider.tsx b/src/utils/wallets/wallets.provider.tsx new file mode 100644 index 000000000..d7f7897ce --- /dev/null +++ b/src/utils/wallets/wallets.provider.tsx @@ -0,0 +1,117 @@ +import { + createContext, + useEffect, + useState, + type PropsWithChildren +} from "react"; +import { ExtensionStorage } from "~utils/storage"; +import { + getActiveAddress, + getWallets, + openOrSelectWelcomePage, + type StoredWallet +} from "~wallets"; +import { getDecryptionKey } from "~wallets/auth"; + +export type WalletStatus = "noWallets" | "loading" | "locked" | "unlocked"; + +interface WalletsContextState { + walletStatus: WalletStatus; + wallets: StoredWallet[]; + activeAddress: string; + decryptionKey: string; +} + +interface WalletsContextData extends WalletsContextState { + // isAuthenticated: boolean; + // signIn + // signOut +} + +const WALLETS_CONTEXT_INITIAL_STATE: WalletsContextState = { + walletStatus: "noWallets", + wallets: [], + activeAddress: "", + decryptionKey: "" +}; + +export const WalletsContext = createContext({ + ...WALLETS_CONTEXT_INITIAL_STATE + // isAuthenticated: false, + // signIn + // signOut +}); + +interface AuthRequestProviderProps extends PropsWithChildren { + redirectToWelcome?: boolean; +} + +export function WalletsProvider({ + redirectToWelcome, + children +}: AuthRequestProviderProps) { + const [walletsContextState, setWalletsContextState] = + useState(WALLETS_CONTEXT_INITIAL_STATE); + + useEffect(() => { + async function checkWalletState() { + const [activeAddress, wallets, decryptionKey] = await Promise.all([ + getActiveAddress(), + getWallets(), + getDecryptionKey() + ]); + + const hasWallets = activeAddress && wallets.length > 0; + + let walletStatus: WalletStatus = "noWallets"; + + if (hasWallets && decryptionKey) { + walletStatus = "unlocked"; + } else if (!decryptionKey) { + walletStatus = "locked"; + } else if (redirectToWelcome) { + // This should only happen when opening the regular popup, but not for the auth popup, as the + // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: + + openOrSelectWelcomePage(true); + + window.top.close(); + } + + setWalletsContextState({ + walletStatus, + wallets, + activeAddress, + decryptionKey + }); + + const coverElement = document.getElementById("cover"); + + if (coverElement) { + if (walletStatus === "noWallets") { + coverElement.removeAttribute("aria-hidden"); + } else { + coverElement.setAttribute("aria-hidden", "true"); + } + } + } + + ExtensionStorage.watch({ + decryption_key: checkWalletState + }); + + checkWalletState(); + + return () => { + ExtensionStorage.unwatch({ + decryption_key: checkWalletState + }); + }; + }, [redirectToWelcome]); + + return ( + + {children} + + ); +} diff --git a/src/wallets/index.ts b/src/wallets/index.ts index 534ce6aae..bfe961033 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -55,38 +55,6 @@ export async function getWallets() { return wallets || []; } -/** - * Hook to get if there are no wallets added - */ -export const useNoWallets = () => { - const [state, setState] = useState(false); - - useEffect(() => { - (async () => { - const activeAddress = await getActiveAddress(); - const wallets = await getWallets(); - - setState(!activeAddress && wallets.length === 0); - })(); - }, []); - - return state; -}; - -/** - * Hook for decryption key - */ -export function useDecryptionKey(): [string, (val: string) => void] { - const [decryptionKey, setDecryptionKey] = useStorage({ - key: "decryption_key", - instance: ExtensionStorage - }); - - const set = (val: string) => setDecryptionKey(btoa(val)); - - return [decryptionKey ? atob(decryptionKey) : undefined, set]; -} - /** * Get the active address */ diff --git a/src/wallets/router/auth/auth-router.hook.ts b/src/wallets/router/auth/auth-router.hook.ts index 2950519bc..3a119852d 100644 --- a/src/wallets/router/auth/auth-router.hook.ts +++ b/src/wallets/router/auth/auth-router.hook.ts @@ -1,15 +1,23 @@ import type { BaseLocationHook } from "wouter"; import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; +import { NOOP } from "~utils/misc"; +import type { AuthRoutePath } from "~wallets/router/auth/auth.routes"; +import { useExtensionStatusOverride } from "~wallets/router/extension/extension-router.hook"; +import { ExtensionOverrides } from "~wallets/router/extension/extension.routes"; export const useAuthRequestsLocation: BaseLocationHook = () => { + const override = useExtensionStatusOverride(); const { authRequest: currentAuthRequest } = useCurrentAuthRequest("any"); + if (override) return [override, NOOP]; + + if (!currentAuthRequest) return [ExtensionOverrides.Loading, NOOP]; + // The authID has been added to the URL so that the auto-scroll and view transition effect work when switching // between different `AuthRequest`s of the same type: - const currentAuthRequestType = currentAuthRequest - ? `/${currentAuthRequest.type}/${currentAuthRequest.authID}` - : ""; + const location = + `/${currentAuthRequest.type}/${currentAuthRequest.authID}` satisfies AuthRoutePath; // TODO: Implement a navigate function that selects a different AuthRequest and also use whenever possible: - return [currentAuthRequestType, (path: string) => ""]; + return [location, (authID: string) => {}]; }; diff --git a/src/wallets/router/auth/auth.routes.ts b/src/wallets/router/auth/auth.routes.ts index e3618c43d..e41989b77 100644 --- a/src/wallets/router/auth/auth.routes.ts +++ b/src/wallets/router/auth/auth.routes.ts @@ -1,12 +1,15 @@ import { AllowanceAuthRequestView } from "~routes/auth/allowance"; import { BatchSignDataItemAuthRequestView } from "~routes/auth/batchSignDataItem"; import { ConnectAuthRequestView } from "~routes/auth/connect"; +import { LoadingAuthRequestView } from "~routes/auth/loading"; import { SignAuthRequestView } from "~routes/auth/sign"; import { SignatureAuthRequestView } from "~routes/auth/signature"; import { SignDataItemAuthRequestView } from "~routes/auth/signDataItem"; import { SignKeystoneAuthRequestView } from "~routes/auth/signKeystone"; import { SubscriptionAuthRequestView } from "~routes/auth/subscription"; import { TokenAuthRequestView } from "~routes/auth/token"; +import { UnlockAuthRequestView } from "~routes/auth/unlock"; +import { getExtensionOverrides } from "~wallets/router/extension/extension.routes"; import type { RouteConfig } from "~wallets/router/router.types"; export type AuthRoutePath = @@ -34,6 +37,10 @@ export const AuthPaths = { } as const satisfies Record; export const AUTH_ROUTES = [ + ...getExtensionOverrides({ + unlockView: UnlockAuthRequestView, + loadingView: LoadingAuthRequestView + }), { path: AuthPaths.Connect, component: ConnectAuthRequestView diff --git a/src/wallets/router/extension/extension-router.hook.ts b/src/wallets/router/extension/extension-router.hook.ts new file mode 100644 index 000000000..0958f2e3f --- /dev/null +++ b/src/wallets/router/extension/extension-router.hook.ts @@ -0,0 +1,31 @@ +import type { BaseLocationHook } from "wouter"; +import { useHashLocation } from "wouter/use-hash-location"; +import { NOOP } from "~utils/misc"; +import { useWallets } from "~utils/wallets/wallets.hooks"; +import type { WalletStatus } from "~utils/wallets/wallets.provider"; +import type { ExtensionRouteOverride } from "~wallets/router/extension/extension.routes"; + +const WALLET_STATUS_TO_OVERRIDE: Record< + WalletStatus, + ExtensionRouteOverride | null +> = { + noWallets: "/__OVERRIDES/cover", + loading: "/__OVERRIDES/loading", + locked: "/__OVERRIDES/unlock", + unlocked: null +}; + +export function useExtensionStatusOverride() { + const { walletStatus } = useWallets(); + + return WALLET_STATUS_TO_OVERRIDE[walletStatus]; +} + +export const useExtensionLocation: BaseLocationHook = () => { + const override = useExtensionStatusOverride(); + const [wocation, wavigate] = useHashLocation(); + + if (override) return [override, NOOP]; + + return [wocation, wavigate]; +}; diff --git a/src/wallets/router/extension/extension.routes.tsx b/src/wallets/router/extension/extension.routes.tsx new file mode 100644 index 000000000..839963995 --- /dev/null +++ b/src/wallets/router/extension/extension.routes.tsx @@ -0,0 +1,42 @@ +import type React from "react"; +import type { + CommonRouteProps, + RouteConfig, + RouteOverride +} from "~wallets/router/router.types"; + +export type ExtensionRouteOverride = + | `/__OVERRIDES/cover` + | `/__OVERRIDES/unlock` + | `/__OVERRIDES/loading`; + +export const ExtensionOverrides = { + Cover: "/__OVERRIDES/cover", + Unlock: "/__OVERRIDES/unlock", + Loading: "/__OVERRIDES/loading" +} as const satisfies Record; + +export interface GetExtensionOverrideOptions { + unlockView: React.ComponentType; + loadingView: React.ComponentType; +} + +export function getExtensionOverrides({ + unlockView, + loadingView +}: GetExtensionOverrideOptions) { + return [ + { + path: ExtensionOverrides.Cover, + component: () => <> + }, + { + path: ExtensionOverrides.Unlock, + component: unlockView + }, + { + path: ExtensionOverrides.Loading, + component: loadingView + } + ] satisfies RouteConfig[]; +} diff --git a/src/wallets/router/popup/popup.routes.ts b/src/wallets/router/popup/popup.routes.ts index d8b158538..a64cd69ac 100644 --- a/src/wallets/router/popup/popup.routes.ts +++ b/src/wallets/router/popup/popup.routes.ts @@ -1,3 +1,4 @@ +import { LoadingView } from "~components/page/common/loading/loading.view"; import { HomeView } from "~routes/popup"; import { CollectibleView } from "~routes/popup/collectible/[id]"; import { CollectiblesView } from "~routes/popup/collectibles"; @@ -35,6 +36,8 @@ import { AssetView } from "~routes/popup/token/[id]"; import { TokensView } from "~routes/popup/tokens"; import { TransactionView } from "~routes/popup/transaction/[id]"; import { TransactionsView } from "~routes/popup/transaction/transactions"; +import { UnlockView } from "~routes/popup/unlock"; +import { getExtensionOverrides } from "~wallets/router/extension/extension.routes"; import type { RouteConfig } from "~wallets/router/router.types"; export type PopupRoutePath = @@ -120,6 +123,10 @@ export const PopupPaths = { } as const satisfies Record; export const POPUP_ROUTES = [ + ...getExtensionOverrides({ + unlockView: UnlockView, + loadingView: LoadingView + }), { path: PopupPaths.Home, component: HomeView diff --git a/src/wallets/router/router.types.ts b/src/wallets/router/router.types.ts index d604c8c5b..fc5bd0f13 100644 --- a/src/wallets/router/router.types.ts +++ b/src/wallets/router/router.types.ts @@ -10,11 +10,15 @@ export interface CommonRouteProps params: T; } -export type BaseRoutePath = `/${string}`; +export type RoutePath = `/${string}`; + +export type RouteOverride = `/__OVERRIDES/${string}`; export type RouteAuthType = "auth" | "anon"; -export interface RouteConfig

{ +export interface RouteConfig< + P extends RoutePath | RouteOverride = RoutePath | RouteOverride +> { key?: string; path: P; component: React.ComponentType; diff --git a/src/wallets/router/router.utils.ts b/src/wallets/router/router.utils.ts index 37621aa6a..4c007152d 100644 --- a/src/wallets/router/router.utils.ts +++ b/src/wallets/router/router.utils.ts @@ -5,17 +5,26 @@ import { } from "wouter"; import type { RouteConfig, - BaseRoutePath, - ArConnectRoutePath + ArConnectRoutePath, + RoutePath, + RouteOverride } from "~wallets/router/router.types"; +export function isRouteOverride( + path: RoutePath | RouteOverride +): path is RouteOverride { + return path.startsWith("/__OVERRIDES/"); +} + export function prefixRoutes( routes: RouteConfig[], - prefix: BaseRoutePath + prefix: RoutePath ): RouteConfig[] { return routes.map((route) => ({ ...route, - path: `${prefix}${route.path}` + path: isRouteOverride(route.path) + ? (route.path satisfies RouteOverride) + : (`${prefix}${route.path}` satisfies RoutePath) })); } diff --git a/src/wallets/router/routes.component.tsx b/src/wallets/router/routes.component.tsx index 281d1c69d..7fd10adfd 100644 --- a/src/wallets/router/routes.component.tsx +++ b/src/wallets/router/routes.component.tsx @@ -1,3 +1,4 @@ +import { AnimatePresence } from "framer-motion"; import React, { useEffect, useMemo, type PropsWithChildren } from "react"; import { Switch, Route as Woute } from "wouter"; import { Page } from "~components/page/page.component"; @@ -5,7 +6,7 @@ import type { CommonRouteProps, RouteConfig } from "~wallets/router/router.types"; -import { useLocation } from "~wallets/router/router.utils"; +import { BodyScroller, useLocation } from "~wallets/router/router.utils"; export interface RoutesProps { routes: RouteConfig[]; @@ -13,8 +14,6 @@ export interface RoutesProps { pageComponent?: React.ComponentType; } -// TODO: Consider adding `` and `` inside here. - // TODO: Consider adding a prop to `RouteConfig.parseParams` to parse // params globally inside `PageWithComponent` (e.g. to replace the `Number()`) // conversions in the Welcome views. @@ -73,5 +72,11 @@ export function Routes({ ); }, [routes, diffLocation ? location : undefined]); - return memoizedRoutes; + return ( + <> + + + {memoizedRoutes} + + ); } diff --git a/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts b/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts deleted file mode 100644 index 21644993c..000000000 --- a/src/wallets/setup/browser-extension/browser-extension-wallet-setup.hook.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { useState, useEffect } from "react"; -import { handleSyncLabelsAlarm } from "~api/background/handlers/alarms/sync-labels/sync-labels-alarm.handler"; -import { ExtensionStorage } from "~utils/storage"; -import { - getActiveAddress, - getWallets, - openOrSelectWelcomePage -} from "~wallets"; -import { getDecryptionKey } from "~wallets/auth"; -import type { InitialScreenType } from "~wallets/setup/wallet-setup.types"; - -/** - * Hook that opens a new tab if ArConnect has not been set up yet - */ -export function useBrowserExtensionWalletSetUp() { - const [initialScreenType, setInitialScreenType] = - useState("cover"); - - useEffect(() => { - async function checkWalletState() { - const [activeAddress, wallets, decryptionKey] = await Promise.all([ - getActiveAddress(), - getWallets(), - getDecryptionKey() - ]); - - const hasWallets = activeAddress && wallets.length > 0; - - let nextInitialScreenType: InitialScreenType = "cover"; - - if (!hasWallets) { - // This should only happen when opening the regular popup, but not for the auth popup, as the - // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: - - openOrSelectWelcomePage(true); - - window.top.close(); - } else if (!decryptionKey) { - nextInitialScreenType = "locked"; - } else { - nextInitialScreenType = "default"; - } - - setInitialScreenType(nextInitialScreenType); - - const coverElement = document.getElementById("cover"); - - if (coverElement) { - if (nextInitialScreenType === "cover") { - coverElement.removeAttribute("aria-hidden"); - } else { - coverElement.setAttribute("aria-hidden", "true"); - } - } - } - - ExtensionStorage.watch({ - decryption_key: checkWalletState - }); - - checkWalletState(); - - handleSyncLabelsAlarm(); - - return () => { - ExtensionStorage.unwatch({ - decryption_key: checkWalletState - }); - }; - }, []); - - return initialScreenType; -} diff --git a/src/wallets/setup/wallet-setup.types.ts b/src/wallets/setup/wallet-setup.types.ts deleted file mode 100644 index f8578beff..000000000 --- a/src/wallets/setup/wallet-setup.types.ts +++ /dev/null @@ -1 +0,0 @@ -export type InitialScreenType = "default" | "cover" | "locked"; From 8e3589d712f3ed64864c762c861e7cf4b168eb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 6 Dec 2024 17:33:13 +0100 Subject: [PATCH 113/142] Fix gateway-related issues that log messages to the background script's Console. --- .../gateway-update-alarm.handler.ts | 23 +-- src/gateways/cache.ts | 10 +- src/gateways/types.ts | 133 +++++++++++------- src/gateways/wayfinder.ts | 39 ++--- src/lib/wayfinder.ts | 30 ++-- src/utils/log/log.utils.ts | 2 + 6 files changed, 142 insertions(+), 95 deletions(-) diff --git a/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts b/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts index 708ddb23b..7bded32a1 100644 --- a/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts +++ b/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts @@ -3,8 +3,9 @@ import { type Alarms } from "webextension-polyfill"; import { AOProcess } from "~lib/ao"; import { AO_ARNS_PROCESS } from "~lib/arns"; import type { - GatewayAddressRegistryCache, - ProcessedData + GatewayAddressRegistryItem, + GatewayAddressRegistryItemData, + PaginatedResult } from "~gateways/types"; import { RETRY_ALARM, @@ -22,22 +23,26 @@ export async function handleGatewayUpdateAlarm(alarm?: Alarms.Alarm) { return; } - let procData: ProcessedData[] = []; + let garItemsWithChecks: GatewayAddressRegistryItem[] = []; try { // fetch cache const ArIO = new AOProcess({ processId: AO_ARNS_PROCESS }); - const gateways = (await ArIO.read({ + + const gatewaysResult = await ArIO.read< + PaginatedResult + >({ tags: [{ name: "Action", value: "Gateways" }] - })) as GatewayAddressRegistryCache["gateways"]; - const garItems = extractGarItems({ gateways }); + }); + + const garItems = extractGarItems(gatewaysResult.items); // healtcheck - await pingUpdater(garItems, (newData) => { - procData = [...newData]; + await pingUpdater(garItems, (nextGarItemsWithChecks) => { + garItemsWithChecks = nextGarItemsWithChecks; }); - await updateGatewayCache(procData); + await updateGatewayCache(garItemsWithChecks); } catch (err) { console.log("err in handle", err); diff --git a/src/gateways/cache.ts b/src/gateways/cache.ts index e9f834f8d..6b8530884 100644 --- a/src/gateways/cache.ts +++ b/src/gateways/cache.ts @@ -1,5 +1,5 @@ import browser from "webextension-polyfill"; -import type { ProcessedData } from "./types"; +import type { GatewayAddressRegistryItem } from "~gateways/types"; import { ExtensionStorage } from "~utils/storage"; /** Cache storage name */ @@ -11,13 +11,17 @@ export const RETRY_ALARM = "update_gateway_retry"; * Get cache of ar.io gateway list */ export async function getGatewayCache() { - return await ExtensionStorage.get(CACHE_STORAGE_NAME); + return await ExtensionStorage.get( + CACHE_STORAGE_NAME + ); } /** * Update ar.io gateway list cache */ -export async function updateGatewayCache(gateways: ProcessedData[]) { +export async function updateGatewayCache( + gateways: GatewayAddressRegistryItem[] +) { return await ExtensionStorage.set(CACHE_STORAGE_NAME, gateways); } diff --git a/src/gateways/types.ts b/src/gateways/types.ts index 0ed45d016..52db28812 100644 --- a/src/gateways/types.ts +++ b/src/gateways/types.ts @@ -1,67 +1,92 @@ -interface GatewayAddressRegistryItemData { +export interface PaginatedResult { + hasMore: boolean; + items: T[]; + limit: number; + nextCursor: string; + sortBy: string; + sorOrder: string; + totalItems: number; +} + +export interface GatewaySettings { + port: number; + protocol: string; + minDelegatedStake: number; + fqdn: string; + delegateRewardShareRatio: number; + autoStake: boolean; + note: string; + allowDelegatedStaking: boolean; + label: string; + properties: + | string + | { + GRAPHQL: boolean; + ARNS: boolean; + MAX_PAGE_SIZE: number; + }; +} + +export interface GatewayStats { + failedConsecutiveEpochs: number; + observedEpochCount: number; + passedConsecutiveEpochs: number; + totalEpochCount: number; + prescribedEpochCount: number; + passedEpochCount: number; + failedEpochCount: number; +} + +export interface GatewayWeights { + compositeWeight: number; + observerRewardRatioWeight: number; + normalizedCompositeWeight: number; + tenureWeight: number; + gatewayRewardRatioWeight: number; + stakeWeight: number; +} + +export interface GatewayAddressRegistryItemData { + gatewayAddress: string; + observerAddress: string; operatorStake: number; - vaults: GatewayVault[]; - settings: { - label: string; - fqdn: string; - port: number; - protocol: string; - properties?: any; - note?: string; - }; - status: "joined"; - start: number; - end: number; + startTimestamp: number; + status: "joined" | "leaving"; + totalDelegatedStake: number; + settings: GatewaySettings; + stats: GatewayStats; + weights: GatewayWeights; } -export interface GatewayAddressRegistryItem - extends GatewayAddressRegistryItemData { - id: string; - linkFull: string; - linkDisplay: string; - ping: any; - health: any; +export interface GatewayUnknownCheck { + status: "unknown"; +} + +export interface GatewayPendingCheck { + status: "pending"; } -export interface GatewayAddressRegistryCache { - // contractTxId: string; - gateways: Record; - // evaluationOptions: {}; +export interface GatewaySuccessCheck { + status: "success"; + value: number; } -interface GatewayVault { - balance: number; - start: number; - end: number; +export interface GatewayErrorCheck { + status: "error"; + error: "string"; } -export interface ProcessedData { +export type GatewayCheck = + | GatewayUnknownCheck + | GatewayPendingCheck + | GatewaySuccessCheck + | GatewayErrorCheck; + +export interface GatewayAddressRegistryItem + extends GatewayAddressRegistryItemData { id: string; - ping: { - status: string; - value: number; - }; - health: { - status: string; - }; linkFull: string; linkDisplay: string; - operatorStake: number; - vaults: any[]; - settings: { - label: string; - fqdn: string; - port: number; - protocol: string; - properties: any; - note: string; - }; - status: string; - start: number; - end: number; - properties: { - GRAPHQL: boolean; - ARNS: boolean; - MAX_PAGE_SIZE: number; - }; + ping: GatewayCheck; + health: GatewayCheck; } diff --git a/src/gateways/wayfinder.ts b/src/gateways/wayfinder.ts index a2353a76c..7c4a91586 100644 --- a/src/gateways/wayfinder.ts +++ b/src/gateways/wayfinder.ts @@ -8,24 +8,31 @@ import { EventType, trackEvent } from "~utils/analytics"; export async function findGateway( requirements: Requirements ): Promise { - // get if the feature is enabled + // Get if the Wayfinder feature is enabled: const wayfinderEnabled = await getSetting("wayfinder").getValue(); - // wayfinder disabled, but arns is needed - if (!wayfinderEnabled && requirements.arns) { - return { - host: "arweave.dev", - port: 443, - protocol: "https" - }; - } + // This should have been loaded into the cache by handleGatewayUpdateAlarm, but sometimes this function might run + // before that, so in that case we fall back to the same behavior has having the Wayfinder disabled: + const procData = await getGatewayCache(); - // wayfinder disabled or all the chain is needed - if (!wayfinderEnabled || requirements.startBlock === 0) { - return defaultGateway; - } + if (!wayfinderEnabled || !procData) { + if (requirements.arns) { + return { + host: "arweave.dev", + port: 443, + protocol: "https" + }; + } - const procData = await getGatewayCache(); + // wayfinder disabled or all the chain is needed + if (requirements.startBlock === 0) { + return defaultGateway; + } + + throw new Error( + wayfinderEnabled ? "Missing gateway cache" : "Wayfinder disabled" + ); + } try { // this could probably be filtered out during the caching process @@ -34,9 +41,7 @@ export async function findGateway( gateway.ping.status === "success" && gateway.health.status === "success" ); }); - const sortedGateways = sortGatewaysByOperatorStake(filteredGateways); - const top10 = sortedGateways.slice(0, Math.min(10, sortedGateways.length)); const randomIndex = Math.floor(Math.random() * top10.length); const selectedGateway = top10[randomIndex]; @@ -49,12 +54,14 @@ export async function findGateway( protocol: selectedGateway.settings.protocol, requirements }); + return { host: selectedGateway.settings.fqdn, port: selectedGateway.settings.port, protocol: selectedGateway.settings.protocol }; } + for (let i = 0; i < top10.length; i++) { // TODO: if we want it to be random // const index = (randomIndex + i) % top10.length; diff --git a/src/lib/wayfinder.ts b/src/lib/wayfinder.ts index 6e6cb5f79..3d77e59fe 100644 --- a/src/lib/wayfinder.ts +++ b/src/lib/wayfinder.ts @@ -2,10 +2,10 @@ import type { Requirements } from "~gateways/wayfinder"; import { defaultGateway } from "~gateways/gateway"; import Arweave from "arweave"; import type { - GatewayAddressRegistryCache, GatewayAddressRegistryItem, - ProcessedData + GatewayAddressRegistryItemData } from "~gateways/types"; +import { log, LOG_GROUP } from "~utils/log/log.utils"; const pingStaggerDelayMs = 10; // 0.01s const pingTimeout = 5000; // 5s @@ -23,10 +23,10 @@ const properties = { } }; -const pingUpdater = async ( +async function pingUpdater( data: GatewayAddressRegistryItem[], - onUpdate: any -) => { + onUpdate: (garItemsWithChecks: GatewayAddressRegistryItem[]) => void +): Promise { const CLabs = "CDoilQgKg6Pmp4Q0LJ4d84VXRgB3Ay9pIJ_SA617cVk"; const CLabsGateway = data.find((item) => item.id === CLabs); @@ -79,7 +79,7 @@ const pingUpdater = async ( onUpdate(newData); } catch (e) { - console.error(e); + log(LOG_GROUP.GATEWAYS, "Gateway health check error", e); newData[index].health = { status: "error", @@ -89,7 +89,7 @@ const pingUpdater = async ( onUpdate(newData); } } catch (e) { - console.error(e); + log(LOG_GROUP.GATEWAYS, "Gateway ping check error", e); newData[index].ping = { status: "error", @@ -104,10 +104,12 @@ const pingUpdater = async ( }); await Promise.all(pingPromises.map((p) => p())); -}; +} // TODO: MAKE THIS WEIGH HTTP/HTTPS -const sortGatewaysByOperatorStake = (filteredGateways: ProcessedData[]) => { +const sortGatewaysByOperatorStake = ( + filteredGateways: GatewayAddressRegistryItem[] +) => { const sortedGateways = filteredGateways.slice(); sortedGateways.sort((gatewayA, gatewayB) => { @@ -163,10 +165,12 @@ const isValidGateway = (gateway: any, requirements: Requirements): boolean => { }; // FOR CACHING AND GETTING STATUS -const extractGarItems = (garCache: GatewayAddressRegistryCache) => { - return Object.entries(garCache.gateways).map(([txId, item]) => { +function extractGarItems( + gateways: GatewayAddressRegistryItemData[] +): GatewayAddressRegistryItem[] { + return gateways.map((item) => { return { - id: txId, + id: item.gatewayAddress, ping: { status: "unknown" }, health: { status: "unknown" }, linkFull: linkFull( @@ -182,7 +186,7 @@ const extractGarItems = (garCache: GatewayAddressRegistryCache) => { ...item }; }); -}; +} const linkFull = (protocol: string, fqdn: string, port: number) => `${protocol}://${fqdn}:${port}`; diff --git a/src/utils/log/log.utils.ts b/src/utils/log/log.utils.ts index 3903fc1ed..452fe42b9 100644 --- a/src/utils/log/log.utils.ts +++ b/src/utils/log/log.utils.ts @@ -2,6 +2,7 @@ export enum LOG_GROUP { API = "API", AUTH = "AUTH", CHUNKS = "CHUNKS", + GATEWAYS = "GATEWAYS", MSG = "MSG", SETUP = "SETUP" } @@ -10,6 +11,7 @@ const LOG_GROUPS_ENABLED: Record = { [LOG_GROUP.API]: process.env.NODE_ENV === "development", [LOG_GROUP.AUTH]: process.env.NODE_ENV === "development", [LOG_GROUP.CHUNKS]: false, + [LOG_GROUP.GATEWAYS]: false, [LOG_GROUP.MSG]: false, [LOG_GROUP.SETUP]: process.env.NODE_ENV === "development" }; From e37b54ae271d11a6411e7cf126a25107c3e29c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Fri, 6 Dec 2024 19:08:13 +0100 Subject: [PATCH 114/142] Fix minor SVG and HTML (JSX) related issues. --- src/components/popup/home/AoBanner.tsx | 13 +++++----- src/components/popup/home/Balance.tsx | 24 ++++++++++++------- .../subscriptions/subscriptionDetails.tsx | 6 ++--- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/components/popup/home/AoBanner.tsx b/src/components/popup/home/AoBanner.tsx index d423b4436..1d384b31a 100644 --- a/src/components/popup/home/AoBanner.tsx +++ b/src/components/popup/home/AoBanner.tsx @@ -99,9 +99,9 @@ function BannerVector() { width="427" height="80.5" filterUnits="userSpaceOnUse" - color-interpolation-filters="sRGB" + colorInterpolationFilters="sRGB" > - + - - - - + + + + @@ -138,6 +138,7 @@ const Banner = styled.div<{ displayTheme: DisplayTheme; show: boolean }>` position: relative; flex-direction: row; color: #ffffff; + overflow: hidden; background: linear-gradient( 0deg, rgba(0, 0, 0, 0.4) 0%, diff --git a/src/components/popup/home/Balance.tsx b/src/components/popup/home/Balance.tsx index ca76809a9..2b8fc49b9 100644 --- a/src/components/popup/home/Balance.tsx +++ b/src/components/popup/home/Balance.tsx @@ -136,12 +136,16 @@ export default function Balance() { "*".repeat(balance.toFixed(2).length)} AR - - {(!hideBalance && - formatFiatBalance(fiat, currency.toLowerCase())) || - "*".repeat(fiat.toFixed(2).length) + - " " + - currency.toUpperCase()} + + + + {(!hideBalance && + formatFiatBalance(fiat, currency.toLowerCase())) || + "*".repeat(fiat.toFixed(2).length) + + " " + + currency.toUpperCase()} + + - +

)} {activeAppData && ( @@ -319,10 +323,14 @@ const Ticker = styled.span` margin-left: 0.33rem; `; -const FiatBalanceText = styled(GraphText)` +const DivFiatBalanceRow = styled.div` display: flex; align-items: center; gap: 0.41rem; + font-size: 1rem; +`; + +const FiatBalanceText = styled(GraphText)` font-weight: 400; `; diff --git a/src/routes/popup/subscriptions/subscriptionDetails.tsx b/src/routes/popup/subscriptions/subscriptionDetails.tsx index 043e02bcf..81cb8a125 100644 --- a/src/routes/popup/subscriptions/subscriptionDetails.tsx +++ b/src/routes/popup/subscriptions/subscriptionDetails.tsx @@ -514,9 +514,9 @@ export const InfoCircle = () => ( From 1717b0c35915301193333848b455bf7def1df79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Mon, 9 Dec 2024 11:07:44 +0100 Subject: [PATCH 115/142] Remove console.log(). --- .../alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts b/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts index 165adf44b..abe2ab809 100644 --- a/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts +++ b/src/api/background/handlers/alarms/ao-tokens-import/ao-tokens-import-alarm.handler.ts @@ -27,8 +27,6 @@ export async function handleAoTokensImportAlarm(alarm: Alarms.Alarm) { try { const activeAddress = await getActiveAddress(); - console.log("Importing AO tokens..."); - let [aoTokens, aoTokensCache, removedTokenIds = []] = await Promise.all([ getAoTokens(), getAoTokensCache(), From 5478f1585c9224c6c33b23187f847e169ace4a01 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Mon, 9 Dec 2024 23:18:34 +0545 Subject: [PATCH 116/142] refactor: use default gateway for description fetch --- src/routes/popup/collectible/[id].tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/popup/collectible/[id].tsx b/src/routes/popup/collectible/[id].tsx index a43be6e9e..0b4308d00 100644 --- a/src/routes/popup/collectible/[id].tsx +++ b/src/routes/popup/collectible/[id].tsx @@ -48,7 +48,8 @@ export default function Collectible({ id }: Props) { } } }`, - { id } + { id }, + defaultGateway ); const description = getTagValue( From 75dc232f1cebae73b212afc26dfe5d69f2ab458a Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 10 Dec 2024 00:01:15 +0545 Subject: [PATCH 117/142] refactor: use constants for requirements to prevent calling fetchGateway many times --- src/components/dashboard/Wallets.tsx | 4 ++-- src/components/dashboard/list/TokenListItem.tsx | 4 ++-- src/components/dashboard/subsettings/AddToken.tsx | 4 ++-- src/components/popup/Collectible.tsx | 4 ++-- src/gateways/wayfinder.ts | 8 ++++++++ src/routes/auth/token.tsx | 4 ++-- src/routes/popup/collectible/[id].tsx | 4 ++-- src/routes/popup/settings/tokens/index.tsx | 4 ++-- src/routes/popup/settings/wallets/index.tsx | 4 ++-- src/routes/popup/token/[id].tsx | 8 ++------ src/routes/popup/transaction/[id].tsx | 8 ++------ 11 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/components/dashboard/Wallets.tsx b/src/components/dashboard/Wallets.tsx index e0b6aec30..80f1e4672 100644 --- a/src/components/dashboard/Wallets.tsx +++ b/src/components/dashboard/Wallets.tsx @@ -11,7 +11,7 @@ import WalletListItem from "./list/WalletListItem"; import browser from "webextension-polyfill"; import SearchInput from "./SearchInput"; import styled from "styled-components"; -import { useGateway } from "~gateways/wayfinder"; +import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; export default function Wallets() { // wallets @@ -73,7 +73,7 @@ export default function Wallets() { const findProfile = (address: string) => ansProfiles.find((profile) => profile.user === address); - const gateway = useGateway({ startBlock: 0 }); + const gateway = useGateway(FULL_HISTORY); function findAvatar(address: string) { const avatar = findProfile(address)?.avatar; diff --git a/src/components/dashboard/list/TokenListItem.tsx b/src/components/dashboard/list/TokenListItem.tsx index a9da75487..c4305e40c 100644 --- a/src/components/dashboard/list/TokenListItem.tsx +++ b/src/components/dashboard/list/TokenListItem.tsx @@ -9,7 +9,7 @@ import { useTheme } from "~utils/theme"; import { useLocation } from "wouter"; import * as viewblock from "~lib/viewblock"; import styled from "styled-components"; -import { useGateway } from "~gateways/wayfinder"; +import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import { concatGatewayURL } from "~gateways/utils"; import aoLogo from "url:/assets/ecosystem/ao-logo.svg"; import arLogoDark from "url:/assets/ar/logo_dark.png"; @@ -32,7 +32,7 @@ export default function TokenListItem({ token, active, ao, onClick }: Props) { const [image, setImage] = useState(viewblock.getTokenLogo(token.id)); // gateway - const gateway = useGateway({ startBlock: 0 }); + const gateway = useGateway(FULL_HISTORY); useEffect(() => { (async () => { diff --git a/src/components/dashboard/subsettings/AddToken.tsx b/src/components/dashboard/subsettings/AddToken.tsx index 0118c2d1c..f8b6d183d 100644 --- a/src/components/dashboard/subsettings/AddToken.tsx +++ b/src/components/dashboard/subsettings/AddToken.tsx @@ -17,7 +17,7 @@ import { ExtensionStorage } from "~utils/storage"; import { SubTitle } from "./ContactSettings"; import type { TokenType } from "~tokens/token"; import { concatGatewayURL } from "~gateways/utils"; -import { useGateway } from "~gateways/wayfinder"; +import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import { getTokenInfo } from "~tokens/aoTokens/router"; interface AddTokenProps { @@ -26,7 +26,7 @@ interface AddTokenProps { export default function AddToken({ isQuickSetting }: AddTokenProps) { const targetInput = useInput(); - const gateway = useGateway({ startBlock: 0 }); + const gateway = useGateway(FULL_HISTORY); const [tokenType, setTokenType] = useState("asset"); const [token, setToken] = useState(); const [loading, setLoading] = useState(false); diff --git a/src/components/popup/Collectible.tsx b/src/components/popup/Collectible.tsx index ff7f69bdb..e39926be4 100644 --- a/src/components/popup/Collectible.tsx +++ b/src/components/popup/Collectible.tsx @@ -1,6 +1,6 @@ import { type MouseEventHandler } from "react"; import { concatGatewayURL } from "~gateways/utils"; -import { useGateway } from "~gateways/wayfinder"; +import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import { hoverEffect } from "~utils/theme"; import styled from "styled-components"; import placeholderUrl from "url:/assets/placeholder.png"; @@ -8,7 +8,7 @@ import placeholderUrl from "url:/assets/placeholder.png"; import Skeleton from "~components/Skeleton"; export default function Collectible({ id, onClick, ...props }: Props) { // gateway - const gateway = useGateway({ startBlock: 0 }); + const gateway = useGateway(FULL_HISTORY); return ( diff --git a/src/gateways/wayfinder.ts b/src/gateways/wayfinder.ts index 7c4a91586..a34ae44b8 100644 --- a/src/gateways/wayfinder.ts +++ b/src/gateways/wayfinder.ts @@ -5,6 +5,14 @@ import { getGatewayCache } from "./cache"; import { getSetting } from "~settings"; import { EventType, trackEvent } from "~utils/analytics"; +export const FULL_HISTORY: Requirements = { startBlock: 0 }; + +export const STAKED_GQL_FULL_HISTORY: Requirements = { + startBlock: 0, + graphql: true, + ensureStake: true +}; + export async function findGateway( requirements: Requirements ): Promise { diff --git a/src/routes/auth/token.tsx b/src/routes/auth/token.tsx index 0840a371f..feb45e19b 100644 --- a/src/routes/auth/token.tsx +++ b/src/routes/auth/token.tsx @@ -28,7 +28,7 @@ import Title from "~components/popup/Title"; import Head from "~components/popup/Head"; import styled from "styled-components"; import { concatGatewayURL } from "~gateways/utils"; -import { findGateway, useGateway } from "~gateways/wayfinder"; +import { findGateway, FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; @@ -171,7 +171,7 @@ export default function Token() { return () => window.removeEventListener("keydown", listener); }, [done]); - const gateway = useGateway({ startBlock: 0 }); + const gateway = useGateway(FULL_HISTORY); return ( diff --git a/src/routes/popup/collectible/[id].tsx b/src/routes/popup/collectible/[id].tsx index 0b4308d00..6ffc3de86 100644 --- a/src/routes/popup/collectible/[id].tsx +++ b/src/routes/popup/collectible/[id].tsx @@ -9,7 +9,7 @@ import Skeleton from "~components/Skeleton"; import browser from "webextension-polyfill"; import Title from "~components/popup/Title"; import styled from "styled-components"; -import { useGateway } from "~gateways/wayfinder"; +import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import HeadV2 from "~components/popup/HeadV2"; import placeholderUrl from "url:/assets/placeholder.png"; import { useStorage } from "@plasmohq/storage/hook"; @@ -34,7 +34,7 @@ export default function Collectible({ id }: Props) { // price const [price, setPrice] = useState(); - const defaultGateway = useGateway({ startBlock: 0 }); + const defaultGateway = useGateway(FULL_HISTORY); async function getCollectionDescription() { setLoading(true); diff --git a/src/routes/popup/settings/tokens/index.tsx b/src/routes/popup/settings/tokens/index.tsx index b2af2bab4..39cce8da1 100644 --- a/src/routes/popup/settings/tokens/index.tsx +++ b/src/routes/popup/settings/tokens/index.tsx @@ -14,7 +14,7 @@ import { formatAddress } from "~utils/format"; import { getDreForToken } from "~tokens"; import { useTheme } from "~utils/theme"; import * as viewblock from "~lib/viewblock"; -import { useGateway } from "~gateways/wayfinder"; +import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import { concatGatewayURL } from "~gateways/utils"; import aoLogo from "url:/assets/ecosystem/ao-logo.svg"; import arLogoDark from "url:/assets/ar/logo_dark.png"; @@ -163,7 +163,7 @@ function TokenListItem({ token, ao, onClick }: Props) { const [image, setImage] = useState(viewblock.getTokenLogo(token.id)); // gateway - const gateway = useGateway({ startBlock: 0 }); + const gateway = useGateway(FULL_HISTORY); useEffect(() => { (async () => { diff --git a/src/routes/popup/settings/wallets/index.tsx b/src/routes/popup/settings/wallets/index.tsx index 9600e358c..72ddef5ec 100644 --- a/src/routes/popup/settings/wallets/index.tsx +++ b/src/routes/popup/settings/wallets/index.tsx @@ -9,7 +9,7 @@ import type { StoredWallet } from "~wallets"; import { Reorder } from "framer-motion"; import browser from "webextension-polyfill"; import styled from "styled-components"; -import { useGateway } from "~gateways/wayfinder"; +import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import WalletListItem from "~components/dashboard/list/WalletListItem"; import SearchInput from "~components/dashboard/SearchInput"; import HeadV2 from "~components/popup/HeadV2"; @@ -44,7 +44,7 @@ export default function Wallets() { const findProfile = (address: string) => ansProfiles.find((profile) => profile.user === address); - const gateway = useGateway({ startBlock: 0 }); + const gateway = useGateway(FULL_HISTORY); function findAvatar(address: string) { const avatar = findProfile(address)?.avatar; diff --git a/src/routes/popup/token/[id].tsx b/src/routes/popup/token/[id].tsx index 82b1cad27..d6dfe7082 100644 --- a/src/routes/popup/token/[id].tsx +++ b/src/routes/popup/token/[id].tsx @@ -43,7 +43,7 @@ import browser from "webextension-polyfill"; import Skeleton from "~components/Skeleton"; import useSetting from "~settings/hook"; import styled from "styled-components"; -import { useGateway } from "~gateways/wayfinder"; +import { STAKED_GQL_FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import HeadV2 from "~components/popup/HeadV2"; export default function Asset({ id }: Props) { @@ -113,11 +113,7 @@ export default function Asset({ id }: Props) { // token gateway const tokens = useTokens(); - const defaultGateway = useGateway({ - startBlock: 0, - graphql: true, - ensureStake: true - }); + const defaultGateway = useGateway(STAKED_GQL_FULL_HISTORY); const gateway = useMemo( () => tokens.find((t) => t.id === id)?.gateway || defaultGateway, [id, defaultGateway] diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index f0bf2bfe1..1f6ae4157 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -14,7 +14,7 @@ import { useState, type MutableRefObject } from "react"; -import { useGateway } from "~gateways/wayfinder"; +import { STAKED_GQL_FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import { useHistory } from "~utils/hash_router"; import { ChevronDownIcon, @@ -97,11 +97,7 @@ export default function Transaction({ id: rawId, gw, message }: Props) { const [showTags, setShowTags] = useState(false); // arweave gateway - const defaultGateway = useGateway({ - ensureStake: true, - startBlock: 0, - graphql: true - }); + const defaultGateway = useGateway(STAKED_GQL_FULL_HISTORY); const gateway = useMemo(() => { if (!gw) { return defaultGateway; From 70a961c80c566a3ee70ff898ac40f95abb4e500d Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira Date: Mon, 9 Dec 2024 23:43:17 +0000 Subject: [PATCH 118/142] adds reusable method for handle image placeholders while using SquircleImg component --- src/utils/urls/getAppIconPlaceholder.ts | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/utils/urls/getAppIconPlaceholder.ts diff --git a/src/utils/urls/getAppIconPlaceholder.ts b/src/utils/urls/getAppIconPlaceholder.ts new file mode 100644 index 000000000..d92fed895 --- /dev/null +++ b/src/utils/urls/getAppIconPlaceholder.ts @@ -0,0 +1,46 @@ +interface IGetAppIconProps { + name?: string; + url?: string; +} + +/** + * Generates a placeholder text based on the app name or URL. + * + * - If `appInfo.name` is available, it returns the first two uppercase letters of the name. + * - If `url` is used as a fallback: + * - Extracts the first two letters from the subdomain if it is a gateway. + * - Extracts the first two letters from the domain if it is a regular site. + * - Defaults to a lock icon (🔒) if neither `appInfo.name` nor a valid `url` is available. + * + * @param {Object} appInfo - Application information containing the name. + * @param {string} [appInfo.name] - The name of the application. + * @param {string} [url] - A fallback URL to determine placeholder text. + * @returns {string} - The generated placeholder text or default icon. + */ +const getAppIconPlaceholder = (appInfo: IGetAppIconProps): string => { + const appName = appInfo?.name; + + if (appName) { + return appName.slice(0, 2).toUpperCase(); + } + + if (appInfo.url) { + try { + const parsedUrl = new URL(appInfo.url); + const hostnameParts = parsedUrl.hostname.split("."); + + if (hostnameParts.length > 2) { + return hostnameParts[0].slice(0, 2).toUpperCase(); + } else { + const domain = hostnameParts[hostnameParts.length - 2]; + return domain.slice(0, 2).toUpperCase(); + } + } catch { + /* We're using a string based icon to avoid refactoring SquircleImg component + * since it also receives an img prop coming from appInfo. + **/ + return "🔒"; + } + } + return "🔒"; +}; From 70f10420e4cb15fa8206c3824a3d1eedd6cec736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 10 Dec 2024 09:57:43 +0100 Subject: [PATCH 119/142] Fix infinite re-render caused by useGateway params. --- src/gateways/wayfinder.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gateways/wayfinder.ts b/src/gateways/wayfinder.ts index 7c4a91586..96d08386f 100644 --- a/src/gateways/wayfinder.ts +++ b/src/gateways/wayfinder.ts @@ -103,7 +103,12 @@ export function useGateway(requirements: Requirements) { setActiveGateway(recommended); } catch {} })(); - }, [requirements]); + }, [ + requirements.graphql, + requirements.arns, + requirements.startBlock, + requirements.ensureStake + ]); return activeGateway; } From 2f2a5d96bac900c55682ff711e9dc19ee18caa6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 10 Dec 2024 10:06:38 +0100 Subject: [PATCH 120/142] Fix Setting instances not rendering in the Dashboard. --- src/routes/dashboard/dashboard.constants.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/routes/dashboard/dashboard.constants.ts b/src/routes/dashboard/dashboard.constants.ts index 4712bba09..ebf0e6020 100644 --- a/src/routes/dashboard/dashboard.constants.ts +++ b/src/routes/dashboard/dashboard.constants.ts @@ -89,14 +89,7 @@ export const advancedSettings: (DashboardRouteConfig | Setting)[] = [ icon: BellIcon, component: SignSettingsDashboardView }, - ...settings - .filter((setting) => setting.name !== "display_theme") - .map((setting) => ({ - name: setting.name, - displayName: setting.displayName, - description: setting.description, - icon: setting.icon - })), + ...settings.filter((setting) => setting.name !== "display_theme"), // TODO /*{ name: "config", From 248d3d4abe40f3ea088af2c462f785e142789bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 10 Dec 2024 10:47:40 +0100 Subject: [PATCH 121/142] Fix route params used in ConfirmView and RecipientView. --- src/routes/popup/send/confirm.tsx | 5 +++-- src/routes/popup/send/recipient.tsx | 4 ++-- src/routes/popup/transaction/[id].tsx | 4 ++-- src/wallets/router/popup/popup.routes.ts | 2 ++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index ffbc14992..a69e593c6 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -72,16 +72,17 @@ function formatNumber(amount: string, decimalPlaces: number = 2): string { } export interface ConfirmViewParams { - tokenID: string; + token: string; qty?: number; recipient?: string; + message?: string; subscription?: boolean; } export type ConfirmViewProps = CommonRouteProps; export function ConfirmView({ - params: { tokenID, qty: qtyParam, subscription } + params: { token: tokenID, qty: qtyParam, subscription } }: ConfirmViewProps) { const { navigate } = useLocation(); // TODO: Add generic utils to parse params: diff --git a/src/routes/popup/send/recipient.tsx b/src/routes/popup/send/recipient.tsx index 27da74f3e..c220da71a 100644 --- a/src/routes/popup/send/recipient.tsx +++ b/src/routes/popup/send/recipient.tsx @@ -26,7 +26,7 @@ import Arweave from "arweave"; import type { CommonRouteProps } from "~wallets/router/router.types"; export interface RecipientViewParams { - tokenID: string; + token: string; qty: string; message?: string; } @@ -34,7 +34,7 @@ export interface RecipientViewParams { export type RecipientViewProps = CommonRouteProps; export function RecipientView({ - params: { tokenID, qty, message } + params: { token: tokenID, qty, message } }: RecipientViewProps) { const { navigate } = useLocation(); diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 405b93d4c..37b7e74af 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -62,14 +62,14 @@ interface ao { export interface TransactionViewParams { id: string; // encodeURIComponent transformed gateway url - gw?: string; + gateway?: string; message?: boolean; } export type TransactionViewProps = CommonRouteProps; export function TransactionView({ - params: { id: rawId, gw, message } + params: { id: rawId, gateway: gw, message } }: TransactionViewProps) { const { navigate, back } = useLocation(); diff --git a/src/wallets/router/popup/popup.routes.ts b/src/wallets/router/popup/popup.routes.ts index a64cd69ac..31ba58c1b 100644 --- a/src/wallets/router/popup/popup.routes.ts +++ b/src/wallets/router/popup/popup.routes.ts @@ -208,6 +208,8 @@ export const POPUP_ROUTES = [ component: TransactionView }, { + // TODO: This route is incorrect/misleading as a lot of its params are actually ignored and loaded from a temp tx + // stored in the temp storage: path: PopupPaths.Confirm, component: ConfirmView }, From 630a7a6303f1756fb23945ae3728c373f34fdf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 10 Dec 2024 14:32:15 +0100 Subject: [PATCH 122/142] Remove unused RecipientView and fix back functionality. --- src/routes/popup/send/recipient.tsx | 233 ----------------------- src/routes/popup/transaction/[id].tsx | 34 +--- src/wallets/router/popup/popup.routes.ts | 7 - src/wallets/router/router.utils.ts | 36 +++- 4 files changed, 42 insertions(+), 268 deletions(-) delete mode 100644 src/routes/popup/send/recipient.tsx diff --git a/src/routes/popup/send/recipient.tsx b/src/routes/popup/send/recipient.tsx deleted file mode 100644 index c220da71a..000000000 --- a/src/routes/popup/send/recipient.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { - ExtensionStorage, - TRANSFER_TX_STORAGE, - type RawStoredTransfer, - TempTransactionStorage -} from "~utils/storage"; -import { - InputV2, - Section, - Spacer, - Text, - useInput, - useToasts -} from "@arconnect/components"; -import { formatAddress, isAddressFormat } from "~utils/format"; -import { gql } from "~gateways/api"; -import type Transaction from "arweave/web/lib/transaction"; -import { useEffect, useMemo, useState } from "react"; -import { useStorage } from "@plasmohq/storage/hook"; -import { findGateway } from "~gateways/wayfinder"; -import { useLocation } from "~wallets/router/router.utils"; -import browser from "webextension-polyfill"; -import Head from "~components/popup/Head"; -import styled from "styled-components"; -import Arweave from "arweave"; -import type { CommonRouteProps } from "~wallets/router/router.types"; - -export interface RecipientViewParams { - token: string; - qty: string; - message?: string; -} - -export type RecipientViewProps = CommonRouteProps; - -export function RecipientView({ - params: { token: tokenID, qty, message } -}: RecipientViewProps) { - const { navigate } = useLocation(); - - // transaction target input - const targetInput = useInput(); - - // active address - const [activeAddress] = useStorage({ - key: "active_address", - instance: ExtensionStorage - }); - - // last recipients - const [lastRecipients, setLastRecipients] = useState([]); - - useEffect(() => { - (async () => { - if (!activeAddress) return; - - const gateway = await findGateway({ graphql: true }); - - // fetch last outgoing txs - const { data } = await gql( - ` - query($address: [String!]) { - transactions(owners: $address, first: 100) { - edges { - node { - recipient - } - } - } - } - `, - { address: activeAddress }, - gateway - ); - - // filter addresses - const recipients = data.transactions.edges - .filter((tx) => tx.node.recipient !== "") - .map((tx) => tx.node.recipient); - - setLastRecipients([...new Set(recipients)]); - })(); - }, [activeAddress]); - - // possible targets filtered using the search query - // or if the search query is a valid address, display - // only that - const possibleTargets = useMemo(() => { - const query = targetInput.state; - - if (!query || query === "") { - return lastRecipients; - } - - if (isAddressFormat(query)) { - return [targetInput.state]; - } - - return lastRecipients.filter((addr) => - addr.toLowerCase().includes(query.toLowerCase()) - ); - }, [lastRecipients, targetInput]); - - // toasts - const { setToast } = useToasts(); - - // prepare tx - async function send(target: string) { - try { - // create tx - const gateway = await findGateway({}); - const arweave = new Arweave(gateway); - - // save tx json into the session - // to be signed and submitted - const storedTx: Partial = { - type: tokenID === "AR" ? "native" : "token", - gateway: gateway - }; - - if (tokenID !== "AR") { - // create interaction - const tx = await arweave.createTransaction({ - target, - quantity: "0" - }); - - tx.addTag("App-Name", "SmartWeaveAction"); - tx.addTag("App-Version", "0.3.0"); - tx.addTag("Contract", tokenID); - tx.addTag( - "Input", - JSON.stringify({ - function: "transfer", - target: target, - qty: +qty - }) - ); - addTransferTags(tx); - - storedTx.transaction = tx.toJSON(); - } else { - const tx = await arweave.createTransaction({ - target, - quantity: qty.toString(), - data: message ? decodeURIComponent(message) : undefined - }); - - addTransferTags(tx); - - storedTx.transaction = tx.toJSON(); - } - - await TempTransactionStorage.set(TRANSFER_TX_STORAGE, storedTx); - - // push to auth & signature - navigate(`/send/auth/${tokenID}`); - } catch { - return setToast({ - type: "error", - content: browser.i18n.getMessage("transaction_send_error"), - duration: 2000 - }); - } - } - - async function addTransferTags(transaction: Transaction) { - if (message) { - transaction.addTag("Content-Type", "text/plain"); - } - transaction.addTag("Type", "Transfer"); - transaction.addTag("Client", "ArConnect"); - transaction.addTag("Client-Version", browser.runtime.getManifest().version); - } - - return ( - <> - - -
- { - if (e.key !== "Enter" || !isAddressFormat(targetInput.state)) - return; - send(targetInput.state); - }} - /> - - - {possibleTargets.map((recipient, i) => ( -
send(recipient)} key={i}> - {formatAddress(recipient, 10)} -
- ))} -
-
- - ); -} - -const AddressesList = styled.div` - display: flex; - flex-direction: column; - gap: 0.2rem; -`; - -const Address = styled(Text).attrs({ - noMargin: true -})` - padding: 0.86rem 0.7rem; - border-radius: 12px; - cursor: pointer; - background-color: transparent; - transition: all 0.17s ease; - - &:hover { - background-color: rgba(${(props) => props.theme.theme}, 0.12); - } - - &:active { - transform: scale(0.97); - } -`; diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 37b7e74af..266681597 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -15,7 +15,7 @@ import { type MutableRefObject } from "react"; import { useGateway } from "~gateways/wayfinder"; -import { useLocation } from "~wallets/router/router.utils"; +import { useLocation, useSearchParams } from "~wallets/router/router.utils"; import { ChevronDownIcon, ChevronUpIcon, @@ -49,7 +49,10 @@ import BigNumber from "bignumber.js"; import { fetchTokenByProcessId } from "~lib/transactions"; import { useStorage } from "@plasmohq/storage/hook"; import type { StoredWallet } from "~wallets"; -import type { CommonRouteProps } from "~wallets/router/router.types"; +import type { + ArConnectRoutePath, + CommonRouteProps +} from "~wallets/router/router.types"; // pull contacts and check if to address is in contacts @@ -69,12 +72,10 @@ export interface TransactionViewParams { export type TransactionViewProps = CommonRouteProps; export function TransactionView({ - params: { id: rawId, gateway: gw, message } + params: { id, gateway: gw, message } }: TransactionViewProps) { - const { navigate, back } = useLocation(); - - // fixup id - const id = useMemo(() => rawId.split("?")[0], [rawId]); + const { navigate } = useLocation(); + const { back: backPath } = useSearchParams<{ back?: string }>(); // TODO: Should this be a redirect? if (!id) return <>; @@ -304,19 +305,6 @@ export function TransactionView({ })(); }, [id, transaction, gateway, isBinary, isPrintTx]); - // get custom back params - const [backPath, setBackPath] = useState(); - - useEffect(() => { - const search = window.location.href.split("?"); - const params = new URLSearchParams(search[search.length - 1]); - const back = params.get("back"); - - if (!back) return; - - setBackPath(back); - }, []); - // Clears out current transaction useEffect(() => { (async () => { @@ -341,11 +329,7 @@ export function TransactionView({ message ? "message" : "transaction_complete" )} back={() => { - if (backPath === "/notifications" || backPath === "/transactions") { - back(); - } else { - navigate("/"); - } + navigate((backPath as ArConnectRoutePath) || "/"); }} /> {(transaction && ( diff --git a/src/wallets/router/popup/popup.routes.ts b/src/wallets/router/popup/popup.routes.ts index 31ba58c1b..df1f0b1b7 100644 --- a/src/wallets/router/popup/popup.routes.ts +++ b/src/wallets/router/popup/popup.routes.ts @@ -12,7 +12,6 @@ import { ReceiveView } from "~routes/popup/receive"; import { SendView } from "~routes/popup/send"; import { SendAuthView } from "~routes/popup/send/auth"; import { ConfirmView } from "~routes/popup/send/confirm"; -import { RecipientView } from "~routes/popup/send/recipient"; import { ApplicationsView } from "~routes/popup/settings/apps"; import { AppSettingsView } from "~routes/popup/settings/apps/[url]"; import { AppPermissionsView } from "~routes/popup/settings/apps/[url]/permissions"; @@ -65,7 +64,6 @@ export type PopupRoutePath = | `/transaction/${string}/${string}` | `/send/confirm/${string}/${string}/${string}` | `/send/confirm/${string}/${string}/${string}/${string}` - | `/send/recipient/${string}/${string}/${string}` | `/quick-settings` | `/quick-settings/wallets` | `/quick-settings/wallets/${string}` @@ -104,7 +102,6 @@ export const PopupPaths = { Collectible: "/collectible/:id", Transaction: "/transaction/:id/:gateway?", Confirm: "/send/confirm/:token/:qty/:recipient/:message?", - Recipient: "/send/recipient/:token/:qty/:message?", QuickSettings: "/quick-settings", Wallets: "/quick-settings/wallets", Wallet: "/quick-settings/wallets/:address", @@ -213,10 +210,6 @@ export const POPUP_ROUTES = [ path: PopupPaths.Confirm, component: ConfirmView }, - { - path: PopupPaths.Recipient, - component: RecipientView - }, { path: PopupPaths.QuickSettings, component: QuickSettingsView diff --git a/src/wallets/router/router.utils.ts b/src/wallets/router/router.utils.ts index 4c007152d..a976f5bb3 100644 --- a/src/wallets/router/router.utils.ts +++ b/src/wallets/router/router.utils.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useSearch as useWearch, useLocation as useWouterLocation @@ -38,6 +38,23 @@ export function BodyScroller() { return null; } +// This is just a temporary fix until either: +// - Wouter adds support for `history`. +// - We replace Wouter with a more capable routing library. +// - We implement a proper HistoryProvider that listens for location/history changes and updates its state accordingly. + +interface CustomHistoryEntry { + to: ArConnectRoutePath; + options?: { + replace?: boolean; + state?: S; + }; +} + +const customHistory: CustomHistoryEntry[] = []; + +const HISTORY_SIZE_LIMIT = 32; + export type NavigateAction = "prev" | "next" | "up" | number; function isNavigateAction( @@ -101,14 +118,27 @@ export function useLocation() { } } + customHistory.push({ to: toPath, options }); + customHistory.splice(0, customHistory.length - HISTORY_SIZE_LIMIT); + return wavigate(toPath, options); }, [wocation, wavigate] ); const back = useCallback(() => { - history.back(); - }, []); + // Remove current route...: + customHistory.pop(); + + // ...and read the last one where we want to navigate to: + const lastRoute = customHistory[customHistory.length - 1]; + + // Navigate to the previous route (if available): + if (lastRoute) wavigate(lastRoute.to, lastRoute.options); + + // Wouter doesn't handle history, so this won't work: + // history.back(); + }, [wocation, wavigate]); return { location: wocation, From f6ef12f0fbb7c28e08718b54fdfce8d89c44a906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 10 Dec 2024 15:27:07 +0100 Subject: [PATCH 123/142] Fix back functionality and add clarification comments about certain misleading aspects. --- src/routes/popup/transaction/[id].tsx | 13 +++++++++++-- src/wallets/router/router.utils.ts | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 266681597..34ef67618 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -74,7 +74,7 @@ export type TransactionViewProps = CommonRouteProps; export function TransactionView({ params: { id, gateway: gw, message } }: TransactionViewProps) { - const { navigate } = useLocation(); + const { navigate, back } = useLocation(); const { back: backPath } = useSearchParams<{ back?: string }>(); // TODO: Should this be a redirect? @@ -329,7 +329,16 @@ export function TransactionView({ message ? "message" : "transaction_complete" )} back={() => { - navigate((backPath as ArConnectRoutePath) || "/"); + // This is misleading and `backPath` is only used to indicate whether the back button actually navigates + // back or goes straight to Home. This is because this page is also accessed from the Home > Transactions + // tab items, which set `backPath = "/transactions"`, but pressing the back button would instead (but + // correctly) navigate Home. Also, in the `else` block it looks like there are other options, but actually + // there aren't; that branch always does `navigate("/")`: + if (backPath === "/notifications" || backPath === "/transactions") { + back(); + } else { + navigate((backPath as ArConnectRoutePath) || "/"); + } }} /> {(transaction && ( diff --git a/src/wallets/router/router.utils.ts b/src/wallets/router/router.utils.ts index a976f5bb3..6dc0cb7bd 100644 --- a/src/wallets/router/router.utils.ts +++ b/src/wallets/router/router.utils.ts @@ -135,6 +135,7 @@ export function useLocation() { // Navigate to the previous route (if available): if (lastRoute) wavigate(lastRoute.to, lastRoute.options); + else wavigate("/"); // Wouter doesn't handle history, so this won't work: // history.back(); From 603f712b94ebaec9a049771338464e6a4758042b Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 12 Dec 2024 14:32:51 +0545 Subject: [PATCH 124/142] fix: add transaction completion page and support multiple gateways for loading transactions --- assets/_locales/en/messages.json | 4 + assets/_locales/zh_CN/messages.json | 4 + .../gateway-update-alarm.handler.ts | 7 +- src/gateways/gateway.ts | 12 +- src/gateways/wayfinder.ts | 76 +++++++- src/lib/wayfinder.ts | 6 +- src/routes/popup/send/completed.tsx | 172 ++++++++++++++++++ src/routes/popup/send/confirm.tsx | 12 +- src/routes/popup/transaction/[id].tsx | 16 +- src/wallets/router/popup/popup.routes.ts | 7 + 10 files changed, 298 insertions(+), 18 deletions(-) create mode 100644 src/routes/popup/send/completed.tsx diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index 39334e87c..171e2933f 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -1333,6 +1333,10 @@ "message": "Click to view your last signed transaction", "description": "View last tx prompt content" }, + "view_transaction_details": { + "message": "View transaction details", + "description": "View transaction details link text" + }, "sign_item": { "message": "Sign item", "description": "Sign message popup title" diff --git a/assets/_locales/zh_CN/messages.json b/assets/_locales/zh_CN/messages.json index 33596b5d8..c931c6d01 100644 --- a/assets/_locales/zh_CN/messages.json +++ b/assets/_locales/zh_CN/messages.json @@ -1321,6 +1321,10 @@ "message": "点击查看您最近签署的交易", "description": "View last tx prompt content" }, + "view_transaction_details": { + "message": "查看交易详情", + "description": "View transaction details link text" + }, "sign_item": { "message": "签署项目", "description": "Sign message popup title" diff --git a/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts b/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts index 7bded32a1..d77a217ef 100644 --- a/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts +++ b/src/api/background/handlers/alarms/gateway-update/gateway-update-alarm.handler.ts @@ -32,7 +32,12 @@ export async function handleGatewayUpdateAlarm(alarm?: Alarms.Alarm) { const gatewaysResult = await ArIO.read< PaginatedResult >({ - tags: [{ name: "Action", value: "Gateways" }] + tags: [ + { name: "Action", value: "Paginated-Gateways" }, + { name: "Sort-By", value: "operatorStake" }, + { name: "Sort-Order", value: "desc" }, + { name: "Limit", value: "100" } + ] }); const garItems = extractGarItems(gatewaysResult.items); diff --git a/src/gateways/gateway.ts b/src/gateways/gateway.ts index e4227d335..7b968a197 100644 --- a/src/gateways/gateway.ts +++ b/src/gateways/gateway.ts @@ -49,12 +49,14 @@ export const fallbackGateway = { protocol: "https" }; +export const goldskyGateway: Gateway = { + host: "arweave-search.goldsky.com", + port: 443, + protocol: "https" +}; + export const printTxWorkingGateways: Gateway[] = [ - { - host: "arweave-search.goldsky.com", - port: 443, - protocol: "https" - }, + goldskyGateway, { host: "permagate.io", port: 443, diff --git a/src/gateways/wayfinder.ts b/src/gateways/wayfinder.ts index 15ecd4781..cd82b9ec1 100644 --- a/src/gateways/wayfinder.ts +++ b/src/gateways/wayfinder.ts @@ -1,5 +1,10 @@ import { isValidGateway, sortGatewaysByOperatorStake } from "~lib/wayfinder"; -import { defaultGateway, type Gateway } from "./gateway"; +import { + defaultGateway, + goldskyGateway, + suggestedGateways, + type Gateway +} from "./gateway"; import { useEffect, useState } from "react"; import { getGatewayCache } from "./cache"; import { getSetting } from "~settings"; @@ -121,6 +126,75 @@ export function useGateway(requirements: Requirements) { return activeGateway; } +export async function findGraphqlGateways(count?: number) { + try { + const gateways = await getGatewayCache(); + + if (!gateways?.length) { + return suggestedGateways; + } + + const filteredGateways = gateways.filter( + ({ ping, health }) => + ping.status === "success" && health.status === "success" + ); + + if (!filteredGateways.length) { + return suggestedGateways; + } + + return sortGatewaysByOperatorStake(filteredGateways) + .filter((gateway: any) => gateway?.properties?.GRAPHQL) + .slice(0, count || filteredGateways.length) + .map(({ settings: { fqdn, port, protocol } }) => ({ + host: fqdn, + port, + protocol + })); + } catch { + return suggestedGateways; + } +} + +export function useGraphqlGateways(count?: number) { + const [graphqlGateways, setGraphqlGateways] = useState([]); + + useEffect(() => { + const fetchGateways = async () => { + try { + const gateways = await findGraphqlGateways(count); + const hasDefaultGateway = gateways.some( + (g) => g.host === defaultGateway.host + ); + const hasGoldskyGateway = gateways.some( + (g) => g.host === goldskyGateway.host + ); + + const finalGateways = [...gateways]; + + if (!hasDefaultGateway) { + finalGateways.unshift(defaultGateway); + } + + if (!hasGoldskyGateway) { + const insertionIndex = Math.min(2, finalGateways.length); + finalGateways.splice(insertionIndex, 0, goldskyGateway); + } + + setGraphqlGateways( + finalGateways.slice(0, count || finalGateways.length) + ); + } catch { + setGraphqlGateways(suggestedGateways); + } + }; + + fetchGateways(); + }, [count]); + + return graphqlGateways; +} + export interface Requirements { /* Whether the gateway should support GraphQL requests */ graphql?: boolean; diff --git a/src/lib/wayfinder.ts b/src/lib/wayfinder.ts index 3d77e59fe..330455b80 100644 --- a/src/lib/wayfinder.ts +++ b/src/lib/wayfinder.ts @@ -34,7 +34,7 @@ async function pingUpdater( .sort((a, b) => b.operatorStake - a.operatorStake) .slice(0, 5); - if (CLabsGateway && !newData.some((item) => item.id === CLabsGateway)) { + if (CLabsGateway && !newData.some((item) => item.id === CLabs)) { newData.push(CLabsGateway); } @@ -149,10 +149,10 @@ const fetchGatewayProperties = async (txn) => { }; const isValidGateway = (gateway: any, requirements: Requirements): boolean => { - if (requirements.graphql && !gateway.properties.GRAPHQL) { + if (requirements.graphql && !gateway?.properties?.GRAPHQL) { return false; } - if (requirements.arns && !gateway.properties.ARNS) { + if (requirements.arns && !gateway?.properties?.ARNS) { return false; } if ( diff --git a/src/routes/popup/send/completed.tsx b/src/routes/popup/send/completed.tsx new file mode 100644 index 000000000..7c31a8f00 --- /dev/null +++ b/src/routes/popup/send/completed.tsx @@ -0,0 +1,172 @@ +import { AnimatePresence, type Variants, motion } from "framer-motion"; +import { Section, Text } from "@arconnect/components"; +import { useLocation, useSearchParams } from "~wallets/router/router.utils"; +import { CheckIcon, ShareIcon } from "@iconicicons/react"; +import browser from "webextension-polyfill"; +import styled from "styled-components"; +import HeadV2 from "~components/popup/HeadV2"; +import type { + ArConnectRoutePath, + CommonRouteProps +} from "~wallets/router/router.types"; +import { SendButton } from "."; + +export interface TransactionCompletedParams { + id: string; +} + +export type TransactionCompletedViewProps = + CommonRouteProps; + +export function TransactionCompletedView({ + params: { id } +}: TransactionCompletedViewProps) { + const { navigate, back } = useLocation(); + const { back: backPath, isAo } = useSearchParams<{ + back?: string; + isAo: boolean; + }>(); + + function handleOpen() { + const url = isAo + ? `https://www.ao.link/#/message/${id}` + : `https://viewblock.io/arweave/tx/${id}`; + + browser.tabs.create({ url }); + } + + if (!id) return null; + + return ( + + + { + // This is misleading and `backPath` is only used to indicate whether the back button actually navigates + // back or goes straight to Home. This is because this page is also accessed from the Home > Transactions + // tab items, which set `backPath = "/transactions"`, but pressing the back button would instead (but + // correctly) navigate Home. Also, in the `else` block it looks like there are other options, but actually + // there aren't; that branch always does `navigate("/")`: + if (backPath === "/notifications" || backPath === "/transactions") { + back(); + } else { + navigate((backPath as ArConnectRoutePath) || "/"); + } + }} + /> + + + + + + {browser.i18n.getMessage("transaction_complete")} + + {browser.i18n.getMessage("transaction_id")}: {id} + + + navigate( + `/transaction/${id}${ + backPath ? `?back=${encodeURIComponent(backPath)}` : "" + }` as ArConnectRoutePath + ) + } + > + {browser.i18n.getMessage("view_transaction_details")} + + + + + + {id && ( + +
+ + {isAo ? "AOLink" : "Viewblock"} + + +
+
+ )} +
+
+ ); +} + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 100vh; +`; + +const opacityAnimation: Variants = { + hidden: { opacity: 0 }, + shown: { opacity: 1 } +}; + +const MainWrapper = styled.div` + display: flex; + flex-direction: column; + flex: 1; +`; + +const BodyWrapper = styled.div` + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 16px; +`; + +const Circle = styled.div` + width: 75px; + height: 75px; + border-radius: 50%; + background-color: rgba(20, 209, 16, 0.25); + border: 4px solid #14d110; + display: flex; + justify-content: center; + align-items: center; +`; + +const TextContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + text-align: center; + max-width: 90%; +`; + +const Title = styled(Text).attrs({ + noMargin: true +})` + font-size: 20px; + word-break: break-word; + overflow-wrap: break-word; + color: ${(props) => props.theme.primaryTextv2}; +`; + +const SubTitle = styled(Text).attrs({ + noMargin: true +})` + display: flex; + flex-wrap: wrap; + font-size: 16px; + color: ${(props) => props.theme.secondaryTextv2}; + word-break: break-word; + overflow-wrap: break-word; +`; + +const LinkText = styled(Text)` + font-size: 16px; + color: ${(props) => props.theme.primary}; + cursor: pointer; +`; diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index a69e593c6..276d4ea47 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -366,7 +366,7 @@ export function ConfirmView({ content: browser.i18n.getMessage("sent_tx"), duration: 2000 }); - navigate(`/transaction/${res}`); + navigate(`/send/completed/${res}?isAo=true`); setIsLoading(false); } return res; @@ -428,7 +428,7 @@ export function ConfirmView({ uToken ? navigate("/") : navigate( - `/transaction/${ + `/send/completed/${ convertedTransaction.id }?back=${encodeURIComponent("/")}` ); @@ -496,7 +496,7 @@ export function ConfirmView({ uToken ? navigate("/") : navigate( - `/transaction/${ + `/send/completed/${ convertedTransaction.id }?back=${encodeURIComponent("/")}` ); @@ -586,7 +586,7 @@ export function ConfirmView({ content: browser.i18n.getMessage("sent_tx"), duration: 2000 }); - navigate(`/transaction/${res}`); + navigate(`/send/completed/${res}?isAo=true`); setIsLoading(false); } return res; @@ -687,7 +687,9 @@ export function ConfirmView({ uToken ? navigate("/") : navigate( - `/transaction/${transaction.id}?back=${encodeURIComponent("/")}` + `/send/completed/${transaction.id}?back=${encodeURIComponent( + "/" + )}` ); } catch (e) { console.log(e); diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 90f1eee66..8e81e4062 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -14,7 +14,11 @@ import { useState, type MutableRefObject } from "react"; -import { STAKED_GQL_FULL_HISTORY, useGateway } from "~gateways/wayfinder"; +import { + STAKED_GQL_FULL_HISTORY, + useGateway, + useGraphqlGateways +} from "~gateways/wayfinder"; import { useLocation, useSearchParams } from "~wallets/router/router.utils"; import { ChevronDownIcon, @@ -122,13 +126,16 @@ export function TransactionView({ return urlToGateway(decodeURIComponent(gw)); }, [gw, defaultGateway]); + const graphqlGateways = useGraphqlGateways(5); + // arweave client const arweave = useMemo(() => new Arweave(gateway), [gateway]); useEffect(() => { - if (!id) return; + if (!id || !graphqlGateways.length) return; let timeoutID: number | undefined; + let fetchCount = 0; const fetchTx = async () => { const cachedTx = JSON.parse(localStorage.getItem("latest_tx") || "{}"); @@ -136,6 +143,8 @@ export function TransactionView({ // load cached tx if (cachedTx?.id === id) setTransaction(cachedTx); + const gateway = graphqlGateways[fetchCount % graphqlGateways.length]; + const { data } = await gql( ` query($id: ID!) { @@ -170,6 +179,7 @@ export function TransactionView({ ); if (!data.transaction) { + fetchCount++; timeoutID = setTimeout(fetchTx, 5000); } else { timeoutID = undefined; @@ -217,7 +227,7 @@ export function TransactionView({ return () => { if (timeoutID) clearTimeout(timeoutID); }; - }, [id, gateway]); + }, [id, graphqlGateways]); // transaction confirmations const [confirmations, setConfirmations] = useState(0); diff --git a/src/wallets/router/popup/popup.routes.ts b/src/wallets/router/popup/popup.routes.ts index df1f0b1b7..1ba1adb22 100644 --- a/src/wallets/router/popup/popup.routes.ts +++ b/src/wallets/router/popup/popup.routes.ts @@ -11,6 +11,7 @@ import { PurchaseView } from "~routes/popup/purchase"; import { ReceiveView } from "~routes/popup/receive"; import { SendView } from "~routes/popup/send"; import { SendAuthView } from "~routes/popup/send/auth"; +import { TransactionCompletedView } from "~routes/popup/send/completed"; import { ConfirmView } from "~routes/popup/send/confirm"; import { ApplicationsView } from "~routes/popup/settings/apps"; import { AppSettingsView } from "~routes/popup/settings/apps/[url]"; @@ -64,6 +65,7 @@ export type PopupRoutePath = | `/transaction/${string}/${string}` | `/send/confirm/${string}/${string}/${string}` | `/send/confirm/${string}/${string}/${string}/${string}` + | `/send/completed/${string}` | `/quick-settings` | `/quick-settings/wallets` | `/quick-settings/wallets/${string}` @@ -102,6 +104,7 @@ export const PopupPaths = { Collectible: "/collectible/:id", Transaction: "/transaction/:id/:gateway?", Confirm: "/send/confirm/:token/:qty/:recipient/:message?", + TransactionCompleted: "/send/completed/:id", QuickSettings: "/quick-settings", Wallets: "/quick-settings/wallets", Wallet: "/quick-settings/wallets/:address", @@ -210,6 +213,10 @@ export const POPUP_ROUTES = [ path: PopupPaths.Confirm, component: ConfirmView }, + { + path: PopupPaths.TransactionCompleted, + component: TransactionCompletedView + }, { path: PopupPaths.QuickSettings, component: QuickSettingsView From 6632ff5d6c9515f67983c3063d1e0ff7a3fe59a1 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 12 Dec 2024 14:42:43 +0545 Subject: [PATCH 125/142] refactor: update back in the header --- src/routes/popup/send/completed.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/routes/popup/send/completed.tsx b/src/routes/popup/send/completed.tsx index 7c31a8f00..b753ce76e 100644 --- a/src/routes/popup/send/completed.tsx +++ b/src/routes/popup/send/completed.tsx @@ -42,18 +42,7 @@ export function TransactionCompletedView({ { - // This is misleading and `backPath` is only used to indicate whether the back button actually navigates - // back or goes straight to Home. This is because this page is also accessed from the Home > Transactions - // tab items, which set `backPath = "/transactions"`, but pressing the back button would instead (but - // correctly) navigate Home. Also, in the `else` block it looks like there are other options, but actually - // there aren't; that branch always does `navigate("/")`: - if (backPath === "/notifications" || backPath === "/transactions") { - back(); - } else { - navigate((backPath as ArConnectRoutePath) || "/"); - } - }} + back={() => navigate((backPath as ArConnectRoutePath) || "/")} /> From 540274a7ef0afa4061f10e0085600c72b7cdf802 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 12 Dec 2024 21:23:49 +0545 Subject: [PATCH 126/142] refactor: update sign header, humanize timestamps & show token fiat amount if present --- src/routes/auth/sign.tsx | 30 ++++++++++++++++++++++++---- src/tokens/aoTokens/ao.ts | 3 ++- src/utils/timestamp.ts | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 src/utils/timestamp.ts diff --git a/src/routes/auth/sign.tsx b/src/routes/auth/sign.tsx index c8a913997..2a42f0c38 100644 --- a/src/routes/auth/sign.tsx +++ b/src/routes/auth/sign.tsx @@ -32,6 +32,8 @@ import BigNumber from "bignumber.js"; import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; +import { getTagValue } from "~tokens/aoTokens/ao"; +import { humanizeTimestampTags } from "~utils/timestamp"; export function SignAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = @@ -93,13 +95,29 @@ export function SignAuthRequestView() { // @ts-expect-error const tags = transaction.get("tags") as Tag[]; - - return tags.map((tag) => ({ + const decodedTags = tags.map((tag) => ({ name: tag.get("name", { decode: true, string: true }), value: tag.get("value", { decode: true, string: true }) })); + + return humanizeTimestampTags(decodedTags); }, [transaction]); + const headerTitle = useMemo(() => { + if (!tags.length) return browser.i18n.getMessage("titles_sign"); + + const actionValue = getTagValue("Action", tags); + const isAOTransaction = tags.some( + (tag) => tag.name === "Data-Protocol" && tag.value === "ao" + ); + + if (isAOTransaction && actionValue) { + return actionValue.replace(/-/g, " "); + } + + return browser.i18n.getMessage("titles_sign"); + }, [tags]); + // Check if it's a printTx const isPrintTx = useMemo(() => { return ( @@ -208,7 +226,7 @@ export function SignAuthRequestView() { return (
- + {(!page && (
@@ -225,7 +243,11 @@ export function SignAuthRequestView() {
) : ( - {formatFiatBalance(fiatPrice, currency)} + +fiatPrice > 0 && ( + + {formatFiatBalance(fiatPrice, currency)} + + ) )} {isPrintTx diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index 98c1120aa..db4516b33 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -17,6 +17,7 @@ import type { KeystoneSigner } from "~wallets/hardware/keystone"; import browser from "webextension-polyfill"; import { fetchTokenByProcessId } from "~lib/transactions"; import { timeoutPromise } from "~utils/promises/timeout"; +import type { DecodedTag } from "~api/modules/sign/tags"; export type AoInstance = ReturnType; @@ -413,7 +414,7 @@ export function useAoTokensCache(): [TokenInfoWithBalance[], boolean] { /** * Find the value for a tag name */ -export const getTagValue = (tagName: string, tags: Tag[]) => +export const getTagValue = (tagName: string, tags: (Tag | DecodedTag)[]) => tags.find((t) => t.name === tagName)?.value; export const sendAoTransfer = async ( diff --git a/src/utils/timestamp.ts b/src/utils/timestamp.ts new file mode 100644 index 000000000..a4fce006b --- /dev/null +++ b/src/utils/timestamp.ts @@ -0,0 +1,41 @@ +import type { DecodedTag } from "~api/modules/sign/tags"; + +export const TIMESTAMP_TAGS = ["Expires-At", "X-Timestamp"] as const; +type TimestampTag = (typeof TIMESTAMP_TAGS)[number]; + +const TIMESTAMP_TAG_SET = Object.freeze(new Set(TIMESTAMP_TAGS)); + +const timestampFormatter = new Intl.DateTimeFormat("en-US", { + day: "2-digit", + month: "2-digit", + year: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false +}); + +const isTimestampTag = (tag: string): tag is TimestampTag => + TIMESTAMP_TAG_SET.has(tag as TimestampTag); + +export const humanizeTimestamp = ( + timestamp: string | number | Date | undefined +): string => { + if (!timestamp) return ""; + + try { + const date = timestamp instanceof Date ? timestamp : new Date(+timestamp); + if (isNaN(date.getTime())) throw new Error("Invalid date"); + return timestampFormatter.format(date); + } catch { + return String(timestamp); + } +}; + +export const humanizeTimestampTags = (tags: DecodedTag[]): DecodedTag[] => { + return tags.map(({ name, ...rest }) => + isTimestampTag(name) + ? { name, ...rest, value: humanizeTimestamp(rest.value) } + : { name, ...rest } + ); +}; From 194ff2677d83c85514a48c62807b2fd78bbc2a94 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 13 Dec 2024 20:11:00 +0545 Subject: [PATCH 127/142] refactor: update the default assets array --- .../dashboard/subsettings/AddToken.tsx | 17 ++++++++++++++--- src/tokens/aoTokens/ao.ts | 7 +++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/components/dashboard/subsettings/AddToken.tsx b/src/components/dashboard/subsettings/AddToken.tsx index 985db4afb..f7bc052ba 100644 --- a/src/components/dashboard/subsettings/AddToken.tsx +++ b/src/components/dashboard/subsettings/AddToken.tsx @@ -20,6 +20,7 @@ import { concatGatewayURL } from "~gateways/utils"; import { FULL_HISTORY, useGateway } from "~gateways/wayfinder"; import type { CommonRouteProps } from "~wallets/router/router.types"; import { getTokenInfo } from "~tokens/aoTokens/router"; +import { AO_NATIVE_TOKEN, AO_NATIVE_TOKEN_INFO } from "~utils/ao_import"; export interface AddTokenDashboardViewProps extends CommonRouteProps { isQuickSetting?: boolean; @@ -48,11 +49,17 @@ export function AddTokenDashboardView({ throw new Error("Token already added"); } - aoTokens.push({ + const tokenToImport = { ...token, processId: targetInput.state, type: tokenType - }); + }; + + if (tokenToImport.processId === AO_NATIVE_TOKEN) { + aoTokens.unshift(tokenToImport); + } else { + aoTokens.push(tokenToImport); + } await ExtensionStorage.set("ao_tokens", aoTokens); setToast({ type: "success", @@ -71,7 +78,11 @@ export function AddTokenDashboardView({ //TODO double check isAddress(targetInput.state); - const token = await getTokenInfo(targetInput.state); + const isAoToken = targetInput.state === AO_NATIVE_TOKEN; + + const token = isAoToken + ? AO_NATIVE_TOKEN_INFO + : await getTokenInfo(targetInput.state); setToken(token); setLoading(false); } catch (err) { diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index 98c1120aa..a91283bf4 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -34,6 +34,13 @@ export const defaultAoTokens: TokenInfo[] = [ Denomination: 12, Logo: "26yDr08SuwvNQ4VnhAfV4IjJcOOlQ4tAQLc1ggrCPu0", processId: "NG-0lVX882MG5nhARrSzyprEK6ejonHpdUmaaMPsHE8" + }, + { + Name: "Wrapped AR", + Ticker: "wAR", + Denomination: 12, + Logo: "L99jaxRKQKJt9CqoJtPaieGPEhJD3wNhR4iGqc8amXs", + processId: "xU9zFkq3X2ZQ6olwNVvr1vUWIjc3kXTWr7xKQD6dh10" } ]; From 2952a4d93c454c974b584a71772bb4611b1991e0 Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira Date: Fri, 13 Dec 2024 14:26:16 +0000 Subject: [PATCH 128/142] adds reusable method for checking gateways & refactor appPlaceholder one --- src/applications/application.ts | 17 +++++- src/components/HeadAuth.tsx | 14 +++-- src/components/popup/HeadV2.tsx | 6 +-- src/utils/urls/getAppIconPlaceholder.ts | 72 ++++++++++++------------- src/utils/urls/isGateway.ts | 45 ++++++++++++++++ 5 files changed, 106 insertions(+), 48 deletions(-) create mode 100644 src/utils/urls/isGateway.ts diff --git a/src/applications/application.ts b/src/applications/application.ts index 107ddc03a..a05414126 100644 --- a/src/applications/application.ts +++ b/src/applications/application.ts @@ -26,7 +26,8 @@ export default class Application { } /** - * Get all settings for the app + * Private method used to retrieve all settings for an app + * @returns settings objects or an empty object */ async #getSettings() { const settings = await this.#storage.get>( @@ -71,6 +72,10 @@ export default class Application { async getAppData(): Promise { const settings = await this.#getSettings(); + if (!settings.logo) { + return {}; + } + return { name: settings.name, logo: settings.logo @@ -193,6 +198,16 @@ export interface AppInfo { logo?: string; } +/** + * App Logo info to be used to derive the proper placeholder + * extends AppInfo's name and logo + * adding an optional type: "default" | "gateway" and placeholder + */ +export interface AppLogoInfo extends AppInfo { + type?: "default" | "gateway"; + placeholder?: string; +} + /** * Params to add an app with */ diff --git a/src/components/HeadAuth.tsx b/src/components/HeadAuth.tsx index 9bada4a05..ebe186c69 100644 --- a/src/components/HeadAuth.tsx +++ b/src/components/HeadAuth.tsx @@ -1,12 +1,13 @@ import type React from "react"; import { useEffect, useState } from "react"; import styled from "styled-components"; -import type { AppInfo } from "~applications/application"; +import type { AppInfo, AppLogoInfo } from "~applications/application"; import Application from "~applications/application"; import HeadV2 from "~components/popup/HeadV2"; import { useAuthRequests } from "~utils/auth/auth.hooks"; import type { AuthRequestStatus } from "~utils/auth/auth.types"; import browser from "webextension-polyfill"; +import { generateLogoPlaceholder } from "~utils/urls/getAppIconPlaceholder"; export interface HeadAuthProps { title?: string; @@ -27,7 +28,7 @@ export const HeadAuth: React.FC = ({ // `/src/routes/auth/connect.tsx` calls `addApp()`, so the `appInfo` prop (`appInfoProp`) is used as initial / // fallback value: - const [appInfo, setAppInfo] = useState(appInfoProp); + const [appLogoInfo, setAppLogoInfo] = useState(appInfoProp); const authRequest = authRequests[currentAuthRequestIndex]; @@ -39,17 +40,20 @@ export const HeadAuth: React.FC = ({ const app = new Application(url); const appInfo = await app.getAppData(); + const appLogoPlaceholder = await generateLogoPlaceholder(url); // TODO: The fallback to get the name from the URL might not work properly as it might be the name of a gateway. // It might be better to just show a lock icon as fallback. Also, this logic might be better placed inside the // `Application` class. - setAppInfo({ + setAppLogoInfo({ name: appInfo.name || appInfoProp.name || new URL(url).hostname.split(".").slice(-2).join("."), - logo: appInfo.logo || appInfoProp.logo + logo: appInfo.logo || appInfoProp.logo, + type: appLogoPlaceholder.type, + placeholder: appLogoPlaceholder.placeholder }); } @@ -73,7 +77,7 @@ export const HeadAuth: React.FC = ({ showOptions={false} showBack={!!back} back={back} - appInfo={appInfo} + appInfo={appLogoInfo} onAppInfoClick={handleAppInfoClicked} /> diff --git a/src/components/popup/HeadV2.tsx b/src/components/popup/HeadV2.tsx index b9c07ad58..1c96bcb2d 100644 --- a/src/components/popup/HeadV2.tsx +++ b/src/components/popup/HeadV2.tsx @@ -21,7 +21,7 @@ import keystoneLogo from "url:/assets/hardware/keystone.png"; import WalletSwitcher from "./WalletSwitcher"; import styled from "styled-components"; import { svgie } from "~utils/svgies"; -import type { AppInfo } from "~applications/application"; +import type { AppLogoInfo } from "~applications/application"; import Application from "~applications/application"; import Squircle from "~components/Squircle"; @@ -32,7 +32,7 @@ export interface HeadV2Props { showBack?: boolean; padding?: string; back?: (...args) => any; - appInfo?: AppInfo; + appInfo?: AppLogoInfo; onAppInfoClick?: () => void; } @@ -104,7 +104,7 @@ export default function HeadV2({ const [, goBack] = useHistory(); const appName = appInfo?.name; - const appIconPlaceholderText = appName?.slice(0, 2).toUpperCase(); + const appIconPlaceholderText = appInfo.placeholder; const SquircleWrapper = onAppInfoClick ? ButtonSquircle : React.Fragment; diff --git a/src/utils/urls/getAppIconPlaceholder.ts b/src/utils/urls/getAppIconPlaceholder.ts index d92fed895..414adec94 100644 --- a/src/utils/urls/getAppIconPlaceholder.ts +++ b/src/utils/urls/getAppIconPlaceholder.ts @@ -1,46 +1,40 @@ -interface IGetAppIconProps { - name?: string; - url?: string; -} - +import type { AppLogoInfo } from "~applications/application"; +import { isGateway } from "./isGateway"; /** - * Generates a placeholder text based on the app name or URL. - * - * - If `appInfo.name` is available, it returns the first two uppercase letters of the name. - * - If `url` is used as a fallback: - * - Extracts the first two letters from the subdomain if it is a gateway. - * - Extracts the first two letters from the domain if it is a regular site. - * - Defaults to a lock icon (🔒) if neither `appInfo.name` nor a valid `url` is available. - * - * @param {Object} appInfo - Application information containing the name. - * @param {string} [appInfo.name] - The name of the application. - * @param {string} [url] - A fallback URL to determine placeholder text. - * @returns {string} - The generated placeholder text or default icon. + * Generates a logo placeholder based on the base domain. + * If the URL is a gateway (determined by a GET call), it uses the first two letters of the subdomain. + * Otherwise, it defaults to the first two letters of the base domain. + * @param url - The URL to parse. + * @returns - A promise resolving to the logo placeholder. */ -const getAppIconPlaceholder = (appInfo: IGetAppIconProps): string => { - const appName = appInfo?.name; +export async function generateLogoPlaceholder( + url: string +): Promise { + try { + const { hostname } = new URL(url); - if (appName) { - return appName.slice(0, 2).toUpperCase(); - } + const parts = hostname.split("."); - if (appInfo.url) { - try { - const parsedUrl = new URL(appInfo.url); - const hostnameParts = parsedUrl.hostname.split("."); + const baseDomain = parts.length > 2 ? parts.slice(-2).join(".") : hostname; - if (hostnameParts.length > 2) { - return hostnameParts[0].slice(0, 2).toUpperCase(); - } else { - const domain = hostnameParts[hostnameParts.length - 2]; - return domain.slice(0, 2).toUpperCase(); - } - } catch { - /* We're using a string based icon to avoid refactoring SquircleImg component - * since it also receives an img prop coming from appInfo. - **/ - return "🔒"; + const isGatewayUrl = await isGateway(url); + + if (isGatewayUrl) { + // For gateways, take the first two letters of the first subdomain + const subdomain = parts[0]; + return { + type: "gateway", + placeholder: subdomain.slice(0, 2).toUpperCase() + }; + } else { + // For non-gateways, take the first two letters of the base domain + return { + type: "default", + placeholder: baseDomain.slice(0, 2).toUpperCase() + }; } + } catch (error) { + console.error(`Error generating logo placeholder for URL: ${url}`, error); + return undefined; } - return "🔒"; -}; +} diff --git a/src/utils/urls/isGateway.ts b/src/utils/urls/isGateway.ts new file mode 100644 index 000000000..21abdf1a2 --- /dev/null +++ b/src/utils/urls/isGateway.ts @@ -0,0 +1,45 @@ +interface GatewayResponse { + version: number; + release: number; + queue_length: number; + peers: number; + node_state_latency: number; + network: string; + height: number; + current: string; + blocks: number; +} + +/** + * Determines if a given URL is a gateway. + * @param url - The URL to check. + * @returns - A boolean indicating whether the URL is a gateway. + */ +export async function isGateway(url: string): Promise { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.statusText}`); + } + + const data = (await response.json()) as Partial; + + // Check if the response contains all expected keys + const requiredKeys: (keyof GatewayResponse)[] = [ + "version", + "release", + "queue_length", + "peers", + "node_state_latency", + "network", + "height", + "current", + "blocks" + ]; + + return requiredKeys.every((key) => key in data); + } catch (error) { + console.error(`Error fetching data from URL: ${url}`, error); + return false; + } +} From 20025b569f9da25b001c5c56866cf282fc03d21e Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira Date: Fri, 13 Dec 2024 14:29:17 +0000 Subject: [PATCH 129/142] revert guard --- src/applications/application.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/applications/application.ts b/src/applications/application.ts index a05414126..f3eddacf2 100644 --- a/src/applications/application.ts +++ b/src/applications/application.ts @@ -72,10 +72,6 @@ export default class Application { async getAppData(): Promise { const settings = await this.#getSettings(); - if (!settings.logo) { - return {}; - } - return { name: settings.name, logo: settings.logo From 30a693f4f4f0f015724c71e905282870c786e22e Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira Date: Fri, 13 Dec 2024 15:28:28 +0000 Subject: [PATCH 130/142] remove TODO comment --- src/components/HeadAuth.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/HeadAuth.tsx b/src/components/HeadAuth.tsx index 0aad5ba4f..51de22397 100644 --- a/src/components/HeadAuth.tsx +++ b/src/components/HeadAuth.tsx @@ -33,7 +33,6 @@ export const HeadAuth: React.FC = ({ authRequests[currentAuthRequestIndex] || {}; const [appLogoInfo, setAppLogoInfo] = useState(appInfoProp); - useEffect(() => { async function loadAppInfo() { if (!url) return; @@ -42,16 +41,12 @@ export const HeadAuth: React.FC = ({ const appInfo = await app.getAppData(); const appLogoPlaceholder = await generateLogoPlaceholder(url); - // TODO: The fallback to get the name from the URL might not work properly as it might be the name of a gateway. - // It might be better to just show a lock icon as fallback. Also, this logic might be better placed inside the - // `Application` class. - setAppLogoInfo({ name: appInfo.name || fallbackName || new URL(url).hostname.split(".").slice(-2).join("."), - logo: appInfo.logo || fallbackLogo + logo: appInfo.logo || fallbackLogo, type: appLogoPlaceholder.type, placeholder: appLogoPlaceholder.placeholder }); From 08ff76177790da01d10995ab88ac741e8882ac61 Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira Date: Fri, 13 Dec 2024 16:23:20 +0000 Subject: [PATCH 131/142] address candidateGatewayUrl check before checking gateway --- src/utils/urls/getAppIconPlaceholder.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils/urls/getAppIconPlaceholder.ts b/src/utils/urls/getAppIconPlaceholder.ts index 414adec94..89dac5a91 100644 --- a/src/utils/urls/getAppIconPlaceholder.ts +++ b/src/utils/urls/getAppIconPlaceholder.ts @@ -15,9 +15,13 @@ export async function generateLogoPlaceholder( const parts = hostname.split("."); - const baseDomain = parts.length > 2 ? parts.slice(-2).join(".") : hostname; + const baseDomain = parts.slice(-2).join("."); - const isGatewayUrl = await isGateway(url); + const candidateGatewayUrl = + parts.length > 1 ? parts.slice(1).join(".") : null; + + const isGatewayUrl = + !!candidateGatewayUrl && (await isGateway(candidateGatewayUrl)); if (isGatewayUrl) { // For gateways, take the first two letters of the first subdomain From 3c61e84b6dc5104d1183848fa5b52f892393d652 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 13 Dec 2024 22:14:42 +0545 Subject: [PATCH 132/142] feat: add ao transfer events to analytics --- .../sign_data_item.background.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/api/modules/sign_data_item/sign_data_item.background.ts b/src/api/modules/sign_data_item/sign_data_item.background.ts index d6d0bfae0..be8b71eaf 100644 --- a/src/api/modules/sign_data_item/sign_data_item.background.ts +++ b/src/api/modules/sign_data_item/sign_data_item.background.ts @@ -13,6 +13,7 @@ import Arweave from "arweave"; import { requestUserAuthorization } from "../../../utils/auth/auth.utils"; import BigNumber from "bignumber.js"; import { createDataItem } from "~utils/data_item"; +import { EventType, trackDirect } from "~utils/analytics"; const background: BackgroundModuleFunction = async ( appData, @@ -28,6 +29,8 @@ const background: BackgroundModuleFunction = async ( const app = new Application(appData.url); const allowance = await app.getAllowance(); const alwaysAsk = allowance.enabled && allowance.limit.eq(BigNumber("0")); + let isTransferTx = false; + let amount = "0"; if ( dataItem.tags?.some( @@ -37,6 +40,7 @@ const background: BackgroundModuleFunction = async ( (tag) => tag.name === "Data-Protocol" && tag.value === "ao" ) ) { + isTransferTx = true; try { const quantityTag = dataItem.tags?.find((tag) => tag.name === "Quantity"); if (quantityTag) { @@ -48,6 +52,7 @@ const background: BackgroundModuleFunction = async ( } quantityTag.value = quantityBigNum.toFixed(0, BigNumber.ROUND_FLOOR); + amount = quantityTag.value; } } catch (e) { if (e?.message === "INVALID_QUANTITY") { @@ -116,6 +121,9 @@ const background: BackgroundModuleFunction = async ( // remove keyfile freeDecryptedWallet(decryptedWallet.keyfile); + // analytics + await trackSigned(app, appData.url, dataItem.target, amount, isTransferTx); + return Array.from(dataEntry.getRaw()); } else { // create bundlr tx as a data entry @@ -142,8 +150,29 @@ const background: BackgroundModuleFunction = async ( } catch (e) { throw new Error(e?.message || e); } + // analytics + await trackSigned(app, appData.url, dataItem.target, amount, isTransferTx); + return Array.from(dataEntry.getRaw()); } }; +async function trackSigned( + app: Application, + appUrl: string, + tokenId: string, + amount: string, + isTransferTx: boolean +) { + if (isTransferTx && amount !== "0") { + const appInfo = await app.getAppData(); + await trackDirect(EventType.SIGNED, { + appName: appInfo.name, + appUrl, + tokenId, + amount + }).catch(() => {}); + } +} + export default background; From 63835c868052c8229270ed80ea6a78895b6a4899 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 13 Dec 2024 22:28:15 +0545 Subject: [PATCH 133/142] refactor: add try-catch to prevent errors --- .../sign_data_item.background.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/api/modules/sign_data_item/sign_data_item.background.ts b/src/api/modules/sign_data_item/sign_data_item.background.ts index be8b71eaf..88f9ab7c8 100644 --- a/src/api/modules/sign_data_item/sign_data_item.background.ts +++ b/src/api/modules/sign_data_item/sign_data_item.background.ts @@ -164,15 +164,17 @@ async function trackSigned( amount: string, isTransferTx: boolean ) { - if (isTransferTx && amount !== "0") { - const appInfo = await app.getAppData(); - await trackDirect(EventType.SIGNED, { - appName: appInfo.name, - appUrl, - tokenId, - amount - }).catch(() => {}); - } + try { + if (isTransferTx && amount !== "0") { + const appInfo = await app.getAppData(); + await trackDirect(EventType.SIGNED, { + appName: appInfo.name, + appUrl, + tokenId, + amount + }); + } + } catch {} } export default background; From bc3683575c71a035b28459ad4558b3b24520589a Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Sat, 14 Dec 2024 21:17:42 +0545 Subject: [PATCH 134/142] fix: resolve issue with placeholder & related code --- src/components/HeadAuth.tsx | 4 ++-- src/components/popup/HeadV2.tsx | 2 +- src/utils/urls/getAppIconPlaceholder.ts | 21 ++++++++++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/components/HeadAuth.tsx b/src/components/HeadAuth.tsx index 51de22397..bd00b80f0 100644 --- a/src/components/HeadAuth.tsx +++ b/src/components/HeadAuth.tsx @@ -47,8 +47,8 @@ export const HeadAuth: React.FC = ({ fallbackName || new URL(url).hostname.split(".").slice(-2).join("."), logo: appInfo.logo || fallbackLogo, - type: appLogoPlaceholder.type, - placeholder: appLogoPlaceholder.placeholder + type: appLogoPlaceholder?.type, + placeholder: appLogoPlaceholder?.placeholder }); } diff --git a/src/components/popup/HeadV2.tsx b/src/components/popup/HeadV2.tsx index b644d502d..f450d2d5e 100644 --- a/src/components/popup/HeadV2.tsx +++ b/src/components/popup/HeadV2.tsx @@ -100,7 +100,7 @@ export default function HeadV2({ const hardwareApi = useHardwareApi(); const appName = appInfo?.name; - const appIconPlaceholderText = appInfo.placeholder; + const appIconPlaceholderText = appInfo?.placeholder; const SquircleWrapper = onAppInfoClick ? ButtonSquircle : React.Fragment; diff --git a/src/utils/urls/getAppIconPlaceholder.ts b/src/utils/urls/getAppIconPlaceholder.ts index 89dac5a91..c8a1d3317 100644 --- a/src/utils/urls/getAppIconPlaceholder.ts +++ b/src/utils/urls/getAppIconPlaceholder.ts @@ -1,5 +1,22 @@ import type { AppLogoInfo } from "~applications/application"; import { isGateway } from "./isGateway"; + +/** + * Ensures a URL has a valid HTTP/HTTPS protocol prefix. + * If no protocol is present, HTTPS is added as the default. + * + * @param url - The URL string to normalize + * @returns The URL with a valid protocol prefix + */ +function ensureUrlProtocol(url: string): string { + try { + const hasProtocol = /^https?:\/\//i.test(url); + return hasProtocol ? url : `https://${url}`; + } catch { + return url; + } +} + /** * Generates a logo placeholder based on the base domain. * If the URL is a gateway (determined by a GET call), it uses the first two letters of the subdomain. @@ -11,6 +28,7 @@ export async function generateLogoPlaceholder( url: string ): Promise { try { + url = ensureUrlProtocol(url); const { hostname } = new URL(url); const parts = hostname.split("."); @@ -21,7 +39,8 @@ export async function generateLogoPlaceholder( parts.length > 1 ? parts.slice(1).join(".") : null; const isGatewayUrl = - !!candidateGatewayUrl && (await isGateway(candidateGatewayUrl)); + !!candidateGatewayUrl && + (await isGateway(ensureUrlProtocol(candidateGatewayUrl))); if (isGatewayUrl) { // For gateways, take the first two letters of the first subdomain From 6105bd58a4174458ffae4517c664f80eca7c536b Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Sat, 14 Dec 2024 12:10:18 -0800 Subject: [PATCH 135/142] fix: hotfix for css bg issue --- src/components/hardware/HardwareWalletTheme.tsx | 8 ++++++++ src/utils/wallets/wallets.provider.tsx | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/hardware/HardwareWalletTheme.tsx b/src/components/hardware/HardwareWalletTheme.tsx index 615fef9c8..9362589fa 100644 --- a/src/components/hardware/HardwareWalletTheme.tsx +++ b/src/components/hardware/HardwareWalletTheme.tsx @@ -34,6 +34,14 @@ export function ArConnectThemeProvider({ children }: PropsWithChildren<{}>) { const hardwareApi = useHardwareApi(); const theme = useTheme(); const themeModifier = hardwareApi ? hardwareThemeModifier : noThemeModifier; + const isWelcomePage = window.location.pathname.includes("welcome"); + + useEffect(() => { + if (isWelcomePage) { + document.documentElement.style.removeProperty("--backgroundColor"); + document.documentElement.style.removeProperty("--textColor"); + } + }, [isWelcomePage]); useEffect(() => { const reducedMotionPreference = window.matchMedia( diff --git a/src/utils/wallets/wallets.provider.tsx b/src/utils/wallets/wallets.provider.tsx index d7f7897ce..df58a6511 100644 --- a/src/utils/wallets/wallets.provider.tsx +++ b/src/utils/wallets/wallets.provider.tsx @@ -67,7 +67,7 @@ export function WalletsProvider({ if (hasWallets && decryptionKey) { walletStatus = "unlocked"; - } else if (!decryptionKey) { + } else if (!decryptionKey && hasWallets) { walletStatus = "locked"; } else if (redirectToWelcome) { // This should only happen when opening the regular popup, but not for the auth popup, as the @@ -75,7 +75,7 @@ export function WalletsProvider({ openOrSelectWelcomePage(true); - window.top.close(); + return; } setWalletsContextState({ From 94024a557389a1c41ad9fd74c7f1151de7388236 Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Mon, 16 Dec 2024 10:49:27 -0800 Subject: [PATCH 136/142] fix: retain gateways cache --- src/components/dashboard/Reset.tsx | 7 ++++++- src/components/hardware/HardwareWalletTheme.tsx | 8 -------- src/utils/wallets/wallets.provider.tsx | 10 +++++----- src/wallets/index.ts | 6 ++++-- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/dashboard/Reset.tsx b/src/components/dashboard/Reset.tsx index 6c3eeb1ca..61ea873dc 100644 --- a/src/components/dashboard/Reset.tsx +++ b/src/components/dashboard/Reset.tsx @@ -25,9 +25,14 @@ export function ResetDashboardView() { // reset ArConnect async function reset() { try { - // get all keys + // get all keys except gateways const allStoredKeys = Object.keys( (await browser.storage.local.get(null)) || {} + ).filter((key) => key !== "gateways"); + + // remove all keys except gateways + await Promise.allSettled( + allStoredKeys.map((key) => ExtensionStorage.remove(key)) ); // remove all keys diff --git a/src/components/hardware/HardwareWalletTheme.tsx b/src/components/hardware/HardwareWalletTheme.tsx index 9362589fa..615fef9c8 100644 --- a/src/components/hardware/HardwareWalletTheme.tsx +++ b/src/components/hardware/HardwareWalletTheme.tsx @@ -34,14 +34,6 @@ export function ArConnectThemeProvider({ children }: PropsWithChildren<{}>) { const hardwareApi = useHardwareApi(); const theme = useTheme(); const themeModifier = hardwareApi ? hardwareThemeModifier : noThemeModifier; - const isWelcomePage = window.location.pathname.includes("welcome"); - - useEffect(() => { - if (isWelcomePage) { - document.documentElement.style.removeProperty("--backgroundColor"); - document.documentElement.style.removeProperty("--textColor"); - } - }, [isWelcomePage]); useEffect(() => { const reducedMotionPreference = window.matchMedia( diff --git a/src/utils/wallets/wallets.provider.tsx b/src/utils/wallets/wallets.provider.tsx index df58a6511..f57c81adf 100644 --- a/src/utils/wallets/wallets.provider.tsx +++ b/src/utils/wallets/wallets.provider.tsx @@ -65,15 +65,15 @@ export function WalletsProvider({ let walletStatus: WalletStatus = "noWallets"; - if (hasWallets && decryptionKey) { - walletStatus = "unlocked"; - } else if (!decryptionKey && hasWallets) { - walletStatus = "locked"; + if (hasWallets) { + walletStatus = decryptionKey ? "unlocked" : "locked"; } else if (redirectToWelcome) { // This should only happen when opening the regular popup, but not for the auth popup, as the // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: - openOrSelectWelcomePage(true); + await openOrSelectWelcomePage(true); + + window.top.close(); return; } diff --git a/src/wallets/index.ts b/src/wallets/index.ts index bfe961033..88f45e468 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -118,9 +118,11 @@ export async function openOrSelectWelcomePage(force = false) { (await browser.storage.local.get(null)) || {} ); - // remove all keys + // remove all keys except gateways await Promise.allSettled( - allStoredKeys.map((key) => ExtensionStorage.remove(key)) + allStoredKeys + .filter((key) => key !== "gateways") + .map((key) => ExtensionStorage.remove(key)) ); const url = browser.runtime.getURL("tabs/welcome.html"); From 353bc0ca6b1dbbafdbd05ca3020da6acade9bed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Mon, 16 Dec 2024 18:42:01 +0100 Subject: [PATCH 137/142] Fix welcome page stored theme/vars issue and prevent extension popup ficker. --- src/components/dashboard/Reset.tsx | 17 ++--------- .../hardware/HardwareWalletTheme.tsx | 28 +++++++++++++------ src/utils/storage.ts | 23 +++++++++++++++ src/utils/wallets/wallets.provider.tsx | 2 +- src/wallets/index.ts | 17 ++--------- 5 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/components/dashboard/Reset.tsx b/src/components/dashboard/Reset.tsx index 61ea873dc..5d522a009 100644 --- a/src/components/dashboard/Reset.tsx +++ b/src/components/dashboard/Reset.tsx @@ -7,7 +7,7 @@ import { useToasts, type DisplayTheme } from "@arconnect/components"; -import { ExtensionStorage } from "~utils/storage"; +import { resetStorage } from "~utils/storage"; import { TrashIcon } from "@iconicicons/react"; import browser from "webextension-polyfill"; import styled from "styled-components"; @@ -25,20 +25,7 @@ export function ResetDashboardView() { // reset ArConnect async function reset() { try { - // get all keys except gateways - const allStoredKeys = Object.keys( - (await browser.storage.local.get(null)) || {} - ).filter((key) => key !== "gateways"); - - // remove all keys except gateways - await Promise.allSettled( - allStoredKeys.map((key) => ExtensionStorage.remove(key)) - ); - - // remove all keys - await Promise.allSettled( - allStoredKeys.map((key) => ExtensionStorage.remove(key)) - ); + await resetStorage(); // close window window.top.close(); diff --git a/src/components/hardware/HardwareWalletTheme.tsx b/src/components/hardware/HardwareWalletTheme.tsx index 615fef9c8..69f2af252 100644 --- a/src/components/hardware/HardwareWalletTheme.tsx +++ b/src/components/hardware/HardwareWalletTheme.tsx @@ -7,11 +7,13 @@ import { ARCONNECT_DARK_THEME, ARCONNECT_LIGHT_THEME, Provider as ThemeProvider, - type ArconnectTheme + type ArconnectTheme, + type DisplayTheme } from "@arconnect/components"; - -const ARCONNECT_THEME_BACKGROUND_COLOR = "ARCONNECT_THEME_BACKGROUND_COLOR"; -const ARCONNECT_THEME_TEXT_COLOR = "ARCONNECT_THEME_TEXT_COLOR"; +import { + ARCONNECT_THEME_BACKGROUND_COLOR, + ARCONNECT_THEME_TEXT_COLOR +} from "~utils/storage"; /** * Modify the theme if the active wallet is a hardware wallet. We transform the @@ -54,19 +56,27 @@ export function ArConnectThemeProvider({ children }: PropsWithChildren<{}>) { theme === "dark" ? ARCONNECT_DARK_THEME : ARCONNECT_LIGHT_THEME )} > - + {children} ); } -export function ThemeBackgroundObserver() { +interface ThemeBackgroundObserverProps { + theme?: DisplayTheme; +} + +export function ThemeBackgroundObserver({ + theme +}: ThemeBackgroundObserverProps) { const styledComponentsTheme = useStyledComponentsTheme(); const backgroundColor = styledComponentsTheme.background; const textColor = styledComponentsTheme.primaryText; useEffect(() => { + if (!theme) return; + let formattedBackgroundColor = ""; if (backgroundColor.length === 3 || backgroundColor.length === 6) { @@ -83,9 +93,11 @@ export function ThemeBackgroundObserver() { formattedBackgroundColor ); } - }, [backgroundColor]); + }, [theme, backgroundColor]); useEffect(() => { + if (!theme) return; + let formattedTextColor = ""; if (textColor.length === 3 || textColor.length === 6) { @@ -99,7 +111,7 @@ export function ThemeBackgroundObserver() { if (formattedTextColor) { localStorage.setItem(ARCONNECT_THEME_TEXT_COLOR, formattedTextColor); } - }, [textColor]); + }, [theme, textColor]); return null; } diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 3652f3086..d52012055 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,6 +1,7 @@ import type Transaction from "arweave/web/lib/transaction"; import { type Gateway } from "~gateways/gateway"; import { Storage } from "@plasmohq/storage"; +import browser from "webextension-polyfill"; /** * Default local extension storage, with values @@ -42,3 +43,25 @@ export interface RawStoredTransfer { gateway: Gateway; transaction: ReturnType; } + +export const ARCONNECT_THEME_BACKGROUND_COLOR = + "ARCONNECT_THEME_BACKGROUND_COLOR"; +export const ARCONNECT_THEME_TEXT_COLOR = "ARCONNECT_THEME_TEXT_COLOR"; + +/** + * Clear all storage keys except for gateways. + */ +export async function resetStorage() { + localStorage.removeItem(ARCONNECT_THEME_BACKGROUND_COLOR); + localStorage.removeItem(ARCONNECT_THEME_TEXT_COLOR); + + // get all keys except gateways + const allStoredKeys = Object.keys( + (await browser.storage.local.get(null)) || {} + ).filter((key) => key !== "gateways"); + + // remove all keys except gateways + await Promise.allSettled( + allStoredKeys.map((key) => ExtensionStorage.remove(key)) + ); +} diff --git a/src/utils/wallets/wallets.provider.tsx b/src/utils/wallets/wallets.provider.tsx index f57c81adf..bb7bf7050 100644 --- a/src/utils/wallets/wallets.provider.tsx +++ b/src/utils/wallets/wallets.provider.tsx @@ -71,7 +71,7 @@ export function WalletsProvider({ // This should only happen when opening the regular popup, but not for the auth popup, as the // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: - await openOrSelectWelcomePage(true); + openOrSelectWelcomePage(true); window.top.close(); diff --git a/src/wallets/index.ts b/src/wallets/index.ts index 88f45e468..02d8194fd 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -1,8 +1,6 @@ import type { JWKInterface } from "arweave/node/lib/wallet"; -import { useStorage } from "@plasmohq/storage/hook"; -import { ExtensionStorage } from "~utils/storage"; +import { ExtensionStorage, resetStorage } from "~utils/storage"; import type { HardwareWallet } from "./hardware"; -import { useEffect, useState } from "react"; import browser from "webextension-polyfill"; import Arweave from "arweave/web/common"; import { @@ -112,18 +110,7 @@ export async function openOrSelectWelcomePage(force = false) { // Make sure we clear any stored value from previous installations before // opening the welcome page to onboard the user: - - // get all keys - const allStoredKeys = Object.keys( - (await browser.storage.local.get(null)) || {} - ); - - // remove all keys except gateways - await Promise.allSettled( - allStoredKeys - .filter((key) => key !== "gateways") - .map((key) => ExtensionStorage.remove(key)) - ); + await resetStorage(); const url = browser.runtime.getURL("tabs/welcome.html"); const welcomePageTabs = await browser.tabs.query({ url }); From 097fae9fd309beed489b352c33c564cd66a7536d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 17 Dec 2024 12:57:19 +0100 Subject: [PATCH 138/142] Check if localStorage is available before using it. --- src/utils/storage.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/storage.ts b/src/utils/storage.ts index d52012055..29afb3e92 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -52,8 +52,10 @@ export const ARCONNECT_THEME_TEXT_COLOR = "ARCONNECT_THEME_TEXT_COLOR"; * Clear all storage keys except for gateways. */ export async function resetStorage() { - localStorage.removeItem(ARCONNECT_THEME_BACKGROUND_COLOR); - localStorage.removeItem(ARCONNECT_THEME_TEXT_COLOR); + if (typeof localStorage !== "undefined") { + localStorage.removeItem(ARCONNECT_THEME_BACKGROUND_COLOR); + localStorage.removeItem(ARCONNECT_THEME_TEXT_COLOR); + } // get all keys except gateways const allStoredKeys = Object.keys( From 08ea738b6b41ba3f3666cb42337f5263b7832df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 17 Dec 2024 13:10:25 +0100 Subject: [PATCH 139/142] Move resetStorage() function to its own file that's only imported from the browser extension context. --- src/components/dashboard/Reset.tsx | 2 +- .../hardware/HardwareWalletTheme.tsx | 2 +- src/utils/storage.ts | 25 ----------------- src/utils/storage.utils.ts | 28 +++++++++++++++++++ src/wallets/index.ts | 3 +- 5 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 src/utils/storage.utils.ts diff --git a/src/components/dashboard/Reset.tsx b/src/components/dashboard/Reset.tsx index 5d522a009..938cb975a 100644 --- a/src/components/dashboard/Reset.tsx +++ b/src/components/dashboard/Reset.tsx @@ -7,11 +7,11 @@ import { useToasts, type DisplayTheme } from "@arconnect/components"; -import { resetStorage } from "~utils/storage"; import { TrashIcon } from "@iconicicons/react"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { useTheme } from "~utils/theme"; +import { resetStorage } from "~utils/storage.utils"; export function ResetDashboardView() { // reset modal diff --git a/src/components/hardware/HardwareWalletTheme.tsx b/src/components/hardware/HardwareWalletTheme.tsx index 69f2af252..9c39984ed 100644 --- a/src/components/hardware/HardwareWalletTheme.tsx +++ b/src/components/hardware/HardwareWalletTheme.tsx @@ -13,7 +13,7 @@ import { import { ARCONNECT_THEME_BACKGROUND_COLOR, ARCONNECT_THEME_TEXT_COLOR -} from "~utils/storage"; +} from "~utils/storage.utils"; /** * Modify the theme if the active wallet is a hardware wallet. We transform the diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 29afb3e92..3652f3086 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,7 +1,6 @@ import type Transaction from "arweave/web/lib/transaction"; import { type Gateway } from "~gateways/gateway"; import { Storage } from "@plasmohq/storage"; -import browser from "webextension-polyfill"; /** * Default local extension storage, with values @@ -43,27 +42,3 @@ export interface RawStoredTransfer { gateway: Gateway; transaction: ReturnType; } - -export const ARCONNECT_THEME_BACKGROUND_COLOR = - "ARCONNECT_THEME_BACKGROUND_COLOR"; -export const ARCONNECT_THEME_TEXT_COLOR = "ARCONNECT_THEME_TEXT_COLOR"; - -/** - * Clear all storage keys except for gateways. - */ -export async function resetStorage() { - if (typeof localStorage !== "undefined") { - localStorage.removeItem(ARCONNECT_THEME_BACKGROUND_COLOR); - localStorage.removeItem(ARCONNECT_THEME_TEXT_COLOR); - } - - // get all keys except gateways - const allStoredKeys = Object.keys( - (await browser.storage.local.get(null)) || {} - ).filter((key) => key !== "gateways"); - - // remove all keys except gateways - await Promise.allSettled( - allStoredKeys.map((key) => ExtensionStorage.remove(key)) - ); -} diff --git a/src/utils/storage.utils.ts b/src/utils/storage.utils.ts new file mode 100644 index 000000000..a0a97a7a8 --- /dev/null +++ b/src/utils/storage.utils.ts @@ -0,0 +1,28 @@ +import browser from "webextension-polyfill"; +import { ExtensionStorage } from "~utils/storage"; + +export const ARCONNECT_THEME_BACKGROUND_COLOR = + "ARCONNECT_THEME_BACKGROUND_COLOR"; +export const ARCONNECT_THEME_TEXT_COLOR = "ARCONNECT_THEME_TEXT_COLOR"; + +/** + * Clear all storage keys except for gateways. + */ +export async function resetStorage() { + if (typeof localStorage !== "undefined") { + localStorage.removeItem(ARCONNECT_THEME_BACKGROUND_COLOR); + localStorage.removeItem(ARCONNECT_THEME_TEXT_COLOR); + } + + try { + // get all keys except gateways + const allStoredKeys = Object.keys( + (await browser.storage.local.get(null)) || {} + ).filter((key) => key !== "gateways"); + + // remove all keys except gateways + await Promise.allSettled( + allStoredKeys.map((key) => ExtensionStorage.remove(key)) + ); + } catch {} +} diff --git a/src/wallets/index.ts b/src/wallets/index.ts index 02d8194fd..32e073692 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -1,5 +1,5 @@ import type { JWKInterface } from "arweave/node/lib/wallet"; -import { ExtensionStorage, resetStorage } from "~utils/storage"; +import { ExtensionStorage } from "~utils/storage"; import type { HardwareWallet } from "./hardware"; import browser from "webextension-polyfill"; import Arweave from "arweave/web/common"; @@ -22,6 +22,7 @@ import { import type { ModuleAppData } from "~api/background/background-modules"; import { isNotCancelError } from "~utils/assertions"; import { log, LOG_GROUP } from "~utils/log/log.utils"; +import { resetStorage } from "~utils/storage.utils"; /** * Locally stored wallet From 562ca7626ebd6a30191a060d032483ded58454f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 17 Dec 2024 14:29:54 +0100 Subject: [PATCH 140/142] Fix Mutext not being released. --- src/utils/auth/auth.utils.ts | 51 ++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/utils/auth/auth.utils.ts b/src/utils/auth/auth.utils.ts index f5bf4d180..6e4a402bc 100644 --- a/src/utils/auth/auth.utils.ts +++ b/src/utils/auth/auth.utils.ts @@ -126,13 +126,19 @@ export async function createAuthPopup( moduleAppData: ModuleAppData ) { const unlock = await popupMutex.lock(); - const wallets = await getWallets(); - // TODO: What about getActiveAddress()? + const [activeAddress, wallets] = await Promise.all([ + getActiveAddress(), + getWallets() + ]); - if (wallets.length === 0) { + const hasWallets = activeAddress && wallets.length > 0; + + if (!hasWallets) { openOrSelectWelcomePage(true); + unlock(); + throw new Error(ERR_MSG_NO_WALLETS_ADDED); } @@ -149,22 +155,29 @@ export async function createAuthPopup( ); } - if (!popupWindowTab) { - // TODO: To center this, the injected tab should send the center or dimensions of the screen: - - const window = await browser.windows.create({ - url: `${browser.runtime.getURL("tabs/auth.html")}#/`, - focused: true, - type: "popup", - width: 385, - height: 720 - }); - - setPopupTabID(window.tabs[0].id); - } else { - log(LOG_GROUP.AUTH, "reusePopupTabID =", POPUP_TAB_ID); - - await browser.windows.update(popupWindowTab.windowId, { focused: true }); + try { + if (!popupWindowTab) { + // TODO: To center this, the injected tab should send the center or dimensions of the screen: + + const window = await browser.windows.create({ + url: `${browser.runtime.getURL("tabs/auth.html")}#/`, + focused: true, + type: "popup", + width: 385, + height: 720 + }); + + setPopupTabID(window.tabs[0].id); + } else { + log(LOG_GROUP.AUTH, "reusePopupTabID =", POPUP_TAB_ID); + + await browser.windows.update(popupWindowTab.windowId, { focused: true }); + } + } catch (err) { + console.warn( + `Could not ${popupWindowTab ? "focus" : "open"} "tabs/auth.html":`, + err + ); } let authID: string | undefined; From ace2c39732f2b49516b267caf8934fb7590bc0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=20G=C3=A1mez=20Franco?= Date: Tue, 17 Dec 2024 19:10:14 +0100 Subject: [PATCH 141/142] Fix theme selector not changing background and text colors. --- src/components/hardware/HardwareWalletTheme.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/hardware/HardwareWalletTheme.tsx b/src/components/hardware/HardwareWalletTheme.tsx index 9c39984ed..dfe52d2e5 100644 --- a/src/components/hardware/HardwareWalletTheme.tsx +++ b/src/components/hardware/HardwareWalletTheme.tsx @@ -92,6 +92,11 @@ export function ThemeBackgroundObserver({ ARCONNECT_THEME_BACKGROUND_COLOR, formattedBackgroundColor ); + + document.documentElement.style.setProperty( + "--backgroundColor", + formattedBackgroundColor + ); } }, [theme, backgroundColor]); @@ -110,6 +115,11 @@ export function ThemeBackgroundObserver({ if (formattedTextColor) { localStorage.setItem(ARCONNECT_THEME_TEXT_COLOR, formattedTextColor); + + document.documentElement.style.setProperty( + "--textColor", + formattedTextColor + ); } }, [theme, textColor]); From a19bb1fcb6d848e29115cc2fe5781ad6c1544be3 Mon Sep 17 00:00:00 2001 From: 7i7o Date: Tue, 17 Dec 2024 15:30:37 -0300 Subject: [PATCH 142/142] fix rollback await removal on openOrSelectWelcomePage --- src/utils/wallets/wallets.provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/wallets/wallets.provider.tsx b/src/utils/wallets/wallets.provider.tsx index bb7bf7050..f57c81adf 100644 --- a/src/utils/wallets/wallets.provider.tsx +++ b/src/utils/wallets/wallets.provider.tsx @@ -71,7 +71,7 @@ export function WalletsProvider({ // This should only happen when opening the regular popup, but not for the auth popup, as the // `createAuthPopup` will open the welcome page directly, instead of the popup, if needed: - openOrSelectWelcomePage(true); + await openOrSelectWelcomePage(true); window.top.close();