Skip to content

Commit

Permalink
add tsdoc
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahZinsmeister committed Jan 25, 2022
1 parent 550fb41 commit 2e36fb5
Show file tree
Hide file tree
Showing 16 changed files with 217 additions and 68 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', './packages/*/tsconfig.json'],
},
plugins: ['@typescript-eslint', 'react-hooks'],
plugins: ['@typescript-eslint', 'react-hooks', 'eslint-plugin-tsdoc'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
Expand All @@ -14,6 +14,7 @@ module.exports = {
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'tsdoc/syntax': 'warn',
},
env: {
browser: true,
Expand Down
44 changes: 23 additions & 21 deletions README.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"bootstrap": "lerna bootstrap",
"clean": "lerna clean --yes",
"lint": "yarn run eslint . --ext .ts,.tsx",
"lint": "yarn run eslint --ext .ts,.tsx .",
"test": "jest",
"build": "lerna run build",
"prestart": "yarn build",
Expand All @@ -25,6 +25,7 @@
"@walletconnect/ethereum-provider": "^1.7.1",
"eslint": "^8.4.1",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-tsdoc": "^0.2.14",
"eth-provider": "^0.9.4",
"jest": "^27.2.4",
"lerna": "^4.0.0",
Expand Down
41 changes: 26 additions & 15 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ export type Web3ReactHooks = ReturnType<typeof getStateHooks> &

export type Web3ReactPriorityHooks = ReturnType<typeof getPriorityConnector>

/**
* Wraps the initialization of a `connector`. Creates a zustand `store` with `actions` bound to it, and then passes
* these to the connector as specified in `f`. Also creates a variety of `hooks` bound to this `store`.
*
* @typeParam T - The type of the `connector` returned from `f`.
* @param f - A function which is called with `actions` bound to the returned `store`.
* @param allowedChainIds - An optional array of chainIds which the `connector` may connect to. If the `connector` is
* connected to a chainId which is not allowed, a ChainIdNotAllowedError error will be reported.
* If this argument is unspecified, the `connector` may connect to any chainId.
* @returns [connector, hooks, store] - The initialized connector, a variety of hooks, and a zustand store.
*/
export function initializeConnector<T extends Connector>(
f: (actions: Actions) => T,
allowedChainIds?: number[]
Expand All @@ -32,10 +43,16 @@ function computeIsActive({ chainId, accounts, activating, error }: Web3ReactStat
return Boolean(chainId && accounts && !activating && !error)
}

/**
* Creates a variety of convenience `hooks` that return data associated with the first of the `initializedConnectors`
* that is active.
*
* @param initializedConnectors - Two or more [connector, hooks] arrays, as returned from initializeConnector.
* @returns hooks - A variety of convenience hooks that wrap the hooks returned from initializeConnector.
*/
export function getPriorityConnector(...initializedConnectors: [Connector, Web3ReactHooks][]) {
// the following code calls hooks in a map a lot, which technically violates the eslint rule.
// this is ok, though, because initializedConnectors never changes, so the same number of hooks
// are always called, and they're always the same
// the following code calls hooks in a map a lot, which violates the eslint rule.
// this is ok, though, because initializedConnectors never changes, so the same hooks are called each time

function useActiveIndex() {
// eslint-disable-next-line react-hooks/rules-of-hooks
Expand All @@ -51,43 +68,37 @@ export function getPriorityConnector(...initializedConnectors: [Connector, Web3R
function usePriorityChainId() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const values = initializedConnectors.map(([, { useChainId }]) => useChainId())
const index = useActiveIndex()
return values[index ?? 0]
return values[useActiveIndex() ?? 0]
}

function usePriorityAccounts() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const values = initializedConnectors.map(([, { useAccounts }]) => useAccounts())
const index = useActiveIndex()
return values[index ?? 0]
return values[useActiveIndex() ?? 0]
}

function usePriorityIsActivating() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const values = initializedConnectors.map(([, { useIsActivating }]) => useIsActivating())
const index = useActiveIndex()
return values[index ?? 0]
return values[useActiveIndex() ?? 0]
}

function usePriorityError() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const values = initializedConnectors.map(([, { useError }]) => useError())
const index = useActiveIndex()
return values[index ?? 0]
return values[useActiveIndex() ?? 0]
}

function usePriorityAccount() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const values = initializedConnectors.map(([, { useAccount }]) => useAccount())
const index = useActiveIndex()
return values[index ?? 0]
return values[useActiveIndex() ?? 0]
}

function usePriorityIsActive() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const values = initializedConnectors.map(([, { useIsActive }]) => useIsActive())
const index = useActiveIndex()
return values[index ?? 0]
return values[useActiveIndex() ?? 0]
}

