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