1#![cfg(target_arch = "wasm32")]
7
8use gbp_core::{MemberId, PayloadCodec};
9use gbp_node::{Event, GroupNode as RustGroupNode};
10use gtp::{GtpAccept, GtpClient as RustGtpClient};
11use js_sys::{Array, Object, Reflect, Uint8Array};
12use openmls::prelude::tls_codec::Serialize as TlsSerialize;
13use openmls::prelude::{KeyPackageIn, OpenMlsProvider, ProtocolVersion};
14use tls_codec::Deserialize as TlsDeserialize;
15use std::cell::RefCell;
16use wasm_bindgen::prelude::*;
17
18fn set(obj: &Object, key: &str, val: &JsValue) {
21 Reflect::set(obj, &JsValue::from_str(key), val).unwrap_throw();
22}
23
24fn u8s(bytes: &[u8]) -> JsValue {
25 Uint8Array::from(bytes).into()
26}
27
28fn js_err(msg: impl std::fmt::Display) -> JsValue {
29 JsValue::from_str(&msg.to_string())
30}
31
32fn event_to_js(ev: Event) -> JsValue {
33 let obj = Object::new();
34 match ev {
35 Event::PayloadReceived(p) => {
36 set(&obj, "kind", &"payload_received".into());
37 set(&obj, "streamType", &JsValue::from_f64(p.stream_type.as_u8() as f64));
38 set(&obj, "plaintext", &u8s(&p.plaintext));
39 set(&obj, "sequenceNo", &JsValue::from_f64(p.sequence_no as f64));
40 set(&obj, "codec", &JsValue::from_f64(p.codec as u8 as f64));
41 }
42 Event::StateChanged { from, to } => {
43 set(&obj, "kind", &"state_changed".into());
44 set(&obj, "from", &JsValue::from_str(&from.to_string()));
45 set(&obj, "to", &JsValue::from_str(&to.to_string()));
46 }
47 Event::EpochAdvanced { epoch, transition_id } => {
48 set(&obj, "kind", &"epoch_advanced".into());
49 set(&obj, "epoch", &js_sys::BigInt::from(epoch).into());
50 set(&obj, "transitionId", &JsValue::from_f64(transition_id as f64));
51 }
52 Event::Error { code, reason, fatal, retryable, .. } => {
53 set(&obj, "kind", &"error".into());
54 set(&obj, "code", &JsValue::from_f64(code as f64));
55 set(&obj, "reason", &JsValue::from_str(&reason));
56 set(&obj, "fatal", &JsValue::from_bool(fatal));
57 set(&obj, "retryable", &JsValue::from_bool(retryable));
58 }
59 Event::Control { from, opcode, transition_id, .. } => {
60 set(&obj, "kind", &"control".into());
61 set(&obj, "from", &JsValue::from_f64(from as f64));
62 set(&obj, "opcode", &JsValue::from_f64(opcode as u8 as f64));
63 set(&obj, "transitionId", &JsValue::from_f64(transition_id as f64));
64 }
65 _ => {
66 set(&obj, "kind", &"other".into());
67 }
68 }
69 obj.into()
70}
71
72#[wasm_bindgen]
85pub struct MlsContext {
86 inner: RefCell<gbp_mls::MlsContext>,
87 kp_bytes: Vec<u8>,
88}
89
90#[wasm_bindgen]
91impl MlsContext {
92 #[wasm_bindgen(js_name = "create")]
97 pub fn create(user_id: &str) -> Result<MlsContext, JsValue> {
98 let (ctx, kpb) = gbp_mls::MlsContext::new_member(user_id.as_bytes())
99 .map_err(|e| js_err(e))?;
100 let kp_bytes = kpb.key_package()
101 .tls_serialize_detached()
102 .map_err(|e| js_err(format!("kp serialize: {e:?}")))?;
103 Ok(MlsContext { inner: RefCell::new(ctx), kp_bytes })
104 }
105
106 #[wasm_bindgen(getter, js_name = "keyPackage")]
109 pub fn key_package(&self) -> Uint8Array {
110 Uint8Array::from(self.kp_bytes.as_slice())
111 }
112
113 #[wasm_bindgen(getter)]
115 pub fn epoch(&self) -> u64 {
116 self.inner.borrow().epoch()
117 }
118
119 #[wasm_bindgen(getter, js_name = "groupId")]
121 pub fn group_id(&self) -> Uint8Array {
122 Uint8Array::from(self.inner.borrow().group_id_16().as_slice())
123 }
124
125 #[wasm_bindgen(js_name = "invite")]
132 pub fn invite(&self, key_package_bytes: &[u8]) -> Result<Uint8Array, JsValue> {
133 let mut ctx = self.inner.borrow_mut();
134 let kp_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes.as_ref())
135 .map_err(|e| js_err(format!("kp parse: {e:?}")))?;
136 let kp = kp_in
137 .validate(ctx.provider.crypto(), ProtocolVersion::Mls10)
138 .map_err(|e| js_err(format!("kp validate: {e:?}")))?;
139 let welcome = ctx.invite(&[kp]).map_err(|e| js_err(e))?;
140 Ok(Uint8Array::from(welcome.as_slice()))
141 }
142
143 #[wasm_bindgen(js_name = "acceptWelcome")]
148 pub fn accept_welcome(&self, welcome_bytes: &[u8]) -> Result<(), JsValue> {
149 self.inner.borrow_mut()
150 .accept_welcome(welcome_bytes)
151 .map_err(|e| js_err(e))
152 }
153}
154
155#[wasm_bindgen]
166pub struct GroupNode {
167 inner: RefCell<RustGroupNode>,
168}
169
170#[wasm_bindgen]
171impl GroupNode {
172 #[wasm_bindgen(js_name = "create")]
174 pub fn create(leaf_index: u32, group_id_bytes: &[u8]) -> GroupNode {
175 let gid: [u8; 16] = group_id_bytes.try_into().unwrap_or([0u8; 16]);
176 GroupNode { inner: RefCell::new(RustGroupNode::new(leaf_index as MemberId, gid)) }
177 }
178
179 #[wasm_bindgen(js_name = "bootstrapAsCreator")]
181 pub fn bootstrap_as_creator(&self, epoch: u64) {
182 self.inner.borrow_mut().bootstrap_as_creator(epoch);
183 }
184
185 #[wasm_bindgen(js_name = "bootstrapAsJoiner")]
190 pub fn bootstrap_as_joiner(&self, epoch: u64, expected_first_tid: u32) {
191 self.inner.borrow_mut().bootstrap_as_joiner(epoch, expected_first_tid);
192 }
193
194 #[wasm_bindgen(js_name = "onWire")]
206 pub fn on_wire(&self, mls: &MlsContext, wire_bytes: &[u8]) -> Array {
207 let mut node = self.inner.borrow_mut();
208 let mut mls_inner = mls.inner.borrow_mut();
209 let events = node.on_wire(&mut *mls_inner, wire_bytes).unwrap_or_default();
210 let arr = Array::new();
211 for ev in events {
212 arr.push(&event_to_js(ev));
213 }
214 arr
215 }
216
217 #[wasm_bindgen(js_name = "checkTimeouts")]
219 pub fn check_timeouts(&self) -> Array {
220 let arr = Array::new();
221 for ev in self.inner.borrow_mut().check_timeouts() {
222 arr.push(&event_to_js(ev));
223 }
224 arr
225 }
226
227 #[wasm_bindgen(getter, js_name = "lastTransitionId")]
229 pub fn last_transition_id(&self) -> u32 {
230 self.inner.borrow().last_transition_id
231 }
232
233 #[wasm_bindgen(getter, js_name = "currentEpoch")]
235 pub fn current_epoch(&self) -> u64 {
236 self.inner.borrow().current_epoch
237 }
238
239 #[wasm_bindgen(getter, js_name = "memberId")]
241 pub fn member_id(&self) -> u32 {
242 self.inner.borrow().member_id
243 }
244}
245
246#[wasm_bindgen]
261pub struct GtpClient {
262 inner: RefCell<RustGtpClient>,
263}
264
265#[wasm_bindgen]
266impl GtpClient {
267 #[wasm_bindgen(js_name = "create")]
269 pub fn create() -> GtpClient {
270 GtpClient { inner: RefCell::new(RustGtpClient::new()) }
271 }
272
273 #[wasm_bindgen(js_name = "send")]
278 pub fn send(
279 &self,
280 node: &GroupNode,
281 mls: &MlsContext,
282 target: u32,
283 message_id: u64,
284 text: &str,
285 ) -> JsValue {
286 let mut gtp = self.inner.borrow_mut();
287 let mut n = node.inner.borrow_mut();
288 let mut m = mls.inner.borrow_mut();
289 match gtp.send(&mut *n, &mut *m, target as MemberId, message_id, text, PayloadCodec::Cbor) {
290 Ok(frame) => {
291 let obj = Object::new();
292 set(&obj, "wire", &u8s(&frame.wire));
293 set(&obj, "to", &JsValue::from_f64(frame.to as f64));
294 obj.into()
295 }
296 Err(_) => JsValue::NULL,
297 }
298 }
299
300 #[wasm_bindgen(js_name = "accept")]
306 pub fn accept(&self, plaintext: &[u8], epoch: u64) -> JsValue {
307 let mut gtp = self.inner.borrow_mut();
308 match gtp.accept(plaintext, epoch, PayloadCodec::Cbor) {
309 Ok(result) => {
310 let (msg, status) = match result {
311 GtpAccept::New(m) => (m, "new"),
312 GtpAccept::Duplicate(m) => (m, "duplicate"),
313 };
314 let text = String::from_utf8_lossy(&msg.content).into_owned();
315 let obj = Object::new();
316 set(&obj, "text", &JsValue::from_str(&text));
317 set(&obj, "messageId", &js_sys::BigInt::from(msg.message_id).into());
318 set(&obj, "senderId", &JsValue::from_f64(msg.sender_id as f64));
319 set(&obj, "status", &JsValue::from_str(status));
320 obj.into()
321 }
322 Err(_) => JsValue::NULL,
323 }
324 }
325
326 #[wasm_bindgen(js_name = "reset")]
328 pub fn reset(&self) {
329 self.inner.borrow_mut().reset();
330 }
331}
332
333#[cfg(test)]
336mod tests;