以[email protected]
环境为例,开发Android应用。
MetaMask一键登录
示例Demo
import {WebView} from 'react-native-webview';
import {useRef} from 'react';
const 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 (
);
};
export default BrowserTab;
Demo演示
流程分析:
- 借助
react-native-webview
加载Dapp Web
; - 绑定
webviewRef
实例,便于后续通信; 在
injectedJavaScriptBeforeContentLoaded
内容加载之前注入JS脚本;Dapp
实现会判断isMetaMask
环境展示标识- 注入业务相关的符合eip-1102等规范的事件实现,如
eth_requestAccounts
、signTransaction
—— 在Dapp
中进行操作,会调用对应的注入方法;
绑定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
- 由于平台差异,
document.addEventListener("message", handler)
在Android
和IOS
环境中有所不同(https://github.com/react-native-webview/react-native-webview/issues/356)。
Metamask相关代码
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); };