Skip to content

Commit

Permalink
Refactor api
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljscript committed Apr 25, 2024
1 parent cca9342 commit c571195
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 54 deletions.
7 changes: 4 additions & 3 deletions apps/wallet-mobile/src/features/Discover/common/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {connectionStorageMaker, dappConnectorMaker} from '@yoroi/dapp-connector'
import {dappConnectorApiMaker} from '@yoroi/dapp-connector'
import {App} from '@yoroi/types'
import {Alert, Image} from 'react-native'

Expand All @@ -14,8 +15,7 @@ export function hasProtocol(url: string) {
}

export const urlWithProtocol = (url: string, defaultProtocol = 'https://') => {
const sanitizedURL = hasProtocol(url) ? url : `${defaultProtocol}${url}`
return sanitizedURL
return hasProtocol(url) ? url : `${defaultProtocol}${url}`
}

export const getDomainFromUrl = (url: string) => {
Expand Down Expand Up @@ -59,6 +59,7 @@ export const getGoogleSearchItem = (searchQuery: string): DAppItem => ({
export const isGoogleSearchItem = (dApp: DAppItem) => dApp.id === googleDappId

export const createDappConnector = (appStorage: App.Storage, wallet: YoroiWallet) => {
const api = dappConnectorApiMaker()
const handlerWallet = {
id: wallet.id,
networkId: wallet.networkId,
Expand All @@ -73,5 +74,5 @@ export const createDappConnector = (appStorage: App.Storage, wallet: YoroiWallet
},
}
const storage = connectionStorageMaker({storage: appStorage.join('dapp-connections/')})
return dappConnectorMaker(storage, handlerWallet)
return dappConnectorMaker(storage, handlerWallet, api)
}
5 changes: 3 additions & 2 deletions apps/wallet-mobile/src/features/Discover/common/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {DappConnector, useDappConnector} from '@yoroi/dapp-connector'
import {useDappConnector} from '@yoroi/dapp-connector'
import {DappConnectorManager} from '@yoroi/dapp-connector/src/dapp-connector'
import * as React from 'react'
import {WebView, WebViewMessageEvent} from 'react-native-webview'

Expand Down Expand Up @@ -45,7 +46,7 @@ const getInjectableMessage = (message: unknown) => {
return `(() => window.dispatchEvent(new MessageEvent('message', ${event})))()`
}

const getInitScript = (sessionId: string, dappConnector: DappConnector) => {
const getInitScript = (sessionId: string, dappConnector: DappConnectorManager) => {
return dappConnector.getWalletConnectorScript({
iconUrl: walletConfig.iconUrl,
apiVersion: walletConfig.apiVersion,
Expand Down
3 changes: 2 additions & 1 deletion packages/dapp-connector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@
"jest": {
"collectCoverage": true,
"coveragePathIgnorePatterns": [
"connector.js"
"connector.js",
"manager.mocks.ts"
],
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
Expand Down
48 changes: 27 additions & 21 deletions packages/dapp-connector/src/adapters/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import {dappConnectorApiGetDappList} from './api'
import {dappConnectorApiMaker} from './api'
import {managerMock} from '../manager.mocks'

describe('dappConnectorApiGetDappList', () => {
it('should throw error if fetching fails', async () => {
const fakeFetchData = () => Promise.reject(new Error('fake error'))
await expect(() => dappConnectorApiGetDappList({request: fakeFetchData})()).rejects.toThrow()
})
describe('dappConnectorApiMaker', () => {
describe('getDApps', () => {
it('should throw error if fetching fails', async () => {
const fakeFetchData = () => Promise.reject(new Error('fake error'))
await expect(() => dappConnectorApiMaker({request: fakeFetchData}).getDApps()).rejects.toThrow()
})

it('should throw error if response is invalid', async () => {
const fakeResult = {tag: 'right' as const, value: {data: {dapps: 1}}} as const
const fakeFetchData = () => Promise.resolve(fakeResult)
await expect(() => dappConnectorApiGetDappList({request: fakeFetchData as any})()).rejects.toThrow()
})
it('should throw error if response is invalid', async () => {
const fakeResult = {tag: 'right' as const, value: {data: {dapps: 1}}} as const
const fakeFetchData = () => Promise.resolve(fakeResult)
await expect(() => dappConnectorApiMaker({request: fakeFetchData as any}).getDApps()).rejects.toThrow()
})

it('should throw error if response is left', async () => {
const fakeResult = {tag: 'left' as const, error: {status: 404, message: 'Not found', responseData: null}} as const
const fakeFetchData = () => Promise.resolve(fakeResult)
await expect(() => dappConnectorApiGetDappList({request: fakeFetchData as any})()).rejects.toThrow()
})
it('should throw error if response is left', async () => {
const fakeResult = {tag: 'left' as const, error: {status: 404, message: 'Not found', responseData: null}} as const
const fakeFetchData = () => Promise.resolve(fakeResult)
await expect(() => dappConnectorApiMaker({request: fakeFetchData as any}).getDApps()).rejects.toThrow()
})

it('should return data if response is valid', async () => {
const fakeResult = {tag: 'right' as const, value: {data: {dapps: [], filters: {}}}} as const
const fakeFetchData = () => Promise.resolve(fakeResult)
const result = await dappConnectorApiGetDappList({request: fakeFetchData as any})()
expect(result).toEqual(fakeResult.value.data)
it('should return data if response is valid', async () => {
const fakeResult = {tag: 'right' as const, value: {data: await managerMock.getDAppList()}} as const
const fakeFetchData = () => Promise.resolve(fakeResult)
const result = await dappConnectorApiMaker({request: fakeFetchData as any}).getDApps()
expect(result).toEqual({
...fakeResult.value.data,
dapps: fakeResult.value.data.dapps.map((d) => ({...d, logo: 'https://daehx1qv45z7c.cloudfront.net/icon.png'})),
})
})
})
})
9 changes: 7 additions & 2 deletions packages/dapp-connector/src/adapters/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import {z} from 'zod'
const dappListHost = 'https://daehx1qv45z7c.cloudfront.net'
const initialDeps = freeze({request: fetchData}, true)

export const dappConnectorApiGetDappList = ({request}: {request: FetchData} = initialDeps) => {
return async (fetcherConfig?: AxiosRequestConfig): Promise<DappListResponse> => {
export type Api = {
getDApps: (fetcherConfig?: AxiosRequestConfig) => Promise<DappListResponse>
}

export const dappConnectorApiMaker = ({request}: {request: FetchData} = initialDeps): Api => {
const getDApps = async (fetcherConfig?: AxiosRequestConfig): Promise<DappListResponse> => {
const config = {url: `${dappListHost}/data.json`} as const

const response = await request<unknown>(config, fetcherConfig)
Expand All @@ -32,6 +36,7 @@ export const dappConnectorApiGetDappList = ({request}: {request: FetchData} = in
filters: value.filters,
}
}
return {getDApps}
}

const DappResponseSchema = z.object({
Expand Down
17 changes: 15 additions & 2 deletions packages/dapp-connector/src/dapp-connector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import {storageMock} from './storage.mocks'
import {mockedData} from './mocks'
import {connectionStorageMaker} from './adapters/async-storage'
import {dappConnectorMaker} from './dapp-connector'
import {Api, dappConnectorApiMaker} from './adapters/api'
import {mockedDAppList} from './manager.mocks'

const getDappConnector = (wallet = mockWallet) => {
const storage = connectionStorageMaker({storage: storageMock})
return dappConnectorMaker(storage, wallet)
const api: Api = {
getDApps: async () => mockedDAppList,
}
return dappConnectorMaker(storage, wallet, api)
}

describe('DappConnector', () => {
Expand All @@ -30,7 +35,8 @@ describe('DappConnector', () => {
describe('connection management', () => {
it('should init with default storage', async () => {
const storage = connectionStorageMaker()
const connector = dappConnectorMaker(storage, mockWallet)
const api = dappConnectorApiMaker()
const connector = dappConnectorMaker(storage, mockWallet, api)
expect(connector).toBeDefined()
})

Expand Down Expand Up @@ -248,6 +254,13 @@ describe('DappConnector', () => {
expect(sendMessage).toHaveBeenCalledWith('1', mockedData[walletId].usedAddresses)
})
})

describe('getDAppList', () => {
it('should return list of dapps and filters', async () => {
const dappConnector = getDappConnector()
expect(await dappConnector.getDAppList()).toEqual(mockedDAppList)
})
})
})

const createEvent = (method: string, params?: object) => {
Expand Down
45 changes: 29 additions & 16 deletions packages/dapp-connector/src/dapp-connector.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,55 @@
import {resolverHandleEvent} from './resolver'
import {connectWallet} from './connector'
import {Storage} from './adapters/async-storage'
import {DappConnection, Storage} from './adapters/async-storage'
import {Api, DappListResponse} from './adapters/api'

export const dappConnectorMaker = (storage: Storage, wallet: Wallet): DappConnector => {
return new DappConnector(storage, wallet)
export type DappConnectorManager = {
getDAppList(): Promise<DappListResponse>
listAllConnections(): Promise<DappConnection[]>
removeConnection(options: {walletId?: string; dappOrigin: string}): Promise<void>
addConnection(options: {dappOrigin: string; walletId?: string}): Promise<void>
getWalletConnectorScript(props: {iconUrl: string; apiVersion: string; walletName: string; sessionId: string}): string
handleEvent(
eventData: string,
trustedUrl: string,
sendMessage: (id: string, result: unknown, error?: Error) => void,
): Promise<void>
}

export class DappConnector {
constructor(private storage: Storage, private wallet: Wallet) {}
export const dappConnectorMaker = (storage: Storage, wallet: Wallet, api: Api): DappConnector => {
return new DappConnector(storage, wallet, api)
}

export class DappConnector implements DappConnectorManager {
constructor(private storage: Storage, private wallet: Wallet, private api: Api) {}

async getDAppList() {
return this.api.getDApps()
}

public listAllConnections = async () => {
async listAllConnections() {
return this.storage.read()
}

public removeConnection = async (options: {walletId?: string; dappOrigin: string}) => {
async removeConnection(options: {walletId?: string; dappOrigin: string}) {
const walletId = options.walletId ?? this.wallet.id
return this.storage.remove({walletId, dappOrigin: options.dappOrigin})
}

public addConnection = async (options: {dappOrigin: string; walletId?: string}) => {
async addConnection(options: {dappOrigin: string; walletId?: string}) {
const walletId = options.walletId ?? this.wallet.id
return this.storage.save({walletId, dappOrigin: options.dappOrigin})
}

public getWalletConnectorScript = (props: {
iconUrl: string
apiVersion: string
walletName: string
sessionId: string
}) => {
getWalletConnectorScript(props: {iconUrl: string; apiVersion: string; walletName: string; sessionId: string}) {
return connectWallet({...props, supportedExtensions})
}

public handleEvent = async (
async handleEvent(
eventData: string,
trustedUrl: string,
sendMessage: (id: string, result: unknown, error?: Error) => void,
) => {
) {
return await resolverHandleEvent(eventData, trustedUrl, this.wallet, sendMessage, this.storage)
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/dapp-connector/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export {connectionStorageMaker, type Storage} from './adapters/async-storage'
export {type DappConnector, dappConnectorMaker} from './dapp-connector'
export * from './translators/reactjs/DappConnectorProvider'
export {useDappList} from './translators/reactjs/useDappList'
export {type DappListResponse} from './adapters/api'
export {type DappListResponse, dappConnectorApiMaker} from './adapters/api'
38 changes: 38 additions & 0 deletions packages/dapp-connector/src/manager.mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {DappConnectorManager} from './dapp-connector'
import {DappListResponse} from './adapters/api'

export const managerMock: DappConnectorManager = {
getDAppList(): Promise<DappListResponse> {
return Promise.resolve(mockedDAppList)
},
listAllConnections(): Promise<[]> {
return Promise.resolve([])
},
removeConnection(): Promise<void> {
return Promise.resolve()
},
addConnection(): Promise<void> {
return Promise.resolve()
},
getWalletConnectorScript(): string {
return ''
},
handleEvent(): Promise<void> {
return Promise.resolve()
},
}

export const mockedDAppList = {
dapps: [
{
id: 'example',
description: 'Example DApp',
logo: 'icon.png',
name: 'Example DApp',
category: 'example',
uri: 'https://example.com',
origins: ['https://example.com'],
},
],
filters: {'Category 1': ['Example 1', 'Example 2']},
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from 'react'
import {DappConnector} from '../../dapp-connector'
import {DappConnectorManager} from '../../dapp-connector'

const Context = React.createContext<{manager: DappConnector} | null>(null)
const Context = React.createContext<{manager: DappConnectorManager} | null>(null)

type Props = {
children: React.ReactNode
manager: DappConnector
manager: DappConnectorManager
}

export const DappConnectorProvider = ({children, manager}: Props) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ import {renderHook, waitFor} from '@testing-library/react-native'
import * as React from 'react'
import {QueryClientProvider} from 'react-query'
import {queryClientFixture} from '@yoroi/common'
import {DappConnectorProvider} from './DappConnectorProvider'
import {managerMock} from '../../manager.mocks'

describe('useDappList', () => {
it('should return list of dapps and filters', async () => {
const client = queryClientFixture()

const wrapper = (props: React.PropsWithChildren) => <QueryClientProvider {...props} client={client} />
const wrapper = ({children}: React.PropsWithChildren) => (
<QueryClientProvider client={client}>
<DappConnectorProvider manager={managerMock}>{children}</DappConnectorProvider>
</QueryClientProvider>
)

const {result} = renderHook(() => useDappList(), {wrapper})
await waitFor(() => expect(result.current.data?.dapps).toBeDefined())
await waitFor(() => expect(result.current.data?.filters).toBeDefined())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {useQuery, UseQueryOptions} from 'react-query'
import {dappConnectorApiGetDappList, DappListResponse} from '../../adapters/api'
import {DappListResponse} from '../../adapters/api'
import {useDappConnector} from './DappConnectorProvider'

export const useDappList = (options?: UseQueryOptions<DappListResponse, Error, DappListResponse, 'dappList'>) => {
const {manager} = useDappConnector()
return useQuery('dappList', {
...options,
queryFn: dappConnectorApiGetDappList(),
queryFn: manager.getDAppList,
})
}

0 comments on commit c571195

Please sign in to comment.