Skip to content

Commit d448091

Browse files
authored
Merge pull request #174 from vu3th/feat/enable-dapp-browser
Feat/enable dapp browser
2 parents f868f97 + f3869ac commit d448091

File tree

10 files changed

+297
-151
lines changed

10 files changed

+297
-151
lines changed

README.md

+33-62
Original file line numberDiff line numberDiff line change
@@ -46,90 +46,59 @@
4646
<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">
4747

4848

49-
## Installation
49+
## Getting Started
5050

51-
Minimum
52-
```bash
53-
npm install pinia @vue-dapp/core
54-
```
51+
### SPA with Vite
5552

56-
With the wallet modal
5753
```bash
5854
npm install pinia @vue-dapp/core @vue-dapp/modal
5955
```
6056

61-
Maximum
62-
```bash
63-
npm install pinia @vue-dapp/core @vue-dapp/modal @vue-dapp/walletconnect @vue-dapp/coinbase
57+
```ts [main.ts]
58+
import { createPinia } from 'pinia'
59+
app.use(createPinia())
6460
```
6561

66-
## Example
67-
6862
```vue
6963
<script lang="ts" setup>
70-
import {
71-
useVueDapp,
72-
BrowserWalletConnector,
73-
VueDappProvider,
74-
type ConnWallet,
75-
} from '@vue-dapp/core'
76-
import { VueDappModal } from '@vue-dapp/modal'
77-
import '@vue-dapp/modal/dist/style.css' // make sure to add css for the modal
64+
import { BrowserWalletConnector, useVueDapp } from '@vue-dapp/core'
65+
import { VueDappModal, useVueDappModal } from '@vue-dapp/modal'
66+
import '@vue-dapp/modal/dist/style.css'
7867
79-
const { status, isConnected, address, chainId, error, disconnect, addConnector } = useVueDapp()
68+
const { addConnectors, isConnected, wallet, disconnect } = useVueDapp()
8069
81-
const isModalOpen = ref(false)
70+
addConnectors([new BrowserWalletConnector()])
8271
83-
function onClickConnectBtn() {
72+
function onClickConnectButton() {
8473
if (isConnected.value) disconnect()
85-
else isModalOpen.value = true
86-
}
87-
88-
if (process.client) { // only when using Nuxt 3
89-
addConnector(new BrowserWalletConnector())
90-
}
91-
92-
function handleConnect(wallet: ConnWallet) {
93-
console.log('handleConnect', wallet)
94-
95-
// example with ethers v6
96-
const ethersProvider = new ethers.providers.Web3Provider(provider)
97-
const signer = await ethersProvider.getSigner()
98-
99-
dappStore.setUser({
100-
address,
101-
signer: markRaw(signer),
102-
chainId,
103-
})
104-
}
105-
106-
function handleDisconnect() {
107-
console.log('handleDisconnect')
108-
109-
// example
110-
dappStore.resetUser()
74+
else useVueDappModal().open()
11175
}
11276
</script>
11377
11478
<template>
115-
<div>
116-
<VueDappProvider @connect="handleConnect" @disconnect="handleDisconnect">
117-
<button @click="onClickConnectBtn">{{ isConnected ? 'Disconnect' : 'Connect' }}</button>
118-
119-
<div>status: {{ status }}</div>
120-
<div>isConnected: {{ isConnected }}</div>
121-
<div>error: {{ error }}</div>
79+
<button @click="onClickConnectButton">{{ isConnected ? 'Disconnect' : 'Connect' }}</button>
12280
123-
<div v-if="isConnected">
124-
<div>chainId: {{ chainId }}</div>
125-
<div>address: {{ address }}</div>
126-
</div>
81+
<div>status: {{ wallet.status }}</div>
82+
<div>isConnected: {{ isConnected }}</div>
83+
<div>error: {{ wallet.error }}</div>
12784
128-
<VueDappModal v-model="isModalOpen" dark auto-connect />
129-
</VueDappProvider>
85+
<div v-if="isConnected">
86+
<div>chainId: {{ wallet.chainId }}</div>
87+
<div>address: {{ wallet.address }}</div>
13088
</div>
89+
90+
<VueDappModal dark auto-connect />
13191
</template>
92+
```
93+
94+
### SSR with Nuxt 3
13295

96+
```bash
97+
npm install pinia @pinia/nuxt @vue-dapp/core @vue-dapp/nuxt @vue-dapp/modal
98+
```
99+
100+
```ts
101+
modules: ['@pinia/nuxt', '@vue-dapp/nuxt']
133102
```
134103

135104
## Examples
@@ -140,9 +109,11 @@ function handleDisconnect() {
140109
## Development
141110

142111
```
112+
pnpm i
143113
pnpm build
144-
pnpm dev
145114
pnpm -F core watch
115+
pnpm -F modal watch
116+
pnpm dev
146117
```
147118

148119

app/app.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const hideConnectingModal = computed(() => {
7171

7272
<NuxtPage />
7373

74-
<VueDappModal autoConnect :hideConnectingModal="hideConnectingModal"> </VueDappModal>
74+
<VueDappModal :hideConnectingModal="hideConnectingModal"> </VueDappModal>
7575
</NuxtLayout>
7676
</n-config-provider>
7777
</template>

app/pages/index.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ const items = computed<Item[]>(() => [
9898
<p v-if="wallet.status === 'connected'">Disconnect</p>
9999
</n-button>
100100

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

103103
<ClientOnly>
104104
<Vue3EasyDataTable

app/pages/userAgent.vue

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
const userAgent = ref('')
3+
4+
onMounted(() => {
5+
userAgent.value = navigator.userAgent
6+
})
7+
8+
function alertUserAgent() {
9+
alert(navigator.userAgent)
10+
}
11+
</script>
12+
13+
<template>
14+
<div>
15+
<div>
16+
{{ userAgent }}
17+
</div>
18+
19+
<button @click="alertUserAgent">Alert userAgent</button>
20+
</div>
21+
</template>
22+
23+
<style lang="scss"></style>

packages/core/src/browserWalletConnector.ts

+20-14
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,23 @@ export class BrowserWalletConnector extends Connector<EIP1193Provider, BrowserWa
3535
useEIP6963().subscribe()
3636
}
3737

38-
static async checkConnection(RDNS: string | RDNS) {
39-
if (typeof window !== 'undefined' && !!window.ethereum) {
40-
const { provider } = this.getProvider(RDNS)
41-
if ((await provider.request({ method: 'eth_accounts' })).length !== 0) {
42-
return true
43-
}
38+
async connect(options?: ConnectOptions) {
39+
const { timeout, rdns, isWindowEthereum } = options ?? {
40+
timeout: undefined,
41+
rdns: undefined,
42+
isWindowEthereum: false,
4443
}
45-
return false
46-
}
4744

48-
async connect(options?: ConnectOptions) {
49-
const { timeout, rdns } = options ?? { timeout: undefined, rdns: undefined }
45+
let provider, info
5046

51-
const { info, provider } = this.getProvider(rdns)
47+
if (isWindowEthereum) {
48+
provider = this.getWindowEthereumProvider()
49+
if (!provider) throw new ProviderNotFoundError('window.ethereum not found')
50+
} else {
51+
const { provider: _provider, info: _info } = this.getProvider(rdns)
52+
provider = _provider
53+
info = _info
54+
}
5255

5356
let accounts, chainId
5457

@@ -87,11 +90,14 @@ export class BrowserWalletConnector extends Connector<EIP1193Provider, BrowserWa
8790
}
8891
}
8992

90-
getProvider(rdns?: RDNS | string): EIP6963ProviderDetail {
91-
return BrowserWalletConnector.getProvider(rdns)
93+
getWindowEthereumProvider(): EIP1193Provider | null {
94+
if (typeof window !== 'undefined' && !!window.ethereum) {
95+
return window.ethereum
96+
}
97+
return null
9298
}
9399

94-
static getProvider(rdns?: RDNS | string): EIP6963ProviderDetail {
100+
getProvider(rdns?: RDNS | string): EIP6963ProviderDetail {
95101
const { providerDetails } = useEIP6963()
96102
if (providerDetails.value.length < 1) throw new ProviderNotFoundError('providerDetails.length < 1')
97103

packages/core/src/services/connect.ts

+20-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { useStore } from '../store'
33
import { ConnectOptions, ConnectorName, RDNS } from '../types'
44
import { AutoConnectError, ConnectError, ConnectorNotFoundError } from '../errors'
55
import { normalizeChainId } from '../utils'
6-
import { BrowserWalletConnector } from '../browserWalletConnector'
76
import {
87
getLastConnectedBrowserWallet,
98
removeLastConnectedBrowserWallet,
@@ -100,26 +99,36 @@ export function useConnect(pinia?: any) {
10099
}
101100
}
102101

103-
async function autoConnect(rdns?: RDNS | string) {
104-
const lastRdns = getLastConnectedBrowserWallet()
105-
if (!lastRdns) return
102+
const isWindowEthereumAvailable = typeof window !== 'undefined' && !!window.ethereum
106103

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

109-
const bwConnector = walletStore.connectors.find(conn => conn.name === 'BrowserWallet')
108+
let options = {}
110109

111-
if (!bwConnector || !rdns) return
110+
if (isWindowEthereum) {
111+
if (!isWindowEthereumAvailable) return
112+
options = { isWindowEthereum }
113+
} else {
114+
const lastRdns = getLastConnectedBrowserWallet()
115+
116+
rdns = rdns || lastRdns
117+
if (!rdns) return
118+
119+
options = { rdns }
120+
}
112121

113122
try {
114-
const isConnected = await BrowserWalletConnector.checkConnection(rdns)
115-
if (isConnected) {
116-
await connectTo(bwConnector.name, { rdns })
117-
}
123+
await connectTo(browserWallet.name, options)
118124
} catch (err: any) {
119125
throw new AutoConnectError(err)
120126
}
121127
}
128+
122129
return {
130+
isWindowEthereumAvailable,
131+
123132
wallet: readonly(walletStore.wallet),
124133

125134
status: computed(() => walletStore.wallet.status),

packages/core/src/types/connector.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type ConnectorData<Provider = any> = {
1212

1313
export type ConnectOptions = {
1414
rdns?: string | RDNS
15+
isWindowEthereum?: boolean
1516
timeout?: number
1617
}
1718

packages/core/src/utils/assert.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ export function assertConnected(wallet: Wallet, errMsg?: string): asserts wallet
66
if (!wallet.connectorName) throw new AssertConnectedError(errMsg + ' - connectorName')
77
if (!wallet.provider) throw new AssertConnectedError(errMsg + ' - provider')
88

9-
if (wallet.connectorName === 'BrowserWallet') {
10-
if (!wallet.providerInfo) throw new AssertConnectedError(errMsg + ' - providerInfo')
11-
}
9+
// if (wallet.connectorName === 'BrowserWallet') {
10+
// if (!wallet.providerInfo) throw new AssertConnectedError(errMsg + ' - providerInfo')
11+
// }
1212
if (!wallet.connector) throw new AssertConnectedError(errMsg + ' - connector')
1313
if (!wallet.address) throw new AssertConnectedError(errMsg + ' - address')
1414
if (!wallet.chainId) throw new AssertConnectedError(errMsg + ' - chainId')

packages/modal/src/VueDappModal.vue

+45-10
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,45 @@ const modalOpen = computed(() => props.modelValue ?? store.isModalOpen)
3838
3939
const isAutoConnecting = ref(false)
4040
41-
const { connectors, connectTo, autoConnect, status, providerDetails, hasConnector, disconnect } = useVueDapp()
41+
const {
42+
isWindowEthereumAvailable,
43+
connectors,
44+
connectTo,
45+
autoConnect,
46+
status,
47+
providerDetails,
48+
hasConnector,
49+
disconnect,
50+
} = useVueDapp()
4251
4352
// ============================ feat: autoConnect ============================
44-
45-
async function handleAutoConnect() {
53+
onMounted(async () => {
4654
if (props.autoConnect) {
4755
try {
4856
isAutoConnecting.value = true
49-
await autoConnect()
57+
if (isMobileAppBrowser()) {
58+
await autoConnect(undefined, true)
59+
} else {
60+
await autoConnect()
61+
}
5062
} catch (err: any) {
5163
emit('autoConnectError', err)
5264
} finally {
5365
isAutoConnecting.value = false
5466
}
5567
}
56-
}
57-
58-
onMounted(async () => handleAutoConnect())
68+
})
5969
60-
// ============================ feat: auto click BrowserWallet if it's the only connector ============================
6170
watch(modalOpen, async () => {
71+
// ============================ feat: connect to window.ethereum if window.ethereum is available and there's no EIP-6963 providers ============================
72+
if (modalOpen.value && providerDetails.value.length === 0 && isMobileAppBrowser()) {
73+
if (isWindowEthereumAvailable) {
74+
await onClickWallet('BrowserWallet', undefined, true)
75+
}
76+
return
77+
}
78+
79+
// ============================ feat: auto click BrowserWallet if it's the only connector ============================
6280
if (props.autoConnectBrowserWalletIfSolo && modalOpen.value) {
6381
if (
6482
connectors.value.length === 1 && // only one connector
@@ -70,11 +88,11 @@ watch(modalOpen, async () => {
7088
}
7189
})
7290
73-
async function onClickWallet(connName: ConnectorName, rdns?: RDNS | string) {
91+
async function onClickWallet(connName: ConnectorName, rdns?: RDNS | string, isWindowEthereum = false) {
7492
try {
7593
closeModal()
7694
// throw new Error('test connect error')
77-
await connectTo(connName, { rdns })
95+
await connectTo(connName, { rdns, isWindowEthereum })
7896
} catch (err: any) {
7997
emit('connectError', err)
8098
}
@@ -84,6 +102,23 @@ function onClickCancelConnecting() {
84102
disconnect()
85103
}
86104
105+
// Check whether the browser is within a mobile app (such as a WebView) rather than a standalone mobile browser like Chrome App
106+
function isMobileAppBrowser() {
107+
const userAgent = navigator.userAgent
108+
109+
// for ios
110+
if (!userAgent.includes('Safari/') && userAgent.includes('Mobile/')) {
111+
return true
112+
}
113+
114+
// for android
115+
if (userAgent.includes('wv') || userAgent.includes('WebView')) {
116+
return true
117+
}
118+
119+
return false
120+
}
121+
87122
const vClickOutside = {
88123
beforeMount: (el: any, binding: any) => {
89124
el.clickOutsideEvent = (event: MouseEvent) => {

0 commit comments

Comments
 (0)