use qrcode::{EcLevel, QrCode};
#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
pub struct QrCodeMatrix {
pub size: u32,
pub modules: String,
}
#[uniffi::export]
pub fn encode_text_qr(text: String) -> Option<QrCodeMatrix> {
let trimmed = text.trim();
if trimmed.is_empty() {
return None;
}
let code = QrCode::with_error_correction_level(trimmed.as_bytes(), EcLevel::M).ok()?;
let width = code.width();
let cells = code.to_colors();
let mut modules = String::with_capacity(width * width);
for color in cells {
modules.push(if color == qrcode::Color::Dark {
'1'
} else {
'0'
});
}
Some(QrCodeMatrix {
size: width as u32,
modules,
})
}
#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
pub struct DeviceApprovalQrPayload {
pub owner_input: String,
pub device_input: String,
}
#[uniffi::export]
pub fn encode_device_approval_qr(_owner_input: String, _device_input: String) -> String {
String::new()
}
#[uniffi::export]
pub fn decode_device_approval_qr(raw: String) -> Option<DeviceApprovalQrPayload> {
nostr_double_ratchet::parse_compact_device_link_request(&raw)
.ok()
.map(|request| DeviceApprovalQrPayload {
owner_input: String::new(),
device_input: request.device_app_key_pubkey.to_hex(),
})
}
#[cfg(test)]
mod tests {
use super::{decode_device_approval_qr, encode_device_approval_qr, DeviceApprovalQrPayload};
use nostr_sdk::Keys;
#[test]
fn removed_device_approval_qr_encoder_returns_empty() {
let encoded = encode_device_approval_qr("npub-owner".into(), "npub-device".into());
assert_eq!(encoded, "");
}
#[test]
fn compact_device_approval_qr_rejects_wrong_inputs() {
assert!(decode_device_approval_qr("".into()).is_none());
assert!(decode_device_approval_qr("npub1plainvalue".into()).is_none());
assert!(decode_device_approval_qr("https://example.com".into()).is_none());
assert!(decode_device_approval_qr("not-a-compact-link-code".into()).is_none());
assert!(
decode_device_approval_qr(format!("{}.{}.extra", "1".repeat(64), "1".repeat(64)))
.is_none()
);
}
#[test]
fn compact_device_link_qr_decodes_to_device() {
let device = Keys::generate();
let request = Keys::generate();
let encoded = format!(
"{}.{}",
device.public_key().to_hex(),
request.secret_key().to_secret_hex()
);
let decoded = decode_device_approval_qr(encoded.clone()).expect("decode compact request");
assert_eq!(
decoded,
DeviceApprovalQrPayload {
owner_input: String::new(),
device_input: device.public_key().to_hex(),
}
);
let prefixed = format!("prefix:{encoded}");
assert!(decode_device_approval_qr(prefixed).is_none());
}
}