Skip to content

Commit

Permalink
fix: refactor autoConnect and export isNotSafeApp for users to condit…
Browse files Browse the repository at this point in the history
…ionally include the connector or not
  • Loading branch information
johnson86tw committed Nov 2, 2022
1 parent 05c1f3c commit 59d5e26
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 62 deletions.
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VueDapp</title>
</head>
Expand Down
12 changes: 10 additions & 2 deletions demo/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
WalletConnectConnector,
CoinbaseWalletConnector,
SafeConnector,
isNotSafeApp,
Connector,
} from 'vue-dapp'
import { ref, watch } from 'vue'
Expand All @@ -39,7 +41,7 @@ onChainChanged((chainId: any) => {
console.log('chain changed', chainId)
})
const connectors = [
let connectors: Connector[] = [
new MetaMaskConnector({
appUrl: 'http://localhost:3000',
}),
Expand All @@ -54,9 +56,15 @@ const connectors = [
appName: 'Vue Dapp',
jsonRpcUrl: `https://mainnet.infura.io/v3/${infuraId}`,
}),
new SafeConnector(),
]
// If it's in the safe app, change available connectors
// notes: only check whether it's in the iframe
if (!isNotSafeApp()) {
const safe = new SafeConnector()
connectors = [safe, connectors[0]]
}
const { availableNetworks } = useEthers()
const supportedChainId = Object.keys(availableNetworks.value).map((key) =>
Expand Down
45 changes: 18 additions & 27 deletions src/components/Board.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { computed, defineComponent, inject, onMounted } from 'vue'
import { computed, defineComponent, inject, onMounted, ref } from 'vue'
import Modal from './Modal.vue'
import Loader from './Loader.vue'
import WalletConnectIcon from './logos/WalletConnect.vue'
Expand All @@ -9,7 +9,7 @@ import GnosisSafeIcon from './logos/GnosisSafe.vue'
import { useBoard } from '../composables/useBoard'
import { useWallet } from '../composables/useWallet'
import { Connector, MetaMaskConnector, SafeConnector } from '../connectors'
import { Connector } from '../connectors'
export default defineComponent({
components: {
Expand Down Expand Up @@ -42,38 +42,29 @@ export default defineComponent({
const connectors = props.connectors as Connector[]
const isAutoConnecting = ref(false)
const isAutoConnect = inject('autoConnect')
if (isAutoConnect) {
onMounted(async () => {
// auto-connect to Safe as first
let connected = false
const safe = connectors.find(
(conn) => conn.name === 'safe',
) as SafeConnector
if (safe) {
connected = await autoConnect(safe)
if (connected) return
onMounted(async () => {
if (isAutoConnect) {
try {
isAutoConnecting.value = true
await autoConnect(connectors)
} catch (err) {
console.error('Failed to auto-connect')
return
} finally {
isAutoConnecting.value = false
}
console.log('check metamask')
// then check metamask
const metamask = connectors.find(
(conn) => conn.name === 'metaMask',
) as MetaMaskConnector
if (metamask) {
connected = await autoConnect(metamask)
if (connected) return
}
})
}
}
})
const onClickWallet = (connector: Connector) => {
connectWith(connector)
close()
}
return {
isAutoConnect,
isAutoConnecting,
boardOpen,
wallet,
connectors,
Expand Down Expand Up @@ -121,7 +112,7 @@ export default defineComponent({

<slot name="connecting">
<Modal
:modalOpen="wallet.status === 'connecting' && !isAutoConnect"
:modalOpen="wallet.status === 'connecting' && !isAutoConnecting"
:dark="dark"
>
<div class="loading-modal" v-if="wallet.status === 'connecting'">
Expand All @@ -133,7 +124,7 @@ export default defineComponent({

<slot name="loading">
<Modal
:modalOpen="wallet.status === 'loading' && !isAutoConnect"
:modalOpen="wallet.status === 'loading' && !isAutoConnecting"
:dark="dark"
></Modal>
</slot>
Expand Down
72 changes: 50 additions & 22 deletions src/composables/useWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export function useWallet(options: useWalletOptions = { useEthers: true }) {
} catch (err: any) {
await disconnect() // will also clearWallet()
wallet.error = err.message
console.error(err)
return
}

Expand Down Expand Up @@ -133,34 +134,61 @@ export function useWallet(options: useWalletOptions = { useEthers: true }) {
clearWallet()
}

async function autoConnect(connector: Connector): Promise<boolean> {
if (connector.name === 'metaMask') {
const metamask = connector as MetaMaskConnector
async function autoConnect(connectors: Connector[]) {
let connected = false

const isConnected = await MetaMaskConnector.checkConnection()
console.log('metamask is connected: ', isConnected)
const safe = connectors.find(
(conn) => conn.name === 'safe',
) as SafeConnector

// connect to safe at first
if (safe && !connected) {
const isConnected = await autoConnectWith(safe)
if (isConnected) {
try {
await connectWith(metamask)
return true
} catch (err) {
console.error('Failed to auto-connect MetaMask')
}
connected = true
}
}

const metamask = connectors.find(
(conn) => conn.name === 'metaMask',
) as MetaMaskConnector

// if safe not connected, then try connect to metamask
if (metamask && !connected) {
const isConnected = await autoConnectWith(metamask)
if (isConnected) {
connected = true
}
} else if (connector.name === 'safe') {
const safe = connector as SafeConnector

const isSafeApp = await safe.isSafeApp()
if (isSafeApp) {
try {
await connectWith(safe)
return true
} catch (err) {
console.error('Failed to auto-connect Gnosis Safe')
}

async function autoConnectWith(connector: Connector): Promise<boolean> {
if (connector.name === 'metaMask') {
const metamask = connector as MetaMaskConnector

const isConnected = await MetaMaskConnector.checkConnection()
if (isConnected) {
try {
await connectWith(metamask)
return true
} catch (err) {
throw new Error('Failed to auto-connect MetaMask')
}
}
} else if (connector.name === 'safe') {
const safe = connector as SafeConnector

const isSafeApp = await safe.isSafeApp()
if (isSafeApp) {
try {
await connectWith(safe)
return true
} catch (err) {
throw new Error('Failed to auto-connect Gnosis Safe')
}
}
}
return false
}
return false
}

function onDisconnect(callback: OnDisconnectCallback) {
Expand Down
3 changes: 2 additions & 1 deletion src/connectors/connector.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CoinbaseWalletProvider } from '@coinbase/wallet-sdk'
import { providers } from 'ethers'

export type ConnectorData<Provider = any> = {
Expand All @@ -6,7 +7,7 @@ export type ConnectorData<Provider = any> = {
}

export abstract class Connector<
Provider = providers.ExternalProvider,
Provider = providers.ExternalProvider | CoinbaseWalletProvider,
Options = any,
> {
// Connector name
Expand Down
27 changes: 17 additions & 10 deletions src/connectors/safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ import {
ProviderRpcError,
} from './errors'

const __IS_SERVER__ = typeof window === 'undefined'
const __IS_IFRAME__ = !__IS_SERVER__ && window?.parent !== window
export const isServer = typeof window === 'undefined'
export const isIframe = !isServer && window?.parent !== window
export const isNotSafeApp = () => {
const ready = !isServer && isIframe
return ready ? false : true
}

export class SafeConnector extends Connector<SafeAppProvider, SafeOpts> {
readonly name = 'safe'
ready = !__IS_SERVER__ && __IS_IFRAME__
ready = !isServer && isIframe

#provider?: SafeAppProvider
#sdk: SafeAppsSDK
#safe?: SafeInfo
#isSafeApp = false

#onDisconnectHandler?: (error: ProviderRpcError) => void
#onAccountsChangedHandler?: (accounts: string[]) => void
Expand All @@ -33,9 +38,11 @@ export class SafeConnector extends Connector<SafeAppProvider, SafeOpts> {
}

async connect() {
const runningAsSafeApp = await this.isSafeApp()
if (!runningAsSafeApp) {
throw new ConnectorNotFoundError()
if (!this.#isSafeApp) {
const isSafeApp = await this.isSafeApp()
if (!isSafeApp) {
throw new ConnectorNotFoundError()
}
}

const provider = await this.getProvider()
Expand Down Expand Up @@ -66,15 +73,15 @@ export class SafeConnector extends Connector<SafeAppProvider, SafeOpts> {
}

async isSafeApp(): Promise<boolean> {
if (!this.ready) {
return false
}
if (!this.ready) return false

const safe = await Promise.race([
this.#getSafeInfo(),
new Promise<void>((resolve) => setTimeout(resolve, 300)),
])
return !!safe
const isSafeApp = !!safe
this.#isSafeApp = isSafeApp
return isSafeApp
}

async #getSafeInfo(): Promise<SafeInfo> {
Expand Down

0 comments on commit 59d5e26

Please sign in to comment.