core_crypto_ffi/core_crypto/
epoch_observer.rs1use std::sync::Arc;
2
3use async_trait::async_trait;
4use core_crypto::prelude::{ConversationId, Obfuscated};
5#[cfg(target_family = "wasm")]
6use js_sys::{Promise, Uint8Array};
7#[cfg(target_family = "wasm")]
8use log::kv;
9#[cfg(target_family = "wasm")]
10use wasm_bindgen::prelude::*;
11#[cfg(target_family = "wasm")]
12use wasm_bindgen_futures::JsFuture;
13
14use crate::{CoreCrypto, CoreCryptoError, CoreCryptoResult};
15
16#[cfg(not(target_family = "wasm"))]
17#[derive(Debug, thiserror::Error, uniffi::Error)]
18#[uniffi(flat_error)]
19pub enum EpochChangedReportingError {
20 #[error("panic or otherwise unexpected error from foreign code")]
21 Ffi(#[from] uniffi::UnexpectedUniFFICallbackError),
22}
23
24#[cfg(not(target_family = "wasm"))]
26#[uniffi::export(with_foreign)]
27#[async_trait]
28pub trait EpochObserver: Send + Sync {
29 async fn epoch_changed(
44 &self,
45 conversation_id: ConversationId,
46 epoch: u64,
47 ) -> Result<(), EpochChangedReportingError>;
48}
49
50#[cfg(not(target_family = "wasm"))]
56struct ObserverShim(Arc<dyn EpochObserver>);
57
58#[cfg(not(target_family = "wasm"))]
59#[async_trait]
60impl core_crypto::mls::EpochObserver for ObserverShim {
61 async fn epoch_changed(&self, conversation_id: ConversationId, epoch: u64) {
62 if let Err(err) = self.0.epoch_changed(conversation_id.clone(), epoch).await {
63 log::warn!(
66 conversation_id = Obfuscated::new(&conversation_id),
67 epoch,
68 err = log::kv::Value::from_dyn_error(&err);
69 "caught an error when attempting to notify the epoch observer of an epoch change"
70 );
71 }
72 }
73}
74
75#[cfg(not(target_family = "wasm"))]
76#[uniffi::export]
77impl CoreCrypto {
78 pub async fn register_epoch_observer(&self, epoch_observer: Arc<dyn EpochObserver>) -> CoreCryptoResult<()> {
83 let shim = Arc::new(ObserverShim(epoch_observer));
84 self.inner
85 .register_epoch_observer(shim)
86 .await
87 .map_err(CoreCryptoError::generic())
88 }
89}
90
91#[cfg(target_family = "wasm")]
93#[wasm_bindgen]
94pub struct EpochObserver {
95 this_context: JsValue,
96 epoch_changed: js_sys::Function,
97}
98
99#[cfg(target_family = "wasm")]
100unsafe impl Send for EpochObserver {}
102#[cfg(target_family = "wasm")]
103unsafe impl Sync for EpochObserver {}
105
106#[cfg(target_family = "wasm")]
107#[wasm_bindgen]
108impl EpochObserver {
109 #[wasm_bindgen(constructor)]
119 pub fn new(this_context: JsValue, epoch_changed: js_sys::Function) -> CoreCryptoResult<Self> {
120 if epoch_changed.length() != 2 {
122 return Err(CoreCryptoError::ad_hoc(format!(
123 "`epoch_changed` must accept 2 arguments but accepts {}",
124 epoch_changed.length()
125 )));
126 }
127 Ok(Self {
128 this_context,
129 epoch_changed,
130 })
131 }
132}
133
134#[cfg(target_family = "wasm")]
135impl EpochObserver {
136 async fn epoch_changed(&self, conversation_id: ConversationId, epoch: u64) -> Result<(), JsValue> {
143 let converation_id = Uint8Array::from(conversation_id.as_slice());
144
145 let promise = self
146 .epoch_changed
147 .call2(&self.this_context, &converation_id.into(), &epoch.into())?
148 .dyn_into::<Promise>()?;
149 JsFuture::from(promise).await?;
151 Ok(())
152 }
153}
154
155#[cfg(target_family = "wasm")]
156#[async_trait(?Send)]
157impl core_crypto::mls::EpochObserver for EpochObserver {
158 async fn epoch_changed(&self, conversation_id: ConversationId, epoch: u64) {
159 if let Err(err) = self.epoch_changed(conversation_id.clone(), epoch).await {
160 log::warn!(
163 conversation_id = Obfuscated::new(&conversation_id),
164 epoch,
165 err = LoggableJsValue(err);
166 "caught an error when attempting to notify the epoch observer of an epoch change"
167 );
168 }
169 }
170}
171
172#[cfg(target_family = "wasm")]
173#[wasm_bindgen]
174impl CoreCrypto {
175 pub async fn register_epoch_observer(&self, epoch_observer: EpochObserver) -> CoreCryptoResult<()> {
180 self.inner
181 .register_epoch_observer(Arc::new(epoch_observer))
182 .await
183 .map_err(CoreCryptoError::generic())
184 }
185}
186
187#[cfg(target_family = "wasm")]
188struct LoggableJsValue(JsValue);
189
190#[cfg(target_family = "wasm")]
191impl kv::ToValue for LoggableJsValue {
192 fn to_value(&self) -> kv::Value<'_> {
193 if let Some(f) = self.0.as_f64() {
196 return f.into();
197 }
198 if let Some(b) = self.0.as_bool() {
199 return b.into();
200 }
201 if self.0.is_null() || self.0.is_undefined() {
202 return kv::Value::null();
203 }
204 kv::Value::from_debug(&self.0)
205 }
206}