mirror of
https://github.com/status-im/dappconnect-sdks.git
synced 2025-02-02 11:55:04 +00:00
add apps/connector
logger
This commit is contained in:
parent
b286ef2a74
commit
d8d683ae37
5
.changeset/sharp-cheetahs-breathe.md
Normal file
5
.changeset/sharp-cheetahs-breathe.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'connector': patch
|
||||
---
|
||||
|
||||
add logger
|
@ -102,3 +102,11 @@ FLAG_CONNECTOR_ENABLED=1 '/Volumes/Status Desktop/Status.app/Contents/MacOS/nim_
|
||||
## Testing
|
||||
|
||||
Download latest build from last merged PR or build from source. To use the extension see the load steps from [Development](#development) section.
|
||||
|
||||
### Logging
|
||||
|
||||
Browser > Console
|
||||
|
||||
```
|
||||
await connector.enableLogging(true)
|
||||
```
|
||||
|
@ -4,6 +4,11 @@ import configs, { tailwindcssConfigs } from '@status-im/eslint-config'
|
||||
export default [
|
||||
...configs,
|
||||
...tailwindcssConfigs,
|
||||
{
|
||||
rules: {
|
||||
'no-console': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
|
1
apps/connector/global.d.ts
vendored
1
apps/connector/global.d.ts
vendored
@ -3,5 +3,6 @@ import type { Provider } from './src/contents/provider'
|
||||
declare global {
|
||||
interface Window {
|
||||
ethereum: Provider
|
||||
registration?: ServiceWorkerRegistration
|
||||
}
|
||||
}
|
||||
|
@ -46,3 +46,11 @@ chrome.action.onClicked.addListener(async tab => {
|
||||
type: 'status:icon:clicked',
|
||||
} satisfies ServiceWorkerMessage)
|
||||
})
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.method === 'toggleLogging') {
|
||||
storage.set('status:logging', message.params[0])
|
||||
|
||||
sendResponse(message.params[0])
|
||||
}
|
||||
})
|
||||
|
73
apps/connector/src/contents/main.ts
Normal file
73
apps/connector/src/contents/main.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { ProxyMessage } from '~messages/proxy-message'
|
||||
|
||||
import type { RequestArguments } from '~lib/request-arguments'
|
||||
import type { PlasmoCSConfig } from 'plasmo'
|
||||
|
||||
export const config: PlasmoCSConfig = {
|
||||
matches: ['https://*/*'],
|
||||
world: 'MAIN',
|
||||
run_at: 'document_start',
|
||||
all_frames: false,
|
||||
}
|
||||
|
||||
Object.defineProperties(window, {
|
||||
connector: {
|
||||
value: {
|
||||
enableLogging: (value = true) => {
|
||||
// shared web storage with host page and content scripts (Web Storage API)
|
||||
window.localStorage.setItem('status:logging', JSON.stringify(value))
|
||||
|
||||
// service worker extension storage (extension storage API)
|
||||
request({
|
||||
method: 'toggleLogging',
|
||||
params: [value],
|
||||
})
|
||||
|
||||
return value
|
||||
},
|
||||
},
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
})
|
||||
|
||||
async function request(args: RequestArguments) {
|
||||
const { method, params } = args
|
||||
|
||||
const messageChannel = new MessageChannel()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
messageChannel.port1.onmessage = ({ data }) => {
|
||||
try {
|
||||
const message = ProxyMessage.parse(data)
|
||||
|
||||
messageChannel.port1.close()
|
||||
|
||||
switch (message.type) {
|
||||
case 'status:proxy:success': {
|
||||
resolve(message.data)
|
||||
|
||||
return
|
||||
}
|
||||
case 'status:proxy:error': {
|
||||
reject(new Error(message.error.message))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const mainMessage = {
|
||||
type: 'status:main',
|
||||
data: {
|
||||
method,
|
||||
params,
|
||||
},
|
||||
}
|
||||
|
||||
window.postMessage(mainMessage, window.origin, [messageChannel.port2])
|
||||
})
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { logger } from '~lib/logger'
|
||||
import { ProviderRpcError } from '~lib/provider-rpc-error'
|
||||
import { RequestArguments } from '~lib/request-arguments'
|
||||
import { ProxyMessage } from '~messages/proxy-message'
|
||||
@ -96,11 +97,15 @@ export class Provider {
|
||||
this.#listeners.get('connect')?.({ chainId: '0x1' })
|
||||
this.#listeners.get('connected')?.({ chainId: '0x1' })
|
||||
this.connected = true
|
||||
|
||||
logger.info('connected::')
|
||||
}
|
||||
|
||||
if (method === 'wallet_switchEthereumChain') {
|
||||
this.#listeners.get('chainChanged')?.(message.data)
|
||||
this.#listeners.get('networkChanged')?.(message.data)
|
||||
|
||||
logger.info('chainChanged::')
|
||||
}
|
||||
|
||||
resolve(message.data)
|
||||
@ -108,6 +113,8 @@ export class Provider {
|
||||
return
|
||||
}
|
||||
case 'status:proxy:error': {
|
||||
logger.error(message.error)
|
||||
|
||||
// note: for those dApps that make call after having permissions revoked
|
||||
if (
|
||||
message.error.message === 'dApp is not permitted by user' &&
|
||||
@ -148,15 +155,21 @@ export class Provider {
|
||||
}
|
||||
|
||||
public on(event: Event, handler: (args: unknown) => void): void {
|
||||
logger.info('on::', event, handler)
|
||||
|
||||
this.#listeners.set(event, handler)
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public async close(): Promise<void> {
|
||||
public async close(...args: unknown[]): Promise<void> {
|
||||
logger.info('close::', args)
|
||||
|
||||
this.disconnect()
|
||||
}
|
||||
|
||||
public removeListener(event: Event): void {
|
||||
public removeListener(event: Event, handler: (args: unknown) => void): void {
|
||||
logger.info('removeListener::', event, handler)
|
||||
|
||||
// note: not all dapps remove these on disconnect
|
||||
if (event === 'close' || event === 'disconnect') {
|
||||
this.disconnect()
|
||||
@ -166,6 +179,8 @@ export class Provider {
|
||||
}
|
||||
|
||||
public async enable() {
|
||||
logger.info('enable::')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -176,6 +191,8 @@ export class Provider {
|
||||
|
||||
this.connected = false
|
||||
|
||||
logger.info('disconnect::')
|
||||
|
||||
await this.request({
|
||||
method: 'wallet_revokePermissions',
|
||||
params: [
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { getFaviconUrl } from '~lib/get-favicon-url'
|
||||
import { logger } from '~lib/logger'
|
||||
import { MainMessage } from '~messages/main-message'
|
||||
import { ProviderMessage } from '~messages/provider-message'
|
||||
|
||||
import { DesktopClient } from '../lib/desktop-client'
|
||||
@ -13,7 +15,7 @@ export const config: PlasmoCSConfig = {
|
||||
|
||||
const desktopClient = new DesktopClient()
|
||||
|
||||
const handleMessage = async (event: MessageEvent) => {
|
||||
const handleProviderMessage = async (event: MessageEvent) => {
|
||||
if (event.origin !== window.origin) {
|
||||
return
|
||||
}
|
||||
@ -39,6 +41,8 @@ const handleMessage = async (event: MessageEvent) => {
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info('request::', message.data)
|
||||
|
||||
const response = await desktopClient.send({
|
||||
...message.data,
|
||||
name: window.location.hostname,
|
||||
@ -46,6 +50,8 @@ const handleMessage = async (event: MessageEvent) => {
|
||||
iconUrl: getFaviconUrl() ?? '',
|
||||
})
|
||||
|
||||
logger.info('response::', response)
|
||||
|
||||
event.ports[0].postMessage({
|
||||
type: 'status:proxy:success',
|
||||
data: response,
|
||||
@ -71,12 +77,10 @@ const handleMessage = async (event: MessageEvent) => {
|
||||
}
|
||||
}
|
||||
|
||||
const proxyMessage: ProxyMessage = {
|
||||
event.ports[0].postMessage({
|
||||
type: 'status:proxy:error',
|
||||
error: proxyError,
|
||||
}
|
||||
|
||||
event.ports[0].postMessage(proxyMessage)
|
||||
} satisfies ProxyMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,4 +112,43 @@ function isRpcError(
|
||||
)
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessage)
|
||||
window.addEventListener('message', handleProviderMessage)
|
||||
|
||||
const handleMainMessage = async (event: MessageEvent) => {
|
||||
if (event.origin !== window.origin) {
|
||||
return
|
||||
}
|
||||
|
||||
let message: MainMessage
|
||||
try {
|
||||
message = MainMessage.parse(event.data)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
if (message.type !== 'status:main') {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage(
|
||||
chrome.runtime.id,
|
||||
message.data,
|
||||
)
|
||||
|
||||
event.ports[0].postMessage({
|
||||
type: 'status:proxy:success',
|
||||
data: response,
|
||||
} satisfies ProxyMessage)
|
||||
} catch (error) {
|
||||
event.ports[0].postMessage({
|
||||
type: 'status:proxy:error',
|
||||
error: {
|
||||
code: -32603,
|
||||
message: isError(error) ? error.message : 'Internal error',
|
||||
},
|
||||
} satisfies ProxyMessage)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMainMessage)
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { logger } from '~lib/logger'
|
||||
|
||||
export function useLocalStorage<T>(
|
||||
key: string,
|
||||
initialValue: T,
|
||||
@ -10,7 +12,8 @@ export function useLocalStorage<T>(
|
||||
try {
|
||||
const item = window.localStorage.getItem(key)
|
||||
setStoredValue(item ? JSON.parse(item) : initialValue)
|
||||
} catch {
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
setStoredValue(initialValue)
|
||||
}
|
||||
}, [])
|
||||
@ -39,7 +42,9 @@ export function useLocalStorage<T>(
|
||||
setStoredValue(valueToStore)
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore))
|
||||
window.dispatchEvent(new Event('storage'))
|
||||
} catch {}
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
return [storedValue, setValue]
|
||||
|
@ -2,6 +2,8 @@ import { WebSocketProvider } from 'ethers'
|
||||
|
||||
import { config } from '~config'
|
||||
|
||||
import { logger } from './logger'
|
||||
|
||||
import type { RequestArguments } from '~lib/request-arguments'
|
||||
import type { WebSocketLike } from 'ethers'
|
||||
|
||||
@ -19,6 +21,7 @@ export class DesktopClient {
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('stop::')
|
||||
this.#rpcClient?.destroy()
|
||||
this.#rpcClient = null
|
||||
|
||||
@ -27,6 +30,7 @@ export class DesktopClient {
|
||||
|
||||
public async send(args: DesktopRequestArguments) {
|
||||
if (!this.#rpcClient) {
|
||||
logger.info('start::')
|
||||
this.#rpcClient = new WebSocketProvider(
|
||||
config.desktop.rpc.url,
|
||||
'mainnet',
|
||||
@ -41,6 +45,11 @@ export class DesktopClient {
|
||||
|
||||
await waitUntilOpen(this.#rpcClient.websocket)
|
||||
|
||||
logger.info('client::', {
|
||||
method: config.desktop.rpc.method,
|
||||
params: [JSON.stringify(args)],
|
||||
})
|
||||
|
||||
return await this.#rpcClient.send(config.desktop.rpc.method, [
|
||||
JSON.stringify(args),
|
||||
])
|
||||
@ -71,6 +80,10 @@ async function waitUntilOpen(websocket: WebSocketLike) {
|
||||
reject(new Error('Timed out to connect to the RPC server'))
|
||||
}, 30 * 1000)
|
||||
|
||||
if (websocket.readyState === WebSocket.CONNECTING) {
|
||||
logger.warn('Waiting for the RPC server to connect')
|
||||
}
|
||||
|
||||
const onopen = websocket.onopen?.bind(websocket)
|
||||
websocket.onopen = event => {
|
||||
onopen?.(event)
|
||||
|
47
apps/connector/src/lib/logger.ts
Normal file
47
apps/connector/src/lib/logger.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
export const logger = {
|
||||
info(message: unknown, ...args: unknown[]) {
|
||||
log('info', message, ...args)
|
||||
},
|
||||
|
||||
warn(message: unknown, ...args: unknown[]) {
|
||||
log('warn', message, ...args)
|
||||
},
|
||||
|
||||
error(message: unknown, ...args: unknown[]) {
|
||||
log('error', message, ...args)
|
||||
},
|
||||
}
|
||||
|
||||
function log(
|
||||
level: 'info' | 'warn' | 'error',
|
||||
message: unknown,
|
||||
...args: unknown[]
|
||||
) {
|
||||
// service worker context
|
||||
if (
|
||||
typeof window === 'undefined' && // !window throws
|
||||
typeof self !== 'undefined' &&
|
||||
self.registration
|
||||
) {
|
||||
chrome.storage.sync.get('status:logging').then(result => {
|
||||
if (!(result['status:logging'] === 'true')) {
|
||||
return
|
||||
}
|
||||
|
||||
// async logging
|
||||
console[level]('status:connector:', message, ...args)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// other extension contexts
|
||||
if (!(window?.localStorage.getItem('status:logging') === 'true')) {
|
||||
return
|
||||
}
|
||||
|
||||
// synchronous logging
|
||||
console[level]('status:connector:', message, ...args)
|
||||
}
|
@ -2,6 +2,7 @@ import { z } from 'zod'
|
||||
|
||||
/**
|
||||
* @see https://eips.ethereum.org/EIPS/eip-1193#request-1
|
||||
* @see https://www.jsonrpc.org/specification#request_object
|
||||
*/
|
||||
export const RequestArguments = z.object({
|
||||
method: z.string(),
|
||||
|
12
apps/connector/src/messages/main-message.ts
Normal file
12
apps/connector/src/messages/main-message.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
import { RequestArguments } from '~lib/request-arguments'
|
||||
|
||||
export const MainMessage = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('status:main'),
|
||||
data: RequestArguments,
|
||||
}),
|
||||
])
|
||||
|
||||
export type MainMessage = z.infer<typeof MainMessage>
|
Loading…
x
Reference in New Issue
Block a user