diff --git a/.storybook/example.stories.js b/.storybook/example.stories.js index bbfe2ab7..3fd20426 100644 --- a/.storybook/example.stories.js +++ b/.storybook/example.stories.js @@ -54,20 +54,6 @@ stories.add('Component', () => { ); }); -stories.add('web3', () => { - const props = { - token: text('token', ''), - }; - - button('Save Link configuration', reRender); - - return ( -
- -
- ); -}); - stories.add('embedded Link', () => { const props = { token: text('token', ''), diff --git a/Makefile b/Makefile index 9beec787..d182b25a 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ SRC_FILES = $(shell find src -name '*.js|*.tsx|*.ts' | sort) .PHONY: clean clean: - @rm -rf dist lib web3 + @rm -rf dist lib .PHONY: build diff --git a/examples/web3.tsx b/examples/web3.tsx deleted file mode 100644 index 27eb735c..00000000 --- a/examples/web3.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useCallback } from 'react'; - -import { - useEthereumProvider, - EthereumOnboardingOptions, - PlaidWeb3OnSuccess, - EIP1193Provider, -} from 'react-plaid-link/web3'; - -import { PlaidLinkOnExit } from 'react-plaid-link'; - -const PlaidWeb3Link = () => { - const token = ''; - - const [walletProvider, setWalletProvider] = React.useState(); - const [addresses, setAddresses] = React.useState(); - const [checkedExistingProvider, setCheckedExistingProvider] = React.useState( - false - ); - - const onSuccess = useCallback( - async provider => { - setWalletProvider(provider); - }, - [setWalletProvider] - ); - const onExit = useCallback((error, metadata) => { - // log onExit callbacks from Link, handle errors - // https://plaid.com/docs/link/web/#onexit - console.log(error, metadata); - }, []); - - const config: EthereumOnboardingOptions = { - token, - chain: { - chainId: '0x1', - rpcUrl: 'https://rpc.com', - }, - onSuccess, - onExit, - }; - - const { - open, - ready, - getCurrentEthereumProvider, - isProviderActive, - // error, - // exit - } = useEthereumProvider(config); - - React.useEffect(() => { - if (!ready) { - return; - } - (async () => { - const provider = await getCurrentEthereumProvider(config.chain); - if (!provider) { - setCheckedExistingProvider(true); - return; - } - const isActive = await isProviderActive(provider); - if (isActive) { - setWalletProvider(provider); - } - setCheckedExistingProvider(true); - })(); - - return () => {}; - }, [ - ready, - getCurrentEthereumProvider, - setCheckedExistingProvider, - isProviderActive, - setWalletProvider, - ]); - - React.useEffect(() => { - if (!walletProvider) { - return; - } - (async () => { - const addresses = await walletProvider.request({ - method: 'eth_accounts', - }); - setAddresses(addresses); - })(); - }, [walletProvider, setAddresses]); - - if (addresses) { - return {addresses.join(', ')}; - } - - return ( - - ); -}; - -export default PlaidWeb3Link; diff --git a/package.json b/package.json index f480637f..4a66a565 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "registry": "https://registry.npmjs.org", "files": [ "dist", - "web3", "src", "LICENSE" ], diff --git a/rollup.config.js b/rollup.config.js index ed18b697..3de86a00 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -23,22 +23,6 @@ export default [ commonjs(), ], }, - { - input: 'src/web3/index.ts', - external: ['react', 'prop-types'], - output: [ - { file: 'web3/index.js', format: 'cjs' }, - { file: 'web3/index.esm.js', format: 'es' }, - ], - plugins: [ - ts(), - resolve(), - babel({ - extensions: ['.ts', '.tsx'], - }), - commonjs(), - ], - }, // UMD build with inline PropTypes { input: 'src/index.ts', diff --git a/src/factory.ts b/src/factory.ts index 570c2d07..e52660c6 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -5,8 +5,6 @@ import { CommonPlaidLinkOptions, } from './types'; -import { EthereumOnboardingOptions } from './types/web3'; - export interface PlaidFactory { open: (() => void) | Function; submit: ((data: PlaidHandlerSubmissionData) => void)| Function; @@ -102,13 +100,6 @@ const createPlaidHandler = >( }; }; -export const createWeb3Plaid = ( - options: EthereumOnboardingOptions, - creator: (options: EthereumOnboardingOptions) => PlaidHandler -) => { - return createPlaidHandler(options, creator); -}; - export const createPlaid = ( options: PlaidLinkOptions, creator: (options: PlaidLinkOptions) => PlaidHandler diff --git a/src/types/web3.ts b/src/types/web3.ts deleted file mode 100644 index c53869b4..00000000 --- a/src/types/web3.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { CommonPlaidLinkOptions, PlaidHandler, Plaid } from './'; - -import { EIP1193Provider as ImportedEIP1193Provider } from './web3Provider'; - -export interface PlaidWeb3OnSuccessMetadata { - link_session_id: string; - wallet: { - name: string; - }; -} - -export interface ChainInformation { - name: string; - nativeCurrency: { - name: string; - symbol: string; - decimals: number; - }; - blockExplorerUrls: string[]; -} - -export interface ChainOption { - chainId: string; - rpcUrl: string; - chainInformation?: ChainInformation; -} - -export type EIP1193Provider = Omit; - -export type PlaidWeb3OnSuccess = ( - provider: EIP1193Provider, - metadata: PlaidWeb3OnSuccessMetadata -) => void; - -export interface EthereumOnboardingOptions - extends CommonPlaidLinkOptions { - token: string | null; - chain: ChainOption; -} - -export interface PlaidWeb3 { - createEthereumOnboarding: (config: EthereumOnboardingOptions) => PlaidHandler; - getCurrentEthereumProvider: ( - chainOption: ChainOption - ) => Promise; - isProviderActive: (provider: EIP1193Provider) => Promise; - disconnectEthereumProvider: (provider: EIP1193Provider) => Promise; -} - -export interface PlaidGlobalWithWeb3 extends Plaid { - web3: () => Promise; -} diff --git a/src/types/web3Provider.d.ts b/src/types/web3Provider.d.ts deleted file mode 100644 index e9abbcd5..00000000 --- a/src/types/web3Provider.d.ts +++ /dev/null @@ -1,157 +0,0 @@ -// This is a partial version of @web3-onboard/common's EIP-1193 type -// https://github.com/blocknative/web3-onboard/blob/v2-web3-onboard-develop/packages/common/src/types.ts#L345 -// Importing the types via the module would be preferred, but this creates issues when generating a -// rolled up .d.ts. -// Specifically, the default rollup-plugin-ts uses the default tsc --declaration output, -// which does not roll up module type definitions. rollup-plugin-dts only rolls up the immediately -// imported modules, but does not roll-up nested/transitive modules, which these EIP-1193 types -// have via the `eip-712` package. -// So, for now we copy the EIP-1193 types here and change the `eth_signTypedData` type to be -// `unknown` - -declare type ChainId = string; -interface ProviderRpcError extends Error { - message: string; - code: number; - data?: unknown; -} -interface ProviderMessage { - type: string; - data: unknown; -} -interface ProviderInfo { - chainId: ChainId; -} -declare type AccountAddress = string; -/** - * An array of addresses - */ -declare type ProviderAccounts = AccountAddress[]; -declare type ProviderEvent = - | 'connect' - | 'disconnect' - | 'message' - | 'chainChanged' - | 'accountsChanged'; -interface SimpleEventEmitter { - on( - event: ProviderEvent, - listener: - | ConnectListener - | DisconnectListener - | MessageListener - | ChainListener - | AccountsListener - ): void; - removeListener( - event: ProviderEvent, - listener: - | ConnectListener - | DisconnectListener - | MessageListener - | ChainListener - | AccountsListener - ): void; -} -declare type ConnectListener = (info: ProviderInfo) => void; -declare type DisconnectListener = (error: ProviderRpcError) => void; -declare type MessageListener = (message: ProviderMessage) => void; -declare type ChainListener = (chainId: ChainId) => void; -declare type AccountsListener = (accounts: ProviderAccounts) => void; -/** - * The hexadecimal representation of the users - */ -declare type Balance = string; -interface TransactionObject { - data?: string; - from: string; - gas?: string; - gasLimit?: string; - gasPrice?: string; - to: string; - chainId: number; - value?: string; - maxFeePerGas?: string; - maxPriorityFeePerGas?: string; - nonce?: string; -} -interface BaseRequest { - params?: never; -} -interface EthAccountsRequest extends BaseRequest { - method: 'eth_accounts'; -} -interface EthChainIdRequest extends BaseRequest { - method: 'eth_chainId'; -} -interface EthSignTransactionRequest { - method: 'eth_signTransaction'; - params: [TransactionObject]; -} -declare type Address = string; -declare type Message = string; -interface EthSignMessageRequest { - method: 'eth_sign'; - params: [Address, Message]; -} -interface PersonalSignMessageRequest { - method: 'personal_sign'; - params: [Message, Address]; -} -interface EIP712Request { - method: 'eth_signTypedData'; - // This is marked as unknown in this fork - params: [Address, unknown]; -} -interface EthBalanceRequest { - method: 'eth_getBalance'; - params: [string, (number | 'latest' | 'earliest' | 'pending')?]; -} -interface EIP1102Request extends BaseRequest { - method: 'eth_requestAccounts'; -} -interface SelectAccountsRequest extends BaseRequest { - method: 'eth_selectAccounts'; -} -interface EIP3085Request { - method: 'wallet_addEthereumChain'; - params: AddChainParams[]; -} -interface EIP3326Request { - method: 'wallet_switchEthereumChain'; - params: [ - { - chainId: ChainId; - } - ]; -} -declare type AddChainParams = { - chainId: ChainId; - chainName?: string; - nativeCurrency: { - name?: string; - symbol?: string; - decimals: number; - }; - rpcUrls: string[]; -}; -export interface EIP1193Provider extends SimpleEventEmitter { - on(event: 'connect', listener: ConnectListener): void; - on(event: 'disconnect', listener: DisconnectListener): void; - on(event: 'message', listener: MessageListener): void; - on(event: 'chainChanged', listener: ChainListener): void; - on(event: 'accountsChanged', listener: AccountsListener): void; - request(args: EthAccountsRequest): Promise; - request(args: EthBalanceRequest): Promise; - request(args: EIP1102Request): Promise; - request(args: SelectAccountsRequest): Promise; - request(args: EIP3326Request): Promise; - request(args: EIP3085Request): Promise; - request(args: EthChainIdRequest): Promise; - request(args: EthSignTransactionRequest): Promise; - request(args: EthSignMessageRequest): Promise; - request(args: PersonalSignMessageRequest): Promise; - request(args: EIP712Request): Promise; - request(args: { method: string; params?: Array }): Promise; - disconnect?(): void; -} diff --git a/src/web3/index.ts b/src/web3/index.ts deleted file mode 100644 index 9bd1857b..00000000 --- a/src/web3/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useEthereumProvider } from './useEthereumProvider'; -export * from '../types/web3'; diff --git a/src/web3/useEthereumProvider.test.tsx b/src/web3/useEthereumProvider.test.tsx deleted file mode 100644 index 6a4ea34f..00000000 --- a/src/web3/useEthereumProvider.test.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; - -import { EthereumOnboardingOptions, PlaidGlobalWithWeb3 } from '../types/web3'; -import { useEthereumProvider } from './useEthereumProvider'; - -import useScript from 'react-script-hook'; -jest.mock('react-script-hook'); -const mockedUseScript = useScript as jest.Mock; - -const ScriptLoadingState = { - LOADING: [true, null], - LOADED: [false, null], - ERROR: [false, 'SCRIPT_LOAD_ERROR'], -}; - -const ReadyState = { - READY: 'READY', - NOT_READY: 'NOT_READY', - ERROR: 'ERROR', - NO_ERROR: 'NO_ERROR', - HAS_PROVIDER_ACTIVE: 'HAS_PROVIDER_ACTIVE', - NO_PROVIDER_ACTIVE: 'NO_PROVIDER_ACTIVE', -}; - -const HookComponent: React.FC<{ config: EthereumOnboardingOptions }> = ({ - config, -}) => { - const { open, ready, error, isProviderActive } = useEthereumProvider(config); - return ( -
- -
{ready ? ReadyState.READY : ReadyState.NOT_READY}
-
{error ? ReadyState.ERROR : ReadyState.NO_ERROR}
-
- {isProviderActive != null - ? ReadyState.HAS_PROVIDER_ACTIVE - : ReadyState.NO_PROVIDER_ACTIVE}{' '} -
-
- ); -}; - -describe('useEthereumProvider', () => { - const config: EthereumOnboardingOptions = { - token: 'test-token', - chain: { - chainId: '0x1', - rpcUrl: 'https://rpcurl.com', - }, - onSuccess: jest.fn(), - }; - - beforeEach(() => { - mockedUseScript.mockImplementation(() => ScriptLoadingState.LOADED); - window.Plaid = { - create: ({ onLoad }) => { - onLoad && onLoad(); - return { - create: jest.fn(), - open: jest.fn(), - exit: jest.fn(), - destroy: jest.fn(), - }; - }, - open: jest.fn(), - exit: jest.fn(), - destroy: jest.fn(), - web3: jest.fn(() => - Promise.resolve({ - createEthereumOnboarding: jest.fn(), - getCurrentEthereumProvider: jest.fn(), - isProviderActive: jest.fn(), - disconnectEthereumProvider: jest.fn(), - }) - ), - } as PlaidGlobalWithWeb3; - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should render with token', async () => { - render(); - expect(screen.getByRole('button')); - await screen.findByText(ReadyState.READY); - await screen.findByText(ReadyState.HAS_PROVIDER_ACTIVE); - await screen.findByText(ReadyState.NO_ERROR); - }); - - it('should not be ready when script is loading', async () => { - mockedUseScript.mockImplementation(() => ScriptLoadingState.LOADING); - render(); - await screen.findByText(ReadyState.NOT_READY); - await screen.findByText(ReadyState.NO_PROVIDER_ACTIVE); - await screen.findByText(ReadyState.NO_ERROR); - }); - - it('should not be ready if both token and publicKey are missing', async () => { - render(); - await screen.findByText(ReadyState.NOT_READY); - await screen.findByText(ReadyState.NO_ERROR); - }); - - it('should not be ready if script fails to load', async () => { - const consoleSpy = jest - .spyOn(console, 'error') - .mockImplementationOnce(() => {}); - mockedUseScript.mockImplementation(() => ScriptLoadingState.ERROR); - - render(); - await screen.findByText(ReadyState.NOT_READY); - await screen.findByText(ReadyState.NO_PROVIDER_ACTIVE); - await screen.findByText(ReadyState.ERROR); - expect(consoleSpy).toHaveBeenCalledWith( - 'Error loading Plaid', - 'SCRIPT_LOAD_ERROR' - ); - }); - - it('should be ready if token is generated async', async () => { - const config: EthereumOnboardingOptions = { - token: 'test-token', - chain: { - chainId: '0x1', - rpcUrl: 'https://rpcurl.com', - }, - onSuccess: jest.fn(), - }; - - const { rerender } = render( - - ); - await screen.findByText(ReadyState.NOT_READY); - await screen.findByText(ReadyState.NO_PROVIDER_ACTIVE); - await screen.findByText(ReadyState.NO_ERROR); - config.token = 'test-token'; - rerender(); - await screen.findByText(ReadyState.READY); - await screen.findByText(ReadyState.HAS_PROVIDER_ACTIVE); - await screen.findByText(ReadyState.NO_ERROR); - }); - - it('should be ready if token is generated async and script loads after token', async () => { - mockedUseScript.mockImplementation(() => ScriptLoadingState.LOADING); - - const c: EthereumOnboardingOptions = { - token: null, - chain: { - chainId: '0x1', - rpcUrl: 'https://rpcurl.com', - }, - onSuccess: jest.fn(), - }; - const { rerender } = render(); - await screen.findByText(ReadyState.NOT_READY); - await screen.findByText(ReadyState.NO_PROVIDER_ACTIVE); - await screen.findByText(ReadyState.NO_ERROR); - - c.token = 'test-token'; - rerender(); - await screen.findByText(ReadyState.NOT_READY); - await screen.findByText(ReadyState.NO_PROVIDER_ACTIVE); - await screen.findByText(ReadyState.NO_ERROR); - - mockedUseScript.mockImplementation(() => ScriptLoadingState.LOADED); - - rerender(); - await screen.findByText(ReadyState.READY); - await screen.findByText(ReadyState.HAS_PROVIDER_ACTIVE); - await screen.findByText(ReadyState.NO_ERROR); - }); -}); diff --git a/src/web3/useEthereumProvider.ts b/src/web3/useEthereumProvider.ts deleted file mode 100644 index 665420bc..00000000 --- a/src/web3/useEthereumProvider.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { useEffect, useState } from 'react'; -import useScript from 'react-script-hook'; - -import { createWeb3Plaid, PlaidFactory } from '../factory'; -import { - EthereumOnboardingOptions, - PlaidWeb3, - PlaidGlobalWithWeb3, -} from '../types/web3'; -import { PLAID_LINK_STABLE_URL } from '../constants'; - -const noop = () => {}; - -export const useEthereumProvider = (options: EthereumOnboardingOptions) => { - // Asynchronously load the plaid/link/stable url into the DOM - const [loading, error] = useScript({ - src: PLAID_LINK_STABLE_URL, - checkForExisting: true, - }); - - // internal state - const [web3, setWeb3] = useState(null); - const [plaid, setPlaid] = useState(null); - const [iframeLoaded, setIframeLoaded] = useState(false); - - useEffect(() => { - // If the link.js script is still loading, return prematurely - if (loading) { - return; - } - - if (error || !window.Plaid) { - // eslint-disable-next-line no-console - console.error('Error loading Plaid', error); - return; - } - - if (!web3) { - (window.Plaid as PlaidGlobalWithWeb3).web3().then(setWeb3); - return; - } - - // If the token is undefined, return prematurely - if (!options.token) { - return; - } - - // if an old plaid instance exists, destroy it before - // creating a new one - if (plaid != null) { - plaid.exit({ force: true }, () => plaid.destroy()); - } - - const next = createWeb3Plaid( - { - ...options, - onLoad: () => { - setIframeLoaded(true); - options.onLoad && options.onLoad(); - }, - }, - web3.createEthereumOnboarding - ); - - setPlaid(next); - - // destroy the Plaid iframe factory - return () => next.exit({ force: true }, () => next.destroy()); - }, [loading, error, options.token, web3, setWeb3]); - - const ready = plaid != null && (!loading || iframeLoaded) && web3 != null; - - const openNoOp = () => { - if (!options.token) { - console.warn( - 'react-plaid-link: You cannot call open() without a valid token supplied to useEthereumProvider. This is a no-op.' - ); - } - }; - - const returnValue = { - error, - ready, - exit: plaid ? plaid.exit : noop, - open: plaid ? plaid.open : openNoOp, - getCurrentEthereumProvider: - web3 && ready ? web3.getCurrentEthereumProvider : null, - isProviderActive: web3 && ready ? web3.isProviderActive : null, - disconnectEthereumProvider: - web3 && ready ? web3.disconnectEthereumProvider : null, - }; - return returnValue; -}; diff --git a/stories/web3.tsx b/stories/web3.tsx deleted file mode 100644 index 3c5dcbf6..00000000 --- a/stories/web3.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useCallback } from 'react'; - -import { useEthereumProvider } from '../src/web3'; - -const App = props => { - const [addresses, setAddresses] = React.useState(undefined); - - const onSuccess = useCallback( - async provider => { - const accounts = await provider.request({ method: 'eth_accounts' }); - setAddresses(accounts); - }, - [setAddresses] - ); - - const onExit = useCallback( - (err, metadata) => console.log('onExit', err, metadata), - [] - ); - - const config = { - token: props.token, - chain: { - chainId: '0x1', - rpcUrl: 'https://rpc.com', - }, - onSuccess, - onExit, - }; - - const { - open, - ready, - error, - getCurrentEthereumProvider, - } = useEthereumProvider(config); - - const checkProvider = useCallback(() => { - if (!ready) { - return; - } - - (async () => { - const provider = await getCurrentEthereumProvider(config.chain); - if (provider) { - const accounts = await provider.request({ method: 'eth_accounts' }); - setAddresses(accounts); - } - })(); - }, [ready, getCurrentEthereumProvider, setAddresses]); - - if (addresses) { - return
{addresses.join(', ')}
; - } - - return ( - <> - - - - ); -}; - -export default App;