Skip to content

[updater] check() fails on private repos due to wrong header #2618

@SimenZhor

Description

@SimenZhor

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.

  1. 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()?);
  1. Parse the metadata json and look for the asset containing your latest.json. Extract the url 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"
  2. Downoad the asset using the url found in the previous step. This time you need a different Access 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()?);
  1. 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()?);

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions