以
react-native@0.71.7
环境为例,开发Android应用。
MetaMask一键登录
示例Demo
import {WebView} from 'react-native-webview';
import {useRef} from 'react';
const address = '<personal address>';
const injectedJavaScript = `
window.ethereum = {};
window.ethereum.isMetaMask = true;
window.ethereum.isConnected = function() {
console.log('-----------连接成功后调用---------')
return true
};
window.ethereum.wallet = {};
window.ethereum.wallet.address = '${address}';
window.ethereum.selectedAddress = '${address}';
window.ethereum.request = function(args = {}) {
console.log('---------Dapp交互触发该事件-----------', args)
const { method, params } = args
return window.ethereum.send(method, params)
};
window.ethereum.send = function(method, params) {
console.log('---------send-----------', method, params)
return new Promise(function(resolve, reject) {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'bsc',
payload: {
method: method,
params: params,
}
}));
document.addEventListener("message", function(event) {
/**
* wallet端主动调用postMessage触发该事件
* 将webviewRef.current.postMessage回调的event.data作为Promise的值返回
*/
const data = JSON.parse(event.data) || {}
if (data.type === 'ethereum' && data.payload.id === method) {
if (data.payload.error) {
reject(data.payload.error);
} else {
resolve(data.payload.result);
}
}
}, { once: true });
});
};
`;
const BrowserTab = function () {
const webviewRef = useRef(null);
const handleWebViewMessage = async function (event: any) {
/**
* Dapp端交互调用window.ethereum.request时触发
* 根据window.ReactNativeWebView.postMessage传递的不同参数,返回对应的结果
*/
const {data} = event.nativeEvent;
const {type, payload = {}} = JSON.parse(data) || {};
const {method} = payload;
console.log(webviewRef.current);
if (webviewRef.current) {
method === 'eth_requestAccounts' &&
webviewRef.current.postMessage(
JSON.stringify({
type: 'ethereum',
payload: {
id: 'eth_requestAccounts',
result: [address],
},
}),
'*',
);
method === 'eth_chainId' &&
webviewRef.current.postMessage(
JSON.stringify({
type: 'ethereum',
payload: {
id: 'eth_chainId',
result: 5,
},
}),
'*',
);
}
};
return (
<WebView
ref={webviewRef}
source={{uri: 'https://app.uniswap.org/'}}
style={{flex: 1}}
javaScriptEnabled={true}
onMessage={handleWebViewMessage}
injectedJavaScriptBeforeContentLoaded={injectedJavaScript}
/>
);
};
export default BrowserTab;
流程分析:
- 借助
react-native-webview
加载Dapp Web
; - 绑定
webviewRef
实例,便于后续通信; - 在
injectedJavaScriptBeforeContentLoaded
内容加载之前注入JS脚本;
-
Dapp
实现会判断isMetaMask
环境展示标识 - 注入业务相关的符合eip-1102等规范的事件实现,如
eth_requestAccounts
、signTransaction
—— 在Dapp
中进行操作,会调用对应的注入方法;
-
<WebView>
绑定onMessage={handleWebViewMessage}
,监听Dapp
通过window.ReactNativeWebView.postMessage
上报的消息; - 格式化
event.nativeEvent.data
数据,根据method
、params
进行对应的业务代码,通过webviewRef.current.postMessage
将结果返回;
-
webviewRef.current.postMessage
会触发注入脚本window.ethereum.send
方法中的document.addEventListener("message", handler)
回调逻辑; -
Dapp
端会通过Promise
接收返回结果,执行后续逻辑
Notes:
-
injectedJavaScriptBeforeContentLoaded
注入window.ethereum
后获取该属性为undefined
- Warning On Android, this may work, but it is not 100% reliable (see #1609 and #1099).
- 实战演练,发现
react-native-webview@11.13.0
注入无效,react-native-webview@12.1
注入有效
- 由于平台差异,
document.addEventListener("message", handler)
在Android
和IOS
环境中有所不同(https://github.com/react-native-webview/react-native-webview/issues/356)。
Metamask相关代码
-
webView组件渲染相关代码
Webview组件
其中,onMessage
代码实现:
const onMessage = ({ nativeEvent }) => {
let data = nativeEvent.data;
try {
data = typeof data === 'string' ? JSON.parse(data) : data;
if (!data || (!data.type && !data.name)) {
return;
}
if (data.name) {
const origin = new URL(nativeEvent.url).origin;
backgroundBridges.current.forEach((bridge) => {
const bridgeOrigin = new URL(bridge.url).origin;
bridgeOrigin === origin && bridge.onMessage(data);
});
return;
}
} catch (e) {
Logger.error(e, `Browser::onMessage on ${url.current}`);
}
};
const initializeBackgroundBridge = (urlBridge, isMainFrame) => {
const newBridge = new BackgroundBridge({
webview: webviewRef, // webview实例,可以通过postMessage向内部渲染的Dapp通信
url: urlBridge,
getRpcMethodMiddleware: ({ hostname, getProviderState }) =>
getRpcMethodMiddleware({
hostname,
getProviderState,
navigation: props.navigation,
// Website info
url,
title,
icon,
// Bookmarks
isHomepage,
// Show autocomplete
fromHomepage,
toggleUrlModal,
// Wizard
wizardScrollAdjusted,
tabId: props.id,
injectHomePageScripts,
}),
isMainFrame,
});
backgroundBridges.current.push(newBridge);
};
RpcMethod
其中,
rpcMethods
定义了一系列符合以太坊协议的方法:RpcMethod
网友评论