diff --git a/Cargo.lock b/Cargo.lock index 7b0186a0..bfd04588 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "h2" version = "0.3.19" @@ -1444,6 +1455,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stringprep" version = "0.1.2" @@ -1906,15 +1923,17 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walrus" -version = "0.19.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8" +checksum = "2c03529cd0c4400a2449f640d2f27cd1b48c3065226d15e26d98e4429ab0adb7" dependencies = [ "anyhow", + "gimli", "id-arena", "leb128", "log", "walrus-macro", + "wasm-encoder", "wasmparser", ] @@ -1948,9 +1967,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1958,9 +1977,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -1973,9 +1992,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-cli-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21c60239a09bf9bab8dfa752be4e6c637db22296b9ded493800090448692da9" +checksum = "cf8226e223e2dfbe8f921b7f20b82d1b5d86a6b143e9d6286cca8edd16695583" dependencies = [ "anyhow", "base64 0.9.3", @@ -1995,9 +2014,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-externref-xform" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafbe1984f67cc12645f12ab65e6145e8ddce1ab265d0be58435f25bb0ce2608" +checksum = "b8a719be856d8b0802c7195ca26ee6eb02cb9639a12b80be32db960ce9640cb8" dependencies = [ "anyhow", "walrus", @@ -2017,9 +2036,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2027,9 +2046,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -2040,9 +2059,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-multi-value-xform" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581419e3995571a1d2d066e360ca1c0c09da097f5a53c98e6f00d96eddaf0ffe" +checksum = "a12766255d4b9026700376cc81894eeb62903e4414cbc94675f6f9babd9cfb76" dependencies = [ "anyhow", "walrus", @@ -2050,9 +2069,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-bindgen-test" @@ -2080,9 +2099,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-threads-xform" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05d272073981137e8426cf2a6830d43d1f84f988a050b2f8b210f0e266b8983" +checksum = "13c2b14c5b9c2c7aa9dd1eb7161857de9783f40e98582e7f41f2d7c04ffdc155" dependencies = [ "anyhow", "walrus", @@ -2091,9 +2110,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-wasm-conventions" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9c65b1ff5041ea824ca24c519948aec16fb6611c617d601623c0657dfcd47b" +checksum = "aaedf88769cb23c6fd2e3bfed65bcbff6c5d92c8336afbd80d2dfcc8eb5cf047" dependencies = [ "anyhow", "walrus", @@ -2101,9 +2120,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-wasm-interpreter" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5c796220738ab5d44666f37205728a74141c0039d1166bcf8110b26bafaa1e" +checksum = "a8a79039df1e0822e6d66508ec86052993deac201e26060f62abcd85e1daf951" dependencies = [ "anyhow", "log", @@ -2111,6 +2130,15 @@ dependencies = [ "wasm-bindgen-wasm-conventions", ] +[[package]] +name = "wasm-encoder" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-streams" version = "0.2.3" @@ -2139,9 +2167,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.77.1" +version = "0.80.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe3d5405e9ea6c1317a656d6e0820912d8b7b3607823a7596117c8f666daf6f" +checksum = "449167e2832691a1bff24cde28d2804e90e09586a448c8e76984792c44334a6b" [[package]] name = "web-sys" diff --git a/worker-macros/Cargo.toml b/worker-macros/Cargo.toml index ba029187..a3ee347f 100644 --- a/worker-macros/Cargo.toml +++ b/worker-macros/Cargo.toml @@ -17,9 +17,9 @@ worker-sys = { path = "../worker-sys", version = "0.0.10" } syn = "2.0.17" proc-macro2 = "1.0.60" quote = "1.0.28" -wasm-bindgen = "=0.2.87" +wasm-bindgen = "=0.2.89" wasm-bindgen-futures = "0.4.36" -wasm-bindgen-macro-support = "0.2.87" +wasm-bindgen-macro-support = "0.2.89" [features] queue = [] diff --git a/worker-macros/src/durable_object.rs b/worker-macros/src/durable_object.rs index 8482f454..f6b70da0 100644 --- a/worker-macros/src/durable_object.rs +++ b/worker-macros/src/durable_object.rs @@ -1,6 +1,6 @@ use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; -use syn::{spanned::Spanned, Error, FnArg, ImplItem, Item, Type, TypePath}; +use syn::{spanned::Spanned, Error, FnArg, ImplItem, Item, Type, TypePath, Visibility}; pub fn expand_macro(tokens: TokenStream) -> syn::Result { let item = syn::parse2::(tokens)?; @@ -20,7 +20,16 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { let struct_name = imp.self_ty; let items = imp.items; let mut tokenized = vec![]; - let mut has_alarm = false; + + #[derive(Default)] + struct OptionalMethods { + has_alarm: bool, + has_on_message: bool, + has_on_close: bool, + has_on_error: bool, + } + + let mut optional_methods = OptionalMethods::default(); for item in items { let impl_method = match item { @@ -32,6 +41,8 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { "new" => { let mut method = impl_method.clone(); method.sig.ident = Ident::new("_new", method.sig.ident.span()); + method.vis = Visibility::Inherited; + // modify the `state` argument so it is type ObjectState let arg_tokens = method.sig.inputs.first_mut().expect("DurableObject `new` method must have 2 arguments: state and env").into_token_stream(); @@ -65,6 +76,8 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { "fetch" => { let mut method = impl_method.clone(); method.sig.ident = Ident::new("_fetch_raw", method.sig.ident.span()); + method.vis = Visibility::Inherited; + quote! { #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = fetch)] pub fn _fetch(&mut self, req: worker_sys::web_sys::Request) -> js_sys::Promise { @@ -86,10 +99,12 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { } }, "alarm" => { - has_alarm = true; + optional_methods.has_alarm = true; let mut method = impl_method.clone(); method.sig.ident = Ident::new("_alarm_raw", method.sig.ident.span()); + method.vis = Visibility::Inherited; + quote! { #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = alarm)] pub fn _alarm(&mut self) -> js_sys::Promise { @@ -109,24 +124,123 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { #method } - } + }, + "on_message" => { + optional_methods.has_on_message = true; + + let mut method = impl_method.clone(); + method.sig.ident = Ident::new("_on_message_raw", method.sig.ident.span()); + method.vis = Visibility::Inherited; + + quote! { + #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = webSocketMessage)] + pub fn _on_message(&mut self, ws: worker_sys::web_sys::WebSocket, message: String) -> js_sys::Promise { + // SAFETY: + // On the surface, this is unsound because the Durable Object could be dropped + // while JavaScript still has possession of the future. However, + // we know something that Rust doesn't: that the Durable Object will never be destroyed + // while there is still a running promise inside of it, therefore we can let a reference + // to the durable object escape into a static-lifetime future. + let static_self: &'static mut Self = unsafe {&mut *(self as *mut _)}; + + wasm_bindgen_futures::future_to_promise(async move { + static_self._on_message_raw(ws.into(), message).await.map(|_| wasm_bindgen::JsValue::NULL) + .map_err(wasm_bindgen::JsValue::from) + }) + } + + #method + } + }, + "on_close" => { + optional_methods.has_on_close = true; + + let mut method = impl_method.clone(); + method.sig.ident = Ident::new("_on_close_raw", method.sig.ident.span()); + method.vis = Visibility::Inherited; + + quote! { + #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = webSocketClose)] + pub fn _on_close(&mut self, ws: worker_sys::web_sys::WebSocket, code: usize, reason: String, was_clean: bool) -> js_sys::Promise { + // SAFETY: + // On the surface, this is unsound because the Durable Object could be dropped + // while JavaScript still has possession of the future. However, + // we know something that Rust doesn't: that the Durable Object will never be destroyed + // while there is still a running promise inside of it, therefore we can let a reference + // to the durable object escape into a static-lifetime future. + let static_self: &'static mut Self = unsafe {&mut *(self as *mut _)}; + + wasm_bindgen_futures::future_to_promise(async move { + static_self._on_close_raw(ws.into(), code, reason, was_clean).await.map(|_| wasm_bindgen::JsValue::NULL) + .map_err(wasm_bindgen::JsValue::from) + }) + } + + #method + } + }, + "on_error" => { + optional_methods.has_on_error = true; + + let mut method = impl_method.clone(); + method.sig.ident = Ident::new("_on_error_raw", method.sig.ident.span()); + method.vis = Visibility::Inherited; + + quote! { + #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = webSocketError)] + pub fn _on_error(&mut self, ws: worker_sys::web_sys::WebSocket, error: JsValue) -> js_sys::Promise { + // SAFETY: + // On the surface, this is unsound because the Durable Object could be dropped + // while JavaScript still has possession of the future. However, + // we know something that Rust doesn't: that the Durable Object will never be destroyed + // while there is still a running promise inside of it, therefore we can let a reference + // to the durable object escape into a static-lifetime future. + let static_self: &'static mut Self = unsafe {&mut *(self as *mut _)}; + + wasm_bindgen_futures::future_to_promise(async move { + static_self._on_error_raw(ws.into(), error.into()).await.map(|_| wasm_bindgen::JsValue::NULL) + .map_err(wasm_bindgen::JsValue::from) + }) + } + + #method + } + }, _ => panic!() }; tokenized.push(tokens); } - let alarm_tokens = has_alarm.then(|| quote! { + let alarm_tokens = optional_methods.has_alarm.then(|| quote! { async fn alarm(&mut self) -> ::worker::Result { self._alarm_raw().await } }); + + let on_message_tokens = optional_methods.has_on_message.then(|| quote! { + async fn on_message(&mut self, ws: ::worker::WebSocket, message: String) -> ::worker::Result<()> { + self._on_message_raw(ws, message).await + } + }); + + let on_close_tokens = optional_methods.has_on_close.then(|| quote! { + async fn on_close(&mut self, ws: ::worker::WebSocket, code: usize, reason: String, was_clean: bool) -> ::worker::Result<()> { + self._on_close_raw(ws, code, reason, was_clean).await + } + }); + + let on_error_tokens = optional_methods.has_on_error.then(|| quote! { + async fn on_error(&mut self, ws: ::worker::WebSocket, error: ::worker::Error) -> ::worker::Result<()> { + self._on_error_raw(ws, error).await + } + }); + Ok(quote! { #wasm_bindgen_attr impl #struct_name { #(#tokenized)* } - #pound[async_trait::async_trait(?Send)] impl ::worker::durable::DurableObject for #struct_name { fn new(state: ::worker::durable::State, env: ::worker::Env) -> Self { Self::_new(state._inner(), env) @@ -137,6 +251,12 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { } #alarm_tokens + + #on_message_tokens + + #on_close_tokens + + #on_error_tokens } trait __Need_Durable_Object_Trait_Impl_With_durable_object_Attribute { const MACROED: bool = true; } diff --git a/worker-sys/Cargo.toml b/worker-sys/Cargo.toml index 98cffcd4..891f17b2 100644 --- a/worker-sys/Cargo.toml +++ b/worker-sys/Cargo.toml @@ -10,7 +10,7 @@ description = "Low-level extern definitions / FFI bindings to the Cloudflare Wor [dependencies] cfg-if = "1.0.0" js-sys = "0.3.63" -wasm-bindgen = "=0.2.87" +wasm-bindgen = "=0.2.89" [dependencies.web-sys] version = "0.3.63" diff --git a/worker-sys/src/ext/websocket.rs b/worker-sys/src/ext/websocket.rs index 1db0edd1..fe694b75 100644 --- a/worker-sys/src/ext/websocket.rs +++ b/worker-sys/src/ext/websocket.rs @@ -10,6 +10,12 @@ mod glue { #[wasm_bindgen(method, catch)] pub fn accept(this: &WebSocket) -> Result<(), JsValue>; + + #[wasm_bindgen(method, catch, js_name = "serializeAttachment")] + pub fn serialize_attachment(this: &WebSocket, value: JsValue) -> Result<(), JsValue>; + + #[wasm_bindgen(method, catch, js_name = "deserializeAttachment")] + pub fn deserialize_attachment(this: &WebSocket) -> Result; } } @@ -18,10 +24,24 @@ pub trait WebSocketExt { /// /// [CF Documentation](https://developers.cloudflare.com/workers/runtime-apis/websockets#accept) fn accept(&self) -> Result<(), JsValue>; + + fn serialize_attachment(&self, value: JsValue) -> Result<(), JsValue>; + + fn deserialize_attachment(&self) -> Result; } impl WebSocketExt for web_sys::WebSocket { fn accept(&self) -> Result<(), JsValue> { self.unchecked_ref::().accept() } + + fn serialize_attachment(&self, value: JsValue) -> Result<(), JsValue> { + self.unchecked_ref::() + .serialize_attachment(value) + } + + fn deserialize_attachment(&self) -> Result { + self.unchecked_ref::() + .deserialize_attachment() + } } diff --git a/worker-sys/src/types/durable_object/state.rs b/worker-sys/src/types/durable_object/state.rs index 2570846e..cdb04cd3 100644 --- a/worker-sys/src/types/durable_object/state.rs +++ b/worker-sys/src/types/durable_object/state.rs @@ -15,4 +15,23 @@ extern "C" { #[wasm_bindgen(method, js_name=waitUntil)] pub fn wait_until(this: &DurableObjectState, promise: &js_sys::Promise); + + #[wasm_bindgen(method, js_name=acceptWebSocket)] + pub fn accept_web_socket(this: &DurableObjectState, ws: web_sys::WebSocket); + + #[wasm_bindgen(method, js_name=acceptWebSocket)] + pub fn accept_web_socket_with_tags( + this: &DurableObjectState, + ws: web_sys::WebSocket, + tags: Vec, + ); + + #[wasm_bindgen(method, js_name=getWebSockets)] + pub fn get_web_sockets(this: &DurableObjectState) -> Vec; + + #[wasm_bindgen(method, js_name=getWebSockets)] + pub fn get_web_sockets_with_tag( + this: &DurableObjectState, + tag: String, + ) -> Vec; } diff --git a/worker/Cargo.toml b/worker/Cargo.toml index c45a3180..05db63e6 100644 --- a/worker/Cargo.toml +++ b/worker/Cargo.toml @@ -27,7 +27,7 @@ serde = { version = "1.0.164", features = ["derive"] } serde_json = "1.0.96" tokio = { version = "1.28", default-features = false } url = "2.4.0" -wasm-bindgen = "=0.2.87" +wasm-bindgen = "^0.2.89" wasm-bindgen-futures = "0.4.36" serde-wasm-bindgen = "0.6.1" serde_urlencoded = "0.7" diff --git a/worker/src/durable.rs b/worker/src/durable.rs index a99f54a5..a024b0cd 100644 --- a/worker/src/durable.rs +++ b/worker/src/durable.rs @@ -18,7 +18,7 @@ use crate::{ error::Error, request::Request, response::Response, - Result, + Result, WebSocket, }; use async_trait::async_trait; @@ -194,6 +194,31 @@ impl State { pub fn _inner(self) -> DurableObjectState { self.inner } + + pub fn accept_web_socket(&self, ws: WebSocket) { + self.inner.accept_web_socket(ws.as_ref().clone()) + } + + pub fn accept_web_socket_with_tags(&self, ws: WebSocket, tags: Vec) { + self.inner + .accept_web_socket_with_tags(ws.as_ref().clone(), tags); + } + + pub fn get_web_sockets(&self) -> Vec { + self.inner + .get_web_sockets() + .into_iter() + .map(Into::into) + .collect() + } + + pub fn get_web_sockets_with_tag(&self, tag: String) -> Vec { + self.inner + .get_web_sockets_with_tag(tag) + .into_iter() + .map(Into::into) + .collect() + } } impl From for State { @@ -737,12 +762,42 @@ impl DurableObject for Chatroom { } ``` */ -#[async_trait(?Send)] pub trait DurableObject { fn new(state: State, env: Env) -> Self; - async fn fetch(&mut self, req: Request) -> Result; + + fn fetch(&mut self, req: Request) -> impl std::future::Future>; + #[allow(clippy::diverging_sub_expression)] - async fn alarm(&mut self) -> Result { - unimplemented!("alarm() handler not implemented") + fn alarm(&mut self) -> impl std::future::Future> { + async { unimplemented!("alarm() handler not implemented") } + } + + #[allow(unused_variables)] + fn on_message( + &mut self, + ws: WebSocket, + message: String, + ) -> impl std::future::Future> { + async { unimplemented!("on_message() handler not implemented") } + } + + #[allow(unused_variables)] + fn on_close( + &mut self, + ws: WebSocket, + code: usize, + reason: String, + was_clean: bool, + ) -> impl std::future::Future> { + async { unimplemented!("on_close() handler not implemented") } + } + + #[allow(unused_variables)] + fn on_error( + &mut self, + ws: WebSocket, + error: Error, + ) -> impl std::future::Future> { + async { unimplemented!("on_error() handler not implemented") } } } diff --git a/worker/src/websocket.rs b/worker/src/websocket.rs index 8b5a386b..65056e2c 100644 --- a/worker/src/websocket.rs +++ b/worker/src/websocket.rs @@ -197,6 +197,24 @@ impl WebSocket { closures: Some((message_closure, error_closure, close_closure)), }) } + + pub fn serialize_attachment(&self, value: T) -> Result<()> { + self.socket + .serialize_attachment(serde_wasm_bindgen::to_value(&value)?) + .map_err(Error::from) + } + + pub fn deserialize_attachment(&self) -> Result> { + let value = self.socket.deserialize_attachment().map_err(Error::from)?; + + if value.is_null() || value.is_undefined() { + return Ok(None); + } + + serde_wasm_bindgen::from_value::(value) + .map(Some) + .map_err(Error::from) + } } type EvCallback = Closure;