diff --git a/package.json b/package.json index 6c30b847e24..979ac24875a 100644 --- a/package.json +++ b/package.json @@ -253,6 +253,7 @@ "ts-node": "10.4", "ts-unused-exports": "7.0.3", "tsconfig-paths-webpack-plugin": "3.5.2", + "type-fest": "2.12.2", "typescript": "4.5.4", "vm-browserify": "1.1.2", "web-ext": "6.2.0", diff --git a/src/app/common/actions/send-request-account-response.spec.ts b/src/app/common/actions/send-request-account-response.spec.ts new file mode 100644 index 00000000000..713585446cc --- /dev/null +++ b/src/app/common/actions/send-request-account-response.spec.ts @@ -0,0 +1,24 @@ +import { sendRequestAccountResponseToTab } from './send-request-account-response'; +import { sendMessageToTab } from '@shared/messages'; + +jest.mock('@shared/messages', () => ({ + sendMessageToTab: jest.fn().mockImplementation(() => null), +})); + +describe(sendRequestAccountResponseToTab.name, () => { + it('must only return to app with public keys', () => { + sendRequestAccountResponseToTab({ + tabId: '2', + id: '1', + account: { + stxPublicKey: 'pubKey1', + dataPublicKey: 'dataKey1', + stxPrivateKey: 'lskdjfjsldf', + } as any, + }); + expect(sendMessageToTab).toHaveBeenCalledTimes(1); + expect(sendMessageToTab).toHaveBeenCalledWith(2, '1', { + result: [{ dataPublicKey: 'dataKey1', stxPublicKey: 'pubKey1' }], + }); + }); +}); diff --git a/src/app/common/actions/send-request-account-response.ts b/src/app/common/actions/send-request-account-response.ts new file mode 100644 index 00000000000..d0713aef2d1 --- /dev/null +++ b/src/app/common/actions/send-request-account-response.ts @@ -0,0 +1,22 @@ +import { SoftwareWalletAccountWithAddress } from '@app/store/accounts/account.models'; +import { sendMessageToTab } from '@shared/messages'; + +interface SendRequestAccountResponseToTabArgs { + tabId: string; + id: string; + account: SoftwareWalletAccountWithAddress; +} +export function sendRequestAccountResponseToTab(args: SendRequestAccountResponseToTabArgs) { + const { tabId, id, account } = args; + + const safeAccountKeys = { + stxPublicKey: account.stxPublicKey, + dataPublicKey: account.dataPublicKey, + }; + + return sendMessageToTab(parseInt(tabId), id, { result: [safeAccountKeys] }); +} + +export function sendUserDeniesAccountRequest({ tabId, id }: { tabId: string; id: string }) { + return sendMessageToTab(parseInt(tabId), id, { error: { code: 4000, message: 'lskdjflksjdfl' } }); +} diff --git a/src/app/common/hooks/auth/use-save-auth-request-callback.ts b/src/app/common/hooks/auth/use-save-auth-request-callback.ts index c0749077022..8d628efb26a 100644 --- a/src/app/common/hooks/auth/use-save-auth-request-callback.ts +++ b/src/app/common/hooks/auth/use-save-auth-request-callback.ts @@ -31,7 +31,7 @@ export function useSaveAuthRequest() { appURL: new URL(origin), }); - navigate(RouteUrls.ChooseAccount); + navigate(RouteUrls.AccountAuthentication); }, [saveAuthRequest, navigate] ); diff --git a/src/app/common/hooks/use-key-actions.ts b/src/app/common/hooks/use-key-actions.ts index cbaf6b69622..b8fee586ed3 100644 --- a/src/app/common/hooks/use-key-actions.ts +++ b/src/app/common/hooks/use-key-actions.ts @@ -7,7 +7,7 @@ import { clearSessionLocalData } from '@app/common/store-utils'; import { createNewAccount, stxChainActions } from '@app/store/chains/stx-chain.actions'; import { keyActions } from '@app/store/keys/key.actions'; import { useAnalytics } from './analytics/use-analytics'; -import { sendMessage } from '@shared/messages'; +import { sendMessageToBackground } from '@shared/messages'; import { InternalMethods } from '@shared/message-types'; import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actions'; @@ -23,7 +23,7 @@ export function useKeyActions() { generateWalletKey() { const secretKey = generateSecretKey(256); - sendMessage({ + sendMessageToBackground({ method: InternalMethods.ShareInMemoryKeyToBackground, payload: { secretKey, keyId: 'default' }, }); @@ -45,12 +45,12 @@ export function useKeyActions() { async signOut() { void analytics.track('sign_out'); dispatch(keyActions.signOut()); - sendMessage({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); + sendMessageToBackground({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); clearSessionLocalData(); }, lockWallet() { - sendMessage({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); + sendMessageToBackground({ method: InternalMethods.RemoveInMemoryKeys, payload: undefined }); return dispatch(inMemoryKeyActions.lockWallet()); }, }), diff --git a/src/app/components/account/account-title.tsx b/src/app/components/account/account-title.tsx new file mode 100644 index 00000000000..ff094eb066c --- /dev/null +++ b/src/app/components/account/account-title.tsx @@ -0,0 +1,28 @@ +import { BoxProps } from '@stacks/ui'; + +import { SoftwareWalletAccountWithAddress } from '@app/store/accounts/account.models'; +import { Title } from '../typography'; + +interface AccountTitlePlaceholderProps extends BoxProps { + account: SoftwareWalletAccountWithAddress; +} +export function AccountTitlePlaceholder({ account, ...rest }: AccountTitlePlaceholderProps) { + const name = `Account ${account?.index + 1}`; + return ( + + {name} + + ); +} + +interface AccountTitleProps extends BoxProps { + account: SoftwareWalletAccountWithAddress; + name: string; +} +export function AccountTitle({ account, name, ...rest }: AccountTitleProps) { + return ( + + {name} + + ); +} diff --git a/src/app/features/account-picker/account-picker.layout.tsx b/src/app/features/account-picker/account-picker.layout.tsx new file mode 100644 index 00000000000..f5b5d9b903a --- /dev/null +++ b/src/app/features/account-picker/account-picker.layout.tsx @@ -0,0 +1,24 @@ +import { Flex, Stack, Text } from '@stacks/ui'; + +import { AppIcon } from '@app/components/app-icon'; +import { Title } from '@app/components/typography'; + +interface AccountPickerLayoutProps { + appName?: string; + children: React.ReactNode; +} +export function AccountPickerLayout(props: AccountPickerLayoutProps) { + const { appName, children } = props; + return ( + + + + + Choose an account + to connect to {appName} + + + {children} + + ); +} diff --git a/src/app/pages/choose-account/components/accounts.tsx b/src/app/features/account-picker/accounts.tsx similarity index 72% rename from src/app/pages/choose-account/components/accounts.tsx rename to src/app/features/account-picker/accounts.tsx index ee0f6e864dd..174db288138 100644 --- a/src/app/pages/choose-account/components/accounts.tsx +++ b/src/app/features/account-picker/accounts.tsx @@ -1,12 +1,12 @@ -import { useCallback, Suspense, memo, useState, useMemo } from 'react'; +import { Suspense, memo, useMemo } from 'react'; import { FiPlusCircle } from 'react-icons/fi'; import { Virtuoso } from 'react-virtuoso'; -import { Box, BoxProps, color, FlexProps, Spinner, Stack } from '@stacks/ui'; +import { Box, color, FlexProps, Spinner, Stack } from '@stacks/ui'; import { truncateMiddle } from '@stacks/ui-utils'; -import { Caption, Text, Title } from '@app/components/typography'; +import { Caption, Text } from '@app/components/typography'; import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; -import { useWallet } from '@app/common/hooks/use-wallet'; + import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'; import { useCreateAccount } from '@app/common/hooks/account/use-create-account'; import type { SoftwareWalletAccountWithAddress } from '@app/store/accounts/account.models'; @@ -20,34 +20,11 @@ import { import { slugify } from '@app/common/utils'; import { useAccounts, useHasCreatedAccount } from '@app/store/accounts/account.hooks'; import { useAddressBalances } from '@app/query/balance/balance.hooks'; +import { AccountTitle, AccountTitlePlaceholder } from '@app/components/account/account-title'; const loadingProps = { color: '#A1A7B3' }; const getLoadingProps = (loading: boolean) => (loading ? loadingProps : {}); -interface AccountTitlePlaceholderProps extends BoxProps { - account: SoftwareWalletAccountWithAddress; -} -const AccountTitlePlaceholder = ({ account, ...rest }: AccountTitlePlaceholderProps) => { - const name = `Account ${account?.index + 1}`; - return ( - - {name} - - ); -}; - -interface AccountTitleProps extends BoxProps { - account: SoftwareWalletAccountWithAddress; - name: string; -} -const AccountTitle = ({ account, name, ...rest }: AccountTitleProps) => { - return ( - - {name} - - ); -}; - interface AccountItemProps extends FlexProps { selectedAddress?: string | null; isLoading: boolean; @@ -84,11 +61,7 @@ const AccountItem = memo((props: AccountItemProps) => { /> } > - + @@ -131,21 +104,14 @@ const AddAccountAction = memo(() => { ); }); -export const Accounts = memo(() => { - const { wallet, finishSignIn } = useWallet(); - const accounts = useAccounts(); - const { decodedAuthRequest } = useOnboardingState(); - const [selectedAccount, setSelectedAccount] = useState(null); - - const signIntoAccount = useCallback( - async (index: number) => { - setSelectedAccount(index); - await finishSignIn(index); - }, - [finishSignIn] - ); +interface AccountPickerProps { + selectedAccountIndex: number | null; + onAccountSelected(index: number): void; +} +export function AccountPicker(props: AccountPickerProps) { + const { onAccountSelected, selectedAccountIndex } = props; - if (!wallet || !accounts || !decodedAuthRequest) return null; + const accounts = useAccounts(); return ( <> @@ -158,12 +124,12 @@ export const Accounts = memo(() => { itemContent={(index, account) => ( onAccountSelected(index)} /> )} /> ); -}); +} diff --git a/src/app/pages/account-authentication/account-authentication.tsx b/src/app/pages/account-authentication/account-authentication.tsx new file mode 100644 index 00000000000..237d6e09a30 --- /dev/null +++ b/src/app/pages/account-authentication/account-authentication.tsx @@ -0,0 +1,45 @@ +import { memo, useCallback, useEffect, useState } from 'react'; + +import { useRouteHeader } from '@app/common/hooks/use-route-header'; +import { useWallet } from '@app/common/hooks/use-wallet'; +import { useAppDetails } from '@app/common/hooks/auth/use-app-details'; +import { Header } from '@app/components/header'; +import { AccountPicker } from '@app/features/account-picker/accounts'; +import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'; +import { useAccounts } from '@app/store/accounts/account.hooks'; +import { AccountPickerLayout } from '@app/features/account-picker/account-picker.layout'; + +export const AuthenticateAccount = memo(() => { + const accounts = useAccounts(); + const { name: appName } = useAppDetails(); + const { decodedAuthRequest } = useOnboardingState(); + const { cancelAuthentication, wallet, finishSignIn } = useWallet(); + const [selectedAccountIndex, setSelectedAccountIndex] = useState(null); + + useRouteHeader(
); + + const signIntoAccount = async (index: number) => { + setSelectedAccountIndex(index); + await finishSignIn(index); + }; + + const handleUnmount = useCallback(async () => { + cancelAuthentication(); + }, [cancelAuthentication]); + + useEffect(() => { + window.addEventListener('beforeunload', handleUnmount); + return () => window.removeEventListener('beforeunload', handleUnmount); + }, [handleUnmount]); + + if (!wallet || !accounts || !decodedAuthRequest) return null; + + return ( + + signIntoAccount(index)} + selectedAccountIndex={selectedAccountIndex} + /> + + ); +}); diff --git a/src/app/pages/choose-account-request/account-request.tsx b/src/app/pages/choose-account-request/account-request.tsx new file mode 100644 index 00000000000..b370fcc8a06 --- /dev/null +++ b/src/app/pages/choose-account-request/account-request.tsx @@ -0,0 +1,58 @@ +import { logger } from '@shared/logger'; + +import { useRouteHeader } from '@app/common/hooks/use-route-header'; +import { useAppDetails } from '@app/common/hooks/auth/use-app-details'; +import { Header } from '@app/components/header'; +import { AccountPicker } from '@app/features/account-picker/accounts'; +import { useAccounts } from '@app/store/accounts/account.hooks'; +import { AccountPickerLayout } from '@app/features/account-picker/account-picker.layout'; +import { + sendRequestAccountResponseToTab, + sendUserDeniesAccountRequest, +} from '@app/common/actions/send-request-account-response'; + +import { useAccountRequestSearchParams } from './use-account-request-search-params'; +import { useEffect } from 'react'; + +export function AccountRequest() { + const accounts = useAccounts(); + const { name: appName } = useAppDetails(); + + const { tabId, id } = useAccountRequestSearchParams(); + + useRouteHeader(
); + + const returnAccountDetailsToApp = (index: number) => { + if (!accounts) throw new Error('Cannot request account details with no account'); + + if (!tabId || !id) { + logger.error('Missing either tabId or uuid. Both values are necessary to respond to app'); + return; + } + + sendRequestAccountResponseToTab({ tabId, id, account: accounts[index] }); + window.close(); + }; + + const handleUnmount = () => { + if (!tabId || !id) { + logger.error('Missing either tabId or uuid. Both values are necessary to respond to app'); + return; + } + sendUserDeniesAccountRequest({ tabId, id }); + }; + + useEffect(() => { + window.addEventListener('beforeunload', handleUnmount); + return () => window.removeEventListener('beforeunload', handleUnmount); + }, []); + + return ( + + returnAccountDetailsToApp(index)} + selectedAccountIndex={null} + /> + + ); +} diff --git a/src/app/pages/choose-account-request/use-account-request-search-params.ts b/src/app/pages/choose-account-request/use-account-request-search-params.ts new file mode 100644 index 00000000000..82ae533d80c --- /dev/null +++ b/src/app/pages/choose-account-request/use-account-request-search-params.ts @@ -0,0 +1,14 @@ +import { useMemo } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +export function useAccountRequestSearchParams() { + const [searchParams] = useSearchParams(); + + return useMemo( + () => ({ + tabId: searchParams.get('tabId'), + id: searchParams.get('id'), + }), + [searchParams] + ); +} diff --git a/src/app/pages/choose-account/choose-account.tsx b/src/app/pages/choose-account/choose-account.tsx deleted file mode 100644 index eb2ef917cb1..00000000000 --- a/src/app/pages/choose-account/choose-account.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { memo, useCallback, useEffect } from 'react'; -import { Flex, Stack, Text } from '@stacks/ui'; - -import { useRouteHeader } from '@app/common/hooks/use-route-header'; -import { Title } from '@app/components/typography'; -import { AppIcon } from '@app/components/app-icon'; -import { useWallet } from '@app/common/hooks/use-wallet'; -import { useAppDetails } from '@app/common/hooks/auth/use-app-details'; -import { Header } from '@app/components/header'; -import { Accounts } from '@app/pages/choose-account/components/accounts'; - -export const ChooseAccount = memo(() => { - const { name: appName } = useAppDetails(); - const { cancelAuthentication } = useWallet(); - - useRouteHeader(
); - - const handleUnmount = useCallback(async () => { - cancelAuthentication(); - }, [cancelAuthentication]); - - useEffect(() => { - window.addEventListener('beforeunload', handleUnmount); - return () => window.removeEventListener('beforeunload', handleUnmount); - }, [handleUnmount]); - - return ( - - - - - Choose an account - to connect to {appName} - - - - - ); -}); diff --git a/src/app/pages/home/home.tsx b/src/app/pages/home/home.tsx index df9f345cff9..551767d94b8 100644 --- a/src/app/pages/home/home.tsx +++ b/src/app/pages/home/home.tsx @@ -36,7 +36,7 @@ export function Home() { ); useEffect(() => { - if (decodedAuthRequest) navigate(RouteUrls.ChooseAccount); + if (decodedAuthRequest) navigate(RouteUrls.AccountAuthentication); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/app/pages/onboarding/set-password/set-password.tsx b/src/app/pages/onboarding/set-password/set-password.tsx index 89f37000ad5..794375835c0 100644 --- a/src/app/pages/onboarding/set-password/set-password.tsx +++ b/src/app/pages/onboarding/set-password/set-password.tsx @@ -62,7 +62,7 @@ export const SetPasswordPage = () => { if (!wallet) return; const { accounts } = wallet; if (accounts && accounts.length > 1) { - navigate(RouteUrls.ChooseAccount); + navigate(RouteUrls.AccountAuthentication); } else { await finishSignIn(0); } diff --git a/src/app/pages/unlock.tsx b/src/app/pages/unlock.tsx index 159b0412650..8fdb0a248b5 100644 --- a/src/app/pages/unlock.tsx +++ b/src/app/pages/unlock.tsx @@ -52,7 +52,7 @@ export function Unlock(): JSX.Element { await unlockWallet(password); if (decodedAuthRequest) { - navigate(RouteUrls.ChooseAccount); + navigate(RouteUrls.AccountAuthentication); } else { navigate(RouteUrls.Home); } diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx index cc9e90159d7..25b1f319775 100644 --- a/src/app/routes/app-routes.tsx +++ b/src/app/routes/app-routes.tsx @@ -5,7 +5,7 @@ import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { Container } from '@app/components/container/container'; import { LoadingSpinner } from '@app/components/loading-spinner'; import { MagicRecoveryCode } from '@app/pages/onboarding/magic-recovery-code/magic-recovery-code'; -import { ChooseAccount } from '@app/pages/choose-account/choose-account'; +import { AuthenticateAccount } from '@app/pages/account-authentication/account-authentication'; import { TransactionRequest } from '@app/pages/transaction-request/transaction-request'; import { SignIn } from '@app/pages/onboarding/sign-in/sign-in'; import { ReceiveTokens } from '@app/pages/receive-tokens/receive-tokens'; @@ -28,6 +28,7 @@ import { RouteUrls } from '@shared/route-urls'; import { useOnWalletLock } from './hooks/use-on-wallet-lock'; import { useOnSignOut } from './hooks/use-on-sign-out'; import { OnboardingGate } from './onboarding-gate'; +import { AccountRequest } from '@app/pages/choose-account-request/account-request'; export function AppRoutes(): JSX.Element | null { const { pathname } = useLocation(); @@ -108,11 +109,21 @@ export function AppRoutes(): JSX.Element | null { } /> }> - + + + + } + /> + + }> + } diff --git a/src/app/store/apps/apps.actions.ts b/src/app/store/apps/apps.actions.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/store/apps/apps.selectors.ts b/src/app/store/apps/apps.selectors.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/store/apps/apps.slice.ts b/src/app/store/apps/apps.slice.ts new file mode 100644 index 00000000000..8bd48a14f85 --- /dev/null +++ b/src/app/store/apps/apps.slice.ts @@ -0,0 +1,17 @@ +import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; + +interface AppDetails { + domain: string; +} + +const appsAdapter = createEntityAdapter({ + selectId: entity => entity.domain, +}); + +export const appsSlice = createSlice({ + name: 'apps', + initialState: appsAdapter.getInitialState(), + reducers: { + appConnected: appsAdapter.addOne, + }, +}); diff --git a/src/app/store/index.ts b/src/app/store/index.ts index b2eb1e34c41..eb47923f143 100644 --- a/src/app/store/index.ts +++ b/src/app/store/index.ts @@ -20,10 +20,12 @@ import { inMemoryKeySlice } from './in-memory-key/in-memory-key.slice'; import { ExtensionStorage } from './utils/extension-storage'; import { onboardingSlice } from './onboarding/onboarding.slice'; import { analyticsSlice } from './analytics/analytics.slice'; +import { appsSlice } from './apps/apps.slice'; const storage = new ExtensionStorage(chrome.storage.local, chrome.runtime); const rootReducer = combineReducers({ + apps: appsSlice.reducer, keys: keySlice.reducer, chains: combineReducers({ stx: stxChainSlice.reducer, diff --git a/src/app/store/keys/key.actions.ts b/src/app/store/keys/key.actions.ts index 5fa98213f4d..6ae9c00b0bc 100644 --- a/src/app/store/keys/key.actions.ts +++ b/src/app/store/keys/key.actions.ts @@ -7,7 +7,7 @@ import { AppThunk } from '@app/store'; import { stxChainSlice } from '../chains/stx-chain.slice'; import { defaultKeyId, keySlice } from './key.slice'; import { selectCurrentKey } from './key.selectors'; -import { sendMessage } from '@shared/messages'; +import { sendMessageToBackground } from '@shared/messages'; import { InternalMethods } from '@shared/message-types'; import { inMemoryKeySlice } from '../in-memory-key/in-memory-key.slice'; import { selectDefaultWalletKey } from '../in-memory-key/in-memory-key.selectors'; @@ -35,7 +35,7 @@ const setWalletEncryptionPassword = (password: string): AppThunk => { const { encryptedSecretKey, salt } = await encryptMnemonic({ secretKey, password }); const highestAccountIndex = await restoredWalletHighestGeneratedAccountIndex(secretKey); - sendMessage({ + sendMessageToBackground({ method: InternalMethods.ShareInMemoryKeyToBackground, payload: { secretKey, keyId: defaultKeyId }, }); @@ -63,7 +63,7 @@ const unlockWalletAction = (password: string): AppThunk => { const { secretKey } = await decryptMnemonic({ password, ...currentKey }); - sendMessage({ + sendMessageToBackground({ method: InternalMethods.ShareInMemoryKeyToBackground, payload: { secretKey: secretKey, keyId: defaultKeyId }, }); diff --git a/src/background/background.ts b/src/background/background.ts index f02934ff6ef..e752c2988fa 100755 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -9,7 +9,6 @@ import { logger } from '@shared/logger'; import { CONTENT_SCRIPT_PORT, LegacyMessageFromContentScript, - MESSAGE_SOURCE, RpcMethods, SupportedRpcMessages, } from '@shared/message-types'; @@ -21,6 +20,7 @@ import { handleLegacyExternalMethodFormat, inferLegacyMessage, } from './legacy-external-message-handler'; +import { popupCenter } from './popup-center'; initSentry(); initContextMenuActions(); @@ -43,6 +43,7 @@ chrome.runtime.onInstalled.addListener(details => { chrome.runtime.onConnect.addListener(port => Sentry.wrap(() => { if (port.name !== CONTENT_SCRIPT_PORT) return; + port.onMessage.addListener( (message: LegacyMessageFromContentScript | SupportedRpcMessages, port) => { if (inferLegacyMessage(message)) { @@ -55,10 +56,11 @@ chrome.runtime.onConnect.addListener(port => switch (message.method) { case RpcMethods[RpcMethods.stx_requestAccounts]: { - chrome.tabs.sendMessage(port.sender.tab.id, { - source: MESSAGE_SOURCE, - id: message.id, - results: { publicKey: 'sldkfjs' }, + const params = new URLSearchParams(); + params.set('tabId', port.sender.tab.id.toString()); + params.set('id', message.id); + popupCenter({ + url: `/popup-center.html#${RouteUrls.AccountRequest}?${params.toString()}`, }); break; } diff --git a/src/background/legacy-external-message-handler.ts b/src/background/legacy-external-message-handler.ts index bbf468bd6e4..77149b20932 100644 --- a/src/background/legacy-external-message-handler.ts +++ b/src/background/legacy-external-message-handler.ts @@ -17,7 +17,8 @@ async function openRequestInFullPage(path: string, urlParams: URLSearchParams) { export function inferLegacyMessage(message: any): message is LegacyMessageFromContentScript { // Now that we use a RPC communication style, we can infer // legacy message types by presence of an id - return !Object.hasOwn(message, 'id'); + const hasIdProp = 'id' in message; + return !hasIdProp; } export async function handleLegacyExternalMethodFormat( diff --git a/src/inpage/inpage.ts b/src/inpage/inpage.ts index 0253411f553..0c1275ee992 100644 --- a/src/inpage/inpage.ts +++ b/src/inpage/inpage.ts @@ -10,10 +10,18 @@ import { LegacyMessageToContentScript, MESSAGE_SOURCE, RpcMethodNames, + RpcRequestArgs, + RpcResponseArgs, TransactionResponseMessage, } from '@shared/message-types'; import { logger } from '@shared/logger'; +declare global { + interface Crypto { + randomUUID: () => string; + } +} + type CallableMethods = keyof typeof ExternalMethods; interface ExtensionResponse { @@ -109,16 +117,15 @@ const provider: StacksProvider = { }); }, - async request(method: RpcMethodNames, params?: any[]) { + async request(method: RpcMethodNames, params?: any[]): Promise { return new Promise((resolve, _reject) => { const id = crypto.randomUUID(); - const event = new CustomEvent(DomEventName.rpcRequest, { + const event = new CustomEvent(DomEventName.rpcRequest, { detail: { jsonrpc: '2.0', id, method, params }, }); document.dispatchEvent(event); const handleMessage = (event: MessageEvent) => { if (event.data.id !== id) return; - window.removeEventListener('message', handleMessage); resolve(event.data); }; @@ -136,22 +143,6 @@ const provider: StacksProvider = { }, }; }, -} as StacksProvider & { request(): Promise }; +} as StacksProvider & { request(): Promise }; window.StacksProvider = provider; - -interface RpcRequestArgs { - method: RpcMethodNames; - params?: any[]; -} - -interface RpcEventArgs extends RpcRequestArgs { - jsonrpc: '2.0'; - id: string; -} - -declare global { - interface Crypto { - randomUUID: () => string; - } -} diff --git a/src/shared/message-types.ts b/src/shared/message-types.ts index 6aee0dcb29d..a81f65d87ef 100644 --- a/src/shared/message-types.ts +++ b/src/shared/message-types.ts @@ -36,6 +36,28 @@ export interface Message // // RPC Methods, SIP pending +interface RpcBaseArgs { + jsonrpc: '2.0'; + id: string; +} + +export interface RpcRequestArgs extends RpcBaseArgs { + method: RpcMethodNames; + params?: any[]; +} + +interface RpcSuccessResponseArgs extends RpcBaseArgs { + results: any; +} + +interface RpcErrorResponseArgs extends RpcBaseArgs { + error: { + code: number; + message: string; + }; +} + +export type RpcResponseArgs = RpcSuccessResponseArgs | RpcErrorResponseArgs; export enum RpcMethods { stx_requestAccounts, @@ -55,18 +77,6 @@ type TestAction = RpcMessage<'stx_testAnotherMethod'>; export type SupportedRpcMessages = RequestAccounts | TestAction; -// interface SupportedMessagesReturnTypeMap { -// [RpcMethods.stx_requestAccounts]: { xxx: string }; -// [RpcMethods.stx_testAnotherMethod]: { yyy: string }; -// } - -// function xx(): // method: RpcMethods -// SupportedMessagesReturnTypeMap[Method] { - -// } - -// xx('stx_requestAccounts'); - // // Deprecated methods type AuthenticationRequestMessage = Message; diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 67ee5b8ed0b..782a35666d1 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -1,4 +1,4 @@ -import { ExtensionMethods, InternalMethods, Message } from '@shared/message-types'; +import { ExtensionMethods, InternalMethods, Message, MESSAGE_SOURCE } from '@shared/message-types'; /** * Vault <-> Background Script @@ -28,6 +28,10 @@ export type BackgroundActions = | RequestInMemoryKeys | RemoveInMemoryKeys; -export function sendMessage(message: BackgroundActions) { +export function sendMessageToBackground(message: BackgroundActions) { return chrome.runtime.sendMessage(message); } + +export function sendMessageToTab(tabId: number, id: string, message: object) { + return chrome.tabs.sendMessage(tabId, { source: MESSAGE_SOURCE, id, ...message }); +} diff --git a/src/shared/route-urls.ts b/src/shared/route-urls.ts index 4bf00b4de5e..3b312163aaa 100644 --- a/src/shared/route-urls.ts +++ b/src/shared/route-urls.ts @@ -11,7 +11,8 @@ export enum RouteUrls { Home = '/', AddNetwork = '/add-network', Buy = '/buy', - ChooseAccount = '/choose-account', + AccountAuthentication = '/authenticate-account', + AccountRequest = '/request-account', Receive = '/receive', Send = '/send', SignOutConfirm = '/sign-out', diff --git a/test-app/src/components/home.tsx b/test-app/src/components/home.tsx index a9e47771163..278d65e5037 100644 --- a/test-app/src/components/home.tsx +++ b/test-app/src/components/home.tsx @@ -67,7 +67,7 @@ export const Home: React.FC = () => { getStacksProvider() .request('stx_requestAccounts') .then(resp => { - setAccount([resp]); + setAccount(resp); console.log('request acct resp', resp); }); }} diff --git a/yarn.lock b/yarn.lock index d7f36295c0a..d593cf73798 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18660,6 +18660,11 @@ type-detect@4.0.8, type-detect@^4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@2.12.2: + version "2.12.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.2.tgz#80a53614e6b9b475eb9077472fb7498dc7aa51d0" + integrity sha512-qt6ylCGpLjZ7AaODxbpyBZSs9fCI9SkL3Z9q2oxMBQhs/uyY+VD8jHA8ULCGmWQJlBgqvO3EJeAngOHD8zQCrQ== + type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"