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"