Skip to content

Commit

Permalink
Support chrome.storage when available (#213)
Browse files Browse the repository at this point in the history
* WIP

* support chrome.storage when available

* fix lint and typecheck issues

* fix unit test failure

* remove dynamic imports

* mock @uauth/js for unit tests

* reenable XMTP silent onboarding

* bump package version

* refactor imports for better tree shaking

* improve domain CTA

* styling for config modals

* lint fixes

* customized wallet color palette

* common fund wallet CTA

* optional onError callback during sign-in

* add log entry
  • Loading branch information
qrtp authored Oct 29, 2024
1 parent 97118df commit 7459600
Show file tree
Hide file tree
Showing 67 changed files with 1,245 additions and 646 deletions.
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

0 comments on commit 7459600

Please sign in to comment.