webviewWithExtension.dart 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'package:flutter/cupertino.dart';
  5. import 'package:flutter/foundation.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter/services.dart';
  8. import 'package:polkawallet_sdk/api/api.dart';
  9. import 'package:polkawallet_sdk/storage/keyring.dart';
  10. import 'package:polkawallet_sdk/storage/types/keyPairData.dart';
  11. import 'package:polkawallet_sdk/webviewWithExtension/types/signExtrinsicParam.dart';
  12. import 'package:url_launcher/url_launcher.dart';
  13. import 'package:webview_flutter/webview_flutter.dart';
  14. class WebViewWithExtension extends StatefulWidget {
  15. WebViewWithExtension(
  16. this.api,
  17. this.initialUrl,
  18. this.keyring, {
  19. this.onPageFinished,
  20. this.onExtensionReady,
  21. this.onWebViewCreated,
  22. this.onSignBytesRequest,
  23. this.onSignExtrinsicRequest,
  24. this.onConnectRequest,
  25. this.checkAuth,
  26. });
  27. final String initialUrl;
  28. final PolkawalletApi api;
  29. final Keyring keyring;
  30. final Function(String)? onPageFinished;
  31. final Function? onExtensionReady;
  32. final Function(WebViewController)? onWebViewCreated;
  33. final Future<ExtensionSignResult?> Function(SignAsExtensionParam)?
  34. onSignBytesRequest;
  35. final Future<ExtensionSignResult?> Function(SignAsExtensionParam)?
  36. onSignExtrinsicRequest;
  37. final Future<bool?> Function(DAppConnectParam)? onConnectRequest;
  38. final bool Function(String)? checkAuth;
  39. @override
  40. _WebViewWithExtensionState createState() => _WebViewWithExtensionState();
  41. }
  42. class _WebViewWithExtensionState extends State<WebViewWithExtension> {
  43. late WebViewController _controller;
  44. bool _loadingFinished = false;
  45. bool _signing = false;
  46. Future<String> _msgHandler(Map msg) async {
  47. final uri = Uri.parse(msg['url']);
  48. if (msg['msgType'] != 'pub(authorize.tab)' &&
  49. widget.checkAuth != null &&
  50. !widget.checkAuth!(uri.host)) {
  51. return _controller.runJavascriptReturningResult(
  52. 'walletExtension.onAppResponse("${msg['msgType']}${msg['id']}", null, new Error("Rejected"))');
  53. }
  54. switch (msg['msgType']) {
  55. case 'pub(authorize.tab)':
  56. if (widget.onConnectRequest == null) {
  57. return _controller.runJavascriptReturningResult(
  58. 'walletExtension.onAppResponse("${msg['msgType']}${msg['id']}", true)');
  59. }
  60. if (_signing) break;
  61. _signing = true;
  62. final accept = await widget.onConnectRequest!(
  63. DAppConnectParam.fromJson({'id': msg['id'], 'url': msg['url']}));
  64. _signing = false;
  65. return _controller.runJavascriptReturningResult(
  66. 'walletExtension.onAppResponse("${msg['msgType']}${msg['id']}", ${accept ?? false}, null)');
  67. case 'pub(accounts.list)':
  68. case 'pub(accounts.subscribe)':
  69. final List<KeyPairData> ls = widget.keyring.keyPairs;
  70. ls.retainWhere((e) => e.encoding!['content'][1] == 'sr25519');
  71. final List res = ls.map((e) {
  72. return {
  73. 'address': e.address,
  74. 'name': e.name,
  75. 'genesisHash': '',
  76. };
  77. }).toList();
  78. return _controller.runJavascriptReturningResult(
  79. 'walletExtension.onAppResponse("${msg['msgType']}${msg['id']}", ${jsonEncode(res)})');
  80. case 'pub(bytes.sign)':
  81. if (_signing) break;
  82. _signing = true;
  83. final SignAsExtensionParam param =
  84. SignAsExtensionParam.fromJson(msg as Map<String, dynamic>);
  85. final res = await widget.onSignBytesRequest!(param);
  86. _signing = false;
  87. if (res == null || res.signature == null) {
  88. // cancelled
  89. return _controller.runJavascriptReturningResult(
  90. 'walletExtension.onAppResponse("${param.msgType}${msg['id']}", null, new Error("Rejected"))');
  91. }
  92. return _controller.runJavascriptReturningResult(
  93. 'walletExtension.onAppResponse("${param.msgType}${msg['id']}", ${jsonEncode(res.toJson())})');
  94. case 'pub(extrinsic.sign)':
  95. if (_signing) break;
  96. _signing = true;
  97. final SignAsExtensionParam params =
  98. SignAsExtensionParam.fromJson(msg as Map<String, dynamic>);
  99. final result = await widget.onSignExtrinsicRequest!(params);
  100. _signing = false;
  101. if (result == null || result.signature == null) {
  102. // cancelled
  103. return _controller.runJavascriptReturningResult(
  104. 'walletExtension.onAppResponse("${params.msgType}${msg['id']}", null, new Error("Rejected"))');
  105. }
  106. return _controller.runJavascriptReturningResult(
  107. 'walletExtension.onAppResponse("${params.msgType}${msg['id']}", ${jsonEncode(result.toJson())})');
  108. default:
  109. print('Unknown message from dapp: ${msg['msgType']}');
  110. return Future(() => "");
  111. }
  112. return Future(() => "");
  113. }
  114. Future<void> _onFinishLoad(String url) async {
  115. if (_loadingFinished) return;
  116. setState(() {
  117. _loadingFinished = true;
  118. });
  119. if (widget.onPageFinished != null) {
  120. widget.onPageFinished!(url);
  121. }
  122. print('Page loaded: $url');
  123. print('Inject extension js code...');
  124. final jsCode = await rootBundle
  125. .loadString('packages/polkawallet_sdk/js_as_extension/dist/main.js');
  126. _controller.runJavascriptReturningResult(jsCode);
  127. print('js code injected');
  128. if (widget.onExtensionReady != null) {
  129. widget.onExtensionReady!();
  130. }
  131. }
  132. Future<void> _launchWalletConnectLink(Uri url) async {
  133. if (await canLaunchUrl(url)) {
  134. try {
  135. await launchUrl(url, mode: LaunchMode.externalApplication);
  136. } catch (err) {
  137. if (kDebugMode) {
  138. print(err);
  139. }
  140. }
  141. } else {
  142. debugPrint('Could not launch $url');
  143. }
  144. }
  145. @override
  146. Widget build(BuildContext context) {
  147. return WebView(
  148. initialUrl: widget.initialUrl,
  149. javascriptMode: JavascriptMode.unrestricted,
  150. onWebViewCreated: (WebViewController webViewController) {
  151. if (widget.onWebViewCreated != null) {
  152. widget.onWebViewCreated!(webViewController);
  153. }
  154. setState(() {
  155. _controller = webViewController;
  156. });
  157. },
  158. javascriptChannels: <JavascriptChannel>[
  159. JavascriptChannel(
  160. name: 'Extension',
  161. onMessageReceived: (JavascriptMessage message) {
  162. print('msg from dapp: ${message.message}');
  163. final msg = jsonDecode(message.message);
  164. if (msg['path'] != 'extensionRequest') return;
  165. _msgHandler(msg['data']);
  166. },
  167. ),
  168. ].toSet(),
  169. onPageStarted: (String url) {
  170. if (Platform.isAndroid) {
  171. _onFinishLoad(url);
  172. }
  173. },
  174. onPageFinished: (String url) {
  175. if (Platform.isIOS) {
  176. _onFinishLoad(url);
  177. }
  178. },
  179. gestureNavigationEnabled: true,
  180. navigationDelegate: (NavigationRequest request) {
  181. if (request.url.startsWith('wc:')) {
  182. _launchWalletConnectLink(Uri.parse(request.url));
  183. return NavigationDecision.prevent;
  184. }
  185. return NavigationDecision.navigate;
  186. },
  187. );
  188. }
  189. }