Skip to content

Commit

Permalink
finish documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
amr-crabnebula committed Dec 12, 2023
1 parent e8aec24 commit f77fbc4
Show file tree
Hide file tree
Showing 11 changed files with 481 additions and 37 deletions.
129 changes: 129 additions & 0 deletions bindings/updater/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# @crabnebula/updater

Updater for apps that was packaged by [`@crabnebula/packager`](https://www.npmjs.com/package/@crabnebula/packager).

## Checking for an update

you can check for an update using `checkUpdate` function which require the current version of the app and
an options object that specifies the endpoints to request updates from and the public key of the update signature.

```js
import { checkUpdate } from "@crabnebula/updater";

let update = await checkUpdate("0.1.0", {
endpoints: ["http://myserver.com/updates"],
pubkey: "<pubkey here>",
});

if (update !== null) {
update.downloadAndInstall();
} else {
// there is no updates
}
```

## Endpoints

Each endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}`
which will be detected and replaced with the appropriate value before making a request to the endpoint.

- `{{current_version}}`: The version of the app that is requesting the update.
- `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`).
- `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).

for example:

```
https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}
```

will turn into

```
https://releases.myapp.com/windows/x86_64/0.1.0
```

if you need more data, you can set additional request headers in the options object pass to `checkUpdate` to your liking.

## Endpoint Response

The updater expects the endpoint to respond with 2 possible reponses:

1. [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5) in case there is no updates available.
2. [`200 OK`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.1) and a JSON response that could be either a JSON representing all available platform updates
or if using endpoints variables (see above) or a header to attach the current updater target,
then it can just return information for the requested target.

The JSON response is expected to have these fields set:

