feat(updater): allow passing headers & timeout in `Update.download*` methods (#1661)

closes #1634
pull/1531/merge
Amr Bashir 11 months ago committed by GitHub
parent b9bcb2b6af
commit f8255e1db5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"updater-js": "patch"
---
Add a second argument in `Update.download` and `Update.donloadAndInstall` JS APIs to modify headers and timeout when downloading the update.

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(e){"use strict";function t(e,t,s,n){if("a"===s&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(e):n?n.value:t.get(e)}function s(e,t,s,n,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var n,i,r,a;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),i.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:a})=>{if(a===t(this,i,"f")){s(this,i,a+1),t(this,n,"f").call(this,e);const o=Object.keys(t(this,r,"f"));if(o.length>0){let e=a+1;for(const s of o.sort()){if(parseInt(s)!==e)break;{const i=t(this,r,"f")[s];delete t(this,r,"f")[s],t(this,n,"f").call(this,i),e+=1}}s(this,i,e)}}else t(this,r,"f")[a.toString()]=e}))}set onmessage(e){s(this,n,e)}get onmessage(){return t(this,n,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function d(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}n=new WeakMap,i=new WeakMap,r=new WeakMap;class l{get rid(){return t(this,a,"f")}constructor(e){a.set(this,void 0),s(this,a,e)}async close(){return d("plugin:resources|close",{rid:this.rid})}}a=new WeakMap;class c extends l{constructor(e){super(e.rid),this.available=e.available,this.currentVersion=e.currentVersion,this.version=e.version,this.date=e.date,this.body=e.body}async download(e){const t=new o;e&&(t.onmessage=e);const s=await d("plugin:updater|download",{onEvent:t,rid:this.rid});this.downloadedBytes=new l(s)}async install(){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await d("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(e){const t=new o;e&&(t.onmessage=e),await d("plugin:updater|download_and_install",{onEvent:t,rid:this.rid})}async close(){await(this.downloadedBytes?.close()),await super.close()}}return e.Update=c,e.check=async function(e){return e?.headers&&(e.headers=Array.from(new Headers(e.headers).entries())),await d("plugin:updater|check",{...e}).then((e=>e.available?new c(e):null))},e}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(e){"use strict";function t(e,t,s,n){if("a"===s&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(e):n?n.value:t.get(e)}function s(e,t,s,n,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var n,i,r,a;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),i.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:a})=>{if(a===t(this,i,"f")){s(this,i,a+1),t(this,n,"f").call(this,e);const o=Object.keys(t(this,r,"f"));if(o.length>0){let e=a+1;for(const s of o.sort()){if(parseInt(s)!==e)break;{const i=t(this,r,"f")[s];delete t(this,r,"f")[s],t(this,n,"f").call(this,i),e+=1}}s(this,i,e)}}else t(this,r,"f")[a.toString()]=e}))}set onmessage(e){s(this,n,e)}get onmessage(){return t(this,n,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function d(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}n=new WeakMap,i=new WeakMap,r=new WeakMap;class l{get rid(){return t(this,a,"f")}constructor(e){a.set(this,void 0),s(this,a,e)}async close(){return d("plugin:resources|close",{rid:this.rid})}}a=new WeakMap;class c extends l{constructor(e){super(e.rid),this.available=e.available,this.currentVersion=e.currentVersion,this.version=e.version,this.date=e.date,this.body=e.body}async download(e,t){const s=new o;e&&(s.onmessage=e);const n=await d("plugin:updater|download",{onEvent:s,rid:this.rid,...t});this.downloadedBytes=new l(n)}async install(){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await d("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(e,t){const s=new o;e&&(s.onmessage=e),await d("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...t})}async close(){await(this.downloadedBytes?.close()),await super.close()}}return e.Update=c,e.check=async function(e){return e?.headers&&(e.headers=Array.from(new Headers(e.headers).entries())),await d("plugin:updater|check",{...e}).then((e=>e.available?new c(e):null))},e}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}

@ -4,14 +4,14 @@
import { invoke, Channel, Resource } from "@tauri-apps/api/core";
/** Options used to check for updates */
/** Options used when checking for updates */
interface CheckOptions {
/**
* Request headers
*/
headers?: HeadersInit;
/**
* Timeout in seconds
* Timeout in milliseconds
*/
timeout?: number;
/**
@ -24,6 +24,18 @@ interface CheckOptions {
target?: string;
}
/** Options used when downloading an update */
interface DownloadOptions {
/**
* Request headers
*/
headers?: HeadersInit;
/**
* Timeout in milliseconds
*/
timeout?: number;
}
interface UpdateMetadata {
rid: number;
available: boolean;
@ -57,7 +69,10 @@ class Update extends Resource {
}
/** Download the updater package */
async download(onEvent?: (progress: DownloadEvent) => void): Promise<void> {
async download(
onEvent?: (progress: DownloadEvent) => void,
options?: DownloadOptions,
): Promise<void> {
const channel = new Channel<DownloadEvent>();
if (onEvent) {
channel.onmessage = onEvent;
@ -65,6 +80,7 @@ class Update extends Resource {
const downloadedBytesRid = await invoke<number>("plugin:updater|download", {
onEvent: channel,
rid: this.rid,
...options,
});
this.downloadedBytes = new Resource(downloadedBytesRid);
}
@ -87,6 +103,7 @@ class Update extends Resource {
/** Downloads the updater package and installs it */
async downloadAndInstall(
onEvent?: (progress: DownloadEvent) => void,
options?: DownloadOptions,
): Promise<void> {
const channel = new Channel<DownloadEvent>();
if (onEvent) {
@ -95,6 +112,7 @@ class Update extends Resource {
await invoke("plugin:updater|download_and_install", {
onEvent: channel,
rid: this.rid,
...options,
});
}
@ -115,5 +133,5 @@ async function check(options?: CheckOptions): Promise<Update | null> {
}).then((meta) => (meta.available ? new Update(meta) : null));
}
export type { CheckOptions, DownloadEvent };
export type { CheckOptions, DownloadOptions, DownloadEvent };
export { check, Update };

@ -4,10 +4,11 @@
use crate::{Result, Update, UpdaterExt};
use http::{HeaderMap, HeaderName, HeaderValue};
use serde::Serialize;
use tauri::{ipc::Channel, Manager, Resource, ResourceId, Runtime, Webview};
use std::time::Duration;
use std::{str::FromStr, time::Duration};
use url::Url;
#[derive(Debug, Clone, Serialize)]
@ -53,7 +54,7 @@ pub(crate) async fn check<R: Runtime>(
}
}
if let Some(timeout) = timeout {
builder = builder.timeout(Duration::from_secs(timeout));
builder = builder.timeout(Duration::from_millis(timeout));
}
if let Some(ref proxy) = proxy {
let url = Url::parse(proxy.as_str())?;
@ -83,8 +84,25 @@ pub(crate) async fn download<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
on_event: Channel<DownloadEvent>,
headers: Option<Vec<(String, String)>>,
timeout: Option<u64>,
) -> Result<ResourceId> {
let update = webview.resources_table().get::<Update>(rid)?;
let mut update = (*update).clone();
if let Some(headers) = headers {
let mut map = HeaderMap::new();
for (k, v) in headers {
map.append(HeaderName::from_str(&k)?, HeaderValue::from_str(&v)?);
}
update.headers = map;
}
if let Some(timeout) = timeout {
update.timeout = Some(Duration::from_millis(timeout));
}
let mut first_chunk = true;
let bytes = update
.download(
@ -100,6 +118,7 @@ pub(crate) async fn download<R: Runtime>(
},
)
.await?;
Ok(webview.resources_table().add(DownloadedBytes(bytes)))
}
@ -123,9 +142,25 @@ pub(crate) async fn download_and_install<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
on_event: Channel<DownloadEvent>,
headers: Option<Vec<(String, String)>>,
timeout: Option<u64>,
) -> Result<()> {
let update = webview.resources_table().get::<Update>(rid)?;
let mut update = (*update).clone();
if let Some(headers) = headers {
let mut map = HeaderMap::new();
for (k, v) in headers {
map.append(HeaderName::from_str(&k)?, HeaderValue::from_str(&v)?);
}
update.headers = map;
}
if let Some(timeout) = timeout {
update.timeout = Some(Duration::from_millis(timeout));
}
let mut first_chunk = true;
update

@ -68,6 +68,10 @@ pub enum Error {
#[error(transparent)]
Http(#[from] http::Error),
#[error(transparent)]
InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
#[error(transparent)]
InvalidHeaderName(#[from] http::header::InvalidHeaderName),
#[error(transparent)]
Tauri(#[from] tauri::Error),
}

@ -469,10 +469,6 @@ impl Update {
"Accept",
HeaderValue::from_str("application/octet-stream").unwrap(),
);
headers.insert(
"User-Agent",
HeaderValue::from_str("tauri-updater").unwrap(),
);
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if let Some(timeout) = self.timeout {

Loading…
Cancel
Save