diff --git a/example/pubspec.lock b/example/pubspec.lock index 6f14cb5..6854499 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -84,7 +84,7 @@ packages: path: ".." relative: true source: path - version: "0.8.3" + version: "0.8.5" flutter_lints: dependency: "direct dev" description: diff --git a/example/test/flutter_js_test.dart b/example/test/flutter_js_test.dart index 7488483..cdd4b4d 100644 --- a/example/test/flutter_js_test.dart +++ b/example/test/flutter_js_test.dart @@ -1,7 +1,14 @@ +// ignore_for_file: constant_identifier_names, unused_local_variable + import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart' show PlatformException; import 'package:flutter_js/flutter_js.dart'; import 'package:flutter_test/flutter_test.dart'; +String one = '1' * 514; +String two = '2' * 515; +String three = '3' * 516; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -17,6 +24,40 @@ void main() { } on Error catch (_) {} }); + Future longString() async { + final code = '''// js + (async function() { + let ret = '$one' + '$two' + '$three' + return ret + })() + '''; + + const int PROMISE_TIMEOUT = 120; + try { + var res = await jsRuntime.evaluateAsync(code, sourceUrl: ''); + jsRuntime.executePendingJob(); + final prom = await jsRuntime.handlePromise(res, + timeout: const Duration(seconds: PROMISE_TIMEOUT)); + var result = await prom.rawResult; + if (result is JSError || prom.isError) { + throw Exception('JSError: $result'); + } + return result; + } on PlatformException catch (e) { + debugPrint('Platform exception [runJsCodeAsync]: ${e.details}'); + rethrow; + } catch (e) { + debugPrint('Exception [runJsCodeAsync]: rethrow'); + rethrow; + } + } + + test('string concat with > 512 chars', () async { + final result = await longString(); + String match = one + two + three; + expect(result, equals(match)); + }); + test('evaluate javascript', () { final result = jsRuntime.evaluate('Math.pow(5,3)'); if (kDebugMode) { diff --git a/lib/flutter_js.dart b/lib/flutter_js.dart index 04621e1..ccdf768 100644 --- a/lib/flutter_js.dart +++ b/lib/flutter_js.dart @@ -10,14 +10,12 @@ import './extensions/handle_promises.dart'; import './quickjs/quickjs_runtime2.dart'; export './extensions/handle_promises.dart'; -//import 'package:flutter_js/quickjs-sync-server/quickjs_oasis_jsbridge.dart'; //import 'package:flutter_js/quickjs/quickjs_runtime.dart'; export './quickjs/quickjs_runtime.dart'; export './quickjs/quickjs_runtime2.dart'; export 'javascript_runtime.dart'; export 'js_eval_result.dart'; -export 'quickjs-sync-server/quickjs_oasis_jsbridge.dart'; // import condicional to not import ffi libraries when using web as target // import "something.dart" if (dart.library.io) "other.dart"; @@ -34,7 +32,6 @@ JavascriptRuntime getJavascriptRuntime({ int stackSize = extraArgs?['stackSize'] ?? 1024 * 1024; runtime = QuickJsRuntime2(stackSize: stackSize); // FlutterJs engine = FlutterJs(); - // runtime = QuickJsService(engine); } else if (Platform.isWindows) { runtime = QuickJsRuntime2(); } else if (Platform.isLinux) { @@ -49,14 +46,6 @@ JavascriptRuntime getJavascriptRuntime({ return runtime; } -// JavascriptRuntime getJavascriptRuntime({bool xhr = true}) { -// JavascriptRuntime runtime = JavascriptCoreRuntime(); -// // setFetchDebug(true); -// if (xhr) runtime.enableFetch(); -// runtime.enableHandlePromises(); -// return runtime; -// } - final Map _engineMap = {}; MethodChannel _methodChannel = const MethodChannel('io.abner.flutter_js') diff --git a/lib/quickjs-sync-server/quickjs_oasis_jsbridge.dart b/lib/quickjs-sync-server/quickjs_oasis_jsbridge.dart deleted file mode 100644 index 5681e9c..0000000 --- a/lib/quickjs-sync-server/quickjs_oasis_jsbridge.dart +++ /dev/null @@ -1,251 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:isolate'; -import 'dart:ui'; - -import 'package:flutter_js/flutter_js.dart'; -import 'package:sync_http/sync_http.dart'; - -// ReceivePort _callDartReceivePort = new ReceivePort(); - -// void _setupCallDartPorts() { -// IsolateNameServer.registerPortWithName( -// _callDartReceivePort.sendPort, 'QuickJsServiceCallDart'); - -// _callDartReceivePort.listen((portMessage) { -// final decodedMessage = (portMessage as String).split(':'); -// int idEngine = int.parse(decodedMessage[0]); -// String channel = decodedMessage[1]; -// String message = utf8.decode(base64.decode(decodedMessage[2])); -// }); -// } - -class QuickJsService extends JavascriptRuntime { - ReceivePort _receivePort = new ReceivePort(); - SendPort? _callServerSendPort; - - bool _ready = false; - String? _dartAddress; - - final FlutterJs _flutterJs; - - QuickJsService(this._flutterJs) { - _startServer(); - IsolateNameServer.registerPortWithName( - _receivePort.sendPort, 'QuickJsService'); - initChannelFunctions(); - } - - bool get isReady => _ready; - _startServer() async { - _receivePort.listen((message) async { - if (_dartAddress == null) { - _dartAddress = message; - _ready = true; - return; - } - if (_callServerSendPort == null) { - _callServerSendPort = message; - } - }); - Isolate.spawn(startQuickJsServer, _receivePort.sendPort); - } - - void dispose() { - this._callServerSendPort!.send('STOP'); - _flutterJs.dispose(); - } - - JsEvalResult evaluate(String code, {String? sourceUrl}) { - var request = SyncHttpClient.postUrl(new Uri.http( - "localhost:${FlutterJs.httpPort}", - "", - { - "id": _flutterJs.id.toString(), - "password": FlutterJs.httpPassword, - }, - )); - request..write(code); - var response = request.close(); - - var result = response.body!; - - try { - result = json.decode(result); - } catch (e) {} - - return JsEvalResult( - response.body != null && response.body!.isNotEmpty ? result : "", - null, - isPromise: response.body == 'isPromise', - isError: response.statusCode != 200, - ); - } - - @override - JsEvalResult callFunction(dynamic fn, dynamic obj) { - throw UnimplementedError( - 'Call function yet not implemented in FlutterJS through Platform Channel'); - // _flutterJs.callFunction(fn, obj); - } - - @override - T? convertValue(JsEvalResult jsValue) { - if (T is int) { - return int.parse(jsValue.stringResult) as T; - } else if (T is Map || T is List) { - return jsonDecode(jsValue.stringResult) as T?; - } else if (T is double) { - return double.parse(jsValue.stringResult) as T; - } else if (T is num) { - return num.parse(jsValue.stringResult) as T; - } - return jsValue.stringResult as T; - } - - @override - int executePendingJob() { - evaluate('(function(){})();'); - return 0; - } - - @override - String getEngineInstanceId() { - return _flutterJs.id.toString(); - } - - @override - void initChannelFunctions() { - JavascriptRuntime.channelFunctionsRegistered[getEngineInstanceId()] = {}; - } - - @override - String jsonStringify(JsEvalResult jsValue) { - return jsValue.stringResult; - } - - @override - void setInspectable(bool inspectable) { - // Nothing to do. - } - - @override - bool setupBridge(String channelName, dynamic Function(dynamic args) fn) { - // final channelFunctionCallbacks = - // JavascriptRuntime.channelFunctionsRegistered[getEngineInstanceId()]; - // if (channelFunctionCallbacks.keys.contains(channelName)) return false; - - // channelFunctionCallbacks[channelName] = fn; - _flutterJs.addChannel(channelName, (args) { - final mapArgs = json.decode(args!); - final res = fn(mapArgs); - this.evaluate(""" - FLUTTERJS_pendingMessages['${mapArgs['id']}'].resolve(${json.encode(res)}); - """ - .trim()); - return Future.value(res); - }, dartChannelAddress: 'http://$_dartAddress'); - - return true; - } - - @override - Future evaluateAsync(String code, {String? sourceUrl}) async { - String strResult = await _flutterJs.eval(code); - return JsEvalResult( - strResult, - null, - isError: strResult.startsWith('ERROR:'), - isPromise: strResult == '[object Promise]', - ); - } -} - -void startQuickJsServer(SendPort sendPort) async { - var server = new QuickJsSyncServer(); - - server.serve().then((address) { - sendPort.send(address); - sendPort.send(server.dispatchSendPort); - }); -} - -class QuickJsSyncServer { - /// Server address - InternetAddress? address; - - SendPort get dispatchSendPort => _dispatchPort.sendPort; - late ReceivePort _dispatchPort; - - ReceivePort _receiveCallDartResponsePort = ReceivePort(); - - /// Optional server port (note: might be already taken) - /// Defaults to 0 (binds server to a random free port) - late int port; - late HttpServer _server; - SyncHttpClient? client; - - QuickJsSyncServer() { - address = InternetAddress.loopbackIPv4; - port = 0; - _dispatchPort = new ReceivePort(); - - IsolateNameServer.registerPortWithName( - _receiveCallDartResponsePort.sendPort, - 'ReceiveCallDartResponsePort', - ); - - _dispatchPort.listen((message) { - if (message == 'STOP') { - _server.close(); - } - }); - } - - /// Actual port server is listening on - get boundPort => _server.port; - - /// Starts server - Future serve() async { - _server = await HttpServer.bind(this.address, this.port); - _server.listen(_handleReq); - return '${_server.address.address}:${_server.port}'; - } - - _handleReq(HttpRequest request) async { - String path = request.requestedUri.path.replaceFirst('/', ''); - - if (path == '') { - path = 'callDart'; - } - - if (path == 'callDart') { - try { - // if not is eval it should be call to Dart - String message = await utf8.decoder.bind(request).join(); - String? idEngine = request.uri.queryParameters['id']; - String? channel = request.uri.queryParameters['channel']; - - final callDartPort = - IsolateNameServer.lookupPortByName('QuickJsServiceCallDart')!; - - callDartPort - .send("$idEngine:$channel:${base64.encode(utf8.encode(message))}"); - - final result = await _receiveCallDartResponsePort.take(1).first; - - var response = request.response; - response - ..contentLength = result.length - ..write(result) - ..close(); - } catch (err) { - request.response - ..statusCode = 500 - ..write("ERROR") - ..close(); - } - } else {} - } -} diff --git a/lib/quickjs/ffi.dart b/lib/quickjs/ffi.dart index 8235d1b..a4e87f0 100644 --- a/lib/quickjs/ffi.dart +++ b/lib/quickjs/ffi.dart @@ -91,6 +91,7 @@ class JSTag { static const BIG_FLOAT = -9; static const SYMBOL = -8; static const STRING = -7; + static const STRING_ROPE = -6; static const MODULE = -3; /* used internally */ static const FUNCTION_BYTECODE = -2; /* used internally */ static const OBJECT = -1; diff --git a/lib/quickjs/quickjs_runtime2.dart b/lib/quickjs/quickjs_runtime2.dart index dc279b9..617ade9 100644 --- a/lib/quickjs/quickjs_runtime2.dart +++ b/lib/quickjs/quickjs_runtime2.dart @@ -189,7 +189,7 @@ class QuickJsRuntime2 extends JavascriptRuntime { final jsval = jsEval( ctx, command, - name ?? '', + sourceUrl ?? name ?? '', evalFlags ?? JSEvalFlag.GLOBAL, ); diff --git a/lib/quickjs/wrapper.dart b/lib/quickjs/wrapper.dart index 63e2a88..860c851 100644 --- a/lib/quickjs/wrapper.dart +++ b/lib/quickjs/wrapper.dart @@ -154,6 +154,8 @@ dynamic _jsToDart(Pointer ctx, Pointer val, return jsToInt64(ctx, val); case JSTag.STRING: return jsToCString(ctx, val); + case JSTag.STRING_ROPE: + return jsToCString(ctx, val); case JSTag.OBJECT: final rt = jsGetRuntime(ctx); final dartObjectClassId = runtimeOpaques[rt]?.dartObjectClassId; @@ -244,6 +246,7 @@ dynamic _jsToDart(Pointer ctx, Pointer val, return ret; } default: + // print('--- Undefined tag ---'); } return null; } diff --git a/linux/shared/libquickjs_c_bridge_plugin.so b/linux/shared/libquickjs_c_bridge_plugin.so index 4c699fb..1b0231d 100644 Binary files a/linux/shared/libquickjs_c_bridge_plugin.so and b/linux/shared/libquickjs_c_bridge_plugin.so differ