webviewEthInjected.dart 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/foundation.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/services.dart';
  7. import 'package:polkawallet_sdk/api/api.dart';
  8. import 'package:polkawallet_sdk/api/types/walletConnect/payloadData.dart';
  9. import 'package:polkawallet_sdk/consts/settings.dart';
  10. import 'package:polkawallet_sdk/service/eth/rpcApi.dart';
  11. import 'package:polkawallet_sdk/storage/keyringEVM.dart';
  12. import 'package:polkawallet_sdk/webviewWithExtension/types/signExtrinsicParam.dart';
  13. import 'package:url_launcher/url_launcher.dart';
  14. import 'package:webview_flutter/webview_flutter.dart';
  15. class WebViewEthInjected extends StatefulWidget {
  16. WebViewEthInjected(
  17. this.api,
  18. this.initialUrl,
  19. this.keyringEVM, {
  20. this.onPageFinished,
  21. this.onExtensionReady,
  22. this.onWebViewCreated,
  23. this.onSignRequest,
  24. this.onConnectRequest,
  25. this.checkAuth,
  26. });
  27. final String initialUrl;
  28. final PolkawalletApi api;
  29. final KeyringEVM keyringEVM;
  30. final Function(String)? onPageFinished;
  31. final Function? onExtensionReady;
  32. final Function(WebViewController)? onWebViewCreated;
  33. final Future<WCCallRequestResult?> Function(Map)? onSignRequest;
  34. final Future<bool?> Function(DAppConnectParam)? onConnectRequest;
  35. final bool Function(String)? checkAuth;
  36. @override
  37. _WebViewEthInjectedState createState() => _WebViewEthInjectedState();
  38. }
  39. class _WebViewEthInjectedState extends State<WebViewEthInjected> {
  40. late WebViewController _controller;
  41. bool _loadingFinished = false;
  42. bool _signing = false;
  43. Future<String> _respondToDApp(Map msg, Map res) async {
  44. print('respond ${msg['name']} to dapp:');
  45. print(res);
  46. return _controller.runJavascriptReturningResult(
  47. 'msgFromPolkawallet({name: "${msg['name']}", data: ${jsonEncode(res)}})');
  48. }
  49. Future<String> _msgHandler(Map msg) async {
  50. final res = {...(msg['data'] as Map)};
  51. res.remove('toNative');
  52. final uri = Uri.parse(msg['origin']);
  53. final method = res['method'];
  54. final isSigningMethod = SigningMethodsEVM.contains(method);
  55. if (!isSigningMethod) {
  56. final data = await EvmRpcApi.getRpcCall(
  57. widget.api.connectedNode?.endpoint ?? '', res);
  58. if (data['result'] != null) {
  59. res['result'] = data['result'];
  60. } else {
  61. res['error'] = ['unauthorized', 'Rpc call error.'];
  62. }
  63. return _respondToDApp(msg, res);
  64. }
  65. if (method != 'eth_requestAccounts' &&
  66. method != 'eth_accounts' &&
  67. widget.checkAuth != null &&
  68. !widget.checkAuth!(uri.host)) {
  69. res['error'] = ['unauthorized', 'wallet accounts unauthorized.'];
  70. return _respondToDApp(msg, res);
  71. }
  72. switch (method) {
  73. case 'eth_requestAccounts':
  74. case 'eth_accounts':
  75. if (_signing) break;
  76. _signing = true;
  77. final accept = await widget.onConnectRequest!(DAppConnectParam.fromJson(
  78. {'id': res['id'].toString(), 'url': msg['origin']}));
  79. _signing = false;
  80. if (accept == true) {
  81. res['result'] = [widget.keyringEVM.current.address];
  82. return _respondToDApp(msg, res);
  83. }
  84. res['error'] = [
  85. 'userRejectedRequest',
  86. 'User denied account authorization.'
  87. ];
  88. return _respondToDApp(msg, res);
  89. case 'metamask_getProviderState':
  90. final chainId = int.parse(widget.api.connectedNode?.chainId ?? '1');
  91. res['result'] = {
  92. 'accounts': [widget.keyringEVM.current.address],
  93. 'chainId': '0x${chainId.toRadixString(16)}',
  94. 'isUnlocked': true,
  95. 'networkVersion': '0',
  96. };
  97. return _respondToDApp(msg, res);
  98. case 'eth_chainId':
  99. final chainId = int.parse(widget.api.connectedNode?.chainId ?? '1');
  100. // Convert to hex
  101. res['result'] = '0x${chainId.toRadixString(16)}';
  102. return _respondToDApp(msg, res);
  103. case 'eth_sign':
  104. case 'personal_sign':
  105. case 'eth_signTypedData':
  106. case 'eth_signTypedData_v4':
  107. case 'eth_signTransaction':
  108. case 'eth_sendTransaction':
  109. if (_signing) break;
  110. _signing = true;
  111. final signed = await widget.onSignRequest!(msg);
  112. _signing = false;
  113. if (signed == null) {
  114. // cancelled
  115. res['error'] = ['userRejectedRequest', 'User rejected sign request.'];
  116. } else if (signed.result != null) {
  117. res['result'] = signed.result;
  118. } else {
  119. res['error'] = ['userRejectedRequest', signed.error];
  120. }
  121. return _respondToDApp(msg, res);
  122. default:
  123. print('Unknown message from dapp: ${msg['msgType']}');
  124. res['error'] = ['unauthorized', 'Method $method not support.'];
  125. return _respondToDApp(msg, res);
  126. }
  127. return Future(() => "");
  128. }
  129. Future<void> _onFinishLoad(String url) async {
  130. if (_loadingFinished) return;
  131. setState(() {
  132. _loadingFinished = true;
  133. });
  134. if (widget.onPageFinished != null) {
  135. widget.onPageFinished!(url);
  136. }
  137. print('Page loaded: $url');
  138. print('Inject dapp js code...');
  139. final jsCode = await rootBundle.loadString(
  140. 'packages/polkawallet_sdk/js_as_extension/dist/ethereum.js');
  141. await _controller.runJavascriptReturningResult(jsCode);
  142. print('js code injected');
  143. // final List temp = jsonDecode(
  144. // await _controller.runJavascriptReturningResult('Object.keys(window);'));
  145. // temp.forEach((e) => print(e));
  146. if (widget.onExtensionReady != null) {
  147. widget.onExtensionReady!();
  148. }
  149. }
  150. Future<void> _launchWalletConnectLink(Uri url) async {
  151. if (await canLaunchUrl(url)) {
  152. try {
  153. await launchUrl(url, mode: LaunchMode.externalApplication);
  154. } catch (err) {
  155. if (kDebugMode) {
  156. print(err);
  157. }
  158. }
  159. } else {
  160. debugPrint('Could not launch $url');
  161. }
  162. }
  163. @override
  164. Widget build(BuildContext context) {
  165. return WebView(
  166. initialUrl: widget.initialUrl,
  167. javascriptMode: JavascriptMode.unrestricted,
  168. onWebViewCreated: (WebViewController webViewController) {
  169. if (widget.onWebViewCreated != null) {
  170. widget.onWebViewCreated!(webViewController);
  171. }
  172. setState(() {
  173. _controller = webViewController;
  174. });
  175. },
  176. javascriptChannels: <JavascriptChannel>[
  177. JavascriptChannel(
  178. name: 'Extension',
  179. onMessageReceived: (JavascriptMessage message) {
  180. print('msg from dapp: ${message.message}');
  181. final msg = jsonDecode(message.message);
  182. if (msg['path'] != 'extensionRequest') return;
  183. _msgHandler(msg['data']);
  184. },
  185. ),
  186. ].toSet(),
  187. // onPageStarted: (String url) {
  188. // if (Platform.isAndroid) {
  189. // _onFinishLoad(url);
  190. // }
  191. // },
  192. onPageFinished: (String url) {
  193. _onFinishLoad(url);
  194. // if (Platform.isIOS) {
  195. // _onFinishLoad(url);
  196. // }
  197. },
  198. gestureNavigationEnabled: true,
  199. navigationDelegate: (NavigationRequest request) {
  200. if (request.url.startsWith('wc:')) {
  201. _launchWalletConnectLink(Uri.parse(request.url));
  202. return NavigationDecision.prevent;
  203. }
  204. return NavigationDecision.navigate;
  205. },
  206. );
  207. }
  208. }