Skip to content

V2 #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 5, 2019
Merged

V2 #109

28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -70,6 +70,34 @@ daemon.downloading.subscribe(download => {

```

## Version 2

Version 2 of the arduino-create-agent aims to provide a cleaner api based on promises.
It will remain confined to a v2 property on the daemon object until it will be stable.
At the moment it only supports tool management.

```js
daemon.agentV2Found.subscribe(daemonV2 => {
if (!daemonV2) {
// Your Agent doesn't support v2
}
// Your Agent supports v2
});

daemon.v2.installedTools()
.then(tools => console.debug(tools)) // [{"name":"avrdude","version":"6.3.0-arduino9","packager":"arduino"}]

let payload = {
name: 'avrdude',
version: '6.3.0-arduino9',
packager: 'arduino',
url: 'http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino9-i686-w64-mingw32.zip',
checksum: 'SHA-256:f3c5cfa8d0b3b0caee81c5b35fb6acff89c342ef609bf4266734c6266a256d4f',
signature: '7628b488c7ffd21ae1ca657245751a4043c419fbab5c256a020fb53f17eb88686439f54f18e78a80b40fc2de742f79b78ed4338c959216dc8ae8279e482d2d4117eeaf34a281ce2369d1dc4356f782c0940d82610f1c892e913b637391c39e95d4d4dfe82d8dbc5350b833186a70a62c7952917481bad798a9c8b4905df91bd914fbdfd6e98ef75c8f7fb06284278da449ce05b27741d6eda156bbdb906d519ff7d7d5042379fdfc55962b3777fb9240b368552182758c297e39c72943d75d177f2dbb584b2210301250796dbe8af11f0cf06d762fe4f912294f4cdc8aff26715354cfb33010a81342fbbc438912eb424a39fc0c52a9b2bf722051a6f3b024bd'
}
daemon.v2.installTool(payload) // Will install the tool in the system
```

## Development and test features
Just run `npm run dev` and open your browser on http://localhost:8000

9 changes: 7 additions & 2 deletions demo/app.jsx
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ import React from 'react';
import Daemon from '../src';

import { HEX } from './serial_mirror';
import V2 from './v2/v2.jsx';

const chromeExtensionID = 'hfejhkbipnickajaidoppbadcomekkde';

@@ -266,11 +267,15 @@ class App extends React.Component {
<h2>Serial Monitor</h2>

<form onSubmit={this.handleSend}>
<input id="serial-input" value={this.state.serialInput} onChange={this.handleChangeSerial}/>
<input aria-label="serial input" id="serial-input" value={this.state.serialInput} onChange={this.handleChangeSerial}/>
<input type="submit" value="Send" />
</form>

<textarea id="serial-textarea" value={ this.state.serialMonitorContent } readOnly/>
<textarea aria-label="Serial Monitor output" id="serial-textarea" value={ this.state.serialMonitorContent } readOnly/>
</div>

<div className="v2">
<V2 daemon={daemon}></V2>
</div>

<div className="section">
27 changes: 27 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
@@ -53,6 +53,33 @@
.section {
margin: 20px;
}

.install-tool-container .tool-input {
display: flex;
margin: 10px 0;
}

.install-tool-container .tool-input label {
flex: 0 1 200px;
}

.install-tool-container .tool-input input {
flex: 0 1 500px;
}

.v2 {
background-color: #ecf1f1;
padding: 20px;
}

table {
border-collapse: collapse;
}

table td {
border: 1px solid black;
padding: 3px;
}
</style>
</head>
<body>
107 changes: 107 additions & 0 deletions demo/v2/install_tool.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { flatMap } from 'rxjs/operators';

export class V2InstallTool extends React.Component {
constructor() {
super();
this.state = {
name: 'avrdude',
version: '6.3.0-arduino9',
packager: 'arduino',
url: 'http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino9-i686-w64-mingw32.zip',
checksum: 'SHA-256:f3c5cfa8d0b3b0caee81c5b35fb6acff89c342ef609bf4266734c6266a256d4f',
signature: '7628b488c7ffd21ae1ca657245751a4043c419fbab5c256a020fb53f17eb88686439f54f18e78a80b40fc2de742f79b78ed4338c959216dc8ae8279e482d2d4117eeaf34a281ce2369d1dc4356f782c0940d82610f1c892e913b637391c39e95d4d4dfe82d8dbc5350b833186a70a62c7952917481bad798a9c8b4905df91bd914fbdfd6e98ef75c8f7fb06284278da449ce05b27741d6eda156bbdb906d519ff7d7d5042379fdfc55962b3777fb9240b368552182758c297e39c72943d75d177f2dbb584b2210301250796dbe8af11f0cf06d762fe4f912294f4cdc8aff26715354cfb33010a81342fbbc438912eb424a39fc0c52a9b2bf722051a6f3b024bd',
res: ''
};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

componentDidMount() {
this.daemon = this.props.daemon;

this.daemon.agentV2Found.subscribe(daemonV2 => {
if (!daemonV2) {
return;
}
this.daemonV2 = daemonV2;
});
}

handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}

handleSubmit(event) {
event.preventDefault();

this.daemonV2.installTool({
name: this.state.name,
version: this.state.version,
packager: this.state.packager,
checksum: this.state.checksum,
signature: this.state.signature,
url: this.state.url,

})
.then(res => {
this.setState({
res: JSON.stringify(res, null, 2)
});
})
.catch(err => {
this.setState({
res: JSON.stringify(err, null, 2)
});
});
}

render() {
return (
<section className="install-tool-container">
<h2>Install a new tool</h2>
<form onSubmit={this.handleSubmit}>
<div className="tool-input">
<label htmlFor="name">
Name:
</label>
<input type="text" id="name" value={this.state.name} onChange={this.handleChange} />
</div>
<div className="tool-input">
<label htmlFor="version">
Version:
</label>
<input type="text" id="version" value={this.state.version} onChange={this.handleChange} />
</div>
<div className="tool-input">
<label htmlFor="packager">
Packager:
</label>
<input type="text" id="packager" value={this.state.packager} onChange={this.handleChange} />
</div>
<div className="tool-input">
<label htmlFor="url">
Url:
</label>
<input type="text" id="url" value={this.state.url} onChange={this.handleChange} />
</div>
<div className="tool-input">
<label htmlFor="checksum">
Checksum:
</label>
<input type="text" id="checksum" value={this.state.checksum} onChange={this.handleChange} />
</div>
<div className="tool-input">
<label htmlFor="signature">
Signature:
</label>
<input type="text" id="signature" value={this.state.signature} onChange={this.handleChange} />
</div>
<input type="submit" value="Submit" />
</form>
<textarea aria-label="Install tool output" cols="100" rows="10" value={this.state.res} readOnly></textarea>
</section>
);
}
}
60 changes: 60 additions & 0 deletions demo/v2/v2.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { V2InstallTool } from './install_tool.jsx';

class V2 extends React.Component {
constructor() {
super();
this.state = {
tools: []
};
}

componentDidMount() {
this.daemon = this.props.daemon;

this.daemon.agentV2Found.subscribe(daemonV2 => {
if (!daemonV2) {
return;
}
this.daemonV2 = daemonV2;
this.daemonV2.installedTools().then(res => {
this.setState({
tools: res
});
});
});
}

render() {
const tools = this.state.tools.map((tool, i) => <tr key={i}>
<td>{tool.packager}</td>
<td>{tool.name}</td>
<td>{tool.version}</td>
</tr>);

return (
<section>
<h2>V2</h2>
<section>
<h3>Installed tools</h3>
<form onSubmit={this.handleInstallTool}>
<table>
<thead>
<tr>
<th>Packager</th>
<th>Name</th>
<th>Version</th>
</tr>
</thead>
<tbody>{tools}</tbody>
</table>
</form>

<V2InstallTool daemon={this.props.daemon}></V2InstallTool>
</section>
</section >
);
}
}

export default V2;
446 changes: 365 additions & 81 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "arduino-create-agent-js-client",
"version": "2.1.11",
"version": "2.2.0",
"description": "JS module providing discovery of the Arduino Create Plugin and communication with it",
"main": "lib/index.js",
"module": "es/index.js",
@@ -31,18 +31,18 @@
"babel-preset-react": "^6.24.1",
"clean-webpack-plugin": "^1.0.1",
"cross-env": "^5.2.0",
"eslint": "^5.14.1",
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.16.0",
"html-webpack-plugin": "^3.2.0",
"react": "^16.8.2",
"react-dom": "^16.8.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"rimraf": "^2.6.3",
"rollup": "^0.68.2",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-replace": "^2.1.1",
"rollup-plugin-uglify-es": "^0.0.1",
"rollup-watch": "^4.3.1",
"webpack": "^3.11.0",
20 changes: 18 additions & 2 deletions src/socket-daemon.js
Original file line number Diff line number Diff line change
@@ -22,10 +22,11 @@ import io from 'socket.io-client';
import semVerCompare from 'semver-compare';
import { detect } from 'detect-browser';

import { timer } from 'rxjs';
import { timer, BehaviorSubject } from 'rxjs';
import { filter, takeUntil, first } from 'rxjs/operators';

import Daemon from './daemon';
import V2 from './socket-daemon.v2';

// Required agent version
const browser = detect();
@@ -62,10 +63,17 @@ export default class SocketDaemon extends Daemon {

this.openChannel(() => this.socket.emit('command', 'list'));

this.agentV2Found = new BehaviorSubject(null);

this.agentFound
.subscribe(agentFound => {
if (agentFound) {
this._wsConnect();
const v2 = new V2(this.pluginURL);
v2.init().then(() => {
this.v2 = v2;
this.agentV2Found.next(this.v2);
});
}
else {
this.findAgent();
@@ -458,7 +466,15 @@ export default class SocketDaemon extends Daemon {
_upload(uploadPayload, uploadCommandInfo) {
if (Array.isArray(uploadCommandInfo.tools)) {
uploadCommandInfo.tools.forEach(tool => {
this.downloadTool(tool.name, tool.version, tool.packager);
if (this.v2) {
this.downloading.next({ status: this.DOWNLOAD_IN_PROGRESS });
this.v2.installTool(tool).then(() => {
this.downloading.next({ status: this.DOWNLOAD_DONE });
});
}
else {
this.downloadTool(tool.name, tool.version, tool.packager);
}
});
}

52 changes: 52 additions & 0 deletions src/socket-daemon.v2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export default class SocketDaemonV2 {
constructor(daemonURL) {
this.daemonURL = `${daemonURL}/v2`;
}

// init tries an HEAD
init() {
return fetch(`${this.daemonURL}/pkgs/tools/installed`, {
method: 'HEAD',
}).then(res => {
if (res.status !== 200) {
throw Error('v2 not available');
}
return res;
});
}

// installedTools uses the new v2 apis to ask the daemon a list of the tools already present in the system
installedTools() {
return fetch(`${this.daemonURL}/pkgs/tools/installed`, {
method: 'GET',
}).then(res => res.json());
}

// installTool uses the new v2 apis to ask the daemon to download a specific tool on the system
// The expected payload is
// {
// "name": "avrdude",
// "version": "6.3.0-arduino9",
// "packager": "arduino",
// "url": "https://downloads.arduino.cc/...", // system-specific package containing the tool
// "signature": "e7Gh8309...", // proof that the url comes from a trusted source
// "checksum": "SHA256:90384nhfoso8..." // proof that the package wasn't tampered with
// }
installTool(payload) {
return fetch(`${this.daemonURL}/pkgs/tools/installed`, {
method: 'PUT',
body: JSON.stringify(payload)
}).then(res => res.json()
.then((json) => {
if (!res.ok) {
const error = Object.assign({}, json, {
status: res.status,
statusText: res.statusText,
});

return Promise.reject(error);
}
return json;
}));
}
}