123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- import 'dart:async';
- import 'dart:convert';
- import 'package:flutter/services.dart';
- import 'package:flutter_inappwebview/flutter_inappwebview.dart';
- import 'package:polkawallet_sdk/api/types/networkParams.dart';
- import 'package:polkawallet_sdk/service/keyring.dart';
- import 'package:polkawallet_sdk/storage/keyring.dart';
- class WebViewRunner {
- HeadlessInAppWebView? _web;
- Function? _onLaunched;
- late String _jsCode;
- late String _jsCodeEth;
- Map<String, Function> _msgHandlers = {};
- Map<String, Completer> _msgCompleters = {};
- int _evalJavascriptUID = 0;
- bool webViewLoaded = false;
- int jsCodeStarted = -1;
- Timer? _webViewReloadTimer;
- Future<void> launch(
- ServiceKeyring? keyring,
- Keyring keyringStorage,
- Function? onLaunched, {
- String? jsCode,
- Function? socketDisconnectedAction,
- }) async {
- /// reset state before webView launch or reload
- _msgHandlers = {};
- _msgCompleters = {};
- _evalJavascriptUID = 0;
- _onLaunched = onLaunched;
- webViewLoaded = false;
- jsCodeStarted = -1;
- _jsCode = jsCode ??
- await rootBundle
- .loadString('packages/polkawallet_sdk/js_api/dist/main.js');
- print('js file loaded');
- _jsCodeEth = await rootBundle
- .loadString('packages/polkawallet_sdk/js_api_eth/dist/main.js');
- print('js eth file loaded');
- if (_web == null) {
- await _startLocalServer();
- _web = new HeadlessInAppWebView(
- initialOptions: InAppWebViewGroupOptions(
- crossPlatform: InAppWebViewOptions(clearCache: true),
- ),
- initialUrlRequest: URLRequest(
- url: Uri.parse("http://localhost:8080/assets/index.html")),
- onWebViewCreated: (controller) {
- print('HeadlessInAppWebView created!');
- },
- onConsoleMessage: (controller, message) {
- print("CONSOLE MESSAGE: " + message.message);
- if (jsCodeStarted < 0) {
- if (message.message.contains('js loaded')) {
- jsCodeStarted = 1;
- } else {
- jsCodeStarted = 0;
- }
- }
- if (message.message.contains("WebSocket is not connected") &&
- socketDisconnectedAction != null) {
- socketDisconnectedAction();
- }
- if (message.messageLevel != ConsoleMessageLevel.LOG) return;
- try {
- var msg = jsonDecode(message.message);
- final String? path = msg['path'];
- if (_msgCompleters[path!] != null) {
- Completer handler = _msgCompleters[path]!;
- handler.complete(msg['data']);
- if (path.contains('uid=')) {
- _msgCompleters.remove(path);
- }
- }
- if (_msgHandlers[path] != null) {
- Function handler = _msgHandlers[path]!;
- handler(msg['data']);
- }
- } catch (_) {
- // ignore
- }
- },
- onLoadStop: (controller, url) async {
- print('webview loaded $url');
- if (webViewLoaded) return;
- _handleReloaded();
- await _startJSCode(keyring, keyringStorage);
- },
- );
- await _web?.dispose();
- await _web?.run();
- } else {
- _webViewReloadTimer = Timer.periodic(Duration(seconds: 3), (timer) {
- _tryReload();
- });
- }
- }
- void _tryReload() {
- if (!webViewLoaded) {
- _web?.webViewController.reload();
- }
- }
- void _handleReloaded() {
- _webViewReloadTimer?.cancel();
- webViewLoaded = true;
- }
- Future<void> _startLocalServer() async {
- final localhostServer = new InAppLocalhostServer();
- await localhostServer.start();
- }
- Future<void> _startJSCode(
- ServiceKeyring? keyring, Keyring keyringStorage) async {
- // inject js file to webView
- await _web!.webViewController.evaluateJavascript(source: _jsCode);
- await _web!.webViewController.evaluateJavascript(source: _jsCodeEth);
- _onLaunched!();
- }
- int getEvalJavascriptUID() {
- return _evalJavascriptUID++;
- }
- Future<dynamic> evalJavascript(
- String code, {
- bool wrapPromise = true,
- bool allowRepeat = true,
- }) async {
- // check if there's a same request loading
- if (!allowRepeat) {
- for (String i in _msgCompleters.keys) {
- String call = code.split('(')[0];
- if (i.contains(call)) {
- print('request $call loading');
- return _msgCompleters[i]!.future;
- }
- }
- }
- if (!wrapPromise) {
- final res =
- await _web!.webViewController.evaluateJavascript(source: code);
- return res;
- }
- final c = new Completer();
- final uid = getEvalJavascriptUID();
- final method = 'uid=$uid;${code.split('(')[0]}';
- _msgCompleters[method] = c;
- final script = '$code.then(function(res) {'
- ' console.log(JSON.stringify({ path: "$method", data: res }));'
- '}).catch(function(err) {'
- ' console.log(JSON.stringify({ path: "log", data: {call: "$method", error: err.message} }));'
- '});';
- _web!.webViewController.evaluateJavascript(source: script);
- return c.future;
- }
- Future<NetworkParams?> connectNode(List<NetworkParams> nodes) async {
- final isAvatarSupport = (await evalJavascript(
- 'settings.connectAll ? {}:null',
- wrapPromise: false)) !=
- null;
- final dynamic res = await (isAvatarSupport
- ? evalJavascript(
- 'settings.connectAll(${jsonEncode(nodes.map((e) => e.endpoint).toList())})')
- : evalJavascript(
- 'settings.connect(${jsonEncode(nodes.map((e) => e.endpoint).toList())})'));
- if (res != null) {
- final index = nodes.indexWhere((e) => e.endpoint!.trim() == res.trim());
- return nodes[index > -1 ? index : 0];
- }
- return null;
- }
- Future<void> subscribeMessage(
- String code,
- String channel,
- Function callback,
- ) async {
- addMsgHandler(channel, callback);
- evalJavascript(code);
- }
- void unsubscribeMessage(String channel) {
- print('unsubscribe $channel');
- final unsubCall = 'unsub$channel';
- _web!.webViewController
- .evaluateJavascript(source: 'window.$unsubCall && window.$unsubCall()');
- }
- void addMsgHandler(String channel, Function onMessage) {
- _msgHandlers[channel] = onMessage;
- }
- void removeMsgHandler(String channel) {
- _msgHandlers.remove(channel);
- }
- }
|