1use crate::util::js_to_tap_message;
2use js_sys::{Array, Object, Promise, Reflect};
3use std::sync::Arc;
4use tap_agent::agent::TapAgent;
5use tap_agent::{
6 did::DIDGenerationOptions,
7 message::SecurityMode,
8 message_packing::{PackOptions, UnpackOptions},
9 AgentConfig, AgentKeyManager, AgentKeyManagerBuilder, KeyType, Packable, Unpackable,
10};
11
12trait WasmTapAgentExt {
14 fn agent_key_manager(&self) -> &Arc<AgentKeyManager>;
16}
17
18impl WasmTapAgentExt for TapAgent {
19 fn agent_key_manager(&self) -> &Arc<AgentKeyManager> {
20 self.key_manager()
22 }
23}
24use tap_msg::didcomm::PlainMessage;
25use wasm_bindgen::prelude::*;
26use wasm_bindgen_futures::future_to_promise;
27use web_sys::console;
28
29#[wasm_bindgen]
31#[derive(Clone)]
32pub struct WasmTapAgent {
33 agent: TapAgent,
35 nickname: Option<String>,
37 debug: bool,
39 private_key_hex: Option<String>,
41}
42
43#[wasm_bindgen]
44impl WasmTapAgent {
45 #[wasm_bindgen(js_name = fromPrivateKey)]
47 pub async fn from_private_key(
48 private_key_hex: String,
49 key_type_str: String,
50 ) -> Result<WasmTapAgent, JsValue> {
51 #[cfg(any(
54 feature = "crypto-ed25519",
55 feature = "crypto-p256",
56 feature = "crypto-secp256k1"
57 ))]
58 {
59 let private_key_bytes = hex::decode(&private_key_hex)
61 .map_err(|e| JsValue::from_str(&format!("Invalid hex private key: {}", e)))?;
62
63 let key_type = match key_type_str.as_str() {
65 #[cfg(feature = "crypto-ed25519")]
66 "Ed25519" => KeyType::Ed25519,
67 #[cfg(feature = "crypto-p256")]
68 "P256" => KeyType::P256,
69 #[cfg(feature = "crypto-secp256k1")]
70 "Secp256k1" => KeyType::Secp256k1,
71 _ => {
72 return Err(JsValue::from_str(&format!(
73 "Invalid or disabled key type: {}",
74 key_type_str
75 )))
76 }
77 };
78
79 let (agent, did) = TapAgent::from_private_key(&private_key_bytes, key_type, false)
81 .await
82 .map_err(|e| {
83 JsValue::from_str(&format!("Failed to create agent from private key: {}", e))
84 })?;
85
86 if agent.config.debug {
87 console::log_1(&JsValue::from_str(&format!(
88 "Created WASM TAP Agent from private key with DID: {}",
89 did
90 )));
91 }
92
93 Ok(WasmTapAgent {
94 agent,
95 nickname: None,
96 debug: false,
97 private_key_hex: Some(private_key_hex.clone()),
98 })
99 }
100
101 #[cfg(not(any(
102 feature = "crypto-ed25519",
103 feature = "crypto-p256",
104 feature = "crypto-secp256k1"
105 )))]
106 {
107 Err(JsValue::from_str("No cryptographic features enabled"))
108 }
109 }
110
111 #[wasm_bindgen(constructor)]
113 pub fn new(config: JsValue) -> std::result::Result<WasmTapAgent, JsValue> {
114 let nickname =
117 if let Ok(nickname_prop) = Reflect::get(&config, &JsValue::from_str("nickname")) {
118 nickname_prop.as_string()
119 } else {
120 None
121 };
122
123 let debug = if let Ok(debug_prop) = Reflect::get(&config, &JsValue::from_str("debug")) {
124 debug_prop.is_truthy()
125 } else {
126 false
127 };
128
129 let did_string = if let Ok(did_prop) = Reflect::get(&config, &JsValue::from_str("did")) {
131 did_prop.as_string()
132 } else {
133 None
134 };
135
136 let key_manager_builder = AgentKeyManagerBuilder::new();
138 let key_manager = match key_manager_builder.build() {
139 Ok(km) => km,
140 Err(e) => {
141 return Err(JsValue::from_str(&format!(
142 "Failed to build key manager: {}",
143 e
144 )))
145 }
146 };
147
148 let agent = if let Some(did) = did_string {
149 let agent_config = AgentConfig::new(did).with_debug(debug);
151
152 TapAgent::new(agent_config, Arc::new(key_manager))
154 } else {
155 let options = DIDGenerationOptions {
157 key_type: KeyType::Ed25519,
158 };
159 let generated_key = match key_manager.generate_key_without_save(options) {
160 Ok(key) => key,
161 Err(e) => return Err(JsValue::from_str(&format!("Failed to generate key: {}", e))),
162 };
163
164 if let Err(e) = key_manager.add_key_without_save(&generated_key) {
166 return Err(JsValue::from_str(&format!("Failed to add key: {}", e)));
167 }
168
169 let agent_config = AgentConfig::new(generated_key.did.clone()).with_debug(debug);
170 TapAgent::new(agent_config, Arc::new(key_manager))
171 };
172
173 if debug {
174 console::log_1(&JsValue::from_str(&format!(
175 "Created WASM TAP Agent with DID: {}",
176 agent.config.agent_did
177 )));
178 }
179
180 Ok(WasmTapAgent {
181 agent,
182 nickname,
183 debug,
184 private_key_hex: None,
185 })
186 }
187
188 pub fn get_did(&self) -> String {
190 self.agent.config.agent_did.clone()
191 }
192
193 pub fn nickname(&self) -> Option<String> {
195 self.nickname.clone()
196 }
197
198 #[wasm_bindgen(js_name = exportPrivateKey)]
200 pub fn export_private_key(&self) -> Result<String, JsValue> {
201 if let Some(stored_key) = &self.private_key_hex {
203 return Ok(stored_key.clone());
204 }
205
206 let key_manager = self.agent.agent_key_manager();
208 let did = &self.agent.config.agent_did;
209
210 let (private_key_bytes, _key_type) = key_manager
211 .get_private_key(did)
212 .map_err(|e| JsValue::from_str(&format!("Failed to get key for DID {}: {}", did, e)))?;
213
214 Ok(hex::encode(&private_key_bytes))
215 }
216
217 #[wasm_bindgen(js_name = exportPublicKey)]
219 pub fn export_public_key(&self) -> Result<String, JsValue> {
220 let key_manager = self.agent.agent_key_manager();
222
223 let did = &self.agent.config.agent_did;
225
226 let generated_key = key_manager
228 .get_generated_key(did)
229 .map_err(|e| JsValue::from_str(&format!("Failed to get key for DID {}: {}", did, e)))?;
230
231 let hex_public_key = hex::encode(&generated_key.public_key);
233
234 Ok(hex_public_key)
235 }
236
237 #[wasm_bindgen(js_name = packMessage)]
239 pub fn pack_message(&self, message_js: JsValue) -> Promise {
240 let agent = self.agent.clone();
241 let debug = self.debug;
242
243 future_to_promise(async move {
244 let tap_message = match js_to_tap_message(&message_js) {
246 Ok(msg) => msg,
247 Err(e) => {
248 return Err(JsValue::from_str(&format!(
249 "Failed to convert JS message: {}",
250 e
251 )))
252 }
253 };
254
255 let security_mode = SecurityMode::Signed; let sender_kid = {
260 let key_manager = agent.agent_key_manager();
261 if let Ok(key) = key_manager.get_generated_key(&agent.config.agent_did) {
262 if let Some(vm) = key.did_doc.verification_method.first() {
264 Some(vm.id.clone())
265 } else {
266 if agent.config.agent_did.starts_with("did:key:") {
268 let key_part = &agent.config.agent_did[8..]; Some(format!("{}#{}", agent.config.agent_did, key_part))
270 } else {
271 Some(format!("{}#keys-1", agent.config.agent_did))
272 }
273 }
274 } else {
275 if agent.config.agent_did.starts_with("did:key:") {
277 let key_part = &agent.config.agent_did[8..]; Some(format!("{}#{}", agent.config.agent_did, key_part))
279 } else {
280 Some(format!("{}#keys-1", agent.config.agent_did))
281 }
282 }
283 };
284 let recipient_kid = None; let pack_options = PackOptions {
287 security_mode,
288 sender_kid,
289 recipient_kid,
290 };
291
292 let key_manager = agent.agent_key_manager();
294
295 if debug {
297 console::log_1(&JsValue::from_str(&format!(
298 "Packing message: id={}, type={}, from={}, to={:?}",
299 tap_message.id, tap_message.type_, tap_message.from, tap_message.to
300 )));
301 }
302
303 let packed = match tap_message.pack(&**key_manager, pack_options).await {
304 Ok(packed_msg) => {
305 if debug {
306 console::log_1(&JsValue::from_str(&format!(
307 "Packed message length: {}, preview: {}...",
308 packed_msg.len(),
309 &packed_msg.chars().take(50).collect::<String>()
310 )));
311 }
312 packed_msg
313 }
314 Err(e) => {
315 console::error_1(&JsValue::from_str(&format!("Pack error: {:?}", e)));
316 return Err(JsValue::from_str(&format!("Failed to pack message: {}", e)));
317 }
318 };
319
320 if debug {
321 console::log_1(&JsValue::from_str(&format!(
322 "✅ Message packed successfully for sender {}",
323 agent.config.agent_did
324 )));
325 }
326
327 let result = Object::new();
329 Reflect::set(
330 &result,
331 &JsValue::from_str("message"),
332 &JsValue::from_str(&packed),
333 )?;
334
335 let metadata = Object::new();
337 Reflect::set(
338 &metadata,
339 &JsValue::from_str("type"),
340 &JsValue::from_str("signed"),
341 )?;
342 Reflect::set(
343 &metadata,
344 &JsValue::from_str("sender"),
345 &JsValue::from_str(&agent.config.agent_did),
346 )?;
347
348 Reflect::set(&result, &JsValue::from_str("metadata"), &metadata)?;
349
350 Ok(result.into())
351 })
352 }
353
354 #[wasm_bindgen(js_name = unpackMessage)]
356 pub fn unpack_message(&self, packed_message: &str, expected_type: Option<String>) -> Promise {
357 let agent = self.agent.clone();
358 let debug = self.debug;
359 let packed_message = packed_message.to_string(); future_to_promise(async move {
362 let unpack_options = UnpackOptions {
366 expected_security_mode: SecurityMode::Any,
367 expected_recipient_kid: None, require_signature: false,
369 };
370
371 let key_manager = agent.agent_key_manager();
373 let plain_message: PlainMessage =
374 match String::unpack(&packed_message, &**key_manager, unpack_options).await {
375 Ok(msg) => msg,
376 Err(e) => {
377 return Err(JsValue::from_str(&format!(
378 "Failed to unpack message: {}",
379 e
380 )))
381 }
382 };
383
384 if debug {
385 console::log_1(&JsValue::from_str(&format!(
386 "✅ Message unpacked successfully for recipient {}",
387 agent.config.agent_did
388 )));
389 }
390
391 if let Some(expected) = expected_type {
393 if plain_message.type_ != expected {
394 return Err(JsValue::from_str(&format!(
395 "Expected message type {} but got {}",
396 expected, plain_message.type_
397 )));
398 }
399 }
400
401 let result = Object::new();
403
404 Reflect::set(
406 &result,
407 &JsValue::from_str("id"),
408 &JsValue::from_str(&plain_message.id),
409 )?;
410
411 Reflect::set(
413 &result,
414 &JsValue::from_str("type"),
415 &JsValue::from_str(&plain_message.type_),
416 )?;
417
418 Reflect::set(
420 &result,
421 &JsValue::from_str("from"),
422 &JsValue::from_str(&plain_message.from),
423 )?;
424
425 let to_array = Array::new();
426 for to_did in &plain_message.to {
427 to_array.push(&JsValue::from_str(to_did));
428 }
429 Reflect::set(&result, &JsValue::from_str("to"), &to_array)?;
430
431 let body_str = serde_json::to_string(&plain_message.body)
433 .map_err(|e| JsValue::from_str(&format!("Failed to serialize body: {}", e)))?;
434
435 let body_js = js_sys::JSON::parse(&body_str)
436 .map_err(|e| JsValue::from_str(&format!("Failed to parse body: {:?}", e)))?;
437
438 Reflect::set(&result, &JsValue::from_str("body"), &body_js)?;
439
440 if let Some(created) = plain_message.created_time {
442 Reflect::set(
443 &result,
444 &JsValue::from_str("created"),
445 &JsValue::from_f64(created as f64),
446 )?;
447 }
448
449 if let Some(expires) = plain_message.expires_time {
451 Reflect::set(
452 &result,
453 &JsValue::from_str("expires"),
454 &JsValue::from_f64(expires as f64),
455 )?;
456 }
457
458 if let Some(thid) = plain_message.thid {
460 Reflect::set(
461 &result,
462 &JsValue::from_str("thid"),
463 &JsValue::from_str(&thid),
464 )?;
465 }
466
467 if let Some(pthid) = plain_message.pthid {
469 Reflect::set(
470 &result,
471 &JsValue::from_str("pthid"),
472 &JsValue::from_str(&pthid),
473 )?;
474 }
475
476 Ok(result.into())
477 })
478 }
479}