Skip to content

Commit

Permalink
feat: wallet connect extension open tx confirm modal
Browse files Browse the repository at this point in the history
  • Loading branch information
peronczyk committed Jun 24, 2024
1 parent 072e8f1 commit b00ec2a
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .well-known/walletconnect.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
b1cd76a2-edfb-437e-9989-2e611c9f9d32=0b3866d7e128b47bd13f304a2e342497615e75db9bb597f35043c80a40264011
9d0cc95e-718d-4cf7-8000-5f88022fa568=0b3866d7e128b47bd13f304a2e342497615e75db9bb597f35043c80a40264011
7 changes: 2 additions & 5 deletions src/background/bgPopupHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const getAeppUrl = (v: any) => new URL(v.connection.port.sender.url);
export const openPopup = async (
popupType: PopupType,
aepp: string | object,
params: Partial<IPopupProps> = {},
popupProps: Partial<IPopupProps> = {},
) => {
const id = uuid();
const { href, protocol, host } = (typeof aepp === 'object') ? getAeppUrl(aepp) : new URL(aepp);
Expand Down Expand Up @@ -65,16 +65,13 @@ export const openPopup = async (
popups[id] = {
id,
props: {
...popupProps,
app: {
url: href,
name,
protocol,
host,
},
message: params.message,
tx: params.tx,
txBase64: params.txBase64,
data: params.data,
},
};
return popups[id];
Expand Down
28 changes: 12 additions & 16 deletions src/background/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PopupActionType } from '@/types';
import { IPopupMessageData } from '@/types';
import { openPopup, removePopup, getPopup } from './bgPopupHandler';
import { updateDynamicRules } from './redirectRule';

Expand All @@ -25,40 +25,36 @@ import { updateDynamicRules } from './redirectRule';
});
})();

export type PopupMessageData = {
target?: 'background' | 'offscreen';
method?: 'openPopup' | 'removePopup' | 'getPopup';
type?: PopupActionType;
uuid?: string;
params?: any;
payload?: any;
};

