feat(wasm): pack_backup_json / unpack_backup_json
JSON bridge for the SW. Binary fields are base64 in the JSON wrapper; core gets borrowed byte slices.
This commit is contained in:
@@ -305,6 +305,133 @@ pub fn totp_compute(
|
|||||||
Ok(TotpCode { code, expires_at })
|
Ok(TotpCode { code, expires_at })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Backup container bridge ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
use relicario_core::backup::{
|
||||||
|
pack_backup as core_pack_backup,
|
||||||
|
unpack_backup as core_unpack_backup,
|
||||||
|
BackupInput, BackupItem, BackupAttachment,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Pack a vault into a `.relbak` byte vector.
|
||||||
|
///
|
||||||
|
/// `input_json` shape:
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "salt": "<base64>",
|
||||||
|
/// "params_json": "...",
|
||||||
|
/// "devices_json": "...",
|
||||||
|
/// "manifest_enc": "<base64>",
|
||||||
|
/// "settings_enc": "<base64>",
|
||||||
|
/// "items": [{"id": "<hex>", "ciphertext": "<base64>"}, ...],
|
||||||
|
/// "attachments": [{"item_id": "<hex>", "attachment_id": "<hex>", "ciphertext": "<base64>"}, ...],
|
||||||
|
/// "reference_jpg": "<base64>" | null,
|
||||||
|
/// "git_archive": "<base64>" | null
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn pack_backup_json(input_json: &str, passphrase: &str) -> Result<Vec<u8>, JsError> {
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct InJson {
|
||||||
|
salt: String,
|
||||||
|
params_json: String,
|
||||||
|
devices_json: String,
|
||||||
|
manifest_enc: String,
|
||||||
|
settings_enc: String,
|
||||||
|
items: Vec<InItem>,
|
||||||
|
attachments: Vec<InAttachment>,
|
||||||
|
reference_jpg: Option<String>,
|
||||||
|
git_archive: Option<String>,
|
||||||
|
}
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct InItem { id: String, ciphertext: String }
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct InAttachment { item_id: String, attachment_id: String, ciphertext: String }
|
||||||
|
|
||||||
|
let parsed: InJson = serde_json::from_str(input_json)
|
||||||
|
.map_err(|e| JsError::new(&format!("backup input: {e}")))?;
|
||||||
|
|
||||||
|
let b64 = base64::engine::general_purpose::STANDARD;
|
||||||
|
let salt = b64.decode(&parsed.salt).map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
let manifest = b64.decode(&parsed.manifest_enc).map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
let settings = b64.decode(&parsed.settings_enc).map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
let items_bytes: Vec<(String, Vec<u8>)> = parsed.items.iter()
|
||||||
|
.map(|i| {
|
||||||
|
let ct = b64.decode(&i.ciphertext).map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
Ok((i.id.clone(), ct))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, JsError>>()?;
|
||||||
|
let attach_bytes: Vec<(String, String, Vec<u8>)> = parsed.attachments.iter()
|
||||||
|
.map(|a| {
|
||||||
|
let ct = b64.decode(&a.ciphertext).map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
Ok((a.item_id.clone(), a.attachment_id.clone(), ct))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, JsError>>()?;
|
||||||
|
|
||||||
|
let ref_bytes = parsed.reference_jpg.as_deref()
|
||||||
|
.map(|s| b64.decode(s))
|
||||||
|
.transpose()
|
||||||
|
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
let git_bytes = parsed.git_archive.as_deref()
|
||||||
|
.map(|s| b64.decode(s))
|
||||||
|
.transpose()
|
||||||
|
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
|
||||||
|
let items_refs: Vec<BackupItem> = items_bytes.iter()
|
||||||
|
.map(|(id, ct)| BackupItem { id: id.clone(), ciphertext: ct })
|
||||||
|
.collect();
|
||||||
|
let attach_refs: Vec<BackupAttachment> = attach_bytes.iter()
|
||||||
|
.map(|(iid, aid, ct)| BackupAttachment {
|
||||||
|
item_id: iid.clone(),
|
||||||
|
attachment_id: aid.clone(),
|
||||||
|
ciphertext: ct,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let input = BackupInput {
|
||||||
|
salt: &salt,
|
||||||
|
params_json: &parsed.params_json,
|
||||||
|
devices_json: &parsed.devices_json,
|
||||||
|
manifest_enc: &manifest,
|
||||||
|
settings_enc: &settings,
|
||||||
|
items: items_refs,
|
||||||
|
attachments: attach_refs,
|
||||||
|
reference_jpg: ref_bytes.as_deref(),
|
||||||
|
git_archive: git_bytes.as_deref(),
|
||||||
|
};
|
||||||
|
core_pack_backup(input, passphrase).map_err(|e| JsError::new(&e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unpack `.relbak` bytes; returns the JSON shape that mirrors `BackupOutput`,
|
||||||
|
/// with binary fields base64-encoded.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn unpack_backup_json(bytes: &[u8], passphrase: &str) -> Result<String, JsError> {
|
||||||
|
let out = core_unpack_backup(bytes, passphrase)
|
||||||
|
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
|
||||||
|
let b64 = base64::engine::general_purpose::STANDARD;
|
||||||
|
let json = serde_json::json!({
|
||||||
|
"salt": b64.encode(out.salt),
|
||||||
|
"params_json": out.params_json,
|
||||||
|
"devices_json": out.devices_json,
|
||||||
|
"manifest_enc": b64.encode(&out.manifest_enc),
|
||||||
|
"settings_enc": b64.encode(&out.settings_enc),
|
||||||
|
"items": out.items.iter().map(|i| serde_json::json!({
|
||||||
|
"id": i.id,
|
||||||
|
"ciphertext": b64.encode(&i.ciphertext),
|
||||||
|
})).collect::<Vec<_>>(),
|
||||||
|
"attachments": out.attachments.iter().map(|a| serde_json::json!({
|
||||||
|
"item_id": a.item_id,
|
||||||
|
"attachment_id": a.attachment_id,
|
||||||
|
"ciphertext": b64.encode(&a.ciphertext),
|
||||||
|
})).collect::<Vec<_>>(),
|
||||||
|
"reference_jpg": out.reference_jpg.as_ref().map(|b| b64.encode(b)),
|
||||||
|
"git_archive": out.git_archive.as_ref().map(|b| b64.encode(b)),
|
||||||
|
"created_at": out.created_at,
|
||||||
|
});
|
||||||
|
Ok(json.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod session_tests {
|
mod session_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user