Skip to content

Commit bee9fdb

Browse files
committed
Add support for Flow types
1 parent 7422113 commit bee9fdb

11 files changed

+338
-401
lines changed

.flowconfig

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[ignore]
2+
3+
[include]
4+
5+
[libs]
6+
7+
[options]

CONTRIBUTING.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
This is how the Dash Button code is organized, so you can understand where to make changes and how to test your code.
44

5+
## Source Code
6+
7+
The source files are under `src` and are written using modern JavaScript and [Flow](https://flowtype.org/).
8+
59
## Building
610

7-
The source files are under `src` and are written using modern JavaScript. They are compiled with [Babel](https://babeljs.io/) to a version of JavaScript that Node.js understands. These compiled files go in a directory called `build`, which is not committed to Git but is published to npm.
11+
The source files are compiled with [Babel](https://babeljs.io/) to a version of JavaScript that Node.js understands. These compiled files go in a directory called `build`, which is not committed to Git but is published to npm.
812

913
We use [Gulp 4](https://github.com/gulpjs/gulp/tree/4.0) to run Babel. The easiest way to run Gulp is to run `npm run build` or `npm run watch`. Both of these compile the JavaScript in `src` and output it in `build`, but the `watch` command will keep watching your filesystem for any changes and compile files when you save them. It's recommended when you are developing.
1014

LICENSE

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015 James Ide
3+
Copyright (c) 2015-present James Ide
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
SOFTWARE.
22-

circle.yml

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies:
1616
test:
1717
pre:
1818
- yarn run lint
19+
- yarn run flow -- check
1920
override:
2021
- yarn test -- --coverage
2122
post:

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
},
1515
"scripts": {
1616
"build": "gulp build",
17+
"flow": "flow",
1718
"lint": "eslint src",
1819
"prepublish": "gulp clean && gulp build",
1920
"start": "node build/cli.js",
@@ -48,13 +49,14 @@
4849
"dependencies": {
4950
"babel-runtime": "^6.20.0",
5051
"lodash": "^4.17.0",
52+
"nullthrows": "^1.0.0",
5153
"pcap": "mranney/node_pcap#d920204",
5254
"yargs": "^6.5.0"
5355
},
5456
"devDependencies": {
5557
"babel-core": "^6.20.0",
5658
"babel-eslint": "^7.1.1",
57-
"babel-jest": "^17.0.0",
59+
"babel-jest": "^18.0.0",
5860
"babel-plugin-add-module-exports": "^0.2.1",
5961
"babel-plugin-syntax-trailing-function-commas": "^6.20.0",
6062
"babel-plugin-transform-async-to-generator": "^6.4.6",
@@ -67,6 +69,7 @@
6769
"eslint-plugin-babel": "^4.0.0",
6870
"eslint-plugin-flowtype": "^2.26.1",
6971
"eslint-plugin-import": "^2.2.0",
72+
"flow-bin": "^0.37.4",
7073
"gulp": "gulpjs/gulp#4.0",
7174
"gulp-babel": "^6.1.1",
7275
"gulp-changed": "^1.3.2",

src/DashButton.js

+34-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
// @flow
12
import assert from 'assert';
3+
import nullthrows from 'nullthrows';
24
import pcap from 'pcap';
35

46
import MacAddresses from './MacAddresses';
57
import NetworkInterfaces from './NetworkInterfaces';
68
import Packets from './Packets';
79

8-
type Options = {
10+
export type DashButtonOptions = {
911
networkInterface?: string,
1012
};
1113

14+
export type DashButtonListener = (packet: Object) => void | Promise<void>;
15+
16+
type GuardedListener = (packet: Object) => Promise<?Error>;
17+
1218
let pcapSession;
1319

1420
function getPcapSession(interfaceName: string) {
@@ -24,16 +30,22 @@ function getPcapSession(interfaceName: string) {
2430
}
2531

2632
export default class DashButton {
27-
constructor(macAddress: string, options?: Options = {}) {
33+
_macAddress: string;
34+
_networkInterface: string;
35+
_packetListener: Function;
36+
_dashListeners: Set<GuardedListener>;
37+
_isResponding: boolean;
38+
39+
constructor(macAddress: string, options: DashButtonOptions = {}) {
2840
this._macAddress = macAddress;
2941
this._networkInterface = options.networkInterface ||
30-
NetworkInterfaces.getDefault();
42+
nullthrows(NetworkInterfaces.getDefault());
3143
this._packetListener = this._handlePacket.bind(this);
3244
this._dashListeners = new Set();
3345
this._isResponding = false;
3446
}
3547

36-
addListener(listener): Subscription {
48+
addListener(listener: DashButtonListener): Subscription {
3749
if (!this._dashListeners.size) {
3850
let session = getPcapSession(this._networkInterface);
3951
session.addListener('packet', this._packetListener);
@@ -57,8 +69,10 @@ export default class DashButton {
5769
});
5870
}
5971

60-
_createGuardedListener(listener) {
61-
return async(...args) => {
72+
_createGuardedListener(
73+
listener: (...args: *[]) => void | Promise<void>,
74+
): GuardedListener {
75+
return async (...args: *[]): Promise<?Error> => {
6276
try {
6377
await listener(...args);
6478
} catch (error) {
@@ -67,7 +81,7 @@ export default class DashButton {
6781
};
6882
}
6983

70-
async _handlePacket(rawPacket) {
84+
async _handlePacket(rawPacket: Object): Promise<void> {
7185
if (this._isResponding) {
7286
return;
7387
}
@@ -83,19 +97,29 @@ export default class DashButton {
8397
// The listeners are guarded so this should never throw, but wrap it in
8498
// try-catch to be defensive
8599
let listeners = Array.from(this._dashListeners);
86-
await Promise.all(listeners.map(listener => listener(packet)));
100+
let errors = await Promise.all(
101+
listeners.map(listener => listener(packet)),
102+
);
103+
for (let error of errors) {
104+
if (error) {
105+
// TODO: Figure out how to mock `console` with Jest
106+
// console.error(`Listener threw an uncaught error:\n${error.stack}`);
107+
}
108+
}
87109
} finally {
88110
this._isResponding = false;
89111
}
90112
}
91113
}
92114

93115
class Subscription {
94-
constructor(onRemove) {
116+
_remove: () => void;
117+
118+
constructor(onRemove: () => void) {
95119
this._remove = onRemove;
96120
}
97121

98-
remove() {
122+
remove(): void {
99123
if (!this._remove) {
100124
return;
101125
}

src/MacAddresses.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
import padStart from 'lodash/padStart';
33

44
const MacAddresses = {
5-
getEthernetSource(packet): string {
5+
getEthernetSource(packet: *): string {
66
return MacAddresses.decimalToHex(packet.payload.shost.addr);
77
},
88

99
decimalToHex(numbers: Number[]): string {
1010
let hexStrings = numbers.map(decimal =>
11+
// TODO: Use String.prototype.padStart when Node natively supports it, and
12+
// increment the major semver version
1113
padStart(decimal.toString(16), 2, '0'),
1214
);
1315
return hexStrings.join(':');

src/NetworkInterfaces.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
// @flow
12
import os from 'os';
23

34
export default {
4-
getDefault() {
5+
getDefault(): ?string {
56
let interfaces = os.networkInterfaces();
67
let names = Object.keys(interfaces);
78
for (let name of names) {

src/Packets.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22
import pcap from 'pcap';
33

4-
// Dash Buttons send DHCPREQUEST messages (new) and ARP probes (old)
4+
// Dash buttons send DHCPREQUEST messages (new) and ARP probes (old)
55
const PACKET_FILTER = '(arp or (udp and src port 68 and dst port 67 and udp[247:4] == 0x63350103)) and src host 0.0.0.0';
66

77
export default {

src/__tests__/DashButton-test.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import assert from 'assert';
2-
import events from 'events';
32

43
jest.mock('pcap');
54
jest.mock('../NetworkInterfaces');

0 commit comments

Comments
 (0)