feat(updater): add download and install js binding (#1330)

This commit is contained in:
Tony
2024-05-21 18:54:06 +03:00
committed by GitHub
parent f3749e4de8
commit 43224c5d5c
14 changed files with 197 additions and 40 deletions
+44 -1
View File
@@ -5,7 +5,7 @@
use crate::{Result, Update, UpdaterExt};
use serde::Serialize;
use tauri::{ipc::Channel, Manager, ResourceId, Runtime, Webview};
use tauri::{ipc::Channel, Manager, Resource, ResourceId, Runtime, Webview};
use std::time::Duration;
use url::Url;
@@ -35,6 +35,9 @@ pub(crate) struct Metadata {
body: Option<String>,
}
struct DownloadedBytes(pub Vec<u8>);
impl Resource for DownloadedBytes {}
#[tauri::command]
pub(crate) async fn check<R: Runtime>(
webview: Webview<R>,
@@ -75,6 +78,46 @@ pub(crate) async fn check<R: Runtime>(
Ok(metadata)
}
#[tauri::command]
pub(crate) async fn download<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
on_event: Channel,
) -> Result<ResourceId> {
let update = webview.resources_table().get::<Update>(rid)?;
let mut first_chunk = true;
let bytes = update
.download(
|chunk_length, content_length| {
if first_chunk {
first_chunk = !first_chunk;
let _ = on_event.send(DownloadEvent::Started { content_length });
}
let _ = on_event.send(DownloadEvent::Progress { chunk_length });
},
|| {
let _ = on_event.send(&DownloadEvent::Finished);
},
)
.await?;
Ok(webview.resources_table().add(DownloadedBytes(bytes)))
}
#[tauri::command]
pub(crate) async fn install<R: Runtime>(
webview: Webview<R>,
update_rid: ResourceId,
bytes_rid: ResourceId,
) -> Result<()> {
let update = webview.resources_table().get::<Update>(update_rid)?;
let bytes = webview
.resources_table()
.get::<DownloadedBytes>(bytes_rid)?;
update.install(&bytes.0)?;
let _ = webview.resources_table().close(bytes_rid);
Ok(())
}
#[tauri::command]
pub(crate) async fn download_and_install<R: Runtime>(
webview: Webview<R>,
+3 -1
View File
@@ -179,7 +179,9 @@ impl Builder {
})
.invoke_handler(tauri::generate_handler![
commands::check,
commands::download_and_install
commands::download,
commands::install,
commands::download_and_install,
])
.build()
}
+12 -30
View File
@@ -5,7 +5,7 @@
use std::{
collections::HashMap,
ffi::{OsStr, OsString},
io::{Cursor, Read},
io::Cursor,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
@@ -478,19 +478,16 @@ impl Update {
on_chunk(chunk.len(), content_length);
buffer.extend(chunk);
}
on_download_finish();
let mut update_buffer = Cursor::new(&buffer);
verify_signature(&mut update_buffer, &self.signature, &self.config.pubkey)?;
verify_signature(&buffer, &self.signature, &self.config.pubkey)?;
Ok(buffer)
}
/// Installs the updater package downloaded by [`Update::download`]
pub fn install(&self, bytes: Vec<u8>) -> Result<()> {
self.install_inner(bytes)
pub fn install(&self, bytes: impl AsRef<[u8]>) -> Result<()> {
self.install_inner(bytes.as_ref())
}
/// Downloads and installs the updater package
@@ -504,7 +501,7 @@ impl Update {
}
#[cfg(mobile)]
fn install_inner(&self, _bytes: Vec<u8>) -> Result<()> {
fn install_inner(&self, _bytes: &[u8]) -> Result<()> {
Ok(())
}
}
@@ -546,13 +543,13 @@ impl Update {
/// ├── [AppName]_[version]_x64-setup.exe.zip # ZIP generated by tauri-bundler
/// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
/// └── ...
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
fn install_inner(&self, bytes: &[u8]) -> Result<()> {
use windows_sys::{
w,
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
};
let (updater_type, path, _temp) = Self::extract(&bytes)?;
let (updater_type, path, _temp) = Self::extract(bytes)?;
let install_mode = self.config.install_mode();
let mut installer_args = self.installer_args();
@@ -666,7 +663,7 @@ impl Update {
/// We should have an AppImage already installed to be able to copy and install
/// the extract_path is the current AppImage path
/// tmp_dir is where our new AppImage is found
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
fn install_inner(&self, bytes: &[u8]) -> Result<()> {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let extract_path_metadata = self.extract_path.metadata()?;
@@ -694,7 +691,7 @@ impl Update {
std::fs::rename(&self.extract_path, tmp_app_image)?;
#[cfg(feature = "zip")]
if infer::archive::is_gz(&bytes) {
if infer::archive::is_gz(bytes) {
// extract the buffer to the tmp_dir
// we extract our signed archive into our final directory without any temp file
let archive = Cursor::new(bytes);
@@ -743,7 +740,7 @@ impl Update {
/// │ └── Contents # Application contents...
/// │ └── ...
/// └── ...
fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {
fn install_inner(&self, bytes: &[u8]) -> Result<()> {
use flate2::read::GzDecoder;
let cursor = Cursor::new(bytes);
@@ -919,30 +916,15 @@ where
}
// Validate signature
// need to be public because its been used
// by our tests in the bundler
//
// NOTE: The buffer position is not reset.
pub fn verify_signature<R>(
archive_reader: &mut R,
release_signature: &str,
pub_key: &str,
) -> Result<bool>
where
R: Read,
{
fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Result<bool> {
// we need to convert the pub key
let pub_key_decoded = base64_to_string(pub_key)?;
let public_key = PublicKey::decode(&pub_key_decoded)?;
let signature_base64_decoded = base64_to_string(release_signature)?;
let signature = Signature::decode(&signature_base64_decoded)?;
// read all bytes until EOF in the buffer
let mut data = Vec::new();
archive_reader.read_to_end(&mut data)?;
// Validate signature or bail out
public_key.verify(&data, &signature, true)?;
public_key.verify(data, &signature, true)?;
Ok(true)
}