mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-07-22 17:10:53 +02:00
feat(updater): option to not restart after install (#3299)
* feat(updater): option to not restart after install * More platform cfg * Add the same function for `UpdaterBuilder` * Fix missing `#[cfg(windows)]` * Mark `current_exe_args` cfg(windows) * Mark `on_before_exit` cfg(windows) * Mark `installer_args` cfg(windows) * Note about `installer_arg` apply to both * Remove current args and restart together * Only build needed bundle on windows as well * Remove `/NS` since it breaks the msi updater * Add launch updater debug log * Add a folder to test updates * Remove unused `#[allow(unused)]` * Format * messed up git stage * Add change file * Clean up * Disable NSIS compression for API example * Bump wry for v1 test to pull in https://github.com/tauri-apps/wry/pull/1703 * format * Make typescript happy * Close new update on destroy
This commit is contained in:
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(t){"use strict";function e(t,e,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(t):n?n.value:e.get(t)}function s(t,e,s,n,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,s),s}var n,i,a,r,o;"function"==typeof SuppressedError&&SuppressedError;const d="__TAURI_TO_IPC_KEY__";class c{constructor(t){n.set(this,void 0),i.set(this,0),a.set(this,[]),r.set(this,void 0),s(this,n,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}(t=>{const o=t.index;if("end"in t)return void(o==e(this,i,"f")?this.cleanupCallback():s(this,r,o));const d=t.message;if(o==e(this,i,"f")){for(e(this,n,"f").call(this,d),s(this,i,e(this,i,"f")+1);e(this,i,"f")in e(this,a,"f");){const t=e(this,a,"f")[e(this,i,"f")];e(this,n,"f").call(this,t),delete e(this,a,"f")[e(this,i,"f")],s(this,i,e(this,i,"f")+1)}e(this,i,"f")===e(this,r,"f")&&this.cleanupCallback()}else e(this,a,"f")[o]=d})}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){s(this,n,t)}get onmessage(){return e(this,n,"f")}[(n=new WeakMap,i=new WeakMap,a=new WeakMap,r=new WeakMap,d)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[d]()}}async function l(t,e={},s){return window.__TAURI_INTERNALS__.invoke(t,e,s)}class h{get rid(){return e(this,o,"f")}constructor(t){o.set(this,void 0),s(this,o,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}o=new WeakMap;class u extends h{constructor(t){super(t.rid),this.available=!0,this.currentVersion=t.currentVersion,this.version=t.version,this.date=t.date,this.body=t.body,this.rawJson=t.rawJson}async download(t,e){_(e);const s=new c;t&&(s.onmessage=t);const n=await l("plugin:updater|download",{onEvent:s,rid:this.rid,...e});this.downloadedBytes=new h(n)}async install(){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await l("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(t,e){_(e);const s=new c;t&&(s.onmessage=t),await l("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...e})}async close(){await(this.downloadedBytes?.close()),await super.close()}}function _(t){t?.headers&&(t.headers=Array.from(new Headers(t.headers).entries()))}return t.Update=u,t.check=async function(t){_(t);const e=await l("plugin:updater|check",{...t});return e?new u(e):null},t}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(t){"use strict";function e(t,e,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(t):n?n.value:e.get(t)}function s(t,e,s,n,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,s),s}var n,i,a,r,o;"function"==typeof SuppressedError&&SuppressedError;const d="__TAURI_TO_IPC_KEY__";class c{constructor(t){n.set(this,void 0),i.set(this,0),a.set(this,[]),r.set(this,void 0),s(this,n,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}(t=>{const o=t.index;if("end"in t)return void(o==e(this,i,"f")?this.cleanupCallback():s(this,r,o));const d=t.message;if(o==e(this,i,"f")){for(e(this,n,"f").call(this,d),s(this,i,e(this,i,"f")+1);e(this,i,"f")in e(this,a,"f");){const t=e(this,a,"f")[e(this,i,"f")];e(this,n,"f").call(this,t),delete e(this,a,"f")[e(this,i,"f")],s(this,i,e(this,i,"f")+1)}e(this,i,"f")===e(this,r,"f")&&this.cleanupCallback()}else e(this,a,"f")[o]=d})}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){s(this,n,t)}get onmessage(){return e(this,n,"f")}[(n=new WeakMap,i=new WeakMap,a=new WeakMap,r=new WeakMap,d)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[d]()}}async function l(t,e={},s){return window.__TAURI_INTERNALS__.invoke(t,e,s)}class h{get rid(){return e(this,o,"f")}constructor(t){o.set(this,void 0),s(this,o,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}o=new WeakMap;class u extends h{constructor(t){super(t.rid),this.available=!0,this.currentVersion=t.currentVersion,this.version=t.version,this.date=t.date,this.body=t.body,this.rawJson=t.rawJson}async download(t,e){_(e);const s=new c;t&&(s.onmessage=t);const n=await l("plugin:updater|download",{onEvent:s,rid:this.rid,...e});this.downloadedBytes=new h(n)}async install(t){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await l("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid,...t}),this.downloadedBytes=void 0}async downloadAndInstall(t,e){_(e);const s=new c;t&&(s.onmessage=t),await l("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...e})}async close(){await(this.downloadedBytes?.close()),await super.close()}}function _(t){t?.headers&&(t.headers=Array.from(new Headers(t.headers).entries()))}return t.Update=u,t.check=async function(t){_(t);const e=await l("plugin:updater|check",{...t});return e?new u(e):null},t}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
|
||||
|
||||
@@ -40,6 +40,14 @@ interface DownloadOptions {
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
/** Options used when installing an update */
|
||||
interface InstallOptions {
|
||||
/**
|
||||
* If the Windows installer should restart the app after installed, default is `true`
|
||||
*/
|
||||
restartAfterInstall?: boolean
|
||||
}
|
||||
|
||||
interface UpdateMetadata {
|
||||
rid: number
|
||||
currentVersion: string
|
||||
@@ -95,14 +103,15 @@ class Update extends Resource {
|
||||
}
|
||||
|
||||
/** Install downloaded updater package */
|
||||
async install(): Promise<void> {
|
||||
async install(options?: InstallOptions): Promise<void> {
|
||||
if (!this.downloadedBytes) {
|
||||
throw new Error('Update.install called before Update.download')
|
||||
}
|
||||
|
||||
await invoke('plugin:updater|install', {
|
||||
updateRid: this.rid,
|
||||
bytesRid: this.downloadedBytes.rid
|
||||
bytesRid: this.downloadedBytes.rid,
|
||||
...options
|
||||
})
|
||||
|
||||
// Don't need to call close, we did it in rust side already
|
||||
@@ -112,7 +121,7 @@ class Update extends Resource {
|
||||
/** Downloads the updater package and installs it */
|
||||
async downloadAndInstall(
|
||||
onEvent?: (progress: DownloadEvent) => void,
|
||||
options?: DownloadOptions
|
||||
options?: DownloadOptions & InstallOptions
|
||||
): Promise<void> {
|
||||
convertToRustHeaders(options)
|
||||
const channel = new Channel<DownloadEvent>()
|
||||
|
||||
@@ -142,12 +142,22 @@ pub(crate) async fn install<R: Runtime>(
|
||||
webview: Webview<R>,
|
||||
update_rid: ResourceId,
|
||||
bytes_rid: ResourceId,
|
||||
restart_after_install: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let update = webview.resources_table().get::<Update>(update_rid)?;
|
||||
let bytes = webview
|
||||
.resources_table()
|
||||
.get::<DownloadedBytes>(bytes_rid)?;
|
||||
update.install(&bytes.0)?;
|
||||
|
||||
if let Some(restart_after_install) = restart_after_install {
|
||||
let update = (*update).clone();
|
||||
update
|
||||
.restart_after_install(restart_after_install)
|
||||
.install(&bytes.0)?;
|
||||
} else {
|
||||
update.install(&bytes.0)?;
|
||||
}
|
||||
|
||||
let _ = webview.resources_table().close(bytes_rid);
|
||||
Ok(())
|
||||
}
|
||||
@@ -159,6 +169,7 @@ pub(crate) async fn download_and_install<R: Runtime>(
|
||||
on_event: Channel<DownloadEvent>,
|
||||
headers: Option<Vec<(String, String)>>,
|
||||
timeout: Option<u64>,
|
||||
restart_after_install: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let update = webview.resources_table().get::<Update>(rid)?;
|
||||
|
||||
@@ -176,6 +187,10 @@ pub(crate) async fn download_and_install<R: Runtime>(
|
||||
update.timeout = Some(Duration::from_millis(timeout));
|
||||
}
|
||||
|
||||
if let Some(restart_after_install) = restart_after_install {
|
||||
update = update.restart_after_install(restart_after_install);
|
||||
}
|
||||
|
||||
let mut first_chunk = true;
|
||||
|
||||
update
|
||||
|
||||
@@ -32,17 +32,30 @@ impl WindowsUpdateInstallMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn msi_restart_after_install_args(&self) -> &'static [&'static str] {
|
||||
&["AUTOLAUNCHAPP=True"]
|
||||
}
|
||||
|
||||
/// Returns the associated nsis arguments.
|
||||
pub fn nsis_args(&self) -> &'static [&'static str] {
|
||||
// `/P`: Passive
|
||||
// `/S`: Silent
|
||||
// `/R`: Restart
|
||||
match self {
|
||||
Self::Passive => &["/P", "/R"],
|
||||
Self::Quiet => &["/S", "/R"],
|
||||
Self::Passive => &["/P"],
|
||||
Self::Quiet => &["/S"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn nsis_restart_after_install_args(&self) -> &'static [&'static str] {
|
||||
match self {
|
||||
Self::BasicUi => &[],
|
||||
_ => &["/R"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for WindowsUpdateInstallMode {
|
||||
@@ -63,6 +76,8 @@ impl Display for WindowsUpdateInstallMode {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WindowsConfig {
|
||||
/// Additional arguments given to the NSIS or WiX installer.
|
||||
///
|
||||
/// Note: this applies to both WiX and NSIS installers
|
||||
#[serde(
|
||||
default,
|
||||
alias = "installer-args",
|
||||
|
||||
@@ -83,9 +83,9 @@ impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
|
||||
builder = builder.target(target);
|
||||
}
|
||||
|
||||
let args = self.env().args_os;
|
||||
if !args.is_empty() {
|
||||
builder = builder.current_exe_args(args);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
builder = builder.current_exe_args(self.env().args_os);
|
||||
}
|
||||
|
||||
builder.version_comparator = version_comparator.clone();
|
||||
|
||||
+212
-148
@@ -129,16 +129,34 @@ impl RemoteRelease {
|
||||
pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>;
|
||||
pub type OnBeforeRequest = Arc<dyn Fn(ClientBuilder) -> ClientBuilder + Send + Sync + 'static>;
|
||||
pub type VersionComparator = Arc<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>;
|
||||
#[cfg(target_os = "macos")]
|
||||
type MainThreadClosure = Box<dyn FnOnce() + Send + Sync + 'static>;
|
||||
type RunOnMainThread =
|
||||
Box<dyn Fn(MainThreadClosure) -> std::result::Result<(), tauri::Error> + Send + Sync + 'static>;
|
||||
#[cfg(target_os = "macos")]
|
||||
type RunOnMainThread = Arc<dyn Fn(MainThreadClosure) -> tauri::Result<()> + Send + Sync + 'static>;
|
||||
|
||||
// TODO: Move more fields to this in v3 if we can mark those fields non `pub`
|
||||
/// Updater context shared between [`UpdaterBuilder`], [`Updater`] and [`Update`]
|
||||
#[derive(Clone)]
|
||||
struct UpdaterContext {
|
||||
config: Config,
|
||||
configure_client: Option<OnBeforeRequest>,
|
||||
#[cfg(target_os = "macos")]
|
||||
run_on_main_thread: RunOnMainThread,
|
||||
/// App name, used for creating named tempfiles
|
||||
#[cfg(windows)]
|
||||
app_name: String,
|
||||
#[cfg(windows)]
|
||||
installer_args: Vec<OsString>,
|
||||
#[cfg(windows)]
|
||||
current_exe_args: Vec<OsString>,
|
||||
#[cfg(windows)]
|
||||
on_before_exit: Option<OnBeforeExit>,
|
||||
#[cfg(windows)]
|
||||
restart_after_install: bool,
|
||||
}
|
||||
|
||||
pub struct UpdaterBuilder {
|
||||
#[allow(dead_code)]
|
||||
run_on_main_thread: RunOnMainThread,
|
||||
app_name: String,
|
||||
current_version: Version,
|
||||
config: Config,
|
||||
pub(crate) version_comparator: Option<VersionComparator>,
|
||||
executable_path: Option<PathBuf>,
|
||||
target: Option<String>,
|
||||
@@ -147,27 +165,38 @@ pub struct UpdaterBuilder {
|
||||
timeout: Option<Duration>,
|
||||
proxy: Option<Url>,
|
||||
no_proxy: bool,
|
||||
installer_args: Vec<OsString>,
|
||||
current_exe_args: Vec<OsString>,
|
||||
on_before_exit: Option<OnBeforeExit>,
|
||||
configure_client: Option<OnBeforeRequest>,
|
||||
context: UpdaterContext,
|
||||
}
|
||||
|
||||
impl UpdaterBuilder {
|
||||
pub(crate) fn new<R: Runtime>(app: &AppHandle<R>, config: crate::Config) -> Self {
|
||||
let app_ = app.clone();
|
||||
let run_on_main_thread = move |f| app_.run_on_main_thread(f);
|
||||
#[cfg(target_os = "macos")]
|
||||
let run_on_main_thread = {
|
||||
let app_ = app.clone();
|
||||
Arc::new(move |f| app_.run_on_main_thread(f))
|
||||
};
|
||||
Self {
|
||||
run_on_main_thread: Box::new(run_on_main_thread),
|
||||
installer_args: config
|
||||
.windows
|
||||
.as_ref()
|
||||
.map(|w| w.installer_args.clone())
|
||||
.unwrap_or_default(),
|
||||
current_exe_args: Vec::new(),
|
||||
app_name: app.package_info().name.clone(),
|
||||
context: UpdaterContext {
|
||||
#[cfg(windows)]
|
||||
installer_args: config
|
||||
.windows
|
||||
.as_ref()
|
||||
.map(|w| w.installer_args.clone())
|
||||
.unwrap_or_default(),
|
||||
config,
|
||||
configure_client: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
run_on_main_thread,
|
||||
#[cfg(windows)]
|
||||
app_name: app.package_info().name.clone(),
|
||||
#[cfg(windows)]
|
||||
current_exe_args: Vec::new(),
|
||||
#[cfg(windows)]
|
||||
on_before_exit: None,
|
||||
#[cfg(windows)]
|
||||
restart_after_install: true,
|
||||
},
|
||||
current_version: app.package_info().version.clone(),
|
||||
config,
|
||||
version_comparator: None,
|
||||
executable_path: None,
|
||||
target: None,
|
||||
@@ -176,8 +205,6 @@ impl UpdaterBuilder {
|
||||
timeout: None,
|
||||
proxy: None,
|
||||
no_proxy: false,
|
||||
on_before_exit: None,
|
||||
configure_client: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +224,7 @@ impl UpdaterBuilder {
|
||||
pub fn endpoints(mut self, endpoints: Vec<Url>) -> Result<Self> {
|
||||
crate::config::validate_endpoints(
|
||||
&endpoints,
|
||||
self.config.dangerous_insecure_transport_protocol,
|
||||
self.context.config.dangerous_insecure_transport_protocol,
|
||||
)?;
|
||||
|
||||
self.endpoints.replace(endpoints);
|
||||
@@ -251,26 +278,40 @@ impl UpdaterBuilder {
|
||||
}
|
||||
|
||||
pub fn pubkey<S: Into<String>>(mut self, pubkey: S) -> Self {
|
||||
self.config.pubkey = pubkey.into();
|
||||
self.context.config.pubkey = pubkey.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an argument to pass to the Windows installer.
|
||||
///
|
||||
/// Note: this applies to both WiX and NSIS installers
|
||||
#[cfg_attr(not(windows), allow(unused))]
|
||||
pub fn installer_arg<S>(mut self, arg: S) -> Self
|
||||
where
|
||||
S: Into<OsString>,
|
||||
{
|
||||
self.installer_args.push(arg.into());
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.context.installer_args.push(arg.into());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds multiple arguments to pass to the Windows installer.
|
||||
///
|
||||
/// Note: this applies to both WiX and NSIS installers
|
||||
#[cfg_attr(not(windows), allow(unused))]
|
||||
pub fn installer_args<I, S>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: Into<OsString>,
|
||||
{
|
||||
self.installer_args.extend(args.into_iter().map(Into::into));
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.context
|
||||
.installer_args
|
||||
.extend(args.into_iter().map(Into::into));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
@@ -280,14 +321,32 @@ impl UpdaterBuilder {
|
||||
/// [`Self::installer_arg`], [`crate::Builder::installer_arg`]
|
||||
/// and the `plugins > updater > windows > installerArgs` config,
|
||||
/// not the ones managed by us (e.g. `/UPDATER` flag passed to the NSIS installer)
|
||||
#[cfg_attr(not(windows), allow(unused))]
|
||||
pub fn clear_installer_args(mut self) -> Self {
|
||||
self.installer_args.clear();
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.context.installer_args.clear();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Function to run before we run the installer and exit the app through `std::process::exit(0)` on Windows
|
||||
#[cfg_attr(not(windows), allow(unused))]
|
||||
pub fn on_before_exit<F: Fn() + Send + Sync + 'static>(mut self, f: F) -> Self {
|
||||
self.on_before_exit.replace(Arc::new(f));
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.context.on_before_exit.replace(Arc::new(f));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// If the Windows installer should restart the app after installed, default is `true`
|
||||
#[cfg_attr(not(windows), allow(unused))]
|
||||
pub fn restart_after_install(mut self, restart_after_install: bool) -> Self {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.context.restart_after_install = restart_after_install;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
@@ -299,14 +358,14 @@ impl UpdaterBuilder {
|
||||
mut self,
|
||||
f: F,
|
||||
) -> Self {
|
||||
self.configure_client.replace(Arc::new(f));
|
||||
self.context.configure_client.replace(Arc::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Updater> {
|
||||
let endpoints = self
|
||||
.endpoints
|
||||
.unwrap_or_else(|| self.config.endpoints.clone());
|
||||
.unwrap_or_else(|| self.context.config.endpoints.clone());
|
||||
|
||||
if endpoints.is_empty() {
|
||||
return Err(Error::EmptyEndpoints);
|
||||
@@ -324,44 +383,36 @@ impl UpdaterBuilder {
|
||||
};
|
||||
|
||||
Ok(Updater {
|
||||
run_on_main_thread: Arc::new(self.run_on_main_thread),
|
||||
config: self.config,
|
||||
app_name: self.app_name,
|
||||
current_version: self.current_version,
|
||||
version_comparator: self.version_comparator,
|
||||
timeout: self.timeout,
|
||||
proxy: self.proxy,
|
||||
no_proxy: self.no_proxy,
|
||||
endpoints,
|
||||
installer_args: self.installer_args,
|
||||
current_exe_args: self.current_exe_args,
|
||||
arch,
|
||||
target: self.target,
|
||||
headers: self.headers,
|
||||
extract_path,
|
||||
on_before_exit: self.on_before_exit,
|
||||
configure_client: self.configure_client,
|
||||
context: self.context.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl UpdaterBuilder {
|
||||
pub(crate) fn current_exe_args<I, S>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: Into<OsString>,
|
||||
{
|
||||
self.current_exe_args
|
||||
self.context
|
||||
.current_exe_args
|
||||
.extend(args.into_iter().map(Into::into));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Updater {
|
||||
#[allow(dead_code)]
|
||||
run_on_main_thread: Arc<RunOnMainThread>,
|
||||
config: Config,
|
||||
app_name: String,
|
||||
current_version: Version,
|
||||
version_comparator: Option<VersionComparator>,
|
||||
timeout: Option<Duration>,
|
||||
@@ -374,12 +425,7 @@ pub struct Updater {
|
||||
target: Option<String>,
|
||||
headers: HeaderMap,
|
||||
extract_path: PathBuf,
|
||||
on_before_exit: Option<OnBeforeExit>,
|
||||
configure_client: Option<OnBeforeRequest>,
|
||||
#[allow(unused)]
|
||||
installer_args: Vec<OsString>,
|
||||
#[allow(unused)]
|
||||
current_exe_args: Vec<OsString>,
|
||||
context: UpdaterContext,
|
||||
}
|
||||
|
||||
impl Updater {
|
||||
@@ -449,10 +495,10 @@ impl Updater {
|
||||
}
|
||||
|
||||
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
|
||||
if self.config.dangerous_accept_invalid_certs {
|
||||
if self.context.config.dangerous_accept_invalid_certs {
|
||||
request = request.danger_accept_invalid_certs(true);
|
||||
}
|
||||
if self.config.dangerous_accept_invalid_hostnames {
|
||||
if self.context.config.dangerous_accept_invalid_hostnames {
|
||||
request = request.danger_accept_invalid_hostnames(true);
|
||||
}
|
||||
if let Some(timeout) = self.timeout {
|
||||
@@ -467,7 +513,7 @@ impl Updater {
|
||||
request = request.proxy(proxy);
|
||||
}
|
||||
|
||||
if let Some(ref configure_client) = self.configure_client {
|
||||
if let Some(ref configure_client) = self.context.configure_client {
|
||||
request = configure_client(request);
|
||||
}
|
||||
|
||||
@@ -537,10 +583,6 @@ impl Updater {
|
||||
|
||||
let update = if should_update {
|
||||
Some(Update {
|
||||
run_on_main_thread: self.run_on_main_thread.clone(),
|
||||
config: self.config.clone(),
|
||||
on_before_exit: self.on_before_exit.clone(),
|
||||
app_name: self.app_name.clone(),
|
||||
current_version: self.current_version.to_string(),
|
||||
target: target.to_owned(),
|
||||
extract_path: self.extract_path.clone(),
|
||||
@@ -554,9 +596,7 @@ impl Updater {
|
||||
proxy: self.proxy.clone(),
|
||||
no_proxy: self.no_proxy,
|
||||
headers: self.headers.clone(),
|
||||
installer_args: self.installer_args.clone(),
|
||||
current_exe_args: self.current_exe_args.clone(),
|
||||
configure_client: self.configure_client.clone(),
|
||||
context: self.context.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -600,11 +640,6 @@ impl Updater {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
#[allow(dead_code)]
|
||||
run_on_main_thread: Arc<RunOnMainThread>,
|
||||
config: Config,
|
||||
#[allow(unused)]
|
||||
on_before_exit: Option<OnBeforeExit>,
|
||||
/// Update description
|
||||
pub body: Option<String>,
|
||||
/// Version used to check for update
|
||||
@@ -633,14 +668,7 @@ pub struct Update {
|
||||
/// Extract path
|
||||
#[allow(unused)]
|
||||
extract_path: PathBuf,
|
||||
/// App name, used for creating named tempfiles on Windows
|
||||
#[allow(unused)]
|
||||
app_name: String,
|
||||
#[allow(unused)]
|
||||
installer_args: Vec<OsString>,
|
||||
#[allow(unused)]
|
||||
current_exe_args: Vec<OsString>,
|
||||
configure_client: Option<OnBeforeRequest>,
|
||||
context: UpdaterContext,
|
||||
}
|
||||
|
||||
impl Resource for Update {}
|
||||
@@ -661,10 +689,10 @@ impl Update {
|
||||
}
|
||||
|
||||
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
|
||||
if self.config.dangerous_accept_invalid_certs {
|
||||
if self.context.config.dangerous_accept_invalid_certs {
|
||||
request = request.danger_accept_invalid_certs(true);
|
||||
}
|
||||
if self.config.dangerous_accept_invalid_hostnames {
|
||||
if self.context.config.dangerous_accept_invalid_hostnames {
|
||||
request = request.danger_accept_invalid_hostnames(true);
|
||||
}
|
||||
if let Some(timeout) = self.timeout {
|
||||
@@ -676,7 +704,7 @@ impl Update {
|
||||
let proxy = reqwest::Proxy::all(proxy.as_str())?;
|
||||
request = request.proxy(proxy);
|
||||
}
|
||||
if let Some(ref configure_client) = self.configure_client {
|
||||
if let Some(ref configure_client) = self.context.configure_client {
|
||||
request = configure_client(request);
|
||||
}
|
||||
let response = request
|
||||
@@ -709,7 +737,7 @@ impl Update {
|
||||
}
|
||||
on_download_finish();
|
||||
|
||||
verify_signature(&buffer, &self.signature, &self.config.pubkey)?;
|
||||
verify_signature(&buffer, &self.signature, &self.context.config.pubkey)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
@@ -733,6 +761,16 @@ impl Update {
|
||||
fn install_inner(&self, _bytes: &[u8]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether the Windows installer should restart the app after installed, default is `true`
|
||||
#[cfg_attr(not(windows), allow(unused))]
|
||||
pub fn restart_after_install(mut self, restart_after_install: bool) -> Self {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.context.restart_after_install = restart_after_install;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -785,7 +823,6 @@ impl Update {
|
||||
/// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
|
||||
/// └── ...
|
||||
fn install_inner(&self, bytes: &[u8]) -> Result<()> {
|
||||
use std::iter::once;
|
||||
use windows_sys::{
|
||||
w,
|
||||
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
|
||||
@@ -793,48 +830,7 @@ impl Update {
|
||||
|
||||
let updater_type = self.extract(bytes)?;
|
||||
|
||||
let install_mode = self.config.install_mode();
|
||||
let current_args = &self.current_exe_args()[1..];
|
||||
let msi_args;
|
||||
let nsis_args;
|
||||
|
||||
let installer_args: Vec<&OsStr> = match &updater_type {
|
||||
WindowsUpdaterType::Nsis { .. } => {
|
||||
nsis_args = current_args
|
||||
.iter()
|
||||
.map(escape_nsis_current_exe_arg)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
install_mode
|
||||
.nsis_args()
|
||||
.iter()
|
||||
.map(OsStr::new)
|
||||
.chain(once(OsStr::new("/UPDATE")))
|
||||
.chain(once(OsStr::new("/ARGS")))
|
||||
.chain(nsis_args.iter().map(OsStr::new))
|
||||
.chain(self.installer_args())
|
||||
.collect()
|
||||
}
|
||||
WindowsUpdaterType::Msi { path, .. } => {
|
||||
let escaped_args = current_args
|
||||
.iter()
|
||||
.map(escape_msi_property_arg)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
msi_args = OsString::from(format!("LAUNCHAPPARGS=\"{escaped_args}\""));
|
||||
|
||||
[OsStr::new("/i"), path.as_os_str()]
|
||||
.into_iter()
|
||||
.chain(install_mode.msiexec_args().iter().map(OsStr::new))
|
||||
.chain(once(OsStr::new("/promptrestart")))
|
||||
.chain(self.installer_args())
|
||||
.chain(once(OsStr::new("AUTOLAUNCHAPP=True")))
|
||||
.chain(once(msi_args.as_os_str()))
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(on_before_exit) = self.on_before_exit.as_ref() {
|
||||
if let Some(on_before_exit) = self.context.on_before_exit.as_ref() {
|
||||
log::debug!("running on_before_exit hook");
|
||||
on_before_exit();
|
||||
}
|
||||
@@ -846,9 +842,11 @@ impl Update {
|
||||
|p| OsString::from(format!("{p}\\System32\\msiexec.exe")),
|
||||
),
|
||||
};
|
||||
let file = encode_wide(file);
|
||||
let parameters = self.updater_parameters(&updater_type);
|
||||
|
||||
let parameters = installer_args.join(OsStr::new(" "));
|
||||
log::debug!("Executing updater {file:?} with parameters: {parameters:?}");
|
||||
|
||||
let file = encode_wide(file);
|
||||
let parameters = encode_wide(parameters);
|
||||
|
||||
unsafe {
|
||||
@@ -865,18 +863,72 @@ impl Update {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn installer_args(&self) -> Vec<&OsStr> {
|
||||
self.installer_args
|
||||
.iter()
|
||||
.map(OsStr::new)
|
||||
.collect::<Vec<_>>()
|
||||
fn updater_parameters(&self, updater_type: &WindowsUpdaterType) -> OsString {
|
||||
let install_mode = self.context.config.install_mode();
|
||||
let current_args = &self.context.current_exe_args[1..];
|
||||
|
||||
match updater_type {
|
||||
WindowsUpdaterType::Nsis { .. } => {
|
||||
let mut installer_args: Vec<&OsStr> = Vec::new();
|
||||
installer_args.extend(install_mode.nsis_args().iter().map(OsStr::new));
|
||||
installer_args.push(OsStr::new("/UPDATE"));
|
||||
|
||||
let nsis_current_exe_arg;
|
||||
if self.context.restart_after_install {
|
||||
nsis_current_exe_arg = current_args
|
||||
.iter()
|
||||
.map(escape_nsis_current_exe_arg)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
installer_args.extend(
|
||||
install_mode
|
||||
.nsis_restart_after_install_args()
|
||||
.iter()
|
||||
.map(OsStr::new),
|
||||
);
|
||||
installer_args.push(OsStr::new("/ARGS"));
|
||||
installer_args.extend(nsis_current_exe_arg.iter().map(OsStr::new));
|
||||
}
|
||||
|
||||
installer_args.extend(self.installer_args());
|
||||
|
||||
installer_args.join(OsStr::new(" "))
|
||||
}
|
||||
WindowsUpdaterType::Msi { path, .. } => {
|
||||
let mut installer_args: Vec<&OsStr> = vec![OsStr::new("/i"), path.as_os_str()];
|
||||
installer_args.extend(install_mode.msiexec_args().iter().map(OsStr::new));
|
||||
installer_args.push(OsStr::new("/promptrestart"));
|
||||
installer_args.extend(self.installer_args());
|
||||
|
||||
let msi_current_exe_arg;
|
||||
if self.context.restart_after_install {
|
||||
msi_current_exe_arg = format!(
|
||||
"LAUNCHAPPARGS=\"{}\"",
|
||||
current_args
|
||||
.iter()
|
||||
.map(escape_msi_property_arg)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
);
|
||||
|
||||
installer_args.extend(
|
||||
install_mode
|
||||
.msi_restart_after_install_args()
|
||||
.iter()
|
||||
.map(OsStr::new),
|
||||
);
|
||||
installer_args.push(OsStr::new(&msi_current_exe_arg));
|
||||
}
|
||||
|
||||
installer_args.join(OsStr::new(" "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_exe_args(&self) -> Vec<&OsStr> {
|
||||
self.current_exe_args
|
||||
.iter()
|
||||
.map(OsStr::new)
|
||||
.collect::<Vec<_>>()
|
||||
fn installer_args(
|
||||
&self,
|
||||
) -> std::iter::Map<std::slice::Iter<'_, OsString>, fn(&OsString) -> &OsStr> {
|
||||
self.context.installer_args.iter().map(OsStr::new)
|
||||
}
|
||||
|
||||
fn extract(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
|
||||
@@ -890,7 +942,10 @@ impl Update {
|
||||
|
||||
fn make_temp_dir(&self) -> Result<PathBuf> {
|
||||
Ok(tempfile::Builder::new()
|
||||
.prefix(&format!("{}-{}-updater-", self.app_name, self.version))
|
||||
.prefix(&format!(
|
||||
"{}-{}-updater-",
|
||||
self.context.app_name, self.version
|
||||
))
|
||||
.tempdir()?
|
||||
.keep())
|
||||
}
|
||||
@@ -938,7 +993,10 @@ impl Update {
|
||||
|
||||
let temp_dir = self.make_temp_dir()?;
|
||||
let mut temp_file = tempfile::Builder::new()
|
||||
.prefix(&format!("{}-{}-installer", self.app_name, self.version))
|
||||
.prefix(&format!(
|
||||
"{}-{}-installer",
|
||||
self.context.app_name, self.version
|
||||
))
|
||||
.suffix(ext)
|
||||
.rand_bytes(0)
|
||||
.tempfile_in(temp_dir)?;
|
||||
@@ -1277,7 +1335,7 @@ impl Update {
|
||||
);
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let res = (self.run_on_main_thread)(Box::new(move || {
|
||||
let res = (self.context.run_on_main_thread)(Box::new(move || {
|
||||
let mut script =
|
||||
osakit::Script::new_from_source(osakit::Language::AppleScript, &apple_script);
|
||||
script.compile().expect("invalid AppleScript");
|
||||
@@ -1450,7 +1508,7 @@ where
|
||||
}
|
||||
|
||||
// Validate signature
|
||||
fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Result<bool> {
|
||||
fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Result<()> {
|
||||
// we need to convert the pub key
|
||||
let pub_key_decoded = base64_to_string(pub_key)?;
|
||||
let public_key = PublicKey::decode(&pub_key_decoded)?;
|
||||
@@ -1459,7 +1517,7 @@ fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Resu
|
||||
|
||||
// Validate signature or bail out
|
||||
public_key.verify(data, &signature, true)?;
|
||||
Ok(true)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn base64_to_string(base64_string: &str) -> Result<String> {
|
||||
@@ -1498,25 +1556,31 @@ impl PathExt for PathBuf {
|
||||
|
||||
// adapted from https://github.com/rust-lang/rust/blob/1c047506f94cd2d05228eb992b0a6bbed1942349/library/std/src/sys/args/windows.rs#L174
|
||||
#[cfg(windows)]
|
||||
fn escape_nsis_current_exe_arg(arg: &&OsStr) -> String {
|
||||
let arg = arg.to_string_lossy();
|
||||
let mut cmd: Vec<char> = Vec::new();
|
||||
fn escape_nsis_current_exe_arg(arg: impl AsRef<OsStr>) -> OsString {
|
||||
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
||||
|
||||
let arg = arg.as_ref();
|
||||
let mut cmd: Vec<u16> = Vec::new();
|
||||
|
||||
// compared to std we additionally escape `/` so that nsis won't interpret them as a beginning of an nsis argument.
|
||||
let quote = arg.chars().any(|c| c == ' ' || c == '\t' || c == '/') || arg.is_empty();
|
||||
let quote = arg
|
||||
.as_encoded_bytes()
|
||||
.iter()
|
||||
.any(|c| *c == b' ' || *c == b'\t' || *c == b'/')
|
||||
|| arg.is_empty();
|
||||
let escape = true;
|
||||
if quote {
|
||||
cmd.push('"');
|
||||
cmd.push('"' as u16);
|
||||
}
|
||||
let mut backslashes: usize = 0;
|
||||
for x in arg.chars() {
|
||||
for x in arg.encode_wide() {
|
||||
if escape {
|
||||
if x == '\\' {
|
||||
if x == '\\' as u16 {
|
||||
backslashes += 1;
|
||||
} else {
|
||||
if x == '"' {
|
||||
if x == '"' as u16 {
|
||||
// Add n+1 backslashes to total 2n+1 before internal '"'.
|
||||
cmd.extend((0..=backslashes).map(|_| '\\'));
|
||||
cmd.extend((0..=backslashes).map(|_| '\\' as u16));
|
||||
}
|
||||
backslashes = 0;
|
||||
}
|
||||
@@ -1525,10 +1589,10 @@ fn escape_nsis_current_exe_arg(arg: &&OsStr) -> String {
|
||||
}
|
||||
if quote {
|
||||
// Add n backslashes to total 2n before ending '"'.
|
||||
cmd.extend((0..backslashes).map(|_| '\\'));
|
||||
cmd.push('"');
|
||||
cmd.extend((0..backslashes).map(|_| '\\' as u16));
|
||||
cmd.push('"' as u16);
|
||||
}
|
||||
cmd.into_iter().collect()
|
||||
OsString::from_wide(&cmd)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
"endpoints": ["http://localhost:3007"],
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK",
|
||||
"windows": {
|
||||
"installMode": "quiet",
|
||||
"installerArgs": ["/NS"]
|
||||
"installMode": "quiet"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@ struct Update {
|
||||
platforms: HashMap<String, PlatformUpdate>,
|
||||
}
|
||||
|
||||
fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTarget) {
|
||||
fn build_app(cwd: &Path, config: &Config, target: Option<BundleTarget>) {
|
||||
let mut command = Command::new("cargo");
|
||||
command
|
||||
.args(["tauri", "build", "--verbose"])
|
||||
@@ -56,17 +56,10 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa
|
||||
.env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "")
|
||||
.current_dir(cwd);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
command.args(["--bundles", target.name()]);
|
||||
#[cfg(target_os = "macos")]
|
||||
command.args(["--bundles", target.name()]);
|
||||
|
||||
if bundle_updater {
|
||||
#[cfg(windows)]
|
||||
command.args(["--bundles", "msi", "nsis"]);
|
||||
if let Some(target) = target {
|
||||
command.arg("--bundles").arg(target.name());
|
||||
} else {
|
||||
#[cfg(windows)]
|
||||
command.args(["--bundles", target.name()]);
|
||||
command.arg("--no-bundle");
|
||||
}
|
||||
|
||||
let status = command
|
||||
@@ -318,7 +311,7 @@ fn update_app() {
|
||||
{
|
||||
// bundle app update
|
||||
config.version = "1.0.0";
|
||||
build_app(&manifest_dir, &config, true, BundleTarget::default());
|
||||
build_app(&manifest_dir, &config, Some(bundle_target));
|
||||
|
||||
let bundle_updater_ext = if v1_compatible {
|
||||
out_bundle_path
|
||||
@@ -401,7 +394,7 @@ fn update_app() {
|
||||
config.version = "0.1.0";
|
||||
|
||||
// bundle initial app version
|
||||
build_app(&manifest_dir, &config, false, bundle_target);
|
||||
build_app(&manifest_dir, &config, None);
|
||||
|
||||
for expected_exit_code in status_checks {
|
||||
let mut binary_cmd = if cfg!(windows) {
|
||||
|
||||
+3
-3
@@ -3794,7 +3794,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4137,9 +4137,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.24.10"
|
||||
version = "0.24.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45"
|
||||
checksum = "4a2a144c3ab5e83e04724bc8e67cea552ffae413185fda459fafdae173fd985d"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"block",
|
||||
|
||||
Reference in New Issue
Block a user