diff --git a/bindings/wasm/crate/src/lib.rs b/bindings/wasm/crate/src/lib.rs index 2df79871c..b292bcce4 100644 --- a/bindings/wasm/crate/src/lib.rs +++ b/bindings/wasm/crate/src/lib.rs @@ -1,4 +1,4 @@ -use std::cell::Cell; +use std::cell::RefCell; use std::collections::HashMap; use std::sync::Mutex; @@ -11,7 +11,7 @@ extern crate lazy_static; // This whole strategy is to keep a singleton in WASM memory world lazy_static! { - static ref INSTANCE_MAP: Mutex>> = + static ref INSTANCE_MAP: Mutex>> = Mutex::new(HashMap::new()); } @@ -20,7 +20,7 @@ lazy_static! { pub fn new_voodoo_instance() -> String { let mut instances = INSTANCE_MAP.lock().unwrap(); let handle = (instances.len() as u64).to_string(); - instances.insert(handle.clone(), Cell::new(VoodooInstance::new())); + instances.insert(handle.clone(), RefCell::new(VoodooInstance::new())); handle } @@ -29,17 +29,18 @@ pub fn create_outbound_session( sending_handle: &str, receiving_handle: &str, message: &str, -) -> Result { +) -> Result, JsValue> { // Look up both handles in INSTANCE_MAP let instances = INSTANCE_MAP.lock().unwrap(); let mut sending_instance = instances .get(sending_handle) .ok_or("sending_handle not found")? - .take(); + .borrow_mut(); + let receiving_instance = instances .get(receiving_handle) .ok_or("receiving_handle not found")? - .take(); + .borrow(); // Get other party's public situation let receiving_public = receiving_instance.public_account(); @@ -47,14 +48,12 @@ pub fn create_outbound_session( // Create the session let result = sending_instance.create_outbound_session_serialized(&receiving_public, message); - // Put the sending_instance and receiving_instance back, we know the cells exist - instances.get(sending_handle).unwrap().set(sending_instance); - instances - .get(receiving_handle) - .unwrap() - .set(receiving_instance); match result { - Ok((_, ciphertext_json)) => Ok(ciphertext_json), + Ok((session_id, ciphertext_json)) => Ok(vec![ + JsValue::from_str(&session_id), + JsValue::from_str(&ciphertext_json), + ] + .into_boxed_slice()), Err(e) => Err(JsValue::from_str(&e.to_string())), } } @@ -64,17 +63,17 @@ pub fn create_inbound_session( sending_handle: &str, receiving_handle: &str, message: &str, -) -> Result { +) -> Result, JsValue> { // Look up both handles in INSTANCE_MAP let instances = INSTANCE_MAP.lock().unwrap(); let sending_instance = instances .get(sending_handle) .ok_or("sending_handle not found")? - .take(); + .borrow(); let mut receiving_instance = instances .get(receiving_handle) .ok_or("receiving_handle not found")? - .take(); + .borrow_mut(); // Get sender party public let sending_public = sending_instance.public_account(); @@ -82,15 +81,52 @@ pub fn create_inbound_session( // Create the session let result = receiving_instance.create_inbound_session_serialized(&sending_public, message); - // Put the instances back in their cells - instances.get(sending_handle).unwrap().set(sending_instance); - instances - .get(receiving_handle) - .unwrap() - .set(receiving_instance); + match result { + Ok((session_id, ciphertext_json)) => Ok(vec![ + JsValue::from_str(&session_id), + JsValue::from_str(&ciphertext_json), + ] + .into_boxed_slice()), + Err(e) => Err(JsValue::from_str(&e.to_string())), + } +} + +#[wasm_bindgen] +pub fn encrypt_message( + sending_handle: &str, + session_id: &str, + message: &str, +) -> Result { + let instances = INSTANCE_MAP.lock().unwrap(); + let mut instance = instances + .get(sending_handle) + .ok_or("sending_handle not found")? + .borrow_mut(); + + let result = instance.encrypt_message_serialized(session_id, message); + + match result { + Ok(ciphertext_json) => Ok(ciphertext_json), + Err(e) => Err(JsValue::from_str(&e.to_string())), + } +} + +#[wasm_bindgen] +pub fn decrypt_message( + handle: &str, + session_id: &str, + ciphertext: &str, +) -> Result { + let instances = INSTANCE_MAP.lock().unwrap(); + let mut instance = instances + .get(handle) + .ok_or("handle not found")? + .borrow_mut(); + + let result = instance.decrypt_message_serialized(session_id, ciphertext); match result { - Ok((_, plaintext)) => Ok(plaintext), + Ok(plaintext) => Ok(plaintext), Err(e) => Err(JsValue::from_str(&e.to_string())), } } diff --git a/bindings/wasm/src/xmtpv3.ts b/bindings/wasm/src/xmtpv3.ts index 4a23c9742..93c2449c3 100644 --- a/bindings/wasm/src/xmtpv3.ts +++ b/bindings/wasm/src/xmtpv3.ts @@ -3,6 +3,8 @@ import init, { new_voodoo_instance, create_outbound_session, create_inbound_session, + decrypt_message, + encrypt_message, e2e_selftest, } from "./pkg/libxmtp.js"; @@ -20,6 +22,11 @@ export const setWasmInit = (arg: () => InitInput) => { let initialized: Promise | undefined = undefined; +type SessionResult = { + sessionId: string; + payload: string; +}; + export class VoodooInstance { // Handle to the voodooinstance object in the Wasm module public handle: string = ""; @@ -31,16 +38,37 @@ export class VoodooInstance { this.handle = handle; } - createOutboundSession(other: VoodooInstance, msg: string): Promise { - return new Promise((resolve, reject) => { - resolve(this.wasmModule.createOutboundSession(this.handle, other.handle, msg)); - }); + async createOutboundSession( + other: VoodooInstance, + msg: string + ): Promise { + const [sessionId, payload] = this.wasmModule.createOutboundSession( + this.handle, + other.handle, + msg + ); + + return { sessionId, payload }; + } + + async createInboundSession( + other: VoodooInstance, + msg: string + ): Promise { + const [sessionId, payload] = this.wasmModule.createInboundSession( + other.handle, + this.handle, + msg + ); + return { sessionId, payload }; + } + + async encryptMessage(sessionId: string, msg: string): Promise { + return this.wasmModule.encryptMessage(this.handle, sessionId, msg); } - createInboundSession(other: VoodooInstance, msg: string): Promise { - return new Promise((resolve, reject) => { - resolve(this.wasmModule.createInboundSession(other.handle, this.handle, msg)); - }); + async decryptMessage(sessionId: string, ciphertext: string): Promise { + return this.wasmModule.decryptMessage(this.handle, sessionId, ciphertext); } } @@ -54,7 +82,6 @@ export class XMTPv3 { } } - // Manages the Wasm module, which loads a singleton version of our Rust code export class XMTPWasm { private constructor() {} @@ -69,12 +96,42 @@ export class XMTPWasm { return new VoodooInstance(this, handle); } - createOutboundSession(sendHandle: string, receiveHandle: string, msg: string): string { - return create_outbound_session(sendHandle, receiveHandle, msg); + createOutboundSession( + sendHandle: string, + receiveHandle: string, + msg: string + ): [string, string] { + return create_outbound_session(sendHandle, receiveHandle, msg) as [ + string, + string + ]; + } + + createInboundSession( + sendHandle: string, + receiveHandle: string, + msg: string + ): [string, string] { + return create_inbound_session(sendHandle, receiveHandle, msg) as [ + string, + string + ]; + } + + encryptMessage( + sendHandle: string, + sessionId: string, + message: string + ): string { + return encrypt_message(sendHandle, sessionId, message); } - createInboundSession(sendHandle: string, receiveHandle: string, msg: string): string { - return create_inbound_session(sendHandle, receiveHandle, msg); + decryptMessage( + handle: string, + sessionId: string, + ciphertext: string + ): string { + return decrypt_message(handle, sessionId, ciphertext); } /** diff --git a/bindings/wasm/tests/wasmpkg.test.ts b/bindings/wasm/tests/wasmpkg.test.ts index aba67a796..44053c186 100644 --- a/bindings/wasm/tests/wasmpkg.test.ts +++ b/bindings/wasm/tests/wasmpkg.test.ts @@ -12,19 +12,48 @@ it("can instantiate", async () => { it("can run self test", async () => { const xmtp = await XMTPWasm.initialize(); - const xmtpv3 = await xmtp.getXMTPv3(); - const res = await xmtpv3.selfTest(); + const xmtpv3 = xmtp.getXMTPv3(); + const res = xmtpv3.selfTest(); expect(res).toBe(true); }); it("can do a simple conversation", async () => { const wasm = await XMTPWasm.initialize(); - const alice = await wasm.newVoodooInstance(); - const bob = await wasm.newVoodooInstance(); + const alice = wasm.newVoodooInstance(); + const bob = wasm.newVoodooInstance(); - const outboundJson = await alice.createOutboundSession(bob, "hello there"); + const { sessionId, payload } = await alice.createOutboundSession( + bob, + "hello there" + ); + expect(typeof sessionId).toBe("string"); // Unused, but test JSON parseable - const _ = JSON.parse(outboundJson); - const inboundPlaintext = await bob.createInboundSession(alice, outboundJson); - expect(inboundPlaintext).toBe("hello there"); + const _ = JSON.parse(payload); + const { payload: inboundPayload } = await bob.createInboundSession( + alice, + payload + ); + expect(inboundPayload).toBe("hello there"); +}); + +it("can send a message", async () => { + const wasm = await XMTPWasm.initialize(); + const alice = wasm.newVoodooInstance(); + const bob = wasm.newVoodooInstance(); + + const { sessionId, payload } = await alice.createOutboundSession( + bob, + "hello there" + ); + await bob.createInboundSession(alice, payload); + expect(typeof sessionId).toBe("string"); + + const msg = "hello there"; + const encrypted = await alice.encryptMessage(sessionId, msg); + expect(typeof encrypted).toBe("string"); + + // Alice can't decrypt her own message. Does work for Bob though + // const decrypted = await alice.decryptMessage(sessionId, encrypted); + const decrypted = await bob.decryptMessage(sessionId, encrypted); + expect(decrypted).toBe(msg); });