Skip to content

Commit

Permalink
Merge pull request #174 from vu3th/feat/enable-dapp-browser
Browse files Browse the repository at this point in the history
Feat/enable dapp browser
  • Loading branch information
johnson86tw committed Apr 30, 2024
2 parents f868f97 + f3869ac commit d448091
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 151 deletions.
95 changes: 33 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,90 +46,59 @@
<img src="https://github.com/vu3th/vue-dapp/blob/main/app/public/images/overview.png" alt="Vue Dapp Overview" style="max-width:100%;" width="800">


## Installation
## Getting Started

Minimum
```bash
npm install pinia @vue-dapp/core
```
### SPA with Vite

With the wallet modal
```bash
npm install pinia @vue-dapp/core @vue-dapp/modal
```

Maximum
```bash
npm install pinia @vue-dapp/core @vue-dapp/modal @vue-dapp/walletconnect @vue-dapp/coinbase
```ts [main.ts]
import { createPinia } from 'pinia'
app.use(createPinia())
```

## Example

```vue
<script lang="ts" setup>
import {
useVueDapp,
BrowserWalletConnector,
VueDappProvider,
type ConnWallet,
} from '@vue-dapp/core'
import { VueDappModal } from '@vue-dapp/modal'
import '@vue-dapp/modal/dist/style.css' // make sure to add css for the modal
import { BrowserWalletConnector, useVueDapp } from '@vue-dapp/core'
import { VueDappModal, useVueDappModal } from '@vue-dapp/modal'
import '@vue-dapp/modal/dist/style.css'
const { status, isConnected, address, chainId, error, disconnect, addConnector } = useVueDapp()
const { addConnectors, isConnected, wallet, disconnect } = useVueDapp()
const isModalOpen = ref(false)
addConnectors([new BrowserWalletConnector()])
function onClickConnectBtn() {
function onClickConnectButton() {
if (isConnected.value) disconnect()
else isModalOpen.value = true
}
if (process.client) { // only when using Nuxt 3
addConnector(new BrowserWalletConnector())
}
function handleConnect(wallet: ConnWallet) {
console.log('handleConnect', wallet)
// example with ethers v6
const ethersProvider = new ethers.providers.Web3Provider(provider)
const signer = await ethersProvider.getSigner()
dappStore.setUser({
address,
signer: markRaw(signer),
chainId,
})
}
function handleDisconnect() {
console.log('handleDisconnect')
// example
dappStore.resetUser()
else useVueDappModal().open()
}
</script>
<template>
<div>
<VueDappProvider @connect="handleConnect" @disconnect="handleDisconnect">
<button @click="onClickConnectBtn">{{ isConnected ? 'Disconnect' : 'Connect' }}</button>
<div>status: {{ status }}</div>
<div>isConnected: {{ isConnected }}</div>
<div>error: {{ error }}</div>
<button @click="onClickConnectButton">{{ isConnected ? 'Disconnect' : 'Connect' }}</button>
<div v-if="isConnected">
<div>chainId: {{ chainId }}</div>
<div>address: {{ address }}</div>
</div>
<div>status: {{ wallet.status }}</div>
<div>isConnected: {{ isConnected }}</div>
<div>error: {{ wallet.error }}</div>
<VueDappModal v-model="isModalOpen" dark auto-connect />
</VueDappProvider>
<div v-if="isConnected">
<div>chainId: {{ wallet.chainId }}</div>
<div>address: {{ wallet.address }}</div>
</div>
<VueDappModal dark auto-connect />
</template>
```

### SSR with Nuxt 3

```bash
npm install pinia @pinia/nuxt @vue-dapp/core @vue-dapp/nuxt @vue-dapp/modal
```

```ts
modules: ['@pinia/nuxt', '@vue-dapp/nuxt']
```

## Examples
Expand All @@ -140,9 +109,11 @@ function handleDisconnect() {
## Development

```
pnpm i
pnpm build
pnpm dev
pnpm -F core watch
pnpm -F modal watch
pnpm dev
```


Expand Down
2 changes: 1 addition & 1 deletion app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const hideConnectingModal = computed(() => {

<NuxtPage />

<VueDappModal autoConnect :hideConnectingModal="hideConnectingModal"> </VueDappModal>
<VueDappModal :hideConnectingModal="hideConnectingModal"> </VueDappModal>
</NuxtLayout>
</n-config-provider>
</template>
2 changes: 1 addition & 1 deletion app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const items = computed<Item[]>(() => [
<p v-if="wallet.status === 'connected'">Disconnect</p>
</n-button>

<p class="text-red-500" v-if="wallet.error">{{ wallet.error }}</p>
<p class="text-red-500 text-center" v-if="wallet.error">{{ wallet.error }}</p>

<ClientOnly>
<Vue3EasyDataTable
Expand Down
23 changes: 23 additions & 0 deletions app/pages/userAgent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup lang="ts">
const userAgent = ref('')
onMounted(() => {
userAgent.value = navigator.userAgent
})
function alertUserAgent() {
alert(navigator.userAgent)
}
</script>

<template>
<div>
<div>
{{ userAgent }}
</div>

<button @click="alertUserAgent">Alert userAgent</button>
</div>
</template>

<style lang="scss"></style>
34 changes: 20 additions & 14 deletions packages/core/src/browserWalletConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,23 @@ export class BrowserWalletConnector extends Connector<EIP1193Provider, BrowserWa
useEIP6963().subscribe()
}

static async checkConnection(RDNS: string | RDNS) {
if (typeof window !== 'undefined' && !!window.ethereum) {
const { provider } = this.getProvider(RDNS)
if ((await provider.request({ method: 'eth_accounts' })).length !== 0) {
return true
}
async connect(options?: ConnectOptions) {
const { timeout, rdns, isWindowEthereum } = options ?? {
timeout: undefined,
rdns: undefined,
isWindowEthereum: false,
}
return false
}

async connect(options?: ConnectOptions) {
const { timeout, rdns } = options ?? { timeout: undefined, rdns: undefined }
let provider, info

const { info, provider } = this.getProvider(rdns)
if (isWindowEthereum) {
provider = this.getWindowEthereumProvider()
if (!provider) throw new ProviderNotFoundError('window.ethereum not found')
} else {
const { provider: _provider, info: _info } = this.getProvider(rdns)
provider = _provider
info = _info
}

let accounts, chainId

Expand Down Expand Up @@ -87,11 +90,14 @@ export class BrowserWalletConnector extends Connector<EIP1193Provider, BrowserWa
}
}

getProvider(rdns?: RDNS | string): EIP6963ProviderDetail {
return BrowserWalletConnector.getProvider(rdns)
getWindowEthereumProvider(): EIP1193Provider | null {
if (typeof window !== 'undefined' && !!window.ethereum) {
return window.ethereum
}
return null
}

static getProvider(rdns?: RDNS | string): EIP6963ProviderDetail {
getProvider(rdns?: RDNS | string): EIP6963ProviderDetail {
const { providerDetails } = useEIP6963()
if (providerDetails.value.length < 1) throw new ProviderNotFoundError('providerDetails.length < 1')

Expand Down
31 changes: 20 additions & 11 deletions packages/core/src/services/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useStore } from '../store'
import { ConnectOptions, ConnectorName, RDNS } from '../types'
import { AutoConnectError, ConnectError, ConnectorNotFoundError } from '../errors'
import { normalizeChainId } from '../utils'
import { BrowserWalletConnector } from '../browserWalletConnector'
import {
getLastConnectedBrowserWallet,
removeLastConnectedBrowserWallet,
Expand Down Expand Up @@ -100,26 +99,36 @@ export function useConnect(pinia?: any) {
}
}

async function autoConnect(rdns?: RDNS | string) {
const lastRdns = getLastConnectedBrowserWallet()
if (!lastRdns) return
const isWindowEthereumAvailable = typeof window !== 'undefined' && !!window.ethereum

rdns = rdns || lastRdns
async function autoConnect(rdns?: RDNS | string, isWindowEthereum = false) {
const browserWallet = walletStore.connectors.find(conn => conn.name === 'BrowserWallet')
if (!browserWallet) return

const bwConnector = walletStore.connectors.find(conn => conn.name === 'BrowserWallet')
let options = {}

if (!bwConnector || !rdns) return
if (isWindowEthereum) {
if (!isWindowEthereumAvailable) return
options = { isWindowEthereum }
} else {
const lastRdns = getLastConnectedBrowserWallet()

rdns = rdns || lastRdns
if (!rdns) return

options = { rdns }
}

try {
const isConnected = await BrowserWalletConnector.checkConnection(rdns)
if (isConnected) {
await connectTo(bwConnector.name, { rdns })
}
await connectTo(browserWallet.name, options)
} catch (err: any) {
throw new AutoConnectError(err)
}
}

return {
isWindowEthereumAvailable,

wallet: readonly(walletStore.wallet),

status: computed(() => walletStore.wallet.status),
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type ConnectorData<Provider = any> = {

export type ConnectOptions = {
rdns?: string | RDNS
isWindowEthereum?: boolean
timeout?: number
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/utils/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ export function assertConnected(wallet: Wallet, errMsg?: string): asserts wallet
if (!wallet.connectorName) throw new AssertConnectedError(errMsg + ' - connectorName')
if (!wallet.provider) throw new AssertConnectedError(errMsg + ' - provider')

if (wallet.connectorName === 'BrowserWallet') {
if (!wallet.providerInfo) throw new AssertConnectedError(errMsg + ' - providerInfo')
}
// if (wallet.connectorName === 'BrowserWallet') {
// if (!wallet.providerInfo) throw new AssertConnectedError(errMsg + ' - providerInfo')
// }
if (!wallet.connector) throw new AssertConnectedError(errMsg + ' - connector')
if (!wallet.address) throw new AssertConnectedError(errMsg + ' - address')
if (!wallet.chainId) throw new AssertConnectedError(errMsg + ' - chainId')
Expand Down
55 changes: 45 additions & 10 deletions packages/modal/src/VueDappModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,45 @@ const modalOpen = computed(() => props.modelValue ?? store.isModalOpen)
const isAutoConnecting = ref(false)
const { connectors, connectTo, autoConnect, status, providerDetails, hasConnector, disconnect } = useVueDapp()
const {
isWindowEthereumAvailable,
connectors,
connectTo,
autoConnect,
status,
providerDetails,
hasConnector,
disconnect,
} = useVueDapp()
// ============================ feat: autoConnect ============================
async function handleAutoConnect() {
onMounted(async () => {
if (props.autoConnect) {
try {
isAutoConnecting.value = true
await autoConnect()
if (isMobileAppBrowser()) {
await autoConnect(undefined, true)
} else {
await autoConnect()
}
} catch (err: any) {
emit('autoConnectError', err)
} finally {
isAutoConnecting.value = false
}
}
}
onMounted(async () => handleAutoConnect())
})
// ============================ feat: auto click BrowserWallet if it's the only connector ============================
watch(modalOpen, async () => {
// ============================ feat: connect to window.ethereum if window.ethereum is available and there's no EIP-6963 providers ============================
if (modalOpen.value && providerDetails.value.length === 0 && isMobileAppBrowser()) {
if (isWindowEthereumAvailable) {
await onClickWallet('BrowserWallet', undefined, true)
}
return
}
// ============================ feat: auto click BrowserWallet if it's the only connector ============================
if (props.autoConnectBrowserWalletIfSolo && modalOpen.value) {
if (
connectors.value.length === 1 && // only one connector
Expand All @@ -70,11 +88,11 @@ watch(modalOpen, async () => {
}
})
async function onClickWallet(connName: ConnectorName, rdns?: RDNS | string) {
async function onClickWallet(connName: ConnectorName, rdns?: RDNS | string, isWindowEthereum = false) {
try {
closeModal()
// throw new Error('test connect error')
await connectTo(connName, { rdns })
await connectTo(connName, { rdns, isWindowEthereum })
} catch (err: any) {
emit('connectError', err)
}
Expand All @@ -84,6 +102,23 @@ function onClickCancelConnecting() {
disconnect()
}
// Check whether the browser is within a mobile app (such as a WebView) rather than a standalone mobile browser like Chrome App
function isMobileAppBrowser() {
const userAgent = navigator.userAgent
// for ios
if (!userAgent.includes('Safari/') && userAgent.includes('Mobile/')) {
return true
}
// for android
if (userAgent.includes('wv') || userAgent.includes('WebView')) {
return true
}
return false
}
const vClickOutside = {
beforeMount: (el: any, binding: any) => {
el.clickOutsideEvent = (event: MouseEvent) => {
Expand Down

0 comments on commit d448091

Please sign in to comment.