/**
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage#sending_an_asynchronous_response_using_sendresponse
*/
function handleMessage(msg: PopupMessageData, _: any, sendResponse: Function) {
function handleMessage(msg: IPopupMessageData, _: any, sendResponse: Function) {
if (msg.target === 'background') {
const { popupType, aepp, params } = msg.params;
const {
aepp,
id,
popupProps,
popupType,
} = msg.params!;
switch (msg.method) {
case 'openPopup':
openPopup(popupType, aepp, params).then((popupConfig) => {
openPopup(popupType!, aepp!, popupProps).then((popupConfig) => {
sendResponse(popupConfig);
});
return true;
case 'removePopup':
sendResponse(removePopup(msg.params.id));
sendResponse(removePopup(id!));
return false;
case 'getPopup':
sendResponse(getPopup(msg.params.id));
sendResponse(getPopup(id!));
return false;
default:
break;
}
}

// forward messages to the offscreen page
browser.runtime.sendMessage<PopupMessageData>({
browser.runtime.sendMessage<IPopupMessageData>({
...msg,
target: 'offscreen',
});
Expand Down
42 changes: 27 additions & 15 deletions src/composables/walletConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ let web3wallet: Awaited<ReturnType<typeof IWeb3Wallet.init>> | null;
const wcSession = useStorageRef<null | SessionTypes.Struct>(
null,
STORAGE_KEYS.walletConnectSession,
{
backgroundSync: true,
},
);

const wcState = reactive({
Expand All @@ -58,7 +61,7 @@ const wcState = reactive({
/**
* TODO add description
*/
export function useWalletConnect() {
export function useWalletConnect({ offscreen } = { offscreen: false }) {
const { activeAccount, accountsGroupedByProtocol, getLastActiveProtocolAccount } = useAccounts();
const { activeNetwork, networks } = useNetworks();
const { openDefaultModal } = useModals();
Expand All @@ -72,9 +75,9 @@ export function useWalletConnect() {
[key in SupportedRequestMethod]: (p: any) => Promise<string | false>
}> = {
eth_sendTransaction: async (params: SendTransactionParams) => {
const { url, name } = wcSession.value?.peer.metadata! || {};
const senderId = toChecksumAddress(params.from);
const recipientId = toChecksumAddress(params.to);
const { url, name } = wcSession.value?.peer.metadata! || {};
const isCoinTransfer = !!params.value; // `value` is present only when sending ETH
const tag = (params.data) ? Tag.ContractCallTx : Tag.SpendTx;
const modalProps: IModalProps = {
Expand Down Expand Up @@ -313,23 +316,32 @@ export function useWalletConnect() {
if (!composableInitialized) {
composableInitialized = true;

// Restore open WC session after refreshing the tab or extension.
// As the session is not important immediately after opening the app we are delaying it
// until other more important features are ready.
setTimeout(async () => {
if (wcSession.value) {
web3wallet = await initWeb3wallet();
const sessions = web3wallet.getActiveSessions();
const restoredTopic = Object.values(sessions)?.[0]?.topic;

// If restored session is different than the currently open we need to close session.
if (wcSession.value?.topic !== restoredTopic) {
disconnect();
} else if (restoredTopic) {
monitorActiveSessionEvents();
monitorActiveAccountAndNetwork();
// Try to restore open WC session:
// - after refreshing the tab or extension (only once),
// - in the extension offscreen when the new session state is detected (constant monitoring).
watch(wcSession, async (session, oldSession) => {
if (session) {
if (!web3wallet) {
web3wallet = await initWeb3wallet();
}

const sessions = web3wallet.getActiveSessions();
const activeTopic = Object.values(sessions)?.[0]?.topic;

if (!oldSession && activeTopic && activeTopic === session.topic) {
monitorActiveSessionEvents();

if (!offscreen) {
monitorActiveAccountAndNetwork();
}
} else {
disconnect();
}
}
}
}, { deep: true, immediate: true, once: !offscreen });
}, 1000);
}

Expand Down
9 changes: 9 additions & 0 deletions src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,15 @@ export const POPUP_ACTIONS = {
reject: 'reject',
} as const;

export const POPUP_METHODS = {
openPopup: 'openPopup',
removePopup: 'removePopup',
getPopup: 'getPopup',
reload: 'reload',
paste: 'paste',
checkHasAccount: 'checkHasAccount', // TODO check if still used
} as const;

export const AIRGAP_SIGNED_TRANSACTION_MESSAGE_TYPE = 'airgap-signed-transaction';

export const PERMISSION_DEFAULTS: IPermission = {
Expand Down
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Superhero Wallet is a multi-blockchain wallet to manage crypto assets and navigate the web3 and DeFi space. Powered by æternity.",
"manifest_version": 3,
"content_security_policy": {
"extension_pages": "default-src 'self'; script-src 'self'; connect-src *; font-src * data:; img-src * data:; style-src-elem * 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "
"extension_pages": "default-src 'self'; script-src 'self'; connect-src *; font-src * data:; frame-src *; img-src * data:; style-src-elem * 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
},
"permissions": [
"storage",
Expand Down
17 changes: 10 additions & 7 deletions src/offscreen/offscreen.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import '@/lib/initPolyfills';
import '@/protocols/registerAdapters';
import { IS_FIREFOX, UNFINISHED_FEATURES } from '@/constants/environment';
import { IPopupMessageData } from '@/types';
import { IS_FIREFOX, POPUP_METHODS, UNFINISHED_FEATURES } from '@/constants';
import { useWalletConnect } from '@/composables';
import * as wallet from './wallet';
import { useAccounts } from '../composables/accounts';
import { updateDynamicRules } from '../background/redirectRule';
Expand All @@ -11,21 +13,19 @@ if (IS_FIREFOX) {
browser.runtime.onInstalled.addListener(updateDynamicRules);
}

browser.runtime.onMessage.addListener(async (msg: any) => {
const { method } = msg;

if (method === 'reload') {
browser.runtime.onMessage.addListener(async ({ method }: IPopupMessageData) => {
if (method === POPUP_METHODS.reload) {
wallet.disconnect();
window.location.reload();
return null;
}

if (method === 'checkHasAccount') {
if (method === POPUP_METHODS.checkHasAccount) {
const { isLoggedIn } = useAccounts();
return isLoggedIn.value;
}

if (UNFINISHED_FEATURES && method === 'paste') {
if (UNFINISHED_FEATURES && method === POPUP_METHODS.paste) {
let result = '';
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
Expand All @@ -41,3 +41,6 @@ browser.runtime.onMessage.addListener(async (msg: any) => {
});

wallet.init();

// Initialize the WalletConnect state monitoring to allow opening the confirmation popup windows.
useWalletConnect({ offscreen: true });
34 changes: 18 additions & 16 deletions src/offscreen/popupHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
IPopupProps,
PopupType,
} from '@/types';
import { POPUP_METHODS } from '@/constants';
import { executeOrSendMessageToBackground } from './utils';

interface IPopupConfig {
Expand All @@ -16,30 +17,31 @@ const popups: Dictionary<IPopupConfig> = {};
export const openPopup = async (
popupType: PopupType,
aepp: string | object,
params: Partial<IPopupProps> = {},
popupProps: Partial<IPopupProps> = {},
) => executeOrSendMessageToBackground(
'openPopup',
POPUP_METHODS.openPopup,
{
popupProps,
popupType,
aepp,
params,
},
).then((popupConfig) => new Promise<IPopupConfig>((resolve, reject) => {
const popupWithActions = {
...popupConfig,
actions: {
resolve,
reject,
},
};
const { id } = popupWithActions;
popups[id] = popupWithActions;
return popupWithActions;
}));
)
.then((popupConfig) => new Promise<IPopupConfig>((resolve, reject) => {
const popupWithActions = {
...popupConfig,
actions: {
resolve,
reject,
},
};
const { id } = popupWithActions;
popups[id] = popupWithActions;
return popupWithActions;
}));

export const removePopup = async (id: string) => {
delete popups[id];
executeOrSendMessageToBackground('removePopup', { id });
executeOrSendMessageToBackground(POPUP_METHODS.removePopup, { id });
};

export const getPopup = (id: string) => popups[id];
23 changes: 13 additions & 10 deletions src/offscreen/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CONNECTION_TYPES, IS_FIREFOX } from '@/constants';
import type { Runtime } from 'webextension-polyfill';
import type { IPopupMessageData, PopupMethod } from '@/types';
import { CONNECTION_TYPES, IS_FIREFOX, POPUP_METHODS } from '@/constants';
import { openPopup, removePopup, getPopup } from '@/background/bgPopupHandler';
import { PopupMessageData } from '@/background';
import { getCleanModalOptions } from '@/utils';

export const detectConnectionType = (port: Runtime.Port) => {
Expand All @@ -22,21 +22,24 @@ export const detectConnectionType = (port: Runtime.Port) => {
* because we "are" on the background page
* instead call the function directly from bgPopupHandler.ts
*/
export async function executeOrSendMessageToBackground(method: PopupMessageData['method'], params: PopupMessageData['params']) {
export async function executeOrSendMessageToBackground(
method: PopupMethod,
params: Required<IPopupMessageData>['params'],
) {
if (IS_FIREFOX) {
switch (method) {
case 'openPopup':
return openPopup(params.popupType, params.aepp, params.params);
case 'removePopup':
return removePopup(params.id);
case 'getPopup':
return getPopup(params.id);
case POPUP_METHODS.openPopup:
return openPopup(params.popupType!, params.aepp!, params.popupProps);
case POPUP_METHODS.removePopup:
return removePopup(params.id!);
case POPUP_METHODS.getPopup:
return getPopup(params.id!);
default:
return null;
}
}
const cleanParams = getCleanModalOptions<typeof params>(params);
return browser.runtime.sendMessage<PopupMessageData>({
return browser.runtime.sendMessage<IPopupMessageData>({
target: 'background',
method,
params: cleanParams,
Expand Down
4 changes: 2 additions & 2 deletions src/offscreen/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { watch } from 'vue';
import { isEqual } from 'lodash-es';
import type { Runtime } from 'webextension-polyfill';
import { BrowserRuntimeConnection } from '@aeternity/aepp-sdk';
import type { IPopupMessageData } from '@/types';
import { CONNECTION_TYPES, POPUP_ACTIONS } from '@/constants';
import { PopupMessageData } from '@/background';
import { useAccounts, useAeSdk, useNetworks } from '@/composables';
import { removePopup, getPopup } from './popupHandler';
import { detectConnectionType } from './utils';
Expand Down Expand Up @@ -37,7 +37,7 @@ export async function init() {
switch (detectConnectionType(port as Runtime.Port)) {
case CONNECTION_TYPES.POPUP: {
const id = new URL(port?.sender?.url!).searchParams.get('id');
port.onMessage.addListener(async (msg: PopupMessageData) => {
port.onMessage.addListener(async (msg: IPopupMessageData) => {
const popup = getPopup(id!);

if (msg.type === POPUP_ACTIONS.getProps) {
Expand Down
2 changes: 1 addition & 1 deletion src/popup/components/Modals/ConfirmTransactionSign.vue
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export default defineComponent({
const activeAccount = getLastActiveProtocolAccount(protocol);
const transaction = ref<ITransaction>({
protocol,
tx: popupProps.value?.tx,
tx: popupProps.value?.tx || {},
} as ITransaction);
const {
Expand Down
7 changes: 5 additions & 2 deletions src/popup/components/NameItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,13 @@ import {
watch,
} from 'vue';
import { useI18n } from 'vue-i18n';
import { IName } from '@/types';
import { IName, IPopupMessageData } from '@/types';
import { Clipboard } from '@capacitor/clipboard';
import {
IS_EXTENSION,
IS_MOBILE_APP,
MODAL_CONFIRM,
POPUP_METHODS,
UNFINISHED_FEATURES,
} from '@/constants';
import Logger from '@/lib/logger';
Expand Down Expand Up @@ -252,7 +253,9 @@ export default defineComponent({
text = value;
}
} else if (IS_EXTENSION) {
text = await browser!.runtime.sendMessage({ method: 'paste' });
text = await browser!.runtime.sendMessage<IPopupMessageData>({
method: POPUP_METHODS.paste,
});
} else {
try {
text = await navigator.clipboard.readText();
Expand Down
Loading

0 comments on commit b00ec2a

Please sign in to comment.