//! WASM bindings for did:plc operations //! //! This module is only compiled when both: //! - Building for wasm32 target (`target_arch = "wasm32"`) //! - The "wasm" feature is enabled (provides wasm_bindgen and related dependencies) #![cfg(all(target_arch = "wasm32", feature = "wasm"))] use crate::builder::DidBuilder as NativeDidBuilder; use crate::crypto::{SigningKey as NativeSigningKey, VerifyingKey as NativeVerifyingKey}; use crate::did::Did as NativeDid; use crate::document::{DidDocument as NativeDidDocument, ServiceEndpoint as NativeServiceEndpoint}; use crate::operations::Operation as NativeOperation; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; /// WASM wrapper for DID #[wasm_bindgen] pub struct WasmDid { inner: NativeDid, } #[wasm_bindgen] impl WasmDid { /// Parse and validate a DID string /// /// # Errors /// /// Throws a JavaScript error if the DID is invalid #[wasm_bindgen(constructor)] pub fn parse(did_string: &str) -> Result { NativeDid::parse(did_string) .map(|did| WasmDid { inner: did }) .map_err(|e| JsValue::from_str(&e.to_string())) } /// Check if this DID is valid /// /// Since DIDs can only be constructed through validation, this always returns true #[wasm_bindgen(js_name = "isValid")] pub fn is_valid(&self) -> bool { self.inner.is_valid() } /// Get the 24-character identifier portion (without "did:plc:" prefix) #[wasm_bindgen(getter)] pub fn identifier(&self) -> String { self.inner.identifier().to_string() } /// Get the full DID string including "did:plc:" prefix #[wasm_bindgen(js_name = "toString")] pub fn to_string_js(&self) -> String { self.inner.to_string() } /// Get the full DID string as a getter #[wasm_bindgen(getter)] pub fn did(&self) -> String { self.inner.to_string() } } /// WASM wrapper for SigningKey #[wasm_bindgen] pub struct WasmSigningKey { inner: NativeSigningKey, } #[wasm_bindgen] impl WasmSigningKey { /// Generate a new P-256 key pair #[wasm_bindgen(js_name = "generateP256")] pub fn generate_p256() -> WasmSigningKey { WasmSigningKey { inner: NativeSigningKey::generate_p256(), } } /// Generate a new secp256k1 key pair #[wasm_bindgen(js_name = "generateK256")] pub fn generate_k256() -> WasmSigningKey { WasmSigningKey { inner: NativeSigningKey::generate_k256(), } } /// Convert this signing key to a did:key string #[wasm_bindgen(js_name = "toDidKey")] pub fn to_did_key(&self) -> String { self.inner.to_did_key() } } /// WASM wrapper for ServiceEndpoint #[wasm_bindgen] #[derive(Serialize, Deserialize)] pub struct WasmServiceEndpoint { /// Service type (e.g., "AtprotoPersonalDataServer") #[wasm_bindgen(skip)] pub service_type: String, /// Service endpoint URL #[wasm_bindgen(skip)] pub endpoint: String, } #[wasm_bindgen] impl WasmServiceEndpoint { /// Create a new service endpoint #[wasm_bindgen(constructor)] pub fn new(service_type: String, endpoint: String) -> WasmServiceEndpoint { WasmServiceEndpoint { service_type, endpoint, } } /// Get the service type #[wasm_bindgen(getter = serviceType)] pub fn service_type(&self) -> String { self.service_type.clone() } /// Get the endpoint URL #[wasm_bindgen(getter)] pub fn endpoint(&self) -> String { self.endpoint.clone() } } impl From for NativeServiceEndpoint { fn from(wasm: WasmServiceEndpoint) -> Self { NativeServiceEndpoint::new(wasm.service_type, wasm.endpoint) } } /// WASM wrapper for DidBuilder #[wasm_bindgen] pub struct WasmDidBuilder { inner: NativeDidBuilder, } #[wasm_bindgen] impl WasmDidBuilder { /// Create a new DID builder #[wasm_bindgen(constructor)] pub fn new() -> WasmDidBuilder { WasmDidBuilder { inner: NativeDidBuilder::new(), } } /// Add a rotation key #[wasm_bindgen(js_name = "addRotationKey")] pub fn add_rotation_key(mut self, key: WasmSigningKey) -> WasmDidBuilder { self.inner = self.inner.add_rotation_key(key.inner); self } /// Add a verification method #[wasm_bindgen(js_name = "addVerificationMethod")] pub fn add_verification_method(mut self, name: String, key: WasmSigningKey) -> WasmDidBuilder { self.inner = self.inner.add_verification_method(name, key.inner); self } /// Add an also-known-as URI #[wasm_bindgen(js_name = "addAlsoKnownAs")] pub fn add_also_known_as(mut self, uri: String) -> WasmDidBuilder { self.inner = self.inner.add_also_known_as(uri); self } /// Add a service endpoint #[wasm_bindgen(js_name = "addService")] pub fn add_service(mut self, name: String, endpoint: WasmServiceEndpoint) -> WasmDidBuilder { self.inner = self.inner.add_service(name, endpoint.into()); self } /// Build and sign the genesis operation /// /// Returns a JavaScript object with: /// - did: The created DID string /// - operation: The signed genesis operation as JSON /// /// # Errors /// /// Throws a JavaScript error if building fails #[wasm_bindgen] pub fn build(self) -> Result { let (did, operation, _keys) = self .inner .build() .map_err(|e| JsValue::from_str(&e.to_string()))?; // Create a result object let result = js_sys::Object::new(); // Set the DID js_sys::Reflect::set( &result, &JsValue::from_str("did"), &JsValue::from_str(did.as_str()), ) .map_err(|e| JsValue::from_str(&format!("Failed to set did: {:?}", e)))?; // Serialize the operation to JSON let operation_json = serde_json::to_string(&operation) .map_err(|e| JsValue::from_str(&e.to_string()))?; js_sys::Reflect::set( &result, &JsValue::from_str("operation"), &JsValue::from_str(&operation_json), ) .map_err(|e| JsValue::from_str(&format!("Failed to set operation: {:?}", e)))?; Ok(result.into()) } } /// WASM wrapper for DID Document #[wasm_bindgen] pub struct WasmDidDocument { inner: NativeDidDocument, } #[wasm_bindgen] impl WasmDidDocument { /// Parse a DID document from JSON #[wasm_bindgen(js_name = "fromJson")] pub fn from_json(json: &str) -> Result { let doc: NativeDidDocument = serde_json::from_str(json).map_err(|e| JsValue::from_str(&e.to_string()))?; Ok(WasmDidDocument { inner: doc }) } /// Convert this DID document to JSON #[wasm_bindgen(js_name = "toJson")] pub fn to_json(&self) -> Result { serde_json::to_string_pretty(&self.inner).map_err(|e| JsValue::from_str(&e.to_string())) } /// Get the DID this document describes #[wasm_bindgen(getter)] pub fn id(&self) -> String { self.inner.id.to_string() } /// Validate this DID document #[wasm_bindgen] pub fn validate(&self) -> Result<(), JsValue> { self.inner .validate() .map_err(|e| JsValue::from_str(&e.to_string())) } } /// WASM wrapper for VerifyingKey #[wasm_bindgen] pub struct WasmVerifyingKey { inner: NativeVerifyingKey, } #[wasm_bindgen] impl WasmVerifyingKey { /// Create a verifying key from a did:key string #[wasm_bindgen(js_name = "fromDidKey")] pub fn from_did_key(did_key: &str) -> Result { NativeVerifyingKey::from_did_key(did_key) .map(|key| WasmVerifyingKey { inner: key }) .map_err(|e| JsValue::from_str(&e.to_string())) } /// Convert this verifying key to a did:key string #[wasm_bindgen(js_name = "toDidKey")] pub fn to_did_key(&self) -> String { self.inner.to_did_key() } } /// WASM wrapper for Operation #[wasm_bindgen] pub struct WasmOperation { inner: NativeOperation, } #[wasm_bindgen] impl WasmOperation { /// Parse an operation from JSON #[wasm_bindgen(js_name = "fromJson")] pub fn from_json(json: &str) -> Result { let op: NativeOperation = serde_json::from_str(json).map_err(|e| JsValue::from_str(&e.to_string()))?; Ok(WasmOperation { inner: op }) } /// Convert this operation to JSON #[wasm_bindgen(js_name = "toJson")] pub fn to_json(&self) -> Result { serde_json::to_string_pretty(&self.inner).map_err(|e| JsValue::from_str(&e.to_string())) } /// Check if this is a genesis operation #[wasm_bindgen(js_name = "isGenesis")] pub fn is_genesis(&self) -> bool { self.inner.is_genesis() } /// Get the CID of this operation #[wasm_bindgen] pub fn cid(&self) -> Result { self.inner .cid() .map_err(|e| JsValue::from_str(&e.to_string())) } /// Get the prev field (CID of previous operation), or null for genesis #[wasm_bindgen] pub fn prev(&self) -> Option { self.inner.prev().map(|s| s.to_string()) } /// Get the signature of this operation #[wasm_bindgen] pub fn signature(&self) -> String { self.inner.signature().to_string() } /// Get the rotation keys from this operation (if any) #[wasm_bindgen(js_name = "rotationKeys")] pub fn rotation_keys(&self) -> Option> { self.inner.rotation_keys().map(|keys| keys.to_vec()) } /// Verify this operation's signature using the provided rotation keys /// /// Returns true if the signature is valid with at least one of the keys #[wasm_bindgen] pub fn verify(&self, rotation_keys: Vec) -> Result { let native_keys: Vec = rotation_keys.into_iter().map(|k| k.inner).collect(); match self.inner.verify(&native_keys) { Ok(_) => Ok(true), Err(_) => Ok(false), } } /// Verify this operation and return which key index verified it (0-based) /// /// Returns the index of the rotation key that verified the signature, /// or throws an error if none verified #[wasm_bindgen(js_name = "verifyWithKeyIndex")] pub fn verify_with_key_index(&self, rotation_keys: Vec) -> Result { for (i, key) in rotation_keys.iter().enumerate() { if self.inner.verify(&[key.inner]).is_ok() { return Ok(i); } } Err(JsValue::from_str("No rotation key verified the signature")) } } /// Initialize the WASM module /// /// This should be called before using any other functions #[wasm_bindgen(start)] pub fn init() { // WASM module initialization // For better panic messages in development, consider adding console_error_panic_hook }