- `version`: must be a valid semver, with or without a leading `v``, meaning that both `1.0.0`and`v1.0.0`are valid.
- `url`or`platforms.[target].url`: must be a valid url to the update bundle.
- `signature`or`platforms.[target].signature`: must be the content of the generated `.sig`file. The signature may change each time you run build your app so make sure to always update it.
- `format`or`platforms.[target].format`: must be one of `app`, `appimage`, `nsis`or`wix`.

> [!NOTE]
> if using `platforms` object, each key is in the `OS-ARCH` format, where `OS` is one of `linux`, `macos` or `windows`, and `ARCH` is one of `x86_64`, `aarch64`, `i686` or `armv7`, see the example below.
It can also contain these optional fields:

- `notes`: Here you can add notes about the update, like release notes.
- `pub_date`: must be formatted according to [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.8) if present.

Here is an example of the two expected JSON formats:

- **JSON for all platforms**

```json
{
"version": "v1.0.0",
"notes": "Test version",
"pub_date": "2020-06-22T19:25:57Z",
"platforms": {
"darwin-x86_64": {
"signature": "Content of app.tar.gz.sig",
"url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz",
"format": "app"
},
"darwin-aarch64": {
"signature": "Content of app.tar.gz.sig",
"url": "https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz",
"format": "app"
},
"linux-x86_64": {
"signature": "Content of app.AppImage.sig",
"url": "https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz",
"format": "appimage"
},
"windows-x86_64": {
"signature": "Content of app-setup.exe.sig or app.msi.sig, depending on the chosen format",
"url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x64-setup.nsis.zip",
"format": "nsis or wix depending on the chosen format"
}
}
}
```

- **JSON for one platform**

```json
{
"version": "0.2.0",
"pub_date": "2020-09-18T12:29:53+01:00",
"url": "https://mycompany.example.com/myapp/releases/myrelease.tar.gz",
"signature": "Content of the relevant .sig file",
"format": "app or nsis or wix or appimage depending on the release target and the chosen format",
"notes": "These are some release notes"
}
```

## Update install mode on Windows

You can specify which install mode to use on Windows using `windows.installMode` in the options object which can be one of:

- `"Passive"`: There will be a small window with a progress bar. The update will be installed without requiring any user interaction. Generally recommended and the default mode.
- `"BasicUi"`: There will be a basic user interface shown which requires user interaction to finish the installation.
- `"Quiet"`: There will be no progress feedback to the user. With this mode the installer cannot request admin privileges by itself so it only works in user-wide installations or when your app itself already runs with admin privileges. Generally not recommended.

## Licenses

MIT or MIT/Apache 2.0 where applicable.
6 changes: 3 additions & 3 deletions bindings/updater/nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

export const enum WindowsUpdateInstallMode {
/** Specifies there's a basic UI during the installation process, including a final dialog box at the end. */
BasicUi = 0,
BasicUi = 'BasicUi',
/**
* The quiet mode means there's no user interaction required.
* Requires admin privileges if the installer does.
*/
Quiet = 1,
Quiet = 'Quiet',
/** Specifies unattended mode, which means the installation only shows a progress bar. */
Passive = 2
Passive = 'Passive'
}
export interface UpdaterWindowsOptions {
/** Additional arguments given to the NSIS or WiX installer. */
Expand Down
6 changes: 3 additions & 3 deletions bindings/updater/nodejs/src/from_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ impl From<cargo_packager_updater::WindowsUpdateInstallMode> for WindowsUpdateIns
}
}

impl From<cargo_packager_updater::UpdaterWindowsConfig> for UpdaterWindowsOptions {
fn from(value: cargo_packager_updater::UpdaterWindowsConfig) -> Self {
impl From<cargo_packager_updater::WindowsConfig> for UpdaterWindowsOptions {
fn from(value: cargo_packager_updater::WindowsConfig) -> Self {
Self {
installer_args: value.installer_args,
install_mode: value.install_mode.map(Into::into),
}
}
}
impl From<UpdaterWindowsOptions> for cargo_packager_updater::UpdaterWindowsConfig {
impl From<UpdaterWindowsOptions> for cargo_packager_updater::WindowsConfig {
fn from(value: UpdaterWindowsOptions) -> Self {
Self {
installer_args: value.installer_args,
Expand Down
6 changes: 3 additions & 3 deletions bindings/updater/nodejs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use napi::{

mod from_impls;

#[napi_derive::napi]
#[napi_derive::napi(string_enum)]
#[derive(Default)]
pub enum WindowsUpdateInstallMode {
/// Specifies there's a basic UI during the installation process, including a final dialog box at the end.
Expand Down Expand Up @@ -191,7 +191,7 @@ impl Update {
let update = self.create_update()?;

update
.download(
.download_extended(
|c, l| {
if let Some(on_chunk) = &on_chunk {
on_chunk.call(
Expand Down Expand Up @@ -227,7 +227,7 @@ impl Update {
) -> Result<()> {
let update = self.create_update()?;
let bytes = update
.download(
.download_extended(
|c, l| {
if let Some(on_chunk) = &on_chunk {
on_chunk.call(
Expand Down
126 changes: 125 additions & 1 deletion crates/updater/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,131 @@
## cargo-packager-updater
# cargo-packager-updater

Updater for apps that was packaged by [`cargo-packager`](https://docs.rs/cargo-packager).

## Checking for an update

you can check for an update using [`check_update`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/fn.check_update.html) function or construct a new [`Updater`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.Updater.html)
using [`UpdaterBuilder`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.UpdaterBuilder.html), both methods require the current version of the app and
a [`Config`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.Config.html) that specifies the endpoints to request updates from and the public key of the update signature.

```rs
use cargo_packager_updater::{check_update, Config};

let config = Config {
endpoints: vec!["http://myserver.com/updates"],
pubkey: "<pubkey here>",
..Default::default()
};
if let Some(update) = check_update("0.1.0", config).expect("failed while checking for update") {
update.download_and_install().expect("failed to download and install update");
} else {
// there is no updates
}

```

## Endpoints

Each endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}`
which will be detected and replaced with the appropriate value before making a request to the endpoint.

- `{{current_version}}`: The version of the app that is requesting the update.
- `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`).
- `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).

for example:

```
"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
```

will turn into

```
"https://releases.myapp.com/windows/x86_64/0.1.0"
```

if you need more data, you can set additional request headers [`UpdaterBuilder::header`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.UpdaterBuilder.html#method.header) to your liking.

## Endpoint Response

The updater expects the endpoint to respond with 2 possible reponses:

1. [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5) in case there is no updates available.
2. [`200 OK`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.1) and a JSON response that could be either a JSON representing all available platform updates
or if using endpoints variables (see above) or a header to attach the current updater target,
then it can just return information for the requested target.

The JSON response is expected to have these fields set:

- `version`: must be a valid semver, with or without a leading `v``, meaning that both `1.0.0`and`v1.0.0`are valid.
- `url`or`platforms.[target].url`: must be a valid url to the update bundle.
- `signature`or`platforms.[target].signature`: must be the content of the generated `.sig`file. The signature may change each time you run build your app so make sure to always update it.
- `format`or`platforms.[target].format`: must be one of `app`, `appimage`, `nsis`or`wix`.

