Skip to content

Commit

Permalink
make saving of ui optional, add proper return types
Browse files Browse the repository at this point in the history
  • Loading branch information
matthme committed Sep 26, 2024
1 parent ec72ae7 commit 228a433
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 78 deletions.
25 changes: 22 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,29 @@

export function overwriteConfig(adminPort: number, configPath: string, keystoreConnectionUrl: string, bootstrapServerUrl: string, signalingServerUrl: string, allowedOrigin: string, useDpki: boolean, iceServerUrls?: Array<string> | undefined | null, keystoreInProcEnvironmentDir?: string | undefined | null): string
export function defaultConductorConfig(adminPort: number, conductorEnvironmentPath: string, keystoreConnectionUrl: string, bootstrapServerUrl: string, signalingServerUrl: string, allowedOrigin: string, useDpki: boolean, iceServerUrls?: Array<string> | undefined | null, keystoreInProcEnvironmentDir?: string | undefined | null): string
export interface HappAndUiHashes {
happSha256: string
webhappSha256?: string
uiSha256?: string
}
export interface StoredHappPathAndHashes {
happPath: string
happSha256: string
webhappSha256?: string
uiSha256?: string
}
export function happBytesWithCustomProperties(happPath: string, properties: Record<string, string | undefined | null>): Promise<Array<number>>
export function saveHappOrWebhapp(happOrWebHappPath: string, uisDir: string, happsDir: string): Promise<string>
/** Checks that the happ or webhapp is of the correct format */
export function validateHappOrWebhapp(happOrWebhappBytes: Array<number>): Promise<string>
/**
* Saves a happ or a webhapp file. If a uis_dir is specified and it is a webhapp,
* then the UI will be stored in [uis_dir]/[sha 256 of UI]/assets. If no uis_dir
* is specified, only the happ file will be stored.
*/
export function saveHappOrWebhapp(happOrWebHappPath: string, happsDir: string, uisDir?: string | undefined | null): Promise<StoredHappPathAndHashes>
/**
* Checks that the happ or webhapp is of the correct format
* WARNING: The decoding and encoding of the happ bytes seems to affect happ's sha256 hash.
*/
export function validateHappOrWebhapp(happOrWebhappBytes: Array<number>): Promise<HappAndUiHashes>
export interface ZomeCallUnsignedNapi {
cellId: Array<Array<number>>
zomeName: string
Expand Down
198 changes: 123 additions & 75 deletions src/decode_webapp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

use crate::utils::hash_bytes_sha256;

#[napi(object)]
pub struct HappAndUiHashes {
pub happ_sha256: String,
pub webhapp_sha256: Option<String>,
pub ui_sha256: Option<String>,
}

#[napi(object)]
pub struct StoredHappPathAndHashes {
pub happ_path: String,
pub happ_sha256: String,
pub webhapp_sha256: Option<String>,
pub ui_sha256: Option<String>,
}

#[napi]
pub async fn happ_bytes_with_custom_properties(
happ_path: String,
Expand Down Expand Up @@ -35,7 +52,7 @@ pub async fn happ_bytes_with_custom_properties(
println!("yaml_value: {:?}", yaml_value);
let yaml_properties = YamlProperties::from(yaml_value);
role_manifest.dna.modifiers.properties = Some(yaml_properties);
},
}
None => {
role_manifest.dna.modifiers.properties = None;
}
Expand Down Expand Up @@ -63,55 +80,27 @@ pub async fn happ_bytes_with_custom_properties(
Ok(app_bundle_bytes)
}

/// Saves a happ or a webhapp file. If a uis_dir is specified and it is a webhapp,
/// then the UI will be stored in [uis_dir]/[sha 256 of UI]/assets. If no uis_dir
/// is specified, only the happ file will be stored.
#[napi]
pub async fn save_happ_or_webhapp(
happ_or_web_happ_path: String,
uis_dir: String,
happs_dir: String,
) -> napi::Result<String> {
uis_dir: Option<String>,
) -> napi::Result<StoredHappPathAndHashes> {
let happ_or_webhapp_bytes = fs::read(happ_or_web_happ_path)?;

let (app_bundle, maybe_ui_and_webhapp_hash) = match WebAppBundle::decode(&happ_or_webhapp_bytes)
{
match WebAppBundle::decode(&happ_or_webhapp_bytes) {
Ok(web_app_bundle) => {
let mut hasher = Sha256::new();
hasher.update(happ_or_webhapp_bytes);
let web_happ_hash = hex::encode(hasher.finalize());
let web_happ_hash = hash_bytes_sha256(happ_or_webhapp_bytes);

// extracting ui.zip bytes
let web_ui_zip_bytes = web_app_bundle.web_ui_zip_bytes().await.map_err(|e| {
napi::Error::from_reason(format!("Failed to extract ui zip bytes: {}", e))
})?;

let mut hasher = Sha256::new();
hasher.update(web_ui_zip_bytes.clone().into_owned().into_inner());
let ui_hash = hex::encode(hasher.finalize());

let ui_target_dir = PathBuf::from(uis_dir).join(ui_hash.clone()).join("assets");
if !path_exists(&ui_target_dir) {
fs::create_dir_all(&ui_target_dir)?;
}

let ui_zip_path = PathBuf::from(ui_target_dir.clone()).join("ui.zip");

// unzip and store UI
fs::write(
ui_zip_path.clone(),
web_ui_zip_bytes.into_owned().into_inner(),
)
.map_err(|e| {
napi::Error::from_reason(format!("Failed to write Web UI Zip file: {}", e))
})?;

let file = fs::File::open(ui_zip_path.clone()).map_err(|e| {
napi::Error::from_reason(format!("Failed to read Web UI Zip file: {}", e))
})?;

unzip_file(file, ui_target_dir.into())
.map_err(|e| napi::Error::from_reason(format!("Failed to unzip ui.zip: {}", e)))?;

fs::remove_file(ui_zip_path).map_err(|e| {
napi::Error::from_reason(format!("Failed to remove ui.zip after unzipping: {}", e))
})?;
let ui_hash = hash_bytes_sha256(web_ui_zip_bytes.clone().into_owned().into_inner());

// extracting happ bundle
let app_bundle = web_app_bundle.happ_bundle().await.map_err(|e| {
Expand All @@ -121,51 +110,104 @@ pub async fn save_happ_or_webhapp(
))
})?;

(app_bundle, Some((ui_hash, web_happ_hash)))
let app_bundle_bytes = app_bundle.encode().map_err(|e| {
napi::Error::from_reason(format!("Failed to encode happ to bytes: {}", e))
})?;

let happ_hash = hash_bytes_sha256(app_bundle_bytes);

let happ_path = PathBuf::from(happs_dir).join(format!("{}.happ", happ_hash));
let happ_path_string = happ_path
.as_os_str()
.to_str()
.ok_or("Failed to convert happ path to string")
.map_err(|e| napi::Error::from_reason(e))?;

// Store UI if uis_dir is specified
match uis_dir {
Some(dir) => {
let ui_target_dir = PathBuf::from(dir).join(ui_hash.clone()).join("assets");
if !path_exists(&ui_target_dir) {
fs::create_dir_all(&ui_target_dir)?;
}

let ui_zip_path = PathBuf::from(ui_target_dir.clone()).join("ui.zip");

// unzip and store UI
fs::write(
ui_zip_path.clone(),
web_ui_zip_bytes.into_owned().into_inner(),
)
.map_err(|e| {
napi::Error::from_reason(format!("Failed to write Web UI Zip file: {}", e))
})?;

let file = fs::File::open(ui_zip_path.clone()).map_err(|e| {
napi::Error::from_reason(format!("Failed to read Web UI Zip file: {}", e))
})?;

unzip_file(file, ui_target_dir.into()).map_err(|e| {
napi::Error::from_reason(format!("Failed to unzip ui.zip: {}", e))
})?;

fs::remove_file(ui_zip_path).map_err(|e| {
napi::Error::from_reason(format!(
"Failed to remove ui.zip after unzipping: {}",
e
))
})?;
}
None => (),
}

app_bundle.write_to_file(&happ_path).await.map_err(|e| {
napi::Error::from_reason(format!("Failed to write .happ file: {}", e))
})?;

Ok(StoredHappPathAndHashes {
happ_path: happ_path_string.into(),
happ_sha256: happ_hash,
webhapp_sha256: Some(web_happ_hash),
ui_sha256: Some(ui_hash),
})
}
Err(_) => {
let app_bundle = AppBundle::decode(&happ_or_webhapp_bytes).map_err(|e| {
napi::Error::from_reason(format!("Failed to decode happ file: {}", e))
})?;
(app_bundle, None)
}
};

let mut hasher = Sha256::new();
let app_bundle_bytes = app_bundle
.encode()
.map_err(|e| napi::Error::from_reason(format!("Failed to encode happ to bytes: {}", e)))?;
hasher.update(app_bundle_bytes);
let happ_hash = hex::encode(hasher.finalize());
let happ_path = PathBuf::from(happs_dir).join(format!("{}.happ", happ_hash));

app_bundle
.write_to_file(&happ_path)
.await
.map_err(|e| napi::Error::from_reason(format!("Failed to write .happ file: {}", e)))?;

let happ_path_string = happ_path.as_os_str().to_str();
match happ_path_string {
Some(str) => match maybe_ui_and_webhapp_hash {
Some((ui_hash, web_happ_hash)) => Ok(format!(
"{}${}${}${}",
str.to_string(),
happ_hash,
ui_hash,
web_happ_hash,
)),
None => Ok(format!("{}${}", str.to_string(), happ_hash)),
},
None => Err(napi::Error::from_reason(
"Failed to convert happ path to string.",
)),
let app_bundle_bytes = app_bundle.encode().map_err(|e| {
napi::Error::from_reason(format!("Failed to encode happ to bytes: {}", e))
})?;

let happ_hash = hash_bytes_sha256(app_bundle_bytes);

let happ_path = PathBuf::from(happs_dir).join(format!("{}.happ", happ_hash));
let happ_path_string = happ_path
.as_os_str()
.to_str()
.ok_or("Failed to convert happ path to string")
.map_err(|e| napi::Error::from_reason(e))?;

app_bundle.write_to_file(&happ_path).await.map_err(|e| {
napi::Error::from_reason(format!("Failed to write .happ file: {}", e))
})?;
Ok(StoredHappPathAndHashes {
happ_path: happ_path_string.into(),
happ_sha256: happ_hash,
webhapp_sha256: None,
ui_sha256: None,
})
}
}
}

