Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 40 additions & 95 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const updater = new Updater(nw.App.manifest);

let updateStatus = "";
let newManifest = "";
let downloadedFilePath = "";

// Check for new version via current running application.
updater.checkNewVersion((err, newerVersionExists, remoteManifest) => {
if (err) {
updateStatus = `Error checking for updates: ${err.message}`;
Expand All @@ -33,91 +36,37 @@ updater.checkNewVersion((err, newerVersionExists, remoteManifest) => {
}
});

// Download to temporary directory if new version is available.
let downloadStatus = "";
updater.download((err, filePath) => {
if (err) {
downloadStatus = `Error downloading update: ${err.message}`;
return;
}
downloadStatus = "Update downloaded successfully at " + filePath;
downloadedFilePath = filePath;
}, newManifest);
```

It gives you low-level API to:

1. Check the manifest for version (from your running "old" app).
2. If the version is different from the running one, download new package to a temp directory.
3. Unpack the package in temp.
4. Run new app from temp and kill the old one (i.e. still all from the running app).
5. The new app (in temp) will copy itself to the original folder, overwriting the old app.
6. The new app will run itself from original folder and exit the process.

## API

<a name="new_updater"></a>

#### new updater(manifest, options)

Creates new instance of updater. Manifest could be a `package.json` of project.

Note that compressed apps are assumed to be downloaded in the format produced by [nw-builder](https://github.com/nwutils/nw-builder) (or [grunt-nw-builder](https://github.com/nwjs/grunt-nw-builder)).

**Params**

- manifest `object` - See the [manifest schema](#manifest-schema) below.
- options `object` - Optional

<a name="updater#checkNewVersion"></a>

#### updater.checkNewVersion(cb)

Will check the latest available version of the application by requesting the manifest specified in `manifestUrl`.

The callback will always be called; the second parameter indicates whether or not there's a newer version.
This function assumes you use [Semantic Versioning](http://semver.org) and enforces it; if your local version is `0.2.0` and the remote one is `0.1.23456` then the callback will be called with `false` as the second paramter. If on the off chance you don't use semantic versioning, you could manually download the remote manifest and call `download` if you're happy that the remote version is newer.

**Params**

- cb `function` - Callback arguments: error, newerVersionExists (`Boolean`), remoteManifest

<a name="updater#download"></a>
// Unpack the application in the temporary directory
updater.unpack();

#### updater.download(cb, newManifest)
// Run the new application from the temporary directory and kill the old one

Downloads the new app to a temporary folder.
// The new application will copy itself from the temporary directory to the directory where the previous application was running.

**Params**

- cb `function` - called when download completes. Callback arguments: error, downloaded filepath
- newManifest `Object` - see [manifest schema](#manifest-schema) below

**Returns**: `Request` - Request - stream, the stream contains `manifest` property with new manifest and 'content-length' property with the size of package.
<a name="updater#getAppPath"></a>

#### updater.getAppPath()

Returns executed application path

**Returns**: `string`
<a name="updater#getAppExec"></a>

#### updater.getAppExec()

Returns current application executable

**Returns**: `string`
<a name="updater#unpack"></a>

#### updater.unpack(filename, cb, manifest)

Will unpack the `filename` in temporary folder.
For Windows, [unzip](https://www.mkssoftware.com/docs/man1/unzip.1.asp) is used (which is [not signed](https://github.com/edjafarov/node-webkit-updater/issues/68)).
// The new application will run itself from the original directory and exit the process.
```

**Params**
## API Schema

- filename `string`
- cb `function` - Callback arguments: error, unpacked directory
- manifest `object`
| Method | Arguments | Return Type | Description |
| ------ | --------- | ----------- | ----------- |
| new Updater | `manifest: object, options: object \| undefined` | `void` | Creates a new instance of Updater. See the [manifest schema](#manifest-schema) below. |
| checkNewVersion | `cb: (error: Error, newerVersionExists: boolean, remoteManifest: object) => void` | `void` | Checks the latest version of the application by requesting manifest at `manifestUrl`. Semantic versioning is used when comparing versions. |
| download | `cb: (error: Error, filepath: string) => void, newManifest: object` | `void` | Checks the latest version of the application by requesting manifest at `manifestUrl`. Downloads the new app to a temporary folder. |
| getAppPath | | `string` | Returns the executed application path. |
| getAppExec | | `string` | Returns the current application path. |
| unpack | `filename: string, cb: (error: Error, unpackedDir: string) => void, manifest: object` | `string` | Returns the executed application path. |

<a name="updater#runInstaller"></a>

Expand Down Expand Up @@ -161,29 +110,29 @@ Note: if this doesn't work, try `gui.Shell.openItem(execPath)` (see [node-webkit

## Manifest Schema

An example manifest:
Example usage:

```json
{
"name": "updapp",
"version": "0.0.2",
"author": "Eldar Djafarov <djkojb@gmail.com>",
"manifestUrl": "http://localhost:3000/package.json",
"name": "demo",
"version": "0.0.1",
"author": "NW.js Utils <contact@nwutils.io>",
"manifestUrl": "http://localhost:3000/manifest.json",
"packages": {
"mac": {
"url": "http://localhost:3000/releases/updapp/mac/updapp.zip"
"linux-x64": {
"url": "http://localhost:3000/demo-0.0.1-linux-x64.zip"
},
"win": {
"url": "http://localhost:3000/releases/updapp/win/updapp.zip"
"osx-arm64": {
"url": "http://localhost:3000/demo-0.0.1-osx-arm64.zip"
},
"win-x64": {
"url": "http://localhost:3000/demo-0.0.1-win-x64.zip"
},
"linux32": {
"url": "http://localhost:3000/releases/updapp/linux32/updapp.tar.gz"
}
}
}
```

The manifest could be a `package.json` of project, but doesn't have to be.
> Note: The manifest could be a `package.json` of project, but doesn't have to be.

### manifest.name

Expand Down Expand Up @@ -211,18 +160,14 @@ It's assumed your app is stored at the root of your package, use this to overrid

This can also be used to override `manifest.name`; e.g. if your `manifest.name` is `helloWorld` (therefore `helloWorld.app` on Mac) but your Windows executable is named `nw.exe`. Then you'd set `execPath` to `nw.exe`

---

## Troubleshooting

### Mac

If you get an error on Mac about too many files being open, run `ulimit -n 10240`
## Contributing

### Windows
### External contributor

On Windows, there is no "unzip" command built in by default. As a result, this project uses a third party "unzip.exe" in order to extract the downloaded update. On the NWJS site, in the "How to package and distribute your apps" file, one of the recommended methods of distribution is using EnigmaVirtualBox to package the app, nw.exe, and required DLLs into a single EXE file. This method works great for distribution, but unfortunately breaks node-webkit-updater, because it wraps the required unzip.exe file inside of the created EnigmaVirtualBox EXE. As a result, *it is not possible to use EnigmaVirtualBox to distribute your app if you plan on using node-webkit-updater*. Try using InnoSetup instead.
- Use Node.js standard libraries whenever possible.
- Prefer to use syncronous APIs over modern APIs which have been introduced in later versions.

## Contributing
### Maintainer

See [CONTRIBUTING.md](CONTRIBUTING.md)
- npm trusted publishing is used for releases
- a package is released when a maintainer creates a release note for a specific version
47 changes: 47 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@
"semver": "^7.6.2"
},
"devDependencies": {
"@types/chrome": "^0.1.42",
"@types/del": "^3.0.1",
"@types/ncp": "^2.0.8",
"@types/node": "^25.6.2",
"@types/nw.js": "^0.92.0",
"@types/semver": "^7.7.1",
"@types/yauzl-promise": "^4.0.1",
"express": "^5.2.1",
"get-port": "^7.2.0",
"nw-builder": "^4.17.10",
Expand Down
76 changes: 75 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
const fs = await import('node:fs');
const os = await import('node:os');
const path = await import('node:path');
const process = await import('node:process');
const stream = await import('node:stream');

import util from './util';

function semverGt(v1, v2) {
const [major1, minor1, patch1] = v1.replace(/^v/i, '').split('.').map(Number);
const [major2, minor2, patch2] = v2.replace(/^v/i, '').split('.').map(Number);
Expand All @@ -25,7 +28,7 @@ function semverGt(v1, v2) {
/**
* @typedef {object} Packages
* @property {Platform} win - The Windows package
* @property {Platform} mac - The macOS package
* @property {Platform} osx - The macOS package
* @property {Platform} linux32 - The Linux 32-bit package
* @property {Platform} linux64 - The Linux 64-bit package
*/
Expand Down Expand Up @@ -160,6 +163,77 @@ class Updater {
cb(err, null);
});
}

/**
* Returns executed application path.
*
* @returns {string}
*/
getAppPath() {
/**
* @type {Object.<string, string>}
*/
let appPath = {
osx: path.join(process.cwd(), '../../..'),
win: path.dirname(process.execPath)
};
appPath.linux32 = appPath.win;
appPath.linux64 = appPath.win;
return appPath[getHost()];
}

/**
* Returns current application executable.
*
* @returns {string}
*/
getAppExec() {
let execFolder = this.getAppPath();
let exec = {
osx: '',
win: path.basename(process.execPath),
linux32: path.basename(process.execPath),
linux64: path.basename(process.execPath)
};
return path.join(execFolder, exec[platform]);
}

/**
* @private
* @param {Manifest} manifest
* @return {string}
*/
getExecPathRelativeToPackage(manifest) {
const execPath = manifest.packages[platform] && manifest.packages[platform].execPath;

if (execPath) {
return execPath;
}
else {
const suffix = {
win: '.exe',
mac: '.app'
};
return manifest.name + (suffix[platform] || '');
}
};

/**
* Unpack the `filename` in temporary folder.
*
* @param {string} filename
* @param {function} cb - Callback arguments: error, unpacked directory
* @param {Manifest} manifest
*/
unpack(filename, cb, manifest) {
util.decompress(filename, this.options.temporaryDirectory)
.then(() => {
cb(null, path.join(this.options.temporaryDirectory, this.getExecPathRelativeToPackage(manifest)));
})
.catch((err) => {
cb(err, null);
});
}
}

export default Updater;
1 change: 1 addition & 0 deletions src/updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class Updater {
unpack(filename, cb, manifest) {
pUnpack[platform](filename, cb, manifest, this.options.temporaryDirectory);
}

/**
* Runs installer
* @param {string} appPath
Expand Down
Loading
Loading