Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support chrome.storage when available #213

Merged
merged 19 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client.jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const config: InitialOptionsTsJest = {
['@bugsnag/(.*)']: '<rootDir>/tests/mocks/empty.js',
['viem/chains']: '<rootDir>/tests/mocks/empty.js',
['@xmtp/(.*)']: '<rootDir>/tests/mocks/empty.js',
['@uauth/(.*)']: '<rootDir>/tests/mocks/empty.js',
['@pushprotocol/(.*)']: '<rootDir>/tests/mocks/empty.js',
['@ipld/(.*)']: '<rootDir>/tests/mocks/empty.js',
['@ucanto/(.*)']: '<rootDir>/tests/mocks/empty.js',
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unstoppabledomains/ui-components",
"version": "0.0.50-browser-extension.17",
"version": "0.0.51-browser-extension.5",
"private": true,
"description": "An open and reusable suite of Unstoppable Domains management components",
"keywords": [
Expand Down
5 changes: 3 additions & 2 deletions packages/ui-components/src/actions/domainProfileActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {useQuery} from 'react-query';
import config from '@unstoppabledomains/config';

import type {AddressResolution} from '../components/Chat/types';
import type {NftResponse} from '../lib';
import {NftPageSize, isDomainValidForManagement} from '../lib';
import {isDomainValidForManagement} from '../lib/domain/format';
import {fetchApi} from '../lib/fetchApi';
import type {
DomainFieldTypes,
Expand All @@ -19,6 +18,8 @@ import type {
SerializedUserDomainProfileData,
} from '../lib/types/domain';
import {DomainProfileSocialMedia} from '../lib/types/domain';
import type {NftResponse} from '../lib/types/nfts';
import {NftPageSize} from '../lib/types/nfts';

export const DOMAIN_LIST_PAGE_SIZE = 8;

Expand Down
2 changes: 1 addition & 1 deletion packages/ui-components/src/actions/featureFlagActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {useQuery} from 'react-query';
import config, {getLaunchDarklyDefaults} from '@unstoppabledomains/config';
import type {LaunchDarklyCamelFlagSet} from '@unstoppabledomains/config';

import {fetchApi} from '../lib';
import {notifyEvent} from '../lib/error';
import {fetchApi} from '../lib/fetchApi';

const BASE_QUERY_KEY = 'featureFlags';
const queryKey = {
Expand Down
141 changes: 138 additions & 3 deletions packages/ui-components/src/actions/fireBlocksActions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import Bluebird from 'bluebird';
import {utils as EthersUtils} from 'ethers';
import QueryString from 'qs';
import type {Eip712TypedData} from 'web3';
import {utils as web3utils} from 'web3';

import config from '@unstoppabledomains/config';

import {getBlockchainSymbol} from '../components/Manage/common/verification/types';
import {fetchApi} from '../lib';
import {notifyEvent} from '../lib/error';
import {FB_MAX_RETRY, FB_WAIT_TIME_MS} from '../lib/fireBlocks/client';
import {fetchApi} from '../lib/fetchApi';
import {
FB_MAX_RETRY,
FB_WAIT_TIME_MS,
getFireBlocksClient,
} from '../lib/fireBlocks/client';
import {
getBootstrapState,
saveBootstrapState,
Expand All @@ -26,6 +32,8 @@ import type {
GetOperationStatusResponse,
GetTokenResponse,
} from '../lib/types/fireBlocks';
import {EIP_712_KEY} from '../lib/types/fireBlocks';
import {getAsset} from '../lib/wallet/asset';

export enum OperationStatus {
QUEUED = 'QUEUED',
Expand Down Expand Up @@ -181,7 +189,9 @@ export const createTransactionOperation = async (
return undefined;
};

export const getAccessToken = async (
// getAccessTokenInternal called by useFireblocksAccessToken hook. This method should
// not be called directly.
export const getAccessTokenInternal = async (
refreshToken: string,
opts?: {
deviceId: string;
Expand Down Expand Up @@ -768,3 +778,128 @@ export const signAndWait = async (
}
return undefined;
};

export const signMessage = async (
message: string,
auth: {
accessToken: string;
state: Record<string, Record<string, string>>;
saveState: (
state: Record<string, Record<string, string>>,
) => void | Promise<void>;
},
opts: {
address?: string;
chainId?: number;
} = {},
): Promise<string> => {
// retrieve and validate key state
const clientState = getBootstrapState(auth.state);
if (!clientState) {
throw new Error('invalid configuration');
}

// retrieve a new client instance
const client = await getFireBlocksClient(
clientState.deviceId,
auth.accessToken,
{
state: auth.state,
saveState: auth.saveState,
},
);

notifyEvent(
'signing message with fireblocks client',
'info',
'Wallet',
'Signature',
{
meta: {
deviceId: client.getPhysicalDeviceId(),
message,
},
},
);

// determine if a specific chain ID should override based upon a typed
// EIP-712 message
const isTypedMessage = message.includes(EIP_712_KEY);
if (isTypedMessage) {
try {
const typedMessage: Eip712TypedData = JSON.parse(message);
if (typedMessage?.domain?.chainId) {
opts.chainId =
typeof typedMessage.domain.chainId === 'string'
? typedMessage.domain.chainId.startsWith('0x')
? (web3utils.hexToNumber(typedMessage.domain.chainId) as number)
: parseInt(typedMessage.domain.chainId, 10)
: typedMessage.domain.chainId;
}
} catch (e) {
notifyEvent(e, 'warning', 'Wallet', 'Signature', {
msg: 'unable to parse typed message',
});
}
}

// retrieve the asset associated with the optionally requested address,
// otherwise just retrieve the first first asset.
notifyEvent(
'retrieving wallet asset for signature',
'info',
'Wallet',
'Signature',
{
meta: {opts, default: config.WALLETS.SIGNATURE_SYMBOL},
},
);
const asset = getAsset(clientState.assets, {
address: opts.address,
chainId: opts.chainId,
});
if (!asset?.accountId) {
throw new Error('address not found in account');
}

// request an MPC signature of the desired message string
const signatureOp = await signAndWait(
auth.accessToken,
async () => {
return await createSignatureOperation(
auth.accessToken,
asset.accountId!,
asset.id,
message,
isTypedMessage,
);
},
async (txId: string) => {
await client.signTransaction(txId);
},
{
address: opts.address,
onStatusChange: (m: string) => {
notifyEvent(m, 'info', 'Wallet', 'Signature');
},
isComplete: (status: GetOperationStatusResponse) => {
return status?.result?.signature !== undefined;
},
},
);

// validate and return the signature result
if (!signatureOp?.result?.signature) {
throw new Error('signature failed');
}

// indicate complete with successful signature result
notifyEvent('signature successful', 'info', 'Wallet', 'Signature', {
meta: {
opts,
message,
signatureOp,
},
});
return signatureOp.result.signature;
};
2 changes: 1 addition & 1 deletion packages/ui-components/src/actions/pav3Actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import config from '@unstoppabledomains/config';

import {fetchApi} from '../lib';
import {fetchApi} from '../lib/fetchApi';
import type {MappedResolverKey, RecordUpdateResponse} from '../lib/types/pav3';

// confirmRecordUpdate submits a transaction signature to allow a domain record
Expand Down
5 changes: 3 additions & 2 deletions packages/ui-components/src/actions/walletActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import QueryString from 'qs';

import config from '@unstoppabledomains/config';

import type {SerializedWalletBalance, WalletAccountResponse} from '../lib';
import {fetchApi} from '../lib';
import {notifyEvent} from '../lib/error';
import {fetchApi} from '../lib/fetchApi';
import type {SerializedWalletBalance} from '../lib/types/domain';
import type {SerializedIdentityResponse} from '../lib/types/identity';
import type {WalletAccountResponse} from '../lib/types/wallet';

export const createWallet = async (
emailAddress: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ export const UnstoppableMessaging: React.FC<UnstoppableMessagingProps> = ({
// load information about the connected wallet
const fetchUser = async () => {
// retrieve local state about this address
const cachedPushKey = getPushLocalKey(chatAddress);
const cachedXmtpKey = getXmtpLocalKey(chatAddress);
const cachedPushKey = await getPushLocalKey(chatAddress);
const cachedXmtpKey = await getXmtpLocalKey(chatAddress);
if (cachedXmtpKey) {
setXmtpKey(cachedXmtpKey);
setIsChatReady(true);
Expand Down Expand Up @@ -476,11 +476,11 @@ export const UnstoppableMessaging: React.FC<UnstoppableMessagingProps> = ({
}

// determine if XMTP registration will be performed
const xmtpLocalKey = getXmtpLocalKey(chatAddress);
const xmtpLocalKey = await getXmtpLocalKey(chatAddress);
const xmtpSetupRequired = !xmtpLocalKey && !opts.skipXmtp;

// determine if Push Protocol registration will be performed
const pushLocalKey = getPushLocalKey(chatAddress);
const pushLocalKey = await getPushLocalKey(chatAddress);
const pushSetupRequired = !pushLocalKey && !opts.skipPush;

try {
Expand Down Expand Up @@ -511,7 +511,7 @@ export const UnstoppableMessaging: React.FC<UnstoppableMessagingProps> = ({
setConfigState(ConfigurationState.RegisterXmtp);
await initXmtpAccount(chatAddress, web3Context.web3Deps.signer);
}
setXmtpKey(getXmtpLocalKey(chatAddress));
setXmtpKey(await getXmtpLocalKey(chatAddress));
}

// perform Push Protocol setup if required
Expand All @@ -533,7 +533,7 @@ export const UnstoppableMessaging: React.FC<UnstoppableMessagingProps> = ({
setPushUser(pushUserAccount);

// retrieve the user's encryption key and store it locally on the device
if (!getPushLocalKey(chatAddress)) {
if (!(await getPushLocalKey(chatAddress))) {
setConfigState(ConfigurationState.RegisterPush);
const decryptedPvtKey = await PushAPI.chat.decryptPGPKey({
encryptedPGPPrivateKey: pushUserAccount.encryptedPrivateKey,
Expand Down Expand Up @@ -563,7 +563,7 @@ export const UnstoppableMessaging: React.FC<UnstoppableMessagingProps> = ({

// set configuration state
setPushKey(decryptedPvtKey);
setPushLocalKey(chatAddress, decryptedPvtKey);
await setPushLocalKey(chatAddress, decryptedPvtKey);
}

try {
Expand Down
1 change: 1 addition & 0 deletions packages/ui-components/src/components/Chat/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './SupportBubble';
export * from './UnstoppableMessaging';
export * from './protocol';
export * from './storage';
export * from './types';
13 changes: 7 additions & 6 deletions packages/ui-components/src/components/Chat/modal/ChatModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ import config from '@unstoppabledomains/config';
import {makeStyles} from '@unstoppabledomains/ui-kit/styles';

import {useFeatureFlags} from '../../../actions/featureFlagActions';
import {
getDomainSignatureExpiryKey,
getDomainSignatureValueKey,
} from '../../../components/Wallet/ProfileManager';
import useFetchNotifications from '../../../hooks/useFetchNotification';
import {fetchApi, isDomainValidForManagement} from '../../../lib';
import {notifyEvent} from '../../../lib/error';
import useTranslationContext from '../../../lib/i18n';
import type {SerializedCryptoWalletBadge} from '../../../lib/types/badge';
import {
getDomainSignatureExpiryKey,
getDomainSignatureValueKey,
} from '../../../lib/types/domain';
import type {
SerializedRecommendation,
SerializedUserDomainProfileData,
Expand All @@ -60,6 +60,7 @@ import {
getConversations,
isAllowListed,
} from '../protocol/xmtp';
import {localStorageWrapper} from '../storage';
import type {AddressResolution, PayloadData} from '../types';
import {TabType, getCaip10Address} from '../types';
import CallToAction from './CallToAction';
Expand Down Expand Up @@ -455,10 +456,10 @@ export const ChatModal: React.FC<ChatModalProps> = ({

try {
// retrieve optional signature data
const authExpiry = localStorage.getItem(
const authExpiry = await localStorageWrapper.getItem(
getDomainSignatureExpiryKey(authDomain!),
);
const authSignature = localStorage.getItem(
const authSignature = await localStorageWrapper.getItem(
getDomainSignatureValueKey(authDomain!),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import truncateEthAddress from 'truncate-eth-address';

import config from '@unstoppabledomains/config';

import type {
CurrenciesType} from '../../../../lib';
import {
CurrenciesType,
getBlockScanUrl,
isDomainValidForManagement,
} from '../../../../lib';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {SerializedUserDomainProfileData} from '../../../../lib/types/domain
import {DomainProfileKeys} from '../../../../lib/types/domain';
import type {Web3Dependencies} from '../../../../lib/types/web3';
import {formatFileSize, sendRemoteAttachment} from '../../protocol/xmtp';
import {localStorageWrapper} from '../../storage';
import {useConversationComposeStyles} from '../styles';

export const Compose: React.FC<ComposeProps> = ({
Expand Down Expand Up @@ -47,8 +48,13 @@ export const Compose: React.FC<ComposeProps> = ({

// set the primary domain and wallet address at page load time
useEffect(() => {
setAuthDomain(localStorage.getItem(DomainProfileKeys.AuthDomain));
setAuthAddress(conversation?.clientAddress.toLowerCase());
const loadConversation = async () => {
setAuthDomain(
await localStorageWrapper.getItem(DomainProfileKeys.AuthDomain),
);
setAuthAddress(conversation?.clientAddress.toLowerCase());
};
void loadConversation();
}, [conversation]);

// detect if user clicks outside the compose textbox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import truncateEthAddress from 'truncate-eth-address';

import {makeStyles} from '@unstoppabledomains/ui-kit/styles';

import {SerializedRecommendation} from '../../../../lib';
import type {SerializedRecommendation} from '../../../../lib';
import useTranslationContext from '../../../../lib/i18n';
import {getAddressMetadata, isEthAddress} from '../../protocol/resolution';
import {ConversationMeta, isXmtpUser} from '../../protocol/xmtp';
import type {ConversationMeta} from '../../protocol/xmtp';
import { isXmtpUser} from '../../protocol/xmtp';
import type {AddressResolution} from '../../types';
import {TabType} from '../../types';
import CallToAction from '../CallToAction';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import truncateEthAddress from 'truncate-eth-address';
import {makeStyles} from '@unstoppabledomains/ui-kit/styles';

import {getDomainConnections} from '../../../../actions';
import {SerializedRecommendation} from '../../../../lib';
import type {SerializedRecommendation} from '../../../../lib';
import useTranslationContext from '../../../../lib/i18n';
import ChipControlButton from '../../../ChipControlButton';
import {ConversationMeta} from '../../protocol/xmtp';
import type {ConversationMeta} from '../../protocol/xmtp';
import type {AddressResolution} from '../../types';

const useStyles = makeStyles()((theme: Theme) => ({
Expand Down
Loading
Loading