Skip to content

Commit

Permalink
86dt6jqwt - NEON3: Add WC deeplink
Browse files Browse the repository at this point in the history
  • Loading branch information
ricardoak25 committed Jun 5, 2024
1 parent 70229a4 commit 0c42193
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 8 deletions.
50 changes: 50 additions & 0 deletions src/main/deeplink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { app, BrowserWindow, ipcMain } from 'electron'
import path from 'path'

let initialDeepLinkUri: string | undefined = undefined
let hasDeeplink: boolean = false

export function registerNeonDeeplink() {
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('neon', process.execPath, [path.resolve(process.argv[1])])
}
} else {
app.setAsDefaultProtocolClient('neon')
}
}

export function sendDeeplink(mainWindow: BrowserWindow, deeplinkUrl: string | undefined) {
if (deeplinkUrl) {
mainWindow.webContents.send('deeplink', deeplinkUrl)
}
}

export function setDeeplink(deeplinkUrl: string | undefined) {
initialDeepLinkUri = deeplinkUrl
}

export function registerOpenUrl(mainWindow: BrowserWindow | null) {
app.on('open-url', (_event, url) => {
if (!mainWindow) {
initialDeepLinkUri = url
hasDeeplink = true
return
}

mainWindow.webContents.send('deeplink', url)
})
}

export function registerDeeplinkHandler() {
ipcMain.handle('getInitialDeepLinkUri', async () => {
const uri = initialDeepLinkUri
initialDeepLinkUri = undefined
hasDeeplink = false
return uri
})

ipcMain.handle('hasDeeplink', async () => {
return hasDeeplink
})
}
14 changes: 13 additions & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { join } from 'path'
import * as packageJson from '../../package.json'
import icon from '../../resources/icon.png?asset'

import { registerDeeplinkHandler, registerNeonDeeplink, registerOpenUrl, sendDeeplink, setDeeplink } from './deeplink'
import { registerEncryptionHandlers } from './encryption'
import { getLedgerTransport, registerLedgerHandler } from './ledger'
import { setupSentry } from './sentryElectron'
Expand All @@ -19,6 +20,8 @@ import { registerWindowHandlers } from './window'
const gotTheLock = app.requestSingleInstanceLock()
let mainWindow: BrowserWindow | null = null

registerNeonDeeplink()

function createWindow(): void {
mainWindow = new BrowserWindow({
title: `Neon Wallet ${packageJson.version}`,
Expand Down Expand Up @@ -62,10 +65,16 @@ if (!gotTheLock) {
} else {
setupSentry()

app.on('second-instance', () => {
app.on('second-instance', (_event, commandLine) => {
// The commandLine is an array of strings, where the last element is the deep link URL.
const deeplinkUrl = commandLine.pop()
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()

sendDeeplink(mainWindow, deeplinkUrl)
} else {
setDeeplink(deeplinkUrl)
}
})

Expand All @@ -89,6 +98,8 @@ if (!gotTheLock) {
})
})

registerOpenUrl(mainWindow)

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
Expand All @@ -106,4 +117,5 @@ if (!gotTheLock) {
registerEncryptionHandlers()
registerLedgerHandler(bsAggregator)
exposeApiToRenderer(bsAggregator)
registerDeeplinkHandler()
}
7 changes: 7 additions & 0 deletions src/renderer/src/@types/i18next-resources.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ interface Resources {
ledgerConnected: 'New Ledger detected \n{{address}}'
ledgerDisconnected: 'Ledger disconnected \n{{address}}'
}
DappConnection: {
pleaseLogin: 'Please login before connection to a dApp.'
selectAccountModal: {
title: 'Select an account to connect to the dapp.'
selectSourceAccount: 'Select account'
}
}
}
modals: {
import: {
Expand Down
57 changes: 56 additions & 1 deletion src/renderer/src/hooks/useAfterLogin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useEffect } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useWalletConnectWallet } from '@cityofzion/wallet-connect-sdk-wallet-react'
import { IAccountState } from '@renderer/@types/store'
import { UtilsHelper } from '@renderer/helpers/UtilsHelper'

import { useAccountsSelector } from './useAccountSelector'
Expand Down Expand Up @@ -73,7 +74,61 @@ const useRegisterLedgerListeners = () => {
}, [accountsRef, createWallet, deleteWallet, importAccount, commonT, t])
}

function isWalletConnectUri(uri) {
return /^wc:.+@\d.*$/g.test(uri)
}

