Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix JavaScript bindings on the web #468

Merged
merged 20 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions flutter_package/example/native/hub/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ crate-type = ["lib", "cdylib", "staticlib"]
rinf = "7.0.4"
prost = "0.13.0"
tokio = { version = "1", features = ["rt", "sync", "time", "macros"] }
tokio_with_wasm = { version = "0.7.1", features = [
tokio_with_wasm = { version = "0.7.2", features = [
"rt",
"sync",
"time",
"macros",
] }
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2.95"
messages = "0.3.1"
anyhow = "1.0.89"
sample_crate = { path = "../sample_crate" }
2 changes: 1 addition & 1 deletion flutter_package/example/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<meta name="description" content="A new Flutter project.">

<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
Expand Down
13 changes: 5 additions & 8 deletions flutter_package/lib/src/interface_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import 'load_web.dart';
import 'dart:typed_data';
import 'dart:js' as js;
import 'interface.dart';
import 'dart:async';
import 'dart:convert';
Expand All @@ -16,11 +15,11 @@ void setCompiledLibPathReal(String path) {
Future<void> prepareInterfaceReal(
AssignRustSignal assignRustSignal,
) async {
// Load the JavaScript module.
await loadJsFile();

// Listen to Rust via JavaScript
final jsObject = js.context['rinf'] as js.JsObject;
jsObject['send_rust_signal_extern'] = (
// Listen to Rust via JavaScript.
rinfBindingsObject['send_rust_signal_extern'] = (
int messageId,
Uint8List messageBytes,
Uint8List binary,
Expand All @@ -39,8 +38,7 @@ void startRustLogicReal() {
if (wasAlreadyLoaded) {
return;
}
final jsObject = js.context['rinf'] as js.JsObject;
jsObject.callMethod('start_rust_logic_extern', []);
wasmBindingsObject.callMethod('start_rust_logic_extern', []);
}

void stopRustLogicReal() {
Expand All @@ -52,8 +50,7 @@ void sendDartSignalReal(
Uint8List messageBytes,
Uint8List binary,
) {
final jsObject = js.context['rinf'] as js.JsObject;
jsObject.callMethod('send_dart_signal_extern', [
wasmBindingsObject.callMethod('send_dart_signal_extern', [
messageId,
messageBytes,
binary,
Expand Down
29 changes: 20 additions & 9 deletions flutter_package/lib/src/load_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,33 @@ import 'dart:async';

String? jsLibPath;

// When Dart performs hot restart,
// the `rinf` object is already defined
// as a global JavaScript variable.
final wasAlreadyLoaded = js.context.hasProperty('rinf');

void setJsLibPath(String path) {
jsLibPath = path;
}

bool wasAlreadyLoaded = false;
js.JsObject rinfBindingsObject = js.context['rinfBindings'];
js.JsObject wasmBindingsObject = js.context['wasmBindings'];

Future<void> loadJsFile() async {
// When Dart performs hot restart,
// the `rinfBindings` JavaScript object is already defined
// as a global JavaScript variable.
wasAlreadyLoaded = js.context.hasProperty('rinfBindings');

// Stop loading if it already has been done.
if (wasAlreadyLoaded) {
return;
}

// Create the namespace JavaScript object.
// This namespace object is used by Rust
// to call functions defined in Dart.
js.context['rinfBindings'] = js.JsObject.jsify({});

// Prepare to await the module load.
final loadCompleter = Completer<void>();
js.context['completeRinfLoad'] = loadCompleter.complete;
rinfBindingsObject['completeRinfLoad'] = loadCompleter.complete;

// Flutter app doesn't always have the top-level path of the domain.
// Sometimes, the flutter app might be placed in a lower path.
Expand All @@ -36,10 +47,10 @@ Future<void> loadJsFile() async {
scriptElement.type = 'module';
scriptElement.innerHtml = '''
import init, * as wasmBindings from "$fullUrl";
globalThis.wasmBindings = wasmBindings;
await init();
window.rinf = { ...wasmBindings };
completeRinfLoad();
delete window.completeRinfLoad;
rinfBindings.completeRinfLoad();
delete rinfBindings.completeRinfLoad;
''';
document.head!.append(scriptElement);

Expand Down
4 changes: 2 additions & 2 deletions flutter_package/template/native/hub/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ prost = "0.13.0"
tokio = { version = "1", features = ["rt", "macros"] }

# Uncomment below to target the web.
# tokio_with_wasm = { version = "0.7.1", features = ["rt", "macros"] }
# wasm-bindgen = "0.2.93"
# tokio_with_wasm = { version = "0.7.2", features = ["rt", "macros"] }
# wasm-bindgen = "0.2.95"
4 changes: 2 additions & 2 deletions rust_crate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ backtrace = { version = "0.3.69", optional = true }

[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.70"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
wasm-bindgen = "0.2.95"
wasm-bindgen-futures = "0.4.45"

[lints.clippy]
unwrap_used = "deny"
Expand Down
4 changes: 4 additions & 0 deletions rust_crate/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub enum RinfError {
NoDartIsolate,
CannotDecodeMessage,
NoSignalHandler,
NoBindings,
}

impl fmt::Display for RinfError {
Expand All @@ -20,6 +21,9 @@ impl fmt::Display for RinfError {
Self::NoSignalHandler => {
write!(f, "Could not find the handler for Dart signal")
}
Self::NoBindings => {
write!(f, "Rinf bindings are not ready")
}
}
}
}
Expand Down
21 changes: 11 additions & 10 deletions rust_crate/src/interface_web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,29 @@ where

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = rinf, catch)]
// The reason this extern function is marked `catch`
// and returns a `Result` is that the
// `rinfBindings` JavaScript object created by Dart
// does not exist in web workers; it is only available
// in the main JavaScript thread. Loading the function
// fails in web workers.
#[wasm_bindgen(js_namespace = rinfBindings, catch)]
pub fn send_rust_signal_extern(
resource: i32,
message_bytes: Uint8Array,
binary: Uint8Array,
) -> Result<(), JsValue>; // catch the JS exception
) -> Result<(), JsValue>;
}

pub fn send_rust_signal_real(
message_id: i32,
message_bytes: Vec<u8>,
binary: Vec<u8>,
) -> Result<(), RinfError> {
match send_rust_signal_extern(
let result = send_rust_signal_extern(
message_id,
js_sys::Uint8Array::from(message_bytes.as_slice()),
js_sys::Uint8Array::from(binary.as_slice()),
) {
Ok(_) => Ok(()),
Err(e) => {
crate::debug_print!("An error occured during the launch: {e:?}");
Err(RinfError::NoSignalHandler)
}
}
);
result.map_err(|_| RinfError::NoBindings)
}