> [!NOTE]
> if using `platforms` object, each key is in the `OS-ARCH` format, where `OS` is one of `linux`, `macos` or `windows`, and `ARCH` is one of `x86_64`, `aarch64`, `i686` or `armv7`, see the example below.
It can also contain these optional fields:

- `notes`: Here you can add notes about the update, like release notes.
- `pub_date`: must be formatted according to [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.8) if present.

Here is an example of the two expected JSON formats:

- **JSON for all platforms**

```json
{
"version": "v1.0.0",
"notes": "Test version",
"pub_date": "2020-06-22T19:25:57Z",
"platforms": {
"darwin-x86_64": {
"signature": "Content of app.tar.gz.sig",
"url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz",
"format": "app"
},
"darwin-aarch64": {
"signature": "Content of app.tar.gz.sig",
"url": "https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz",
"format": "app"
},
"linux-x86_64": {
"signature": "Content of app.AppImage.sig",
"url": "https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz",
"format": "appimage"
},
"windows-x86_64": {
"signature": "Content of app-setup.exe.sig or app.msi.sig, depending on the chosen format",
"url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x64-setup.nsis.zip",
"format": "nsis or wix depending on the chosen format"
}
}
}
```

- **JSON for one platform**

```json
{
"version": "0.2.0",
"pub_date": "2020-09-18T12:29:53+01:00",
"url": "https://mycompany.example.com/myapp/releases/myrelease.tar.gz",
"signature": "Content of the relevant .sig file",
"format": "app or nsis or wix or appimage depending on the release target and the chosen format",
"notes": "These are some release notes"
}
```

## Update install mode on Windows

You can specify which install mode to use on Windows using [`WindowsConfig::install_mode`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.WindowsConfig.html#structfield.install_mode) which can be on of:

- `"Passive"`: There will be a small window with a progress bar. The update will be installed without requiring any user interaction. Generally recommended and the default mode.
- `"BasicUi"`: There will be a basic user interface shown which requires user interaction to finish the installation.
- `"Quiet"`: There will be no progress feedback to the user. With this mode the installer cannot request admin privileges by itself so it only works in user-wide installations or when your app itself already runs with admin privileges. Generally not recommended.

## Licenses

MIT or MIT/Apache 2.0 where applicable.
6 changes: 3 additions & 3 deletions crates/updater/src/custom_serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use serde::{de::Error, Deserialize, Deserializer};
use time::OffsetDateTime;
use url::Url;

use crate::{ReleaseManifestPlatform, RemoteRelease, RemoteReleaseInner, UpdateFormat};
use crate::{ReleaseManifestPlatform, RemoteRelease, RemoteReleaseData, UpdateFormat};

fn parse_version<'de, D>(deserializer: D) -> std::result::Result<Version, D::Error>
where
Expand Down Expand Up @@ -78,9 +78,9 @@ impl<'de> Deserialize<'de> for RemoteRelease {
notes: release.notes,
pub_date,
data: if let Some(platforms) = release.platforms {
RemoteReleaseInner::Static { platforms }
RemoteReleaseData::Static { platforms }
} else {
RemoteReleaseInner::Dynamic(ReleaseManifestPlatform {
RemoteReleaseData::Dynamic(ReleaseManifestPlatform {
url: release.url.ok_or_else(|| {
Error::custom("the `url` field was not set on the updater response")
})?,
Expand Down
1 change: 1 addition & 0 deletions crates/updater/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,5 @@ pub enum Error {
PersistError(#[from] tempfile::PersistError),
}

/// Convenience alias for `cargo-packager-updater` crate Result type.
pub type Result<T> = std::result::Result<T, Error>;
Loading

0 comments on commit f77fbc4

Please sign in to comment.