From 004ff82e1af059280733ffc4eb07f840d81079f7 Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Tue, 14 Dec 2021 13:10:58 +0800 Subject: [PATCH 1/3] chore: coin98 bridge --- .../injected-script/main/EthBridge/methods.ts | 35 ++++----- packages/injected-script/main/communicate.ts | 30 +++++-- packages/injected-script/sdk/bridgedCoin98.ts | 78 +++++++++++++++++++ .../injected-script/sdk/bridgedEthereum.ts | 5 -- packages/injected-script/sdk/index.ts | 14 +++- packages/injected-script/sdk/tsconfig.json | 2 +- packages/injected-script/shared/index.ts | 18 ++++- .../src/plugins/EVM/UI/SNSAdaptor/index.tsx | 3 +- .../UI/components/InjectedProviderBridge.tsx | 19 +++-- 9 files changed, 159 insertions(+), 45 deletions(-) create mode 100644 packages/injected-script/sdk/bridgedCoin98.ts diff --git a/packages/injected-script/main/EthBridge/methods.ts b/packages/injected-script/main/EthBridge/methods.ts index f7437dc144e7..c8c3f58ca1e6 100644 --- a/packages/injected-script/main/EthBridge/methods.ts +++ b/packages/injected-script/main/EthBridge/methods.ts @@ -1,3 +1,4 @@ +import { get } from 'lodash-unified' import { apply } from '../intrinsic' import { clone_into, handlePromise, sendEvent } from '../utils' @@ -5,44 +6,40 @@ const hasListened: Record = { __proto__: null! } const { has } = Reflect const { Promise, setTimeout, Boolean } = window const { resolve } = Promise +const read = (path: string) => get(window, path) -export function ethBridgeSendRequest(id: number, request: unknown) { - handlePromise(id, () => window.ethereum!.request(request)) +export function ethBridgeSendRequest(path: string, id: number, request: unknown) { + handlePromise(id, () => read(path).request(request)) } -export function ethBridgeIsConnected(id: number) { - handlePromise(id, () => Boolean(window.ethereum!.isConnected())) +export function ethBridgeIsConnected(path: string, id: number) { + handlePromise(id, () => Boolean(read(path).isConnected())) } -export function ethBridgeMetaMaskIsUnlocked(id: number) { - handlePromise(id, async () => { - return Boolean(await window.ethereum!._metamask!.isUnlocked!()) - }) -} -export function ethBridgePrimitiveAccess(id: number, property: string) { - handlePromise(id, () => (window.ethereum! as any)[property]) +export function ethBridgePrimitiveAccess(path: string, id: number, property: string) { + handlePromise(id, () => read(path)[property]) } -export function ethBridgeWatchEvent(event: string) { +export function ethBridgeWatchEvent(path: string, event: string) { if (hasListened[event]) return hasListened[event] = true // Todo: wait until ethereum appears // Note: DO NOT use intrinsics here because ethereum is not. - window.ethereum?.on( + read(path).on( event, - clone_into((...args) => { + clone_into((...args: any[]) => { sendEvent('ethBridgeOnEvent', event, args) }), ) } -function untilEthereumOnlineInner() { - if (has(window, 'ethereum')) return apply<(result: true) => Promise>(resolve, Promise, [true]) +function untilEthereumOnlineInner(name: string) { + if (has(window, name)) return apply<(result: true) => Promise>(resolve, Promise, [true]) return new Promise((r) => { function check() { - if (has(window, 'ethereum')) return r(true) + if (has(window, name)) return r(true) apply(setTimeout, window, [check, 200]) } check() }) } -export function untilEthereumOnline(id: number) { - handlePromise(id, untilEthereumOnlineInner) +export function untilEthereumOnline(path: string, id: number) { + handlePromise(id, untilEthereumOnlineInner.bind(null, path)) } diff --git a/packages/injected-script/main/communicate.ts b/packages/injected-script/main/communicate.ts index b3f4fc5b77c0..5ed308e04c7d 100644 --- a/packages/injected-script/main/communicate.ts +++ b/packages/injected-script/main/communicate.ts @@ -7,7 +7,6 @@ import { dispatchPasteImage } from './EventListenerPatch/dispatchPasteImage' import { ethBridgeSendRequest, ethBridgeIsConnected, - ethBridgeMetaMaskIsUnlocked, ethBridgePrimitiveAccess, ethBridgeWatchEvent, untilEthereumOnline, @@ -41,6 +40,7 @@ document.addEventListener(CustomEventId, (e) => { case 'resolvePromise': return + // solana case 'solanaBridgeRequestListen': return apply(solanaBridgeWatchEvent, null, r[1]) case 'solanaBridgeSendRequest': @@ -54,20 +54,34 @@ document.addEventListener(CustomEventId, (e) => { case 'solanaBridgeOnEvent': return + // ethereum case 'ethBridgeRequestListen': - return apply(ethBridgeWatchEvent, null, r[1]) + return apply(ethBridgeWatchEvent.bind(null, 'ethereum'), null, r[1]) case 'ethBridgeSendRequest': - return apply(ethBridgeSendRequest, null, r[1]) + return apply(ethBridgeSendRequest.bind(null, 'ethereum'), null, r[1]) case 'ethBridgeIsConnected': - return apply(ethBridgeIsConnected, null, r[1]) - case 'ethBridgeMetaMaskIsUnlocked': - return apply(ethBridgeMetaMaskIsUnlocked, null, r[1]) + return apply(ethBridgeIsConnected.bind(null, 'ethereum'), null, r[1]) case 'ethBridgePrimitiveAccess': - return apply(ethBridgePrimitiveAccess, null, r[1]) + return apply(ethBridgePrimitiveAccess.bind(null, 'ethereum'), null, r[1]) case 'untilEthBridgeOnline': - return apply(untilEthereumOnline, null, r[1]) + return apply(untilEthereumOnline.bind(null, 'ethereum'), null, r[1]) case 'ethBridgeOnEvent': return + + // coin98 + case 'coin98BridgeRequestListen': + return apply(ethBridgeWatchEvent.bind(null, 'coin98.provider'), null, r[1]) + case 'coin98BridgeSendRequest': + return apply(ethBridgeSendRequest.bind(null, 'coin98.provider'), null, r[1]) + case 'coin98BridgeIsConnected': + return apply(ethBridgeIsConnected.bind(null, 'coin98.provider'), null, r[1]) + case 'coin98BridgePrimitiveAccess': + return apply(ethBridgePrimitiveAccess.bind(null, 'coin98.provider'), null, r[1]) + case 'untilCoin98BridgeOnline': + return apply(untilEthereumOnline.bind(null, 'coin98'), null, r[1]) + case 'coin98BridgeOnEvent': + return + default: const neverEvent: never = r[0] warn('[@masknet/injected-script]', neverEvent, 'not handled') diff --git a/packages/injected-script/sdk/bridgedCoin98.ts b/packages/injected-script/sdk/bridgedCoin98.ts new file mode 100644 index 000000000000..44d512ff9346 --- /dev/null +++ b/packages/injected-script/sdk/bridgedCoin98.ts @@ -0,0 +1,78 @@ +import type { RequestArguments } from 'web3-core' +import type { JsonRpcPayload, JsonRpcResponse } from 'web3-core-helpers' +import { createPromise, sendEvent } from './utils' + +function request(data: RequestArguments) { + return createPromise((id) => sendEvent('coin98BridgeSendRequest', id, data)) +} + +function send(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void) { + createPromise((id) => + sendEvent('coin98BridgeSendRequest', id, { + method: payload.method, + params: payload.params, + }), + ).then( + (value) => { + callback(null, { + jsonrpc: '2.0', + id: payload.id as number, + result: value, + }) + }, + (error) => { + if (error instanceof Error) callback(error) + }, + ) +} + +/** Interact with the current ethereum provider */ +export const bridgedCoin98Provider: BridgedCoin98Provider = { + request, + send, + sendAsync: send, + on(event, callback) { + if (!bridgedCoin98.has(event)) { + bridgedCoin98.set(event, new Set()) + sendEvent('coin98BridgeRequestListen', event) + } + const map = bridgedCoin98.get(event)! + map.add(callback) + return () => void map.delete(callback) + }, + getProperty(key) { + return createPromise((id) => sendEvent('coin98BridgePrimitiveAccess', id, key)) + }, + isConnected() { + return createPromise((id) => sendEvent('coin98BridgeIsConnected', id)) + }, + untilAvailable() { + return createPromise((id) => sendEvent('untilCoin98BridgeOnline', id)) + }, +} +export interface BridgedCoin98Provider { + /** Wait for window.coin98 object appears. */ + untilAvailable(): Promise + /** Send JSON RPC to the coin98 provider. */ + request(data: RequestArguments): Promise + /** Send JSON RPC */ + send(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void + /** Async send JSON RPC */ + sendAsync(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void + /** Add event listener */ + on(event: string, callback: (...args: any) => void): () => void + /** Access primitive property on the window.coin98 object. */ + getProperty(key: 'isCoin98'): Promise + /** Call window.coin98.provider.isConnected() */ + isConnected(): Promise +} +const bridgedCoin98 = new Map>() +/** @internal */ +export function onCoin98Event(event: string, data: unknown[]) { + for (const f of bridgedCoin98.get(event) || []) { + try { + Reflect.apply(f, null, data) + } catch {} + } + return +} diff --git a/packages/injected-script/sdk/bridgedEthereum.ts b/packages/injected-script/sdk/bridgedEthereum.ts index ea5bd35560f7..e4c294214ff0 100644 --- a/packages/injected-script/sdk/bridgedEthereum.ts +++ b/packages/injected-script/sdk/bridgedEthereum.ts @@ -43,9 +43,6 @@ export const bridgedEthereumProvider: BridgedEthereumProvider = { getProperty(key) { return createPromise((id) => sendEvent('ethBridgePrimitiveAccess', id, key)) }, - _metamaskIsUnlocked() { - return createPromise((id) => sendEvent('ethBridgeMetaMaskIsUnlocked', id)) - }, isConnected() { return createPromise((id) => sendEvent('ethBridgeIsConnected', id)) }, @@ -68,8 +65,6 @@ export interface BridgedEthereumProvider { getProperty( key: 'isMaskWallet' | 'isMetaMask' | 'isCoin98' | 'isMathWallet' | 'isWalletLink', ): Promise - /** MetaMask only, experimental API. */ - _metamaskIsUnlocked(): Promise /** Call window.ethereum.isConnected() */ isConnected(): Promise } diff --git a/packages/injected-script/sdk/index.ts b/packages/injected-script/sdk/index.ts index a3ceb0c050a3..db2db3e8d338 100644 --- a/packages/injected-script/sdk/index.ts +++ b/packages/injected-script/sdk/index.ts @@ -1,10 +1,15 @@ import { CustomEventId, decodeEvent } from '../shared' import { onEthEvent } from './bridgedEthereum' +import { onCoin98Event } from './bridgedCoin98' import { onSolanaEvent } from './bridgedSolana' import { sendEvent, rejectPromise, resolvePromise } from './utils' export { bridgedEthereumProvider } from './bridgedEthereum' export type { BridgedEthereumProvider } from './bridgedEthereum' + +export { bridgedCoin98Provider } from './bridgedCoin98' +export type { BridgedCoin98Provider } from './bridgedCoin98' + export function pasteText(text: string) { sendEvent('paste', text) } @@ -28,17 +33,23 @@ document.addEventListener(CustomEventId, (e) => { switch (r[0]) { case 'ethBridgeOnEvent': return onEthEvent(...r[1]) + case 'coin98BridgeOnEvent': + return onCoin98Event(...r[1]) case 'solanaBridgeOnEvent': return onSolanaEvent(...r[1]) + case 'resolvePromise': return resolvePromise(...r[1]) case 'rejectPromise': return rejectPromise(...r[1]) case 'ethBridgeSendRequest': case 'ethBridgeIsConnected': - case 'ethBridgeMetaMaskIsUnlocked': case 'ethBridgePrimitiveAccess': case 'ethBridgeRequestListen': + case 'coin98BridgeSendRequest': + case 'coin98BridgeIsConnected': + case 'coin98BridgePrimitiveAccess': + case 'coin98BridgeRequestListen': case 'solanaBridgeSendRequest': case 'solanaBridgeIsConnected': case 'solanaBridgePrimitiveAccess': @@ -48,6 +59,7 @@ document.addEventListener(CustomEventId, (e) => { case 'pasteImage': case 'instagramUpload': case 'untilEthBridgeOnline': + case 'untilCoin98BridgeOnline': case 'untilSolanaBridgeOnline': case 'hookInputUploadOnce': break diff --git a/packages/injected-script/sdk/tsconfig.json b/packages/injected-script/sdk/tsconfig.json index c538f95f84fa..a7c4f0e0842b 100644 --- a/packages/injected-script/sdk/tsconfig.json +++ b/packages/injected-script/sdk/tsconfig.json @@ -5,6 +5,6 @@ "outDir": "../dist/sdk", "tsBuildInfoFile": "../dist/sdk.tsbuildinfo" }, - "files": ["./index.ts", "./bridgedEthereum.ts", "./bridgedSolana.ts", "utils.ts"], + "files": ["./index.ts", "./bridgedEthereum.ts", "./bridgedCoin98.ts", "./bridgedSolana.ts", "utils.ts"], "references": [{ "path": "../shared/" }] } diff --git a/packages/injected-script/shared/index.ts b/packages/injected-script/shared/index.ts index 88e5bcd6e3ba..d881bb4cccc1 100644 --- a/packages/injected-script/shared/index.ts +++ b/packages/injected-script/shared/index.ts @@ -15,6 +15,7 @@ export interface InternalEvents { * Call this event, then invoke the file selector (SNS). It will invoke click on some input, then let's replace with the result. */ hookInputUploadOnce: [format: string, fileName: string, file: number[]] + //#region Eth inpage provider bridge /** Request the bridge to listen on an event. */ ethBridgeRequestListen: [eventName: string] @@ -26,12 +27,25 @@ export interface InternalEvents { ethBridgePrimitiveAccess: [req_id: number, property: string] /** Call window.ethereum.isConnected() */ ethBridgeIsConnected: [req_id: number] - /** Call window.ethereum._metamask.isUnlocked() */ - ethBridgeMetaMaskIsUnlocked: [req_id: number] /** Wait until window.ethereum appears */ untilEthBridgeOnline: [req_id: number] //#endregion + //#region Eth inpage provider bridge + /** Request the bridge to listen on an event. */ + coin98BridgeRequestListen: [eventName: string] + /** When a event happened. */ + coin98BridgeOnEvent: [eventName: string, data: unknown[]] + /** Send JSON RPC request. */ + coin98BridgeSendRequest: [req_id: number, request: unknown] + /** Access primitive property on the window.ethereum object. */ + coin98BridgePrimitiveAccess: [req_id: number, property: string] + /** Call window.ethereum.isConnected() */ + coin98BridgeIsConnected: [req_id: number] + /** Wait until window.ethereum appears */ + untilCoin98BridgeOnline: [req_id: number] + //#endregion + //#region Solana inpage provider bridge /** Request the bridge to listen on an event. */ solanaBridgeRequestListen: [eventName: string] diff --git a/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx b/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx index 3240ce22ba8a..701e03398d8b 100644 --- a/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx @@ -15,7 +15,8 @@ const sns: Plugin.SNSAdaptor.Definition = { GlobalInjection() { return ( <> - + + ) diff --git a/packages/mask/src/plugins/EVM/UI/components/InjectedProviderBridge.tsx b/packages/mask/src/plugins/EVM/UI/components/InjectedProviderBridge.tsx index 476d14e3e922..23e2eb73bb25 100644 --- a/packages/mask/src/plugins/EVM/UI/components/InjectedProviderBridge.tsx +++ b/packages/mask/src/plugins/EVM/UI/components/InjectedProviderBridge.tsx @@ -1,20 +1,23 @@ import { useEffect } from 'react' -import { bridgedEthereumProvider } from '@masknet/injected-script' +import { bridgedEthereumProvider, bridgedCoin98Provider } from '@masknet/injected-script' import { useValueRef } from '@masknet/shared' import { isInjectedProvider } from '@masknet/web3-shared-evm' import { EVM_Messages } from '../../messages' import { currentProviderSettings } from '../../../Wallet/settings' import Services from '../../../../extension/service' -export interface InjectedProviderBridgeProps {} +export interface InjectedProviderBridgeProps { + type: 'ethereum' | 'coin98' +} export function InjectedProviderBridge(props: InjectedProviderBridgeProps) { const providerType = useValueRef(currentProviderSettings) + const bridgedProvider = props.type === 'ethereum' ? bridgedEthereumProvider : bridgedCoin98Provider useEffect(() => { return EVM_Messages.events.INJECTED_PROVIDER_RPC_REQUEST.on(async ({ payload }) => { try { - const result = await bridgedEthereumProvider.request({ + const result = await bridgedProvider.request({ method: payload.method, params: payload.params, }) @@ -30,21 +33,21 @@ export function InjectedProviderBridge(props: InjectedProviderBridgeProps) { }) } }) - }, []) + }, [bridgedProvider]) useEffect(() => { - return bridgedEthereumProvider.on('accountsChanged', async (event) => { + return bridgedProvider.on('accountsChanged', async (event) => { if (!isInjectedProvider(providerType)) return Services.Ethereum.notifyInjectedEvent('accountsChanged', event, providerType) }) - }, [providerType]) + }, [providerType, bridgedProvider]) useEffect(() => { - return bridgedEthereumProvider.on('chainChanged', (event) => { + return bridgedProvider.on('chainChanged', (event) => { if (!isInjectedProvider(providerType)) return Services.Ethereum.notifyInjectedEvent('chainChanged', event, providerType) }) - }, [providerType]) + }, [providerType, bridgedProvider]) return null } From 4d3c5d563b2ac9a73f76031dc550844b5cae75cf Mon Sep 17 00:00:00 2001 From: guanbinrui Date: Tue, 14 Dec 2021 18:32:01 +0800 Subject: [PATCH 2/3] refactor: globalVariableBridge --- .../EventListenerPatch/hookInputUploadOnce.ts | 2 +- .../index.ts} | 37 +++++++++------ .../main/SolanaBridge/index.ts | 1 - .../main/SolanaBridge/methods.ts | 43 ------------------ packages/injected-script/main/communicate.ts | 45 ++++++------------- packages/injected-script/sdk/bridgedCoin98.ts | 23 ++-------- .../injected-script/sdk/bridgedEthereum.ts | 25 ++--------- packages/injected-script/sdk/index.ts | 18 ++++---- packages/injected-script/shared/index.ts | 24 +++++++--- .../src/plugins/EVM/UI/SNSAdaptor/index.tsx | 2 +- .../UI/components/ProviderIconClickBait.tsx | 17 ++++--- packages/mask/src/plugins/EVM/hooks/index.ts | 2 +- .../plugins/EVM/hooks/useBridgedProvider.ts | 16 +++++++ .../EVM/hooks/useInjectedProviderConnected.ts | 9 ---- .../EVM/hooks/useInjectedProviderReady.ts | 9 ++-- .../EVM/hooks/useInjectedProviderType.ts | 19 ++++---- .../SNSAdaptor/ConnectWalletDialog/index.tsx | 4 +- 17 files changed, 113 insertions(+), 183 deletions(-) rename packages/injected-script/main/{EthBridge/methods.ts => GlobalVariableBridge/index.ts} (54%) delete mode 100644 packages/injected-script/main/SolanaBridge/index.ts delete mode 100644 packages/injected-script/main/SolanaBridge/methods.ts create mode 100644 packages/mask/src/plugins/EVM/hooks/useBridgedProvider.ts delete mode 100644 packages/mask/src/plugins/EVM/hooks/useInjectedProviderConnected.ts diff --git a/packages/injected-script/main/EventListenerPatch/hookInputUploadOnce.ts b/packages/injected-script/main/EventListenerPatch/hookInputUploadOnce.ts index 3e071de7ecf7..93f29ea783d7 100644 --- a/packages/injected-script/main/EventListenerPatch/hookInputUploadOnce.ts +++ b/packages/injected-script/main/EventListenerPatch/hookInputUploadOnce.ts @@ -8,7 +8,7 @@ const { defineProperty, deleteProperty } = Reflect const setTimeoutCaptured = setTimeout.bind(window) const clearTimeoutCaptured = clearTimeout.bind(window) export function hookInputUploadOnce(...[format, fileName, fileArray]: InternalEvents['hookInputUploadOnce']) { - let timer: number | null = null + let timer: NodeJS.Timeout | null = null const e = new no_xray_Event('change', { bubbles: true, cancelable: true, diff --git a/packages/injected-script/main/EthBridge/methods.ts b/packages/injected-script/main/GlobalVariableBridge/index.ts similarity index 54% rename from packages/injected-script/main/EthBridge/methods.ts rename to packages/injected-script/main/GlobalVariableBridge/index.ts index c8c3f58ca1e6..37ef2fb89bd2 100644 --- a/packages/injected-script/main/EthBridge/methods.ts +++ b/packages/injected-script/main/GlobalVariableBridge/index.ts @@ -1,23 +1,31 @@ -import { get } from 'lodash-unified' import { apply } from '../intrinsic' import { clone_into, handlePromise, sendEvent } from '../utils' +import type { InternalEvents } from '../../shared' const hasListened: Record = { __proto__: null! } const { has } = Reflect -const { Promise, setTimeout, Boolean } = window +const { Promise, setTimeout } = window const { resolve } = Promise -const read = (path: string) => get(window, path) -export function ethBridgeSendRequest(path: string, id: number, request: unknown) { - handlePromise(id, () => read(path).request(request)) -} -export function ethBridgeIsConnected(path: string, id: number) { - handlePromise(id, () => Boolean(read(path).isConnected())) +const read = (path: string) => { + const fragments = path.split('.') + let result = window as any + while (true) { + if (fragments.length === 0) return result + const key = fragments.shift() + result = key ? result[key] : result + } } -export function ethBridgePrimitiveAccess(path: string, id: number, property: string) { + +export function access(path: string, id: number, property: string) { handlePromise(id, () => read(path)[property]) } -export function ethBridgeWatchEvent(path: string, event: string) { + +export function callRequest(path: string, id: number, request: unknown) { + handlePromise(id, () => read(path).request(request)) +} + +export function bindEvent(path: string, bridgeEvent: keyof InternalEvents, event: string) { if (hasListened[event]) return hasListened[event] = true // Todo: wait until ethereum appears @@ -25,12 +33,12 @@ export function ethBridgeWatchEvent(path: string, event: string) { read(path).on( event, clone_into((...args: any[]) => { - sendEvent('ethBridgeOnEvent', event, args) + sendEvent(bridgeEvent, event, args) }), ) } -function untilEthereumOnlineInner(name: string) { +function untilInner(name: string) { if (has(window, name)) return apply<(result: true) => Promise>(resolve, Promise, [true]) return new Promise((r) => { function check() { @@ -40,6 +48,7 @@ function untilEthereumOnlineInner(name: string) { check() }) } -export function untilEthereumOnline(path: string, id: number) { - handlePromise(id, untilEthereumOnlineInner.bind(null, path)) + +export function until(path: string, id: number) { + handlePromise(id, untilInner.bind(null, path)) } diff --git a/packages/injected-script/main/SolanaBridge/index.ts b/packages/injected-script/main/SolanaBridge/index.ts deleted file mode 100644 index 65f48c986cd0..000000000000 --- a/packages/injected-script/main/SolanaBridge/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './methods' diff --git a/packages/injected-script/main/SolanaBridge/methods.ts b/packages/injected-script/main/SolanaBridge/methods.ts deleted file mode 100644 index 1547fdc5d9cc..000000000000 --- a/packages/injected-script/main/SolanaBridge/methods.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { apply } from '../intrinsic' -import { clone_into, handlePromise, sendEvent } from '../utils' - -const hasListened: Record = { __proto__: null! } -const { has } = Reflect -const { Promise, setTimeout, Boolean } = window -const { resolve } = Promise - -export function solanaBridgeSendRequest(id: number, request: unknown) { - handlePromise(id, () => window.solana!.request(request)) -} -export function solanaBridgeIsConnected(id: number) { - handlePromise(id, () => Boolean(window.solana!.isConnected)) -} -export function solanaBridgePrimitiveAccess(id: number, property: string) { - handlePromise(id, () => (window.solana! as any)[property]) -} -export function solanaBridgeWatchEvent(event: string) { - if (hasListened[event]) return - hasListened[event] = true - // Todo: wait until solana appears - // Note: DO NOT use intrinsics here because solana is not. - window.solana?.on( - event, - clone_into((...args) => { - sendEvent('solanaBridgeOnEvent', event, args) - }), - ) -} - -function untilSolanaOnlineInner() { - if (has(window, 'solana')) return apply<(result: true) => Promise>(resolve, Promise, [true]) - return new Promise((r) => { - function check() { - if (has(window, 'solana')) return r(true) - apply(setTimeout, window, [check, 200]) - } - check() - }) -} -export function untilSolanaOnline(id: number) { - handlePromise(id, untilSolanaOnlineInner) -} diff --git a/packages/injected-script/main/communicate.ts b/packages/injected-script/main/communicate.ts index 5ed308e04c7d..2687aba3c159 100644 --- a/packages/injected-script/main/communicate.ts +++ b/packages/injected-script/main/communicate.ts @@ -4,21 +4,8 @@ import { getCustomEventDetail, apply, warn } from './intrinsic' import { dispatchInput } from './EventListenerPatch/dispatchInput' import { dispatchPaste } from './EventListenerPatch/dispatchPaste' import { dispatchPasteImage } from './EventListenerPatch/dispatchPasteImage' -import { - ethBridgeSendRequest, - ethBridgeIsConnected, - ethBridgePrimitiveAccess, - ethBridgeWatchEvent, - untilEthereumOnline, -} from './EthBridge/methods' +import { callRequest, access, bindEvent, until } from './GlobalVariableBridge' import { hookInputUploadOnce } from './EventListenerPatch/hookInputUploadOnce' -import { - solanaBridgeIsConnected, - solanaBridgePrimitiveAccess, - solanaBridgeSendRequest, - solanaBridgeWatchEvent, - untilSolanaOnline, -} from './SolanaBridge' document.addEventListener(CustomEventId, (e) => { const r = decodeEvent(getCustomEventDetail(e as CustomEvent)) @@ -42,43 +29,37 @@ document.addEventListener(CustomEventId, (e) => { // solana case 'solanaBridgeRequestListen': - return apply(solanaBridgeWatchEvent, null, r[1]) + return apply(bindEvent, null, ['solana', 'solanaBridgeOnEvent', ...r[1]]) case 'solanaBridgeSendRequest': - return apply(solanaBridgeSendRequest, null, r[1]) - case 'solanaBridgeIsConnected': - return apply(solanaBridgeIsConnected, null, r[1]) + return apply(callRequest, null, ['solana', ...r[1]]) case 'solanaBridgePrimitiveAccess': - return apply(solanaBridgePrimitiveAccess, null, r[1]) + return apply(access, null, ['solana', ...r[1]]) case 'untilSolanaBridgeOnline': - return apply(untilSolanaOnline, null, r[1]) + return apply(until, null, ['solana', ...r[1]]) case 'solanaBridgeOnEvent': return // ethereum case 'ethBridgeRequestListen': - return apply(ethBridgeWatchEvent.bind(null, 'ethereum'), null, r[1]) + return apply(bindEvent, null, ['ethereum', 'ethBridgeOnEvent', ...r[1]]) case 'ethBridgeSendRequest': - return apply(ethBridgeSendRequest.bind(null, 'ethereum'), null, r[1]) - case 'ethBridgeIsConnected': - return apply(ethBridgeIsConnected.bind(null, 'ethereum'), null, r[1]) + return apply(callRequest, null, ['ethereum', ...r[1]]) case 'ethBridgePrimitiveAccess': - return apply(ethBridgePrimitiveAccess.bind(null, 'ethereum'), null, r[1]) + return apply(access, null, ['ethereum', ...r[1]]) case 'untilEthBridgeOnline': - return apply(untilEthereumOnline.bind(null, 'ethereum'), null, r[1]) + return apply(until, null, ['ethereum', ...r[1]]) case 'ethBridgeOnEvent': return // coin98 case 'coin98BridgeRequestListen': - return apply(ethBridgeWatchEvent.bind(null, 'coin98.provider'), null, r[1]) + return apply(bindEvent, null, ['coin98.provider', 'coin98BridgeOnEvent', ...r[1]]) case 'coin98BridgeSendRequest': - return apply(ethBridgeSendRequest.bind(null, 'coin98.provider'), null, r[1]) - case 'coin98BridgeIsConnected': - return apply(ethBridgeIsConnected.bind(null, 'coin98.provider'), null, r[1]) + return apply(callRequest, null, ['coin98.provider', ...r[1]]) case 'coin98BridgePrimitiveAccess': - return apply(ethBridgePrimitiveAccess.bind(null, 'coin98.provider'), null, r[1]) + return apply(access, null, ['coin98.provider', ...r[1]]) case 'untilCoin98BridgeOnline': - return apply(untilEthereumOnline.bind(null, 'coin98'), null, r[1]) + return apply(until, null, ['coin98', ...r[1]]) case 'coin98BridgeOnEvent': return diff --git a/packages/injected-script/sdk/bridgedCoin98.ts b/packages/injected-script/sdk/bridgedCoin98.ts index 44d512ff9346..f39a1f9843b5 100644 --- a/packages/injected-script/sdk/bridgedCoin98.ts +++ b/packages/injected-script/sdk/bridgedCoin98.ts @@ -1,5 +1,6 @@ import type { RequestArguments } from 'web3-core' import type { JsonRpcPayload, JsonRpcResponse } from 'web3-core-helpers' +import type { EthereumProvider } from '../shared' import { createPromise, sendEvent } from './utils' function request(data: RequestArguments) { @@ -27,7 +28,7 @@ function send(payload: JsonRpcPayload, callback: (error: Error | null, result?: } /** Interact with the current ethereum provider */ -export const bridgedCoin98Provider: BridgedCoin98Provider = { +export const bridgedCoin98Provider: EthereumProvider = { request, send, sendAsync: send, @@ -43,29 +44,11 @@ export const bridgedCoin98Provider: BridgedCoin98Provider = { getProperty(key) { return createPromise((id) => sendEvent('coin98BridgePrimitiveAccess', id, key)) }, - isConnected() { - return createPromise((id) => sendEvent('coin98BridgeIsConnected', id)) - }, untilAvailable() { return createPromise((id) => sendEvent('untilCoin98BridgeOnline', id)) }, } -export interface BridgedCoin98Provider { - /** Wait for window.coin98 object appears. */ - untilAvailable(): Promise - /** Send JSON RPC to the coin98 provider. */ - request(data: RequestArguments): Promise - /** Send JSON RPC */ - send(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void - /** Async send JSON RPC */ - sendAsync(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void - /** Add event listener */ - on(event: string, callback: (...args: any) => void): () => void - /** Access primitive property on the window.coin98 object. */ - getProperty(key: 'isCoin98'): Promise - /** Call window.coin98.provider.isConnected() */ - isConnected(): Promise -} + const bridgedCoin98 = new Map>() /** @internal */ export function onCoin98Event(event: string, data: unknown[]) { diff --git a/packages/injected-script/sdk/bridgedEthereum.ts b/packages/injected-script/sdk/bridgedEthereum.ts index e4c294214ff0..27354de35bce 100644 --- a/packages/injected-script/sdk/bridgedEthereum.ts +++ b/packages/injected-script/sdk/bridgedEthereum.ts @@ -1,5 +1,6 @@ import type { RequestArguments } from 'web3-core' import type { JsonRpcPayload, JsonRpcResponse } from 'web3-core-helpers' +import type { EthereumProvider } from '../shared' import { createPromise, sendEvent } from './utils' function request(data: RequestArguments) { @@ -27,7 +28,7 @@ function send(payload: JsonRpcPayload, callback: (error: Error | null, result?: } /** Interact with the current ethereum provider */ -export const bridgedEthereumProvider: BridgedEthereumProvider = { +export const bridgedEthereumProvider: EthereumProvider = { request, send, sendAsync: send, @@ -43,31 +44,11 @@ export const bridgedEthereumProvider: BridgedEthereumProvider = { getProperty(key) { return createPromise((id) => sendEvent('ethBridgePrimitiveAccess', id, key)) }, - isConnected() { - return createPromise((id) => sendEvent('ethBridgeIsConnected', id)) - }, untilAvailable() { return createPromise((id) => sendEvent('untilEthBridgeOnline', id)) }, } -export interface BridgedEthereumProvider { - /** Wait for window.ethereum object appears. */ - untilAvailable(): Promise - /** Send JSON RPC to the eth provider. */ - request(data: RequestArguments): Promise - /** Send JSON RPC */ - send(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void - /** Async send JSON RPC */ - sendAsync(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void - /** Add event listener */ - on(event: string, callback: (...args: any) => void): () => void - /** Access primitive property on the window.ethereum object. */ - getProperty( - key: 'isMaskWallet' | 'isMetaMask' | 'isCoin98' | 'isMathWallet' | 'isWalletLink', - ): Promise - /** Call window.ethereum.isConnected() */ - isConnected(): Promise -} + const bridgedEthereum = new Map>() /** @internal */ export function onEthEvent(event: string, data: unknown[]) { diff --git a/packages/injected-script/sdk/index.ts b/packages/injected-script/sdk/index.ts index db2db3e8d338..d2531742305e 100644 --- a/packages/injected-script/sdk/index.ts +++ b/packages/injected-script/sdk/index.ts @@ -5,10 +5,7 @@ import { onSolanaEvent } from './bridgedSolana' import { sendEvent, rejectPromise, resolvePromise } from './utils' export { bridgedEthereumProvider } from './bridgedEthereum' -export type { BridgedEthereumProvider } from './bridgedEthereum' - export { bridgedCoin98Provider } from './bridgedCoin98' -export type { BridgedCoin98Provider } from './bridgedCoin98' export function pasteText(text: string) { sendEvent('paste', text) @@ -31,6 +28,11 @@ document.addEventListener(CustomEventId, (e) => { if (r[1].length < 1) return switch (r[0]) { + case 'resolvePromise': + return resolvePromise(...r[1]) + case 'rejectPromise': + return rejectPromise(...r[1]) + case 'ethBridgeOnEvent': return onEthEvent(...r[1]) case 'coin98BridgeOnEvent': @@ -38,22 +40,18 @@ document.addEventListener(CustomEventId, (e) => { case 'solanaBridgeOnEvent': return onSolanaEvent(...r[1]) - case 'resolvePromise': - return resolvePromise(...r[1]) - case 'rejectPromise': - return rejectPromise(...r[1]) case 'ethBridgeSendRequest': - case 'ethBridgeIsConnected': case 'ethBridgePrimitiveAccess': case 'ethBridgeRequestListen': + case 'coin98BridgeSendRequest': - case 'coin98BridgeIsConnected': case 'coin98BridgePrimitiveAccess': case 'coin98BridgeRequestListen': + case 'solanaBridgeSendRequest': - case 'solanaBridgeIsConnected': case 'solanaBridgePrimitiveAccess': case 'solanaBridgeRequestListen': + case 'input': case 'paste': case 'pasteImage': diff --git a/packages/injected-script/shared/index.ts b/packages/injected-script/shared/index.ts index d881bb4cccc1..54ca31854b2a 100644 --- a/packages/injected-script/shared/index.ts +++ b/packages/injected-script/shared/index.ts @@ -1,3 +1,6 @@ +import type { RequestArguments } from 'web3-core' +import type { JsonRpcPayload, JsonRpcResponse } from 'web3-core-helpers' + export const CustomEventId = 'c8a6c18e-f6a3-472a-adf3-5335deb80db6' export interface InternalEvents { /** Simulate a paste event on the activeElement */ @@ -25,8 +28,6 @@ export interface InternalEvents { ethBridgeSendRequest: [req_id: number, request: unknown] /** Access primitive property on the window.ethereum object. */ ethBridgePrimitiveAccess: [req_id: number, property: string] - /** Call window.ethereum.isConnected() */ - ethBridgeIsConnected: [req_id: number] /** Wait until window.ethereum appears */ untilEthBridgeOnline: [req_id: number] //#endregion @@ -40,8 +41,6 @@ export interface InternalEvents { coin98BridgeSendRequest: [req_id: number, request: unknown] /** Access primitive property on the window.ethereum object. */ coin98BridgePrimitiveAccess: [req_id: number, property: string] - /** Call window.ethereum.isConnected() */ - coin98BridgeIsConnected: [req_id: number] /** Wait until window.ethereum appears */ untilCoin98BridgeOnline: [req_id: number] //#endregion @@ -55,8 +54,6 @@ export interface InternalEvents { solanaBridgeSendRequest: [req_id: number, request: unknown] /** Access primitive property on the window.solana object. */ solanaBridgePrimitiveAccess: [req_id: number, property: string] - /** Call window.solana.isConnected() */ - solanaBridgeIsConnected: [req_id: number] /** Wait until window.solana appears */ untilSolanaBridgeOnline: [req_id: number] //#endregion @@ -68,6 +65,21 @@ export interface InternalEvents { rejectPromise: [req_id: number, error: unknown] } +export interface EthereumProvider { + /** Wait for ethereum object appears. */ + untilAvailable(): Promise + /** Send JSON RPC to the ethereum provider. */ + request(data: RequestArguments): Promise + /** Send JSON RPC */ + send(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void + /** Async send JSON RPC */ + sendAsync(payload: JsonRpcPayload, callback: (error: Error | null, result?: JsonRpcResponse) => void): void + /** Add event listener */ + on(event: string, callback: (...args: any) => void): () => void + /** Access primitive property on the ethereum object. */ + getProperty(key: string): Promise +} + export type EventItemBeforeSerialization = keyof InternalEvents extends infer U ? U extends keyof InternalEvents ? readonly [U, InternalEvents[U]] diff --git a/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx b/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx index 701e03398d8b..73e575532b10 100644 --- a/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/EVM/UI/SNSAdaptor/index.tsx @@ -1,8 +1,8 @@ import type { Plugin } from '@masknet/plugin-infra' import { base } from '../../base' import { Web3UI } from '../Web3UI' -import { InjectedProviderBridge } from '../components/InjectedProviderBridge' import { setupStorage, StorageDefaultValue } from '../../storage' +import { InjectedProviderBridge } from '../components/InjectedProviderBridge' import { FortmaticProviderBridge } from '../components/FortmaticProviderBridge' const sns: Plugin.SNSAdaptor.Definition = { diff --git a/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx b/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx index 3bd3f70005c9..e7cef4e7ee97 100644 --- a/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx +++ b/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx @@ -9,10 +9,9 @@ import { NetworkType, ProviderType, resolveProviderDownloadLink, - useWallets, } from '@masknet/web3-shared-evm' import { WalletMessages } from '../../../Wallet/messages' -import { useInjectedProviderReady, useInjectedProviderType } from '../../hooks' +import { useInjectedProviderType } from '../../hooks' export function ProviderIconClickBait({ network, @@ -34,14 +33,18 @@ export function ProviderIconClickBait({ const providerType = provider.type as ProviderType const networkType = network.type as NetworkType - const wallets = useWallets(ProviderType.MaskWallet) - const injectedProviderType = useInjectedProviderType() - const injectedProviderReady = useInjectedProviderReady() + const injectedEthereumProviderType = useInjectedProviderType('ethereum') + const injectedCoin98ProviderType = useInjectedProviderType('coin98') const onClickProvider = useCallback(async () => { // open the download page if (isInjectedProvider(providerType)) { - if (!injectedProviderReady || providerType !== injectedProviderType) { + const isProviderAvailable = + providerType === ProviderType.Coin98 + ? providerType === injectedCoin98ProviderType + : providerType === injectedEthereumProviderType + + if (!isProviderAvailable) { const downloadLink = resolveProviderDownloadLink(providerType) if (downloadLink) window.open(downloadLink, '_blank', 'noopener noreferrer') return @@ -68,7 +71,7 @@ export function ProviderIconClickBait({ unreachable(providerType) } onClick?.(network, provider) - }, [network, provider, wallets, injectedProviderReady, injectedProviderType, onClick]) + }, [network, provider, injectedEthereumProviderType, injectedCoin98ProviderType, onClick]) // hide injected provider in dashboard if (isInjectedProvider(providerType) && location.href.includes('dashboard.html')) return null diff --git a/packages/mask/src/plugins/EVM/hooks/index.ts b/packages/mask/src/plugins/EVM/hooks/index.ts index fa43c3ae4a4c..8da73f029d30 100644 --- a/packages/mask/src/plugins/EVM/hooks/index.ts +++ b/packages/mask/src/plugins/EVM/hooks/index.ts @@ -1,5 +1,5 @@ export * from './useNetwork' export * from './useProvider' -export * from './useInjectedProviderConnected' +export * from './useBridgedProvider' export * from './useInjectedProviderReady' export * from './useInjectedProviderType' diff --git a/packages/mask/src/plugins/EVM/hooks/useBridgedProvider.ts b/packages/mask/src/plugins/EVM/hooks/useBridgedProvider.ts new file mode 100644 index 000000000000..88c732f1e6c6 --- /dev/null +++ b/packages/mask/src/plugins/EVM/hooks/useBridgedProvider.ts @@ -0,0 +1,16 @@ +import { useMemo } from 'react' +import { unreachable } from '@dimensiondev/kit' +import { bridgedCoin98Provider, bridgedEthereumProvider } from '@masknet/injected-script' + +export function useBridgedProvider(type: 'ethereum' | 'coin98') { + return useMemo(() => { + switch (type) { + case 'ethereum': + return bridgedEthereumProvider + case 'coin98': + return bridgedCoin98Provider + default: + unreachable(type) + } + }, [type]) +} diff --git a/packages/mask/src/plugins/EVM/hooks/useInjectedProviderConnected.ts b/packages/mask/src/plugins/EVM/hooks/useInjectedProviderConnected.ts deleted file mode 100644 index 38e0e558afb6..000000000000 --- a/packages/mask/src/plugins/EVM/hooks/useInjectedProviderConnected.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useAsyncRetry } from 'react-use' -import { bridgedEthereumProvider } from '@masknet/injected-script' - -export function useInjectedProviderConnected() { - const { value: injectedProviderConnected = false } = useAsyncRetry(async () => { - return bridgedEthereumProvider.isConnected() - }, []) - return injectedProviderConnected -} diff --git a/packages/mask/src/plugins/EVM/hooks/useInjectedProviderReady.ts b/packages/mask/src/plugins/EVM/hooks/useInjectedProviderReady.ts index 013f9dbab6f7..a8645bb3075f 100644 --- a/packages/mask/src/plugins/EVM/hooks/useInjectedProviderReady.ts +++ b/packages/mask/src/plugins/EVM/hooks/useInjectedProviderReady.ts @@ -1,9 +1,10 @@ import { useAsyncRetry } from 'react-use' -import { bridgedEthereumProvider } from '@masknet/injected-script' +import { useBridgedProvider } from './useBridgedProvider' -export function useInjectedProviderReady() { +export function useInjectedProviderReady(type: 'ethereum' | 'coin98') { + const bridgedProvider = useBridgedProvider(type) const { value: available = false } = useAsyncRetry(async () => { - return bridgedEthereumProvider.untilAvailable() - }, []) + return bridgedProvider.untilAvailable() + }, [bridgedProvider]) return available } diff --git a/packages/mask/src/plugins/EVM/hooks/useInjectedProviderType.ts b/packages/mask/src/plugins/EVM/hooks/useInjectedProviderType.ts index a8ddc0c390d8..b944a24895e0 100644 --- a/packages/mask/src/plugins/EVM/hooks/useInjectedProviderType.ts +++ b/packages/mask/src/plugins/EVM/hooks/useInjectedProviderType.ts @@ -1,30 +1,31 @@ import { useAsyncRetry } from 'react-use' import { ProviderType } from '@masknet/web3-shared-evm' -import { bridgedEthereumProvider } from '@masknet/injected-script' +import { useBridgedProvider } from './useBridgedProvider' import { useInjectedProviderReady } from './useInjectedProviderReady' -export function useInjectedProviderType() { - const injectedProviderReady = useInjectedProviderReady() +export function useInjectedProviderType(type: 'ethereum' | 'coin98') { + const bridgedProvider = useBridgedProvider(type) + const injectedProviderReady = useInjectedProviderReady(type) const { value: injectedProviderType } = useAsyncRetry(async () => { if (!injectedProviderReady) return // WalletLink (Coinbase) - const isWalletLink = await bridgedEthereumProvider.getProperty('isWalletLink') + const isWalletLink = await bridgedProvider.getProperty('isWalletLink') if (isWalletLink) return ProviderType.WalletLink - // Coin98 - const isCoin98 = await bridgedEthereumProvider.getProperty('isCoin98') + // Coin98 under ethereum + const isCoin98 = await bridgedProvider.getProperty('isCoin98') if (isCoin98) return ProviderType.Coin98 // mathwallet - const isMathWallet = await bridgedEthereumProvider.getProperty('isMathWallet') + const isMathWallet = await bridgedProvider.getProperty('isMathWallet') if (isMathWallet) return ProviderType.MathWallet // metamask - const isMetaMask = await bridgedEthereumProvider.getProperty('isMetaMask') + const isMetaMask = await bridgedProvider.getProperty('isMetaMask') if (isMetaMask) return ProviderType.MetaMask return - }, [injectedProviderReady]) + }, [injectedProviderReady, bridgedProvider]) return injectedProviderType } diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx index 8a0402c0a2c8..700fd2952f25 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx @@ -18,7 +18,6 @@ import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { WalletMessages, WalletRPC } from '../../messages' import { ConnectionProgress } from './ConnectionProgress' import Services from '../../../../extension/service' -import { useInjectedProviderType } from '../../../EVM/hooks' const useStyles = makeStyles()((theme) => ({ content: { @@ -30,7 +29,6 @@ export interface ConnectWalletDialogProps {} export function ConnectWalletDialog(props: ConnectWalletDialogProps) { const classes = useStylesExtends(useStyles(), props) - const injectedProviderType = useInjectedProviderType() const [providerType, setProviderType] = useState() const [networkType, setNetworkType] = useState() @@ -138,7 +136,7 @@ export function ConnectWalletDialog(props: ConnectWalletDialogProps) { providerType, }) return true as const - }, [networkType, providerType, injectedProviderType]) + }, [networkType, providerType]) const connection = useAsyncRetry(async () => { if (!open) return true From 4e714dea159d15b096b5a55735d334c6ecb9699d Mon Sep 17 00:00:00 2001 From: Jack Works Date: Wed, 15 Dec 2021 13:08:07 +0800 Subject: [PATCH 3/3] chore: capture 2 intrinsics --- .../main/GlobalVariableBridge/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/injected-script/main/GlobalVariableBridge/index.ts b/packages/injected-script/main/GlobalVariableBridge/index.ts index 37ef2fb89bd2..061050201b5f 100644 --- a/packages/injected-script/main/GlobalVariableBridge/index.ts +++ b/packages/injected-script/main/GlobalVariableBridge/index.ts @@ -6,13 +6,15 @@ const hasListened: Record = { __proto__: null! } const { has } = Reflect const { Promise, setTimeout } = window const { resolve } = Promise +const { split } = String.prototype +const { shift } = Array.prototype -const read = (path: string) => { - const fragments = path.split('.') - let result = window as any +function read(path: string) { + const fragments = apply(split, path, ['.' as any]) + let result: any = window while (true) { if (fragments.length === 0) return result - const key = fragments.shift() + const key = apply(shift, fragments, []) result = key ? result[key] : result } } @@ -28,8 +30,6 @@ export function callRequest(path: string, id: number, request: unknown) { export function bindEvent(path: string, bridgeEvent: keyof InternalEvents, event: string) { if (hasListened[event]) return hasListened[event] = true - // Todo: wait until ethereum appears - // Note: DO NOT use intrinsics here because ethereum is not. read(path).on( event, clone_into((...args: any[]) => { @@ -50,5 +50,5 @@ function untilInner(name: string) { } export function until(path: string, id: number) { - handlePromise(id, untilInner.bind(null, path)) + handlePromise(id, () => untilInner(path)) }