Skip to content
This repository was archived by the owner on May 24, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Add electron package
  • Loading branch information
amaury1093 committed Sep 7, 2018
commit fe0791ee28929e05257a1ee5010bc400d1cdc79e
85 changes: 85 additions & 0 deletions packages/electron/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# @parity/electron

Control the Parity client from electron.

## Getting Started

```bash
yarn add @parity/electron
```

## Usage

```javascript
import parityElectron, { isParityRunning } from '@parity/electron';

// Optional: override default options
parityElectron({
logger: myCustomLoggerFunction
})

isParityRunning()
.then(() => ...);
```

## API

#### `parityElectron(options: Object)`

If you don't want to override the default options, there's no need to call this function. Here `options` can have the following fields:

| Option | Default Value | Description |
| ---------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `options.logger` | `require('debug')` | A function with the same signature as [`debug`](https://github.com/visionmedia/debug). All logs inside `@parity/electron` will then be logged by this function. |

#### `fetchParity(mainWindow: BrowserWindow, options: Object): Promise<String>`

Downloads Parity, saves it to Electron's `userData` folder, and returns the path to the downloaded binary once finished.

| Option | Type | Description |
| ----------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `mainWindow` | [BrowserWindow](https://github.com/electron/electron/blob/master/docs/api/browser-window.md) | The Electron BrowserWindow in which to download the binary. |
| `options.onProgress` | `Function` | Optional callback that receives a number between 0 and 1 representing the progress of the current download. |
| `options.parityChannel` | `String` | Can be `stable`, `beta` or `nightly`. If downloading Parity is needed, determines which version of Parity to download. Defaults to `beta`. |

#### `getParityPath(): Promise<String>`

Returns the path to Parity. It checks (in this order) if Parity is in `$PATH`, in a standard OS location or in Electron's `userData` folder, and returns the first instance of Parity found. The Promise rejects if no Parity instance is found.

#### `defaultParityPath(): Promise<String>`

Returns the path to the Parity path inside Electron's `userData` folder, even if that binary doesn't exist. It's the default download location for [`fetchParity`](#fetchParitymainWindow-BrowserWindow-options-Object-PromiseltStringgt0).

#### `isParityRunning(options: Object): Promise<Boolean>`

| Option | Type | Description |
| ------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `options.wsInterface` | `String` | Hostname portion of the WebSockets server Fether will try to connect to. It should be an interface's IP address. (default: 127.0.0.1) |
| `options.wsPort` | `Number | String` | Port portion of the WebSockets server Fether will try to connect to. (default: 8546) |

Resolves to `true` if Parity is currently running, or to `false` if not.

#### `runParity(options: Object): Promise<Null>`

Spawns a child process to run Parity, with optional additional flags.

| Option | Type | Description |
| ------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `options.flags` | `Array<String>` | Additional flags to pass to Parity, listed as an array, to be passed to `child_process.spawn`. |
| `options.onParityError` | `Function` | Callback with `error` as argument when Parity encounters an error. |

#### `killParity(): Promise<Null>`

If a Parity process has been spawned with [`runParity`](#runParityonParityError-Function-PromiseltNullgt), then it kills this process. The Promise resolves instantly, there's no guarantee that Parity has been cleanly killed.

#### `deleteParity(): Promise<Null>`

If Parity has been downloaded to Electron's `userData` folder, then it deletes the Parity binary file from that folder.

#### `signerNewToken(): Promise<String>`

Runs `parity signer new-token` and resolves with a new secure token to be used in a dapp. Rejects if no token could be extracted.

#### `checkClockSync(): Promise<Object{isClockSync: boolean, timeDrift: number}>`

Use SNTP to check if the local clock is synchronized; return the time drift.
41 changes: 41 additions & 0 deletions packages/electron/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@parity/electron",
"description": "Control the Parity Ethereum node from Electron.",
"version": "1.0.0",
"author": "Parity Team <[email protected]>",
"license": "MIT",
"repository": "https://github.com/paritytech/js-libs/tree/master/packages/electron",
"main": "lib/index.js",
"keywords": [
"Ethereum",
"Electron",
"Parity"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"docs": "typedoc",
"prebuild": "rimraf lib",
"build": "tsc",
"test": "jest"
},
"dependencies": {
"async-retry": "^1.2.1",
"axios": "^0.18.0",
"checksum": "^0.1.1",
"command-exists": "^1.2.6",
"debug": "^3.1.0",
"electron-dl": "^1.11.0",
"promise-any": "^0.2.0",
"sntp": "^3.0.1"
},
"devDependencies": {
"@types/async-retry": "^1.2.1",
"@types/checksum": "^0.1.30",
"electron": "^2.0.2"
},
"peerDependencies": {
"electron": "^2.0.3"
}
}
10 changes: 10 additions & 0 deletions packages/electron/src/ambient.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
//
// SPDX-License-Identifier: MIT

// Packages that don't have their TS typings yet
declare module 'command-exists';
declare module 'electron-dl';
declare module 'promise-any';
declare module 'sntp';
16 changes: 16 additions & 0 deletions packages/electron/src/checkClockSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
//
// SPDX-License-Identifier: MIT

import { time } from 'sntp';

export const MAX_TIME_DRIFT = 10000; // milliseconds

export const checkClockSync = async () => {
const { t: timeDrift }: { t: number } = await time();
return {
isClockSync: timeDrift < MAX_TIME_DRIFT,
timeDrift
};
};
160 changes: 160 additions & 0 deletions packages/electron/src/fetchParity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
//
// SPDX-License-Identifier: MIT

import { app, BrowserWindow } from 'electron';
import axios from 'axios';
import { file } from 'checksum';
import { download } from 'electron-dl';
import { chmod, rename, unlink } from 'fs';
import { promisify } from 'util';
import * as retry from 'async-retry';

import { defaultParityPath, getParityPath } from './getParityPath';
import logger from './utils/logger';

const checksum = promisify(file);
const fsChmod = promisify(chmod);
const fsRename = promisify(rename);
const fsUnlink = promisify(unlink);

const VANITY_URL = 'https://vanity-service.parity.io/parity-binaries';

const getArch = () => {
switch (process.platform) {
case 'darwin':
case 'win32':
return 'x86_64';
default: {
switch (process.arch) {
case 'arm':
return 'arm';
case 'arm64':
return 'aarch64';
case 'x32':
return 'i686';
default:
return 'x86_64';
}
}
}
};

const getOs = () => {
switch (process.platform) {
case 'darwin':
return 'darwin';
case 'win32':
return 'windows';
default:
return 'linux';
}
};

/**
* Remove parity binary or partial binary in the userData folder, if it exists.
*/
export const deleteParity = async () => {
const parityPath = await defaultParityPath();

// Remove parity binary
try {
await fsUnlink(parityPath);
} catch (e) {
/* Do nothing if error. */
}

// Remove parity partial binary (download was still in progress)
try {
await fsUnlink(`${parityPath}.part`);
} catch (e) {
/* Do nothing if error. */
}
};

/**
* Downloads Parity, saves it to Electron's `userData` folder, and returns the
* path to the downloaded binary once finished.
*/
export const fetchParity = async (
mainWindow: BrowserWindow,
{
onProgress = () => {
/* Do nothing by defaut. */
},
parityChannel = 'beta'
}: { onProgress: (progress: number) => void; parityChannel: string }
) => {
try {
const parityPath = retry(
async (_, attempt: number) => {
if (attempt > 1) {
logger()('@parity/electron:main')('Retrying.');
}

// Delete any old Parity if it exists, otherwise electron-dl will
// download the new binary with a (1) at the end of the filename
await deleteParity();

// Fetch the metadata of the correct version of parity
const metadataUrl = `${VANITY_URL}?version=${parityChannel}&os=${getOs()}&architecture=${getArch()}`;
logger()('@parity/electron:main')(`Downloading from ${metadataUrl}.`);
const { data } = await axios.get(metadataUrl);

// Get the binary's url
const {
name,
downloadUrl,
checksum: expectedChecksum
}: {
name: string;
downloadUrl: string;
checksum: string;
} = data[0].files.find(
({ name }: { name: string }) =>
name === 'parity' || name === 'parity.exe'
);

// Start downloading.
const downloadItem = await download(mainWindow, downloadUrl, {
directory: app.getPath('userData'),
filename: `${name}.part`,
onProgress
});
const downloadPath: string = downloadItem.getSavePath();

// Once downloaded, we check the sha256 checksum
// Calculate the actual checksum
// @ts-ignore Types from @types/checksum are incorrect, checksum does
// take 2 arguments: https://github.com/dshaw/checksum/blob/master/checksum.js#L26
const actualChecksum: string = await checksum(downloadPath, {
algorithm: 'sha256'
});
// The 2 checksums should of course match
if (expectedChecksum !== actualChecksum) {
throw new Error(
`Checksum mismatch, expecting ${expectedChecksum}, got ${actualChecksum}.`
);
}

// Set a+x permissions on the downloaded binary
await fsChmod(downloadPath, '755');

// Binary is ready to be used: remove `.part` from filename
await fsRename(downloadPath, await defaultParityPath());

// Double-check that Parity exists now.
return getParityPath();
},
{
retries: 3
}
);

return parityPath;
} catch (err) {
await deleteParity();
throw err;
}
};
Loading