function usePriorityProvider(network?: Networkish) {
Expand Down
6 changes: 6 additions & 0 deletions packages/eip1193/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ function parseChainId(chainId: string) {
}

export class EIP1193 extends Connector {
/** {@inheritdoc Connector.provider} */
provider: Provider

/**
* @param provider - An EIP-1193 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md}) provider.
* @param connectEagerly - A flag indicating whether connection should be initiated when the class is constructed.
*/
constructor(actions: Actions, provider: Provider, connectEagerly = true) {
super(actions)

Expand Down Expand Up @@ -43,6 +48,7 @@ export class EIP1193 extends Connector {
}
}

/** {@inheritdoc Connector.activate} */
public async activate(): Promise<void> {
this.actions.startActivation()

Expand Down
12 changes: 8 additions & 4 deletions packages/empty/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Connector } from '@web3-react/types'

class Empty extends Connector {
/** {@inheritdoc Connector.provider} */
provider: undefined

// eslint-disable-next-line @typescript-eslint/no-empty-function
public activate() {}
/**
* No-op. May be called if it simplifies application code.
*/
public activate() {
void 0
}
}

// @ts-expect-error this is okay because actions aren't ever validated,
// and they're only used to set a protected property
// @ts-expect-error actions aren't validated and are only used to set a protected property, so this is ok
export const EMPTY = new Empty()
17 changes: 10 additions & 7 deletions packages/example/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,15 @@ export const CHAINS: { [chainId: number]: BasicChainInformation | ExtendedChainI
},
}

export const URLS: { [chainId: number]: string[] } = Object.keys(CHAINS).reduce((accumulator, chainId) => {
const validURLs: string[] = CHAINS[Number(chainId)].urls
export const URLS: { [chainId: number]: string[] } = Object.keys(CHAINS).reduce<{ [chainId: number]: string[] }>(
(accumulator, chainId) => {
const validURLs: string[] = CHAINS[Number(chainId)].urls

if (validURLs.length) {
accumulator[chainId] = validURLs
}
if (validURLs.length) {
accumulator[Number(chainId)] = validURLs
}

return accumulator
}, {})
return accumulator
},
{}
)
4 changes: 2 additions & 2 deletions packages/example/connectors/walletConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { URLS } from '../chains'
export const [walletConnect, hooks] = initializeConnector<WalletConnect>(
(actions) =>
new WalletConnect(actions, {
rpc: Object.keys(URLS).reduce((accumulator, chainId) => {
accumulator[chainId] = URLS[Number(chainId)][0]
rpc: Object.keys(URLS).reduce<{ [chainId: number]: string }>((accumulator, chainId) => {
accumulator[Number(chainId)] = URLS[Number(chainId)][0]
return accumulator
}, {}),
}),
Expand Down
13 changes: 13 additions & 0 deletions packages/metamask/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class MetaMask extends Connector {
private readonly options?: Parameters<typeof detectEthereumProvider>[0]
private eagerConnection?: Promise<void>

/**
* @param connectEagerly - A flag indicating whether connection should be initiated when the class is constructed.
* @param options - Options to pass to `@metamask/detect-provider`
*/
constructor(actions: Actions, connectEagerly = true, options?: Parameters<typeof detectEthereumProvider>[0]) {
super(actions)
this.options = options
Expand Down Expand Up @@ -93,6 +97,15 @@ export class MetaMask extends Connector {
})
}

/**
* Initiates a connection.
*
* @param desiredChainIdOrChainParameters - If defined, indicates the desired chain to connect to. If the user is
* already connected to this chain, no additional steps will be taken. Otherwise, the user will be prompted to switch
* to the chain, if one of two conditions is met: either they already have it added in their extension, or the
* argument is of type AddEthereumChainParameter, in which case the user will be prompted to add the chain with the
* specified parameters first, before being prompted to switch.
*/
public async activate(desiredChainIdOrChainParameters?: number | AddEthereumChainParameter): Promise<void> {
const desiredChainId =
typeof desiredChainIdOrChainParameters === 'number'
Expand Down
10 changes: 10 additions & 0 deletions packages/network/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import { Connector } from '@web3-react/types'
type url = string | ConnectionInfo

export class Network extends Connector {
/** {@inheritdoc Connector.provider} */
provider: Eip1193Bridge | undefined

private urlMap: { [chainId: number]: url[] }
private chainId: number
private providerCache: { [chainId: number]: Eip1193Bridge } = {}

/**
* @param urlMap - A mapping from chainIds to RPC urls.
* @param connectEagerly - A flag indicating whether connection should be initiated when the class is constructed.
*/
constructor(actions: Actions, urlMap: { [chainId: number]: url | url[] }, connectEagerly = true) {
super(actions)
this.urlMap = Object.keys(urlMap).reduce<{ [chainId: number]: url[] }>((accumulator, chainId) => {
Expand Down Expand Up @@ -80,6 +85,11 @@ export class Network extends Connector {
}
}

/**
* Initiates a connection.
*
* @param desiredChainId - The desired chain to connect to.
*/
public async activate(desiredChainId = Number(Object.keys(this.urlMap)[0])): Promise<void> {
if (this.urlMap[desiredChainId] === undefined) {
throw new Error(`no url(s) provided for desiredChainId ${desiredChainId}`)
Expand Down
20 changes: 19 additions & 1 deletion packages/store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export function createWeb3ReactStoreAndActions(allowedChainIds?: number[]): [Web
// flag for tracking updates so we don't clobber data when cancelling activation
let nullifier = 0

/**
* Sets activating to true, indicating that an update is in progress.
*
* @returns cancelActivation - A function that cancels the activation by setting activating to false,
* as long as there haven't been any intervening updates.
*/
function startActivation(): () => void {
const nullifierCached = ++nullifier

Expand All @@ -59,6 +65,13 @@ export function createWeb3ReactStoreAndActions(allowedChainIds?: number[]): [Web
}
}

/**
* Used to report a `stateUpdate` which is merged with existing state. The first `stateUpdate` that results in chainId
* and accounts being set will also set activating to false, indicating a successful connection. Similarly, if an
* error is set, the first `stateUpdate` that results in chainId and accounts being set will clear this error.
*
* @param stateUpdate - The state update to report.
*/
function update(stateUpdate: Web3ReactStateUpdate): void {
// validate chainId statically, independent of existing state
if (stateUpdate.chainId !== undefined) {
Expand Down Expand Up @@ -110,7 +123,12 @@ export function createWeb3ReactStoreAndActions(allowedChainIds?: number[]): [Web
})
}

function reportError(error: Error | undefined) {
/**
* Used to report an `error`, which clears all existing state.
*
* @param error - The error to report. If undefined, the state will be reset to its default value.
*/
function reportError(error: Error | undefined): void {
nullifier++

store.setState(() => ({ ...DEFAULT_STATE, error }))
Expand Down
40 changes: 30 additions & 10 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@ export interface Web3ReactState extends State {

export type Web3ReactStore = StoreApi<Web3ReactState>

export interface Web3ReactStateUpdate {
chainId?: number
accounts?: string[]
}
export type Web3ReactStateUpdate =
| {
chainId: number
accounts: string[]
}
| {
chainId: number
accounts?: never
}
| {
chainId?: never
accounts: string[]
}

export interface Actions {
startActivation: () => () => void
Expand Down Expand Up @@ -44,22 +53,33 @@ export interface ProviderRpcError extends Error {
data?: unknown
}

// per EIP-1193
export interface ProviderMessage {
readonly type: string
readonly data: unknown
}

export abstract class Connector {
/**
* An
* EIP-1193 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md}) and
* EIP-1102 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md}) compliant provider.
* This property must be defined while the connector is active.
*/
public provider: Provider | undefined

protected readonly actions: Actions

/**
* @param actions - Methods bound to a zustand store that tracks the state of the connector.
* Actions are used by the connector to report changes in connection status.
*/
constructor(actions: Actions) {
this.actions = actions
}

/**
* Initiate a connection.
*/
public abstract activate(...args: unknown[]): Promise<void> | void

/**
* Initiate a disconnect.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public deactivate(...args: unknown[]): Promise<void> | void {
this.actions.reportError(undefined)
Expand Down
6 changes: 6 additions & 0 deletions packages/url/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ import { Connector } from '@web3-react/types'
type url = string | ConnectionInfo

export class Url extends Connector {
/** {@inheritdoc Connector.provider} */
provider: Eip1193Bridge | undefined

private url: url

/**
* @param url - An RPC url.
* @param connectEagerly - A flag indicating whether connection should be initiated when the class is constructed.
*/
constructor(actions: Actions, url: url, connectEagerly = true) {
super(actions)
this.url = url
Expand Down Expand Up @@ -44,6 +49,7 @@ export class Url extends Connector {
})
}

/** {@inheritdoc Connector.activate} */
public async activate(): Promise<void> {
return this.initialize()
}
Expand Down
Loading

0 comments on commit 2e36fb5

Please sign in to comment.