use std::rc::Rc;
use actr_protocol::{
ActorResult, ActrError, ActrId, ActrType, DataStream, PayloadType, Realm, RpcRequest,
};
use async_trait::async_trait;
use futures_util::future::BoxFuture;
use prost::Message as ProstMessage;
use crate::{Context, Dest, LogLevel, MediaSample};
use actr_web_abi::types as wit;
struct WebContextInner {
self_id: ActrId,
caller_id: Option<ActrId>,
request_id: String,
}
#[derive(Clone)]
pub struct WebContext {
inner: Rc<WebContextInner>,
}
impl WebContext {
pub fn new(self_id: ActrId, caller_id: Option<ActrId>, request_id: String) -> Self {
Self {
inner: Rc::new(WebContextInner {
self_id,
caller_id,
request_id,
}),
}
}
pub fn for_lifecycle() -> Self {
Self::new(ActrId::default(), None, String::new())
}
fn not_implemented(feature: &'static str) -> ActrError {
ActrError::NotImplemented(format!("WebContext::{feature}"))
}
}
fn actr_type_to_wit(t: &ActrType) -> wit::ActrType {
wit::ActrType {
manufacturer: t.manufacturer.clone(),
name: t.name.clone(),
version: t.version.clone(),
}
}
fn actr_type_from_wit(t: &wit::ActrType) -> ActrType {
ActrType {
manufacturer: t.manufacturer.clone(),
name: t.name.clone(),
version: t.version.clone(),
}
}
fn actr_id_to_wit(id: &ActrId) -> wit::ActrId {
wit::ActrId {
realm: wit::Realm {
realm_id: id.realm.realm_id,
},
serial_number: id.serial_number,
actr_type: actr_type_to_wit(&id.r#type),
}
}
fn actr_id_from_wit(id: &wit::ActrId) -> ActrId {
ActrId {
realm: Realm {
realm_id: id.realm.realm_id,
},
serial_number: id.serial_number,
r#type: actr_type_from_wit(&id.actr_type),
}
}
fn wit_error_to_proto(e: wit::ActrError) -> ActrError {
match e {
wit::ActrError::Unavailable(m) => ActrError::Unavailable(m),
wit::ActrError::TimedOut => ActrError::TimedOut,
wit::ActrError::NotFound(m) => ActrError::NotFound(m),
wit::ActrError::PermissionDenied(m) => ActrError::PermissionDenied(m),
wit::ActrError::InvalidArgument(m) => ActrError::InvalidArgument(m),
wit::ActrError::UnknownRoute(m) => ActrError::UnknownRoute(m),
wit::ActrError::DependencyNotFound(p) => {
ActrError::DependencyNotFound {
service_name: p.service_name,
message: p.message,
}
}
wit::ActrError::DecodeFailure(m) => ActrError::DecodeFailure(m),
wit::ActrError::NotImplemented(m) => ActrError::NotImplemented(m),
wit::ActrError::Internal(m) => ActrError::Internal(m),
}
}
fn flatten_js<T>(
outcome: Result<Result<T, wit::ActrError>, wasm_bindgen::JsValue>,
) -> ActorResult<T> {
match outcome {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(wit_error_to_proto(e)),
Err(js) => Err(ActrError::Internal(format!(
"WebContext: host import trap: {}",
js.as_string().unwrap_or_else(|| format!("{js:?}"))
))),
}
}
#[async_trait(?Send)]
impl Context for WebContext {
fn self_id(&self) -> &ActrId {
&self.inner.self_id
}
fn caller_id(&self) -> Option<&ActrId> {
self.inner.caller_id.as_ref()
}
fn request_id(&self) -> &str {
&self.inner.request_id
}
async fn call<R: RpcRequest>(&self, target: &Dest, request: R) -> ActorResult<R::Response> {
let actor = match target {
Dest::Actor(id) => id,
Dest::Shell | Dest::Local => {
return Err(Self::not_implemented("call → Shell/Local dest"));
}
};
let payload = request.encode_to_vec();
let bytes = self
.call_raw(actor, R::route_key(), bytes::Bytes::from(payload))
.await?;
R::Response::decode(bytes.as_ref()).map_err(|e| {
ActrError::DecodeFailure(format!("WebContext::call: response decode failed: {e}"))
})
}
async fn tell<R: RpcRequest>(&self, target: &Dest, message: R) -> ActorResult<()> {
let payload = message.encode_to_vec();
let wit_dest = match target {
Dest::Shell => wit::Dest::Shell,
Dest::Local => wit::Dest::Local,
Dest::Actor(id) => wit::Dest::Actor(actr_id_to_wit(id)),
};
let outcome = actr_web_abi::guest::tell_with_request_id(
self.request_id(),
wit_dest,
R::route_key().to_string(),
payload,
)
.await;
flatten_js(outcome)
}
async fn call_raw(
&self,
target: &ActrId,
route_key: &str,
payload: bytes::Bytes,
) -> ActorResult<bytes::Bytes> {
let outcome = actr_web_abi::guest::call_raw_with_request_id(
self.request_id(),
actr_id_to_wit(target),
route_key.to_string(),
payload.to_vec(),
)
.await;
flatten_js(outcome).map(bytes::Bytes::from)
}
async fn discover_route_candidate(&self, target_type: &ActrType) -> ActorResult<ActrId> {
let outcome = actr_web_abi::guest::discover_with_request_id(
self.request_id(),
actr_type_to_wit(target_type),
)
.await;
flatten_js(outcome).map(|wit_id| actr_id_from_wit(&wit_id))
}
async fn register_stream<F>(&self, _stream_id: String, _callback: F) -> ActorResult<()>
where
F: Fn(DataStream, ActrId) -> BoxFuture<'static, ActorResult<()>> + Send + Sync + 'static,
{
Err(Self::not_implemented("register_stream"))
}
async fn unregister_stream(&self, _stream_id: &str) -> ActorResult<()> {
Err(Self::not_implemented("unregister_stream"))
}
async fn send_data_stream(
&self,
_target: &Dest,
_chunk: DataStream,
_payload_type: PayloadType,
) -> ActorResult<()> {
Err(Self::not_implemented("send_data_stream"))
}
async fn register_media_track<F>(&self, _track_id: String, _callback: F) -> ActorResult<()>
where
F: Fn(MediaSample, ActrId) -> BoxFuture<'static, ActorResult<()>> + Send + Sync + 'static,
{
Err(Self::not_implemented("register_media_track"))
}
async fn unregister_media_track(&self, _track_id: &str) -> ActorResult<()> {
Err(Self::not_implemented("unregister_media_track"))
}
async fn send_media_sample(
&self,
_target: &Dest,
_track_id: &str,
_sample: MediaSample,
) -> ActorResult<()> {
Err(Self::not_implemented("send_media_sample"))
}
async fn add_media_track(
&self,
_target: &Dest,
_track_id: &str,
_codec: &str,
_media_type: &str,
) -> ActorResult<()> {
Err(Self::not_implemented("add_media_track"))
}
async fn remove_media_track(&self, _target: &Dest, _track_id: &str) -> ActorResult<()> {
Err(Self::not_implemented("remove_media_track"))
}
fn log(&self, level: LogLevel, msg: &str) {
let request_id = self.request_id().to_string();
let level = match level {
LogLevel::Trace => "trace",
LogLevel::Debug => "debug",
LogLevel::Info => "info",
LogLevel::Warn => "warn",
LogLevel::Error => "error",
}
.to_string();
let message = msg.to_string();
wasm_bindgen_futures::spawn_local(async move {
let _ =
actr_web_abi::guest::log_message_with_request_id(&request_id, level, message).await;
});
}
}