1pub mod error;
23mod interface;
24pub mod prelude;
25mod protocol;
26pub mod result;
27pub use crate::client::error::Error;
28pub use crate::client::result::Result;
29
30use crate::imports::*;
31use futures_util::select_biased;
32pub use interface::{Interface, Notification};
33use protocol::ProtocolHandler;
34pub use protocol::{BorshProtocol, JsonProtocol};
35use std::fmt::Debug;
36use std::str::FromStr;
37use workflow_core::{channel::Multiplexer, task::yield_now};
38pub use workflow_websocket::client::{
39 ConnectOptions, ConnectResult, ConnectStrategy, Resolver, ResolverResult, WebSocketConfig,
40 WebSocketError,
41};
42
43#[cfg(feature = "wasm32-sdk")]
44pub use workflow_websocket::client::options::IConnectOptions;
45
46pub use workflow_rpc_macros::client_notification as notification;
79
80#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
82pub enum Ctl {
83 Connect,
85 Disconnect,
87}
88
89impl std::fmt::Display for Ctl {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 match self {
92 Ctl::Connect => write!(f, "connect"),
93 Ctl::Disconnect => write!(f, "disconnect"),
94 }
95 }
96}
97
98impl FromStr for Ctl {
99 type Err = Error;
100
101 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
102 match s {
103 "connect" => Ok(Ctl::Connect),
104 "disconnect" => Ok(Ctl::Disconnect),
105 _ => Err(Error::InvalidEvent(s.to_string())),
106 }
107 }
108}
109
110#[async_trait]
112pub trait NotificationHandler: Send + Sync + 'static {
113 async fn handle_notification(&self, data: &[u8]) -> Result<()>;
115}
116
117#[derive(Default)]
119pub struct Options<'url> {
120 pub ctl_multiplexer: Option<Multiplexer<Ctl>>,
122 pub url: Option<&'url str>,
124}
125
126impl<'url> Options<'url> {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn with_url(mut self, url: &'url str) -> Self {
134 self.url = Some(url);
135 self
136 }
137
138 pub fn with_ctl_multiplexer(mut self, ctl_multiplexer: Multiplexer<Ctl>) -> Self {
140 self.ctl_multiplexer = Some(ctl_multiplexer);
141 self
142 }
143}
144
145struct Inner<Ops> {
146 ws: Arc<WebSocket>,
147 is_running: AtomicBool,
148 is_connected: AtomicBool,
149 receiver_is_running: AtomicBool,
150 timeout_is_running: AtomicBool,
151 receiver_shutdown: DuplexChannel,
152 timeout_shutdown: DuplexChannel,
153 timeout_timer_interval: AtomicU64,
154 timeout_duration: AtomicU64,
155 ctl_multiplexer: Option<Multiplexer<Ctl>>,
156 protocol: Arc<dyn ProtocolHandler<Ops>>,
157}
158
159impl<Ops> Inner<Ops>
160where
161 Ops: OpsT,
162{
163 fn new<T>(
164 ws: Arc<WebSocket>,
165 protocol: Arc<dyn ProtocolHandler<Ops>>,
166 options: Options,
167 ) -> Result<Self>
168 where
169 T: ProtocolHandler<Ops> + Send + Sync + 'static,
170 {
171 let inner = Inner {
172 ws,
173 is_running: AtomicBool::new(false),
174 is_connected: AtomicBool::new(false),
175 receiver_is_running: AtomicBool::new(false),
176 receiver_shutdown: DuplexChannel::oneshot(),
177 timeout_is_running: AtomicBool::new(false),
178 timeout_shutdown: DuplexChannel::oneshot(),
179 timeout_duration: AtomicU64::new(60_000),
180 timeout_timer_interval: AtomicU64::new(5_000),
181 ctl_multiplexer: options.ctl_multiplexer,
182 protocol,
183 };
184
185 Ok(inner)
186 }
187
188 #[inline]
189 pub fn is_running(&self) -> bool {
190 self.is_running.load(Ordering::SeqCst)
191 }
192
193 pub fn start(self: &Arc<Self>) -> Result<()> {
194 if !self.is_running.load(Ordering::Relaxed) {
195 self.is_running.store(true, Ordering::SeqCst);
196 self.clone().timeout_task();
197 self.clone().receiver_task();
198 } else {
199 log_warn!("wRPC services are already running: rpc::start() was called multiple times");
200 }
201 Ok(())
202 }
203
204 pub async fn shutdown(self: &Arc<Self>) -> Result<()> {
205 self.ws.disconnect().await?;
206 yield_now().await;
207 if self.is_running.load(Ordering::Relaxed) {
208 self.stop_timeout().await?;
209 self.stop_receiver().await?;
210 self.is_running.store(false, Ordering::SeqCst);
211 }
212 Ok(())
213 }
214
215 fn timeout_task(self: Arc<Self>) {
216 self.timeout_is_running.store(true, Ordering::SeqCst);
217 workflow_core::task::spawn(async move {
218 'outer: loop {
219 let timeout_timer_interval =
220 Duration::from_millis(self.timeout_timer_interval.load(Ordering::SeqCst));
221 select_biased! {
222 _ = workflow_core::task::sleep(timeout_timer_interval).fuse() => {
223 let timeout = Duration::from_millis(self.timeout_duration.load(Ordering::Relaxed));
224 self.protocol.handle_timeout(timeout).await;
225 },
226 _ = self.timeout_shutdown.request.receiver.recv().fuse() => {
227 break 'outer;
228 },
229 }
230 }
231
232 self.timeout_is_running.store(false, Ordering::SeqCst);
233 self.timeout_shutdown.response.sender.send(()).await.unwrap_or_else(|err|
234 log_error!("wRPC client - unable to signal shutdown completion for timeout task: `{err}`"));
235 });
236 }
237
238 fn receiver_task(self: Arc<Self>) {
239 self.receiver_is_running.store(true, Ordering::SeqCst);
240 let receiver_rx = self.ws.receiver_rx().clone();
241 workflow_core::task::spawn(async move {
242 'outer: loop {
243 select_biased! {
244 msg = receiver_rx.recv().fuse() => {
245 match msg {
246 Ok(msg) => {
247 match msg {
248 WebSocketMessage::Binary(_) | WebSocketMessage::Text(_) => {
249 self.protocol.handle_message(msg).await
250 .unwrap_or_else(|err|log_trace!("wRPC error: `{err}`"));
251 }
252 WebSocketMessage::Open => {
253 self.is_connected.store(true, Ordering::SeqCst);
254 if let Some(ctl_channel) = &self.ctl_multiplexer {
255 ctl_channel.try_broadcast(Ctl::Connect).expect("ctl_channel.try_broadcast(Ctl::Connect)");
256 }
257 }
258 WebSocketMessage::Close => {
259 self.is_connected.store(false, Ordering::SeqCst);
260
261 self.protocol.handle_disconnect().await.unwrap_or_else(|err|{
262 log_error!("wRPC error during protocol disconnect: {err}");
263 });
264
265 if let Some(ctl_channel) = &self.ctl_multiplexer {
266 ctl_channel.try_broadcast(Ctl::Disconnect).expect("ctl_channel.try_broadcast(Ctl::Disconnect)");
267 }
268 }
269 }
270 },
271 Err(err) => {
272 log_error!("wRPC client receiver channel error: {err}");
273 break 'outer;
274 }
275 }
276 },
277 _ = self.receiver_shutdown.request.receiver.recv().fuse() => {
278 break 'outer;
279 },
280
281 }
282 }
283
284 self.receiver_is_running.store(false, Ordering::SeqCst);
285 self.receiver_shutdown.response.sender.send(()).await.unwrap_or_else(|err|
286 log_error!("wRPC client - unable to signal shutdown completion for receiver task: `{err}`")
287 );
288 });
289 }
290
291 async fn stop_receiver(&self) -> Result<()> {
292 if !self.receiver_is_running.load(Ordering::SeqCst) {
293 return Ok(());
294 }
295
296 self.receiver_shutdown
297 .signal(())
298 .await
299 .unwrap_or_else(|err| {
300 log_error!("wRPC client unable to signal receiver shutdown: `{err}`")
301 });
302
303 Ok(())
304 }
305
306 async fn stop_timeout(&self) -> Result<()> {
307 if !self.timeout_is_running.load(Ordering::SeqCst) {
308 return Ok(());
309 }
310
311 self.timeout_shutdown
312 .signal(())
313 .await
314 .unwrap_or_else(|err| {
315 log_error!("wRPC client unable to signal timeout shutdown: `{err}`")
316 });
317
318 Ok(())
319 }
320}
321
322#[derive(Clone)]
323enum Protocol<Ops, Id>
324where
325 Ops: OpsT,
326 Id: IdT,
327{
328 Borsh(Arc<BorshProtocol<Ops, Id>>),
329 Json(Arc<JsonProtocol<Ops, Id>>),
330}
331
332impl<Ops, Id> From<Arc<dyn ProtocolHandler<Ops>>> for Protocol<Ops, Id>
333where
334 Ops: OpsT,
335 Id: IdT,
336{
337 fn from(protocol: Arc<dyn ProtocolHandler<Ops>>) -> Self {
338 match protocol.clone().downcast_arc::<BorshProtocol<Ops, Id>>() {
339 Ok(protocol) => Protocol::Borsh(protocol),
340 _ => match protocol.clone().downcast_arc::<JsonProtocol<Ops, Id>>() {
341 Ok(protocol) => Protocol::Json(protocol),
342 _ => {
343 panic!()
344 }
345 },
346 }
347 }
348}
349
350#[derive(Clone)]
355pub struct RpcClient<Ops, Id = Id64>
356where
357 Ops: OpsT,
358 Id: IdT,
359{
360 inner: Arc<Inner<Ops>>,
361 protocol: Protocol<Ops, Id>,
362 ops: PhantomData<Ops>,
363 id: PhantomData<Id>,
364}
365
366impl<Ops, Id> RpcClient<Ops, Id>
367where
368 Ops: OpsT,
369 Id: IdT,
370{
371 pub fn new_with_encoding(
383 encoding: Encoding,
384 interface: Option<Arc<Interface<Ops>>>,
385 options: Options,
386 config: Option<WebSocketConfig>,
387 ) -> Result<RpcClient<Ops, Id>> {
388 match encoding {
389 Encoding::Borsh => Self::new::<BorshProtocol<Ops, Id>>(interface, options, config),
390 Encoding::SerdeJson => Self::new::<JsonProtocol<Ops, Id>>(interface, options, config),
391 }
392 }
393
394 pub fn new<T>(
406 interface: Option<Arc<Interface<Ops>>>,
407 options: Options,
408 config: Option<WebSocketConfig>,
409 ) -> Result<RpcClient<Ops, Id>>
410 where
411 T: ProtocolHandler<Ops> + Send + Sync + 'static,
412 {
413 let url = options.url.map(sanitize_url).transpose()?;
414
415 let ws = Arc::new(WebSocket::new(url.as_deref(), config)?);
416 let protocol: Arc<dyn ProtocolHandler<Ops>> = Arc::new(T::new(ws.clone(), interface));
417 let inner = Arc::new(Inner::new::<T>(ws, protocol.clone(), options)?);
418
419 let client = RpcClient::<Ops, Id> {
420 inner,
421 protocol: protocol.into(),
422 ops: PhantomData,
423 id: PhantomData,
424 };
425
426 Ok(client)
427 }
428
429 pub async fn connect(&self, options: ConnectOptions) -> ConnectResult<Error> {
431 if !self.inner.is_running() {
432 self.inner.start()?;
433 }
434 Ok(self.inner.ws.connect(options).await?)
435 }
436
437 pub async fn shutdown(&self) -> Result<()> {
439 self.inner.shutdown().await?;
440 Ok(())
441 }
442
443 pub fn ctl_multiplexer(&self) -> &Option<Multiplexer<Ctl>> {
446 &self.inner.ctl_multiplexer
447 }
448
449 pub fn is_connected(&self) -> bool {
451 self.inner.ws.is_connected()
452 }
453
454 pub fn url(&self) -> Option<String> {
456 self.inner.ws.url()
457 }
458
459 pub fn set_url(&self, url: &str) -> Result<()> {
464 self.inner.ws.set_url(url);
465 Ok(())
466 }
467
468 pub fn configure(&self, config: WebSocketConfig) {
472 self.inner.ws.configure(config);
473 }
474
475 pub async fn notify<Msg>(&self, op: Ops, payload: Msg) -> Result<()>
483 where
484 Msg: BorshSerialize + Serialize + Send + Sync + 'static,
485 {
486 if !self.is_connected() {
487 return Err(WebSocketError::NotConnected.into());
488 }
489
490 match &self.protocol {
491 Protocol::Borsh(protocol) => {
492 protocol.notify(op, payload).await?;
493 }
494 Protocol::Json(protocol) => {
495 protocol.notify(op, payload).await?;
496 }
497 }
498
499 Ok(())
500 }
501
502 pub async fn call<Req, Resp>(&self, op: Ops, req: Req) -> Result<Resp>
511 where
512 Req: MsgT,
513 Resp: MsgT,
514 {
515 if !self.is_connected() {
516 return Err(WebSocketError::NotConnected.into());
517 }
518
519 match &self.protocol {
520 Protocol::Borsh(protocol) => Ok(protocol.request(op, req).await?),
521 Protocol::Json(protocol) => Ok(protocol.request(op, req).await?),
522 }
523 }
524
525 pub fn trigger_abort(&self) -> Result<()> {
529 Ok(self.inner.ws.trigger_abort()?)
530 }
531}
532
533fn sanitize_url(url: &str) -> Result<String> {
534 let url = url
535 .replace("wrpc://", "ws://")
536 .replace("wrpcs://", "wss://");
537 Ok(url)
538}