UNPKG

exalias

Version:

A simple, maximally extensible, dependency minimized framework for building modern Ethereum dApps

133 lines (109 loc) 3.72 kB
import { ConnectorUpdate } from '@web3-react/types' import { AbstractConnector } from '@web3-react/abstract-connector' import invariant from 'tiny-invariant' type NetworkName = 'mainnet' | 'ropsten' | 'rinkeby' | 'kovan' const chainIdToNetwork: { [network: number]: NetworkName } = { 1: 'mainnet', 3: 'ropsten', 4: 'rinkeby', 42: 'kovan' } interface MagicConnectorArguments { apiKey: string chainId: number email: string } export class UserRejectedRequestError extends Error { public constructor() { super() this.name = this.constructor.name this.message = 'The user rejected the request.' } } export class FailedVerificationError extends Error { public constructor() { super() this.name = this.constructor.name this.message = 'The email verification failed.' } } export class MagicLinkRateLimitError extends Error { public constructor() { super() this.name = this.constructor.name this.message = 'The Magic rate limit has been reached.' } } export class MagicLinkExpiredError extends Error { public constructor() { super() this.name = this.constructor.name this.message = 'The Magic link has expired.' } } export class MagicConnector extends AbstractConnector { private readonly apiKey: string private readonly chainId: number private readonly email: string public magic: any constructor({ apiKey, chainId, email }: MagicConnectorArguments) { invariant(Object.keys(chainIdToNetwork).includes(chainId.toString()), `Unsupported chainId ${chainId}`) invariant(email && email.includes('@'), `Invalid email: ${email}`) super({ supportedChainIds: [chainId] }) this.apiKey = apiKey this.chainId = chainId this.email = email } public async activate(): Promise<ConnectorUpdate> { const MagicSDK = await import('magic-sdk').then(m => m?.default ?? m) const { Magic, RPCError, RPCErrorCode } = MagicSDK if (!this.magic) { this.magic = new Magic(this.apiKey, { network: chainIdToNetwork[this.chainId] }) } const isLoggedIn = await this.magic.user.isLoggedIn() const loggedInEmail = isLoggedIn ? (await this.magic.user.getMetadata()).email : null if (isLoggedIn && loggedInEmail !== this.email) { await this.magic.user.logout() } if (!isLoggedIn) { try { await this.magic.auth.loginWithMagicLink({ email: this.email }) } catch (err) { if (!(err instanceof RPCError)) { throw err } if (err.code === RPCErrorCode.MagicLinkFailedVerification) { throw new FailedVerificationError() } if (err.code === RPCErrorCode.MagicLinkExpired) { throw new MagicLinkExpiredError() } if (err.code === RPCErrorCode.MagicLinkRateLimited) { throw new MagicLinkRateLimitError() } // This error gets thrown when users close the login window. // -32603 = JSON-RPC InternalError if (err.code === -32603) { throw new UserRejectedRequestError() } } } const provider = this.magic.rpcProvider const account = await provider.enable().then((accounts: string[]): string => accounts[0]) return { provider, chainId: this.chainId, account } } public async getProvider(): Promise<any> { return this.magic.rpcProvider } public async getChainId(): Promise<number | string> { return this.chainId } public async getAccount(): Promise<null | string> { return this.magic.rpcProvider.send('eth_accounts').then((accounts: string[]): string => accounts[0]) } public deactivate() {} public async close() { await this.magic.user.logout() this.emitDeactivate() } }