/// Checks that the happ or webhapp is of the correct format
/// WARNING: The decoding and encoding of the happ bytes seems to affect happ's sha256 hash.
#[napi]
pub async fn validate_happ_or_webhapp(happ_or_webhapp_bytes: Vec<u8>) -> napi::Result<String> {
pub async fn validate_happ_or_webhapp(
happ_or_webhapp_bytes: Vec<u8>,
) -> napi::Result<HappAndUiHashes> {
let (app_bundle, maybe_ui_and_webhapp_hash) = match WebAppBundle::decode(&happ_or_webhapp_bytes)
{
Ok(web_app_bundle) => {
Expand Down Expand Up @@ -207,10 +249,16 @@ pub async fn validate_happ_or_webhapp(happ_or_webhapp_bytes: Vec<u8>) -> napi::R
let happ_hash = hex::encode(hasher.finalize());

match maybe_ui_and_webhapp_hash {
Some((ui_hash, web_happ_hash)) => {
Ok(format!("{}${}${}", happ_hash, ui_hash, web_happ_hash))
}
None => Ok(format!("{}", happ_hash)),
Some((ui_hash, web_happ_hash)) => Ok(HappAndUiHashes {
happ_sha256: happ_hash,
webhapp_sha256: Some(web_happ_hash),
ui_sha256: Some(ui_hash),
}),
None => Ok(HappAndUiHashes {
happ_sha256: happ_hash,
webhapp_sha256: None,
ui_sha256: None,
}),
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
use sha2::{Digest, Sha256};


pub fn vec_to_arr<T, const N: usize>(v: Vec<T>) -> [T; N] {
v.try_into()
.unwrap_or_else(|v: Vec<T>| panic!("Expected a Vec of length {} but it was {}", N, v.len()))
}

pub fn hash_bytes_sha256(bytes: Vec<u8>) -> String {
let mut hasher = Sha256::new();
hasher.update(bytes);
hex::encode(hasher.finalize())
}

0 comments on commit 228a433

Please sign in to comment.