const useRegisterDeeplinkListeners = () => {
const { modalNavigate } = useModalNavigate()
const { t: commonWc } = useTranslation('hooks', { keyPrefix: 'DappConnection' })
const [decodedDeeplinkUri, setDecodedDeeplinkUri] = useState<string | null>(null)

const handleDeeplink = useCallback(async (uri: string) => {
if (!uri) return

await window.electron.ipcRenderer.invoke('restore')

const realUri = uri.split('uri=').pop()
if (!realUri) return

const decodedUri = decodeURIComponent(realUri)
if (isWalletConnectUri(decodedUri)) {
setDecodedDeeplinkUri(decodedUri)
return
}

const decodedBase64Uri = atob(decodedUri)
if (isWalletConnectUri(decodedBase64Uri)) {
setDecodedDeeplinkUri(decodedBase64Uri)
}
}, [])

useEffect(() => {
window.electron.ipcRenderer.invoke('getInitialDeepLinkUri').then(handleDeeplink)

window.electron.ipcRenderer.once('deeplink', (_event, uri: string) => {
handleDeeplink(uri)
})
}, [handleDeeplink])

useEffect(() => {
if (!decodedDeeplinkUri) return

modalNavigate('select-account', {
state: {
onSelectAccount: (account: IAccountState) => {
modalNavigate('dapp-connection', { state: { account: account, uri: decodedDeeplinkUri } })
setDecodedDeeplinkUri(null)
},
title: commonWc('selectAccountModal.title'),
buttonLabel: commonWc('selectAccountModal.selectSourceAccount'),
},
})
}, [commonWc, decodedDeeplinkUri, modalNavigate])
}

export const useAfterLogin = () => {
useRegisterWalletConnectListeners()
useRegisterLedgerListeners()
useRegisterDeeplinkListeners()
}
24 changes: 23 additions & 1 deletion src/renderer/src/hooks/useBeforeLogin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StringHelper } from '@renderer/helpers/StringHelper'
import { ToastHelper } from '@renderer/helpers/ToastHelper'
Expand Down Expand Up @@ -70,6 +70,27 @@ const useOverTheAirUpdate = () => {
}, [modalNavigate, hasOverTheAirUpdatesRef])
}

const useDeeplinkListeners = () => {
const { t } = useTranslation('hooks', { keyPrefix: 'DappConnection' })
const [hasDeeplink, setHasDeeplink] = useState<boolean>(false)

const handleDeeplink = useCallback(async (hasUri: boolean) => {
await window.electron.ipcRenderer.invoke('restore')
setHasDeeplink(hasUri)
}, [])

useEffect(() => {
window.electron.ipcRenderer.invoke('hasDeeplink').then(handleDeeplink)
}, [handleDeeplink])

useEffect(() => {
if (hasDeeplink) {
ToastHelper.info({
message: t('pleaseLogin'),
})
}
}, [hasDeeplink, t])
}
const useNetworkChange = () => {
const { networkType } = useNetworkTypeSelector()

Expand Down Expand Up @@ -102,4 +123,5 @@ export const useBeforeLogin = () => {
useOverTheAirUpdate()
useNetworkChange()
useStoreStartup()
useDeeplinkListeners()
}
7 changes: 7 additions & 0 deletions src/renderer/src/locales/en/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,12 @@
"useLedgerFlow": {
"ledgerConnected": "New Ledger detected \n{{address}}",
"ledgerDisconnected": "Ledger disconnected \n{{address}}"
},
"DappConnection": {
"pleaseLogin": "Please login before connection to a dApp.",
"selectAccountModal": {
"title": "Select an account to connect to the dapp.",
"selectSourceAccount": "Select account"
}
}
}
5 changes: 3 additions & 2 deletions src/renderer/src/routes/modals/DappConnection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ type TFormData = {

type TLocationState = {
account: IAccountState
uri?: string
}

export const DappConnectionModal = () => {
const { connect, proposals } = useWalletConnectWallet()
const { modalNavigate } = useModalNavigate()
const { t } = useTranslation('modals', { keyPrefix: 'dappConnection' })
const { account } = useModalState<TLocationState>()
const { account, uri } = useModalState<TLocationState>()
const { actionData, setData, actionState, setError, handleAct } = useActions<TFormData>({
url: '',
url: uri ?? '',
isConnecting: false,
})

Expand Down
4 changes: 2 additions & 2 deletions src/renderer/src/routes/modals/SelectAccount/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type TLocationState = {
onSelectAccount: (contact: IAccountState) => void
title: string
buttonLabel: string
leftIcon: JSX.Element
leftIcon?: JSX.Element
}

export const SelectAccount = () => {
Expand Down Expand Up @@ -53,8 +53,8 @@ export const SelectAccount = () => {
if (!selectedAccount) {
return
}
onSelectAccount(selectedAccount)
modalNavigate(-1)
onSelectAccount(selectedAccount)
}

const filteredWallets = useMemo(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/routes/pages/Send/SelectAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type TAccountParams = {
title: string
modalTitle: string
buttonLabel: string
leftIcon: JSX.Element
leftIcon?: JSX.Element
}

export const SelectAccount = ({
Expand Down

0 comments on commit 0c42193

Please sign in to comment.