-
Notifications
You must be signed in to change notification settings - Fork 431
Description
Hi,
I'm trying to implement a closed-beta updater feature for my app, and just ran into a problem caused by the check()
function overwriting my manually written headers.
Background
Since I'm connecting to a private repo, I'm using a GitHub API endpoint instead of the direct URL to latest.json
. And the API for getting a release asset is a bit funky. If the Accept
header is set to application/vnd.github+json
(which is considered "normal use" by the API, I believe), GitHub will return metadata about the asset - not the actual asset itself. So when using the API to download latest.json
the Accept
header needs to be set to application/octet-stream
instead.
Exact description in the GitHub REST API docs:
To download the asset's binary content:
- If within a browser, fetch the location specified in the browser_download_url key provided in the response.
- Alternatively, set the Accept header of the request to application/octet-stream. The API will either redirect the client to the location, or stream it directly if possible. API clients should handle both a 200 or 302 response.
The problem
I'm building an Updater
object using the UpdaterBuilder
so that I can supply the correct endpoint and necessary headers.
let updater = app
.updater_builder()
.endpoints(vec![url])?
.headers(headers)
.build()?;
When I later call updater.check()
if let Some(update) = updater.check().await? {
// [...]
}
I get an error that updater failed to deserialize the update response:
[2025-04-10][15:41:39][tauri_plugin_updater::updater][ERROR] failed to deserialize update response: unexpected character 'l' while parsing major version number
The reason Updater can't parse the json file is because it's actually working on the json file containing metadata about the asset, and not on the latest.json
itself (verified by setting log-level to TRACE and looking at the console output). I'm almost certain that this is caused by check()
injecting the aforementioned Accept
header here setting it to application/json
. I assume there are two Accept
headers in the outgoing request, but that the GitHub backend decides to go with the one injected by check()
.
Appendix: Updating from private repos
Since I've been wrangling with these headers for a while now, I thought I'd write down the process for accessing a private asset through the GitHub API, as I'm sure someone will come across this issue working on a similar problem.
- Download metadata about the latest release from
https://api.github.com/repos/{owner}/{repo}/releases/latest
using the following headers:
use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION, USER_AGENT};
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, "application/vnd.github+json".parse()?);
headers.insert(USER_AGENT, "my-tauri-app".parse()?);
headers.insert(AUTHORIZATION, format!("Bearer {}", PRIVATE_REPO_TOKEN).parse()?);
- Parse the metadata json and look for the asset containing your
latest.json
. Extract theurl
field from here and use it in the next step. It should look something like this:"url": "https://api.github.com/repos/{owner}/{repo}/releases/assets/805105863"
- Downoad the asset using the
url
found in the previous step. This time you need a differentAccess
header, like mentioned before:
use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION, USER_AGENT};
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, "application/octet-stream".parse()?);
headers.insert(USER_AGENT, "my-tauri-app".parse()?);
headers.insert(AUTHORIZATION, format!("Bearer {}", PRIVATE_REPO_TOKEN).parse()?);
- GitHub also recommends adding a header to specify the API Version you are integrating against. Everything works fine without this header at the time being, so I didn't insert it in the steps above for readability. But here it is anyway:
use reqwest::header::{HeaderMap, HeaderName, ACCEPT, AUTHORIZATION, USER_AGENT};
// Optional header that is recommended by GitHub
let hdr = HeaderName::from_lowercase(b"x-github-api-version")?;
headers.insert(hdr, "2022-11-28".parse()?);