bridgeRunner.dart 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter_inappwebview/flutter_inappwebview.dart';
  4. class BridgeRunner {
  5. HeadlessInAppWebView? _web;
  6. Function? _onLaunched;
  7. String? _jsCode;
  8. Map<String, Function> _msgHandlers = {};
  9. Map<String, Completer> _msgCompleters = {};
  10. Map<String, Function> _reloadHandlers = {};
  11. int _evalJavascriptUID = 0;
  12. bool webViewLoaded = false;
  13. int jsCodeStarted = -1;
  14. Timer? _webViewReloadTimer;
  15. Future<void>? dispose() async {
  16. return _web?.dispose();
  17. }
  18. Future<void> launch(
  19. Function? onLaunched, {
  20. String? jsCode,
  21. Function? socketDisconnectedAction,
  22. }) async {
  23. /// reset state before webView launch or reload
  24. _msgHandlers = {};
  25. _msgCompleters = {};
  26. _reloadHandlers = {};
  27. _evalJavascriptUID = 0;
  28. _onLaunched = onLaunched;
  29. webViewLoaded = false;
  30. jsCodeStarted = -1;
  31. _jsCode = jsCode;
  32. if (_web == null) {
  33. _web = new HeadlessInAppWebView(
  34. windowId: 2,
  35. initialOptions: InAppWebViewGroupOptions(
  36. crossPlatform: InAppWebViewOptions(clearCache: true),
  37. android: AndroidInAppWebViewOptions(useOnRenderProcessGone: true),
  38. ),
  39. androidOnRenderProcessGone: (webView, detail) async {
  40. if (_web?.webViewController == webView) {
  41. webViewLoaded = false;
  42. await _web?.webViewController.clearCache();
  43. await _web?.webViewController.reload();
  44. }
  45. },
  46. initialUrlRequest: URLRequest(
  47. url: Uri.parse(
  48. "http://localhost:8080/packages/polkawallet_sdk/assets/bridge.html")),
  49. onWebViewCreated: (controller) async {
  50. print('Bridge HeadlessInAppWebView created!');
  51. controller.loadUrl(
  52. urlRequest: URLRequest(
  53. url: Uri.parse(
  54. "http://localhost:8080/packages/polkawallet_sdk/assets/bridge.html")));
  55. },
  56. onConsoleMessage: (controller, message) {
  57. print("CONSOLE MESSAGE: " + message.message);
  58. if (jsCodeStarted < 0) {
  59. try {
  60. final msg = jsonDecode(message.message);
  61. if (msg['path'] == 'log') {
  62. if (message.message.contains('js loaded')) {
  63. jsCodeStarted = 1;
  64. } else {
  65. jsCodeStarted = 0;
  66. }
  67. }
  68. } catch (err) {
  69. // ignore
  70. }
  71. }
  72. if (message.message.contains("WebSocket is not connected") &&
  73. socketDisconnectedAction != null) {
  74. socketDisconnectedAction();
  75. }
  76. if (message.messageLevel != ConsoleMessageLevel.LOG) return;
  77. try {
  78. var msg = jsonDecode(message.message);
  79. final String? path = msg['path'];
  80. if (_msgCompleters[path!] != null) {
  81. Completer handler = _msgCompleters[path]!;
  82. handler.complete(msg['data']);
  83. if (path.contains('uid=')) {
  84. _msgCompleters.remove(path);
  85. }
  86. }
  87. if (_msgHandlers[path] != null) {
  88. Function handler = _msgHandlers[path]!;
  89. handler(msg['data']);
  90. }
  91. } catch (_) {
  92. // ignore
  93. }
  94. },
  95. onLoadStop: (controller, url) async {
  96. print('webview loaded $url');
  97. if (webViewLoaded) return;
  98. _handleReloaded();
  99. await _startJSCode();
  100. },
  101. );
  102. await _web?.dispose();
  103. await _web?.run();
  104. } else {
  105. _webViewReloadTimer = Timer.periodic(Duration(seconds: 3), (timer) {
  106. _tryReload();
  107. });
  108. }
  109. }
  110. void _tryReload() {
  111. if (!webViewLoaded) {
  112. _web?.webViewController.reload();
  113. }
  114. }
  115. void _handleReloaded() {
  116. _webViewReloadTimer?.cancel();
  117. webViewLoaded = true;
  118. }
  119. Future<void> _startJSCode() async {
  120. // inject js file to webView
  121. if (_jsCode != null) {
  122. await _web!.webViewController.evaluateJavascript(source: _jsCode!);
  123. }
  124. _onLaunched!();
  125. _reloadHandlers.forEach((_, value) {
  126. value();
  127. });
  128. }
  129. int getEvalJavascriptUID() {
  130. return _evalJavascriptUID++;
  131. }
  132. Future<dynamic> evalJavascript(
  133. String code, {
  134. bool wrapPromise = true,
  135. bool allowRepeat = true,
  136. }) async {
  137. // check if there's a same request loading
  138. if (!allowRepeat) {
  139. for (String i in _msgCompleters.keys) {
  140. String call = code.split('(')[0];
  141. if (i.contains(call)) {
  142. print('request $call loading');
  143. return _msgCompleters[i]!.future;
  144. }
  145. }
  146. }
  147. if (!wrapPromise) {
  148. final res =
  149. await _web!.webViewController.evaluateJavascript(source: code);
  150. return res;
  151. }
  152. final c = new Completer();
  153. final uid = getEvalJavascriptUID();
  154. final method = 'uid=$uid;${code.split('(')[0]}';
  155. _msgCompleters[method] = c;
  156. final script = '$code.then(function(res) {'
  157. ' console.log(JSON.stringify({ path: "$method", data: res }));'
  158. '}).catch(function(err) {'
  159. ' console.log(JSON.stringify({ path: "log", data: {call: "$method", error: err.message} }));'
  160. '});';
  161. _web!.webViewController.evaluateJavascript(source: script);
  162. return c.future;
  163. }
  164. Future<void> subscribeMessage(
  165. String code,
  166. String channel,
  167. Function callback,
  168. ) async {
  169. addMsgHandler(channel, callback);
  170. evalJavascript(code);
  171. }
  172. void unsubscribeMessage(String channel) {
  173. print('unsubscribe $channel');
  174. final unsubCall = 'unsub$channel';
  175. _web!.webViewController
  176. .evaluateJavascript(source: 'window.$unsubCall && window.$unsubCall()');
  177. }
  178. void addMsgHandler(String channel, Function onMessage) {
  179. _msgHandlers[channel] = onMessage;
  180. }
  181. void removeMsgHandler(String channel) {
  182. _msgHandlers.remove(channel);
  183. }
  184. void subscribeReloadAction(String reloadKey, Function reloadAction) {
  185. _reloadHandlers[reloadKey] = reloadAction;
  186. }
  187. void unsubscribeReloadAction(String reloadKey) {
  188. _reloadHandlers.remove(reloadKey);
  189. }
  190. Future<void> reload() async {
  191. webViewLoaded = false;
  192. await _web?.webViewController.clearCache();
  193. return _web?.webViewController.reload();
  194. }
  195. }