diff --git a/landing/siwe.react.js b/landing/siwe.react.js
index 7fd782a0c..d66322113 100644
--- a/landing/siwe.react.js
+++ b/landing/siwe.react.js
@@ -1,177 +1,177 @@
// @flow
import {
useConnectModal,
RainbowKitProvider,
darkTheme,
useModalState,
} from '@rainbow-me/rainbowkit';
import '@rainbow-me/rainbowkit/styles.css';
import invariant from 'invariant';
import _merge from 'lodash/fp/merge.js';
import * as React from 'react';
import { useAccount, useSigner, WagmiConfig } from 'wagmi';
import ConnectedWalletInfo from 'lib/components/connected-wallet-info.react.js';
import type { SIWEWebViewMessage } from 'lib/types/siwe-types.js';
import {
getSIWEStatementForPublicKey,
siweMessageSigningExplanationStatements,
createSIWEMessage,
} from 'lib/utils/siwe-utils.js';
import {
- WagmiENSCacheProvider,
+ AlchemyENSCacheProvider,
wagmiClient,
wagmiChains,
} from 'lib/utils/wagmi-utils.js';
import { SIWEContext } from './siwe-context.js';
import css from './siwe.css';
import { useMonitorForWalletConnectModal } from './walletconnect-hooks.js';
function postMessageToNativeWebView(message: SIWEWebViewMessage) {
window.ReactNativeWebView?.postMessage?.(JSON.stringify(message));
}
async function signInWithEthereum(
address: string,
signer,
nonce: string,
statement: string,
) {
invariant(nonce, 'nonce must be present in signInWithEthereum');
const message = createSIWEMessage(address, statement, nonce);
const signature = await signer.signMessage(message);
postMessageToNativeWebView({
type: 'siwe_success',
address,
message,
signature,
});
}
function SIWE(): React.Node {
const { address } = useAccount();
const { data: signer } = useSigner();
const { siweNonce, siwePrimaryIdentityPublicKey } =
React.useContext(SIWEContext);
const onClick = React.useCallback(() => {
invariant(siweNonce, 'nonce must be present during SIWE attempt');
invariant(
siwePrimaryIdentityPublicKey,
'primaryIdentityPublicKey must be present during SIWE attempt',
);
const statement = getSIWEStatementForPublicKey(
siwePrimaryIdentityPublicKey,
);
signInWithEthereum(address, signer, siweNonce, statement);
}, [address, signer, siweNonce, siwePrimaryIdentityPublicKey]);
const { openConnectModal } = useConnectModal();
const hasNonce = siweNonce !== null && siweNonce !== undefined;
React.useEffect(() => {
if (hasNonce && openConnectModal) {
openConnectModal();
}
}, [hasNonce, openConnectModal]);
const [wcModalOpen, setWCModalOpen] = React.useState(false);
const prevConnectModalOpen = React.useRef(false);
const modalState = useModalState();
const closeTimeoutRef = React.useRef();
const { connectModalOpen } = modalState;
React.useEffect(() => {
if (
!connectModalOpen &&
!wcModalOpen &&
prevConnectModalOpen.current &&
!signer
) {
closeTimeoutRef.current = setTimeout(
() => postMessageToNativeWebView({ type: 'siwe_closed' }),
250,
);
} else if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = undefined;
}
prevConnectModalOpen.current = connectModalOpen;
}, [connectModalOpen, wcModalOpen, signer]);
const onWalletConnectModalUpdate = React.useCallback(update => {
if (update.state === 'closed') {
setWCModalOpen(false);
postMessageToNativeWebView({
type: 'walletconnect_modal_update',
...update,
});
} else {
setWCModalOpen(true);
postMessageToNativeWebView({
type: 'walletconnect_modal_update',
...update,
});
}
}, []);
useMonitorForWalletConnectModal(onWalletConnectModalUpdate);
if (!hasNonce) {
return (
Unable to proceed: nonce not found.
);
} else if (!signer) {
return null;
} else {
return (
Wallet Connected
{siweMessageSigningExplanationStatements}
By signing up, you agree to our{' '}
Terms of Use &{' '}
Privacy Policy.
Sign in using this wallet
);
}
}
function SIWEWrapper(): React.Node {
const theme = React.useMemo(() => {
return _merge(darkTheme())({
radii: {
modal: 0,
modalMobile: 0,
},
colors: {
modalBackdrop: '#242529',
},
});
}, []);
return (
-
+
-
+
);
}
export default SIWEWrapper;
diff --git a/lib/utils/wagmi-utils.js b/lib/utils/wagmi-utils.js
index 0ae571b1e..17d100827 100644
--- a/lib/utils/wagmi-utils.js
+++ b/lib/utils/wagmi-utils.js
@@ -1,79 +1,86 @@
// @flow
import { connectorsForWallets } from '@rainbow-me/rainbowkit';
import {
injectedWallet,
metaMaskWallet,
rainbowWallet,
walletConnectWallet,
// eslint-disable-next-line import/extensions
} from '@rainbow-me/rainbowkit/wallets';
+import { ethers } from 'ethers';
import * as React from 'react';
-import { configureChains, createClient, useProvider } from 'wagmi';
+import { configureChains, createClient } from 'wagmi';
// eslint-disable-next-line import/extensions
import { mainnet } from 'wagmi/chains';
// eslint-disable-next-line import/extensions
import { alchemyProvider } from 'wagmi/providers/alchemy';
// eslint-disable-next-line import/extensions
import { publicProvider } from 'wagmi/providers/public';
import { ENSCacheProvider } from '../components/ens-cache-provider.react.js';
// details can be found at https://wagmi.sh/core/providers/configuring-chains
type WagmiChainConfiguration = {
+chains: mixed,
+provider: mixed,
};
function configureWagmiChains(alchemyKey: ?string): WagmiChainConfiguration {
const availableProviders = alchemyKey
? [alchemyProvider({ apiKey: alchemyKey })]
: [publicProvider()];
const { chains, provider } = configureChains([mainnet], availableProviders);
return { chains, provider };
}
type WagmiClientInput = {
+provider: mixed,
+connectors?: mixed,
};
function createWagmiClient(input: WagmiClientInput): mixed {
const { provider, connectors } = input;
return createClient({
autoConnect: true,
connectors,
provider,
});
}
const { chains, provider } = configureWagmiChains(process.env.COMM_ALCHEMY_KEY);
const wallets = [injectedWallet({ chains })];
const projectId = process.env.COMM_WALLETCONNECT_KEY;
if (projectId) {
wallets.push(
rainbowWallet({ chains, projectId }),
metaMaskWallet({ chains, projectId }),
walletConnectWallet({ chains, projectId }),
);
}
const connectors = connectorsForWallets([
{ groupName: 'Recommended', wallets },
]);
const wagmiClient: mixed = createWagmiClient({ connectors, provider });
const wagmiChains: mixed = chains;
+const ethersAlchemyProvider = new ethers.providers.AlchemyProvider(
+ 'mainnet',
+ process.env.COMM_ALCHEMY_KEY,
+);
+
type Props = {
+children: React.Node,
};
-function WagmiENSCacheProvider(props: Props): React.Node {
+function AlchemyENSCacheProvider(props: Props): React.Node {
const { children } = props;
- const wagmiProvider = useProvider();
return (
- {children}
+
+ {children}
+
);
}
-export { wagmiClient, wagmiChains, WagmiENSCacheProvider };
+export { wagmiClient, wagmiChains, AlchemyENSCacheProvider };
diff --git a/web/app.react.js b/web/app.react.js
index c0d722a61..03c6a26db 100644
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -1,390 +1,390 @@
// @flow
import 'basscss/css/basscss.min.css';
import './theme.css';
import { config as faConfig } from '@fortawesome/fontawesome-svg-core';
import classnames from 'classnames';
import _isEqual from 'lodash/fp/isEqual.js';
import * as React from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { useDispatch } from 'react-redux';
import { WagmiConfig } from 'wagmi';
import {
fetchEntriesActionTypes,
updateCalendarQueryActionTypes,
} from 'lib/actions/entry-actions.js';
import { EditUserAvatarProvider } from 'lib/components/edit-user-avatar-provider.react.js';
import {
ModalProvider,
useModalContext,
} from 'lib/components/modal-provider.react.js';
import { webAndKeyserverCodeVersion } from 'lib/facts/version.js';
import {
createLoadingStatusSelector,
combineLoadingStatuses,
} from 'lib/selectors/loading-selectors.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
import type { LoadingStatus } from 'lib/types/loading-types.js';
import type { Dispatch } from 'lib/types/redux-types.js';
import { registerConfig } from 'lib/utils/config.js';
import { infoFromURL } from 'lib/utils/url-utils.js';
-import { WagmiENSCacheProvider, wagmiClient } from 'lib/utils/wagmi-utils.js';
+import { AlchemyENSCacheProvider, wagmiClient } from 'lib/utils/wagmi-utils.js';
import QrCodeLogin from './account/qr-code-login.react.js';
import WebEditThreadAvatarProvider from './avatars/web-edit-thread-avatar-provider.react.js';
import Calendar from './calendar/calendar.react.js';
import Chat from './chat/chat.react.js';
import { EditModalProvider } from './chat/edit-message-provider.js';
import { TooltipProvider } from './chat/tooltip-provider.js';
import NavigationArrows from './components/navigation-arrows.react.js';
import { initOpaque } from './crypto/opaque-utils.js';
import electron from './electron.js';
import InputStateContainer from './input/input-state-container.react.js';
import InviteLinkHandler from './invite-links/invite-link-handler.react.js';
import InviteLinksRefresher from './invite-links/invite-links-refresher.react.js';
import LoadingIndicator from './loading-indicator.react.js';
import { MenuProvider } from './menu-provider.react.js';
import UpdateModalHandler from './modals/update-modal.react.js';
import SettingsSwitcher from './navigation-panels/settings-switcher.react.js';
import Topbar from './navigation-panels/topbar.react.js';
import useBadgeHandler from './push-notif/badge-handler.react.js';
import { PushNotificationsHandler } from './push-notif/push-notifs-handler.js';
import { updateNavInfoActionType } from './redux/action-types.js';
import DeviceIDUpdater from './redux/device-id-updater.js';
import DisconnectedBarVisibilityHandler from './redux/disconnected-bar-visibility-handler.js';
import DisconnectedBar from './redux/disconnected-bar.js';
import FocusHandler from './redux/focus-handler.react.js';
import { persistConfig } from './redux/persist.js';
import PolicyAcknowledgmentHandler from './redux/policy-acknowledgment-handler.js';
import { useSelector } from './redux/redux-utils.js';
import VisibilityHandler from './redux/visibility-handler.react.js';
import history from './router-history.js';
import { MessageSearchStateProvider } from './search/message-search-state-provider.react.js';
import AccountSettings from './settings/account-settings.react.js';
import DangerZone from './settings/danger-zone.react.js';
import CommunityPicker from './sidebar/community-picker.react.js';
import Splash from './splash/splash.react.js';
import './typography.css';
import css from './style.css';
import { type NavInfo } from './types/nav-types.js';
import { canonicalURLFromReduxState, navInfoFromURL } from './url-utils.js';
initOpaque();
// We want Webpack's css-loader and style-loader to handle the Fontawesome CSS,
// so we disable the autoAddCss logic and import the CSS file. Otherwise every
// icon flashes huge for a second before the CSS is loaded.
import '@fortawesome/fontawesome-svg-core/styles.css';
faConfig.autoAddCss = false;
registerConfig({
// We can't securely cache credentials on web, so we have no way to recover
// from a cookie invalidation
resolveInvalidatedCookie: null,
// We use httponly cookies on web to protect against XSS attacks, so we have
// no access to the cookies from JavaScript
setCookieOnRequest: false,
setSessionIDOnRequest: true,
// Never reset the calendar range
calendarRangeInactivityLimit: null,
platformDetails: {
platform: electron?.platform ?? 'web',
codeVersion: webAndKeyserverCodeVersion,
stateVersion: persistConfig.version,
},
});
type BaseProps = {
+location: {
+pathname: string,
...
},
};
type Props = {
...BaseProps,
// Redux state
+navInfo: NavInfo,
+entriesLoadingStatus: LoadingStatus,
+loggedIn: boolean,
+activeThreadCurrentlyUnread: boolean,
// Redux dispatch functions
+dispatch: Dispatch,
+modals: $ReadOnlyArray,
};
class App extends React.PureComponent {
componentDidMount() {
const {
navInfo,
location: { pathname },
loggedIn,
} = this.props;
const newURL = canonicalURLFromReduxState(navInfo, pathname, loggedIn);
if (pathname !== newURL) {
history.replace(newURL);
}
}
componentDidUpdate(prevProps: Props) {
const {
navInfo,
location: { pathname },
loggedIn,
} = this.props;
if (!_isEqual(navInfo)(prevProps.navInfo)) {
const newURL = canonicalURLFromReduxState(navInfo, pathname, loggedIn);
if (newURL !== pathname) {
history.push(newURL);
}
} else if (pathname !== prevProps.location.pathname) {
const urlInfo = infoFromURL(pathname);
const newNavInfo = navInfoFromURL(urlInfo, { navInfo });
if (!_isEqual(newNavInfo)(navInfo)) {
this.props.dispatch({
type: updateNavInfoActionType,
payload: newNavInfo,
});
}
} else if (loggedIn !== prevProps.loggedIn) {
const newURL = canonicalURLFromReduxState(navInfo, pathname, loggedIn);
if (newURL !== pathname) {
history.replace(newURL);
}
}
if (loggedIn !== prevProps.loggedIn) {
electron?.clearHistory();
}
}
onWordmarkClicked = () => {
this.props.dispatch({
type: updateNavInfoActionType,
payload: { tab: 'chat' },
});
};
render() {
let content;
if (this.props.loggedIn) {
content = (
<>
{this.renderMainContent()}
{this.props.modals}
>
);
} else {
content = (
<>
{this.renderLoginPage()}
{this.props.modals}
>
);
}
return (
-
+
{content}
-
+
);
}
onHeaderDoubleClick = () => electron?.doubleClickTopBar();
stopDoubleClickPropagation = electron ? e => e.stopPropagation() : null;
renderLoginPage() {
const { loginMethod } = this.props.navInfo;
if (loginMethod === 'qr-code') {
return ;
}
return ;
}
renderMainContent() {
const mainContent = this.getMainContentWithSwitcher();
let navigationArrows = null;
if (electron) {
navigationArrows = ;
}
const headerClasses = classnames({
[css.header]: true,
[css['electron-draggable']]: electron,
});
const wordmarkClasses = classnames({
[css.wordmark]: true,
[css['electron-non-draggable']]: electron,
[css['wordmark-macos']]: electron?.platform === 'macos',
});
return (
);
}
getMainContentWithSwitcher() {
const { tab, settingsSection } = this.props.navInfo;
let mainContent;
if (tab === 'settings') {
if (settingsSection === 'account') {
mainContent = ;
} else if (settingsSection === 'danger-zone') {
mainContent = ;
}
return (
);
}
if (tab === 'calendar') {
mainContent = ;
} else if (tab === 'chat') {
mainContent = ;
}
const mainContentClass = classnames(
css['main-content-container'],
css['main-content-container-column'],
);
return (
);
}
}
const fetchEntriesLoadingStatusSelector = createLoadingStatusSelector(
fetchEntriesActionTypes,
);
const updateCalendarQueryLoadingStatusSelector = createLoadingStatusSelector(
updateCalendarQueryActionTypes,
);
const ConnectedApp: React.ComponentType = React.memo(
function ConnectedApp(props) {
const activeChatThreadID = useSelector(
state => state.navInfo.activeChatThreadID,
);
const navInfo = useSelector(state => state.navInfo);
const fetchEntriesLoadingStatus = useSelector(
fetchEntriesLoadingStatusSelector,
);
const updateCalendarQueryLoadingStatus = useSelector(
updateCalendarQueryLoadingStatusSelector,
);
const entriesLoadingStatus = combineLoadingStatuses(
fetchEntriesLoadingStatus,
updateCalendarQueryLoadingStatus,
);
const loggedIn = useSelector(isLoggedIn);
const activeThreadCurrentlyUnread = useSelector(
state =>
!activeChatThreadID ||
!!state.threadStore.threadInfos[activeChatThreadID]?.currentUser.unread,
);
useBadgeHandler();
const dispatch = useDispatch();
const modalContext = useModalContext();
const modals = React.useMemo(
() =>
modalContext.modals.map(([modal, key]) => (
{modal}
)),
[modalContext.modals],
);
return (
);
},
);
function AppWithProvider(props: BaseProps): React.Node {
return (
);
}
export default AppWithProvider;