Skip to content

Commit

Permalink
fix: Improve error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
schw4rzlicht committed Jun 15, 2020
1 parent aaa6466 commit 58c40cf
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 36 deletions.
20 changes: 0 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,6 @@ twitch2ma [configFile]

`configFile` is optional and defaults to `config.json`.

## Known issues

### `Error: addMembership EADDRNOTAVAIL`

The console output looks something like this:

```
Error: addMembership EADDRNOTAVAIL
at Socket.addMembership (dgram.js:817:11)
at Socket.<anonymous> (/usr/local/lib/node_modules/twitch2ma/node_modules/sacn/dist/index.js:41:29)
at Socket.onListening (dgram.js:224:10)
at Socket.emit (events.js:315:20)
at startListening (dgram.js:149:10)
at dgram.js:344:7
at processTicksAndRejections (internal/process/task_queues.js:85:21)
```

This happens when you set up an interface using the `sacn.interface` config option that does not exist on your machine.
Simply use an existing one or omit the option altogether.

## Changelog

Find the changelog [here](docs/CHANGELOG.md) .
Expand Down
2 changes: 1 addition & 1 deletion __tests__/PermissionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ test("sACN lock", async () => {
}
});

expect(sacnReceiver.on).toBeCalledTimes(2);
expect(sacnReceiver.on).toBeCalledTimes(3);

sacnReceiver.on.mock.calls[0][1]({
slotsData: Buffer.from(new Uint8Array(512)),
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"humanize-duration": "^3.23.0",
"libnpm": "^3.0.1",
"lodash": "^4.17.15",
"sacn": "^2.3.1",
"sacn": "^2.3.2",
"semver": "^7.3.2",
"source-map-support": "^0.5.19",
"telnet-client": "^1.4.2",
Expand Down
22 changes: 16 additions & 6 deletions src/lib/Twitch2Ma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {RuntimeInformation} from "./RuntimeInformation";
import {PermissionCollector, PermissionController, PermissionError} from "./PermissionController";

import SACNPermission, {
SACNCorrupt,
SACNError,
SACNLost,
SACNReceiving,
SACNStatus,
Expand Down Expand Up @@ -67,7 +67,8 @@ export default class Twitch2Ma extends EventEmitter {

this.permissionController = new PermissionController()
.withPermissionInstance(new SACNPermission(config)
.withOnStatusHandler(status => this.handleSACNStatus(status)))
.withOnStatusHandler(status => this.handleSACNStatus(status))
.withOnErrorHandler(error => this.handleSACNError(error)))
.withPermissionInstance(new CooldownPermission())
.withPermissionInstance(new OwnerPermission())
.withPermissionInstance(new ModeratorPermission());
Expand Down Expand Up @@ -251,17 +252,26 @@ export default class Twitch2Ma extends EventEmitter {
case SACNStopped:
this.emit(this.onNotice, "sACN status: Stopped listening.");
break;
case SACNCorrupt:
this.emit(this.onNotice, "sACN status: Unexpected packet received. Are you using the \"final\" " +
"protocol version in your MA sACN settings?");
break;
default:
this.emit(this.onNotice, `sACN status: Received unknown status: ${typeof status}`);
// TODO sentry
break;
}
}

private handleSACNError(error: Error) {
switch (error.constructor) {
case SACNError:
this.emit(this.onNotice, error.message);
break;
default:
// @ts-ignore
this.emit(this.onNotice, "sACN error: unknown error" + (error.code ? ` (${error.code})` : ""));
// TODO sentry
break;
}
}

protected emit<Args extends any[]>(event: EventBinder<Args>, ...args: Args) {
try {
super.emit(event, ...args);
Expand Down
63 changes: 55 additions & 8 deletions src/lib/permissions/SACNPermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Packet, Receiver} from "sacn";
import {EventEmitter} from "@d-fischer/typed-event-emitter";
import {EventBinder} from "@d-fischer/typed-event-emitter/lib/EventEmitter";
import {SACNUniverse, UniverseStatus} from "./SACNUniverse";

import _ = require("lodash");

export default class SACNPermission extends EventEmitter implements PermissionInstance {
Expand All @@ -13,12 +14,14 @@ export default class SACNPermission extends EventEmitter implements PermissionIn
private readonly config: Config;
private sACNReceiver: Receiver;
private watchdogTimeout: NodeJS.Timeout;
private running: boolean;

constructor(config: Config) {
super();

this.universes = [];
this.config = config;
this.running = false;

for (const command of this.config.commands) {
if (command.sacn) {
Expand Down Expand Up @@ -52,7 +55,7 @@ export default class SACNPermission extends EventEmitter implements PermissionIn

start(): void {

if (this.universes.length > 0) {
if (this.universes.length > 0 && !this.running) {

let universes = [];
for (const universe of this.universes) {
Expand All @@ -72,7 +75,6 @@ export default class SACNPermission extends EventEmitter implements PermissionIn
_.set(receiverOptions, "iface", this.config.sacn.interface);
}

// FIXME Throws when interface does not exist, see https://github.com/k-yle/sACN/issues/21
this.sACNReceiver = new Receiver(receiverOptions);

this.sACNReceiver.on("packet", (packet: Packet) => {
Expand All @@ -90,28 +92,53 @@ export default class SACNPermission extends EventEmitter implements PermissionIn
});

this.sACNReceiver.on("PacketCorruption", () => {
this.emit(this.onStatus, new SACNCorrupt());
this.emit(this.onError, new SACNCorruptError());
this.stop();
})
});

this.sACNReceiver.on("error", (error: Error) => {
// @ts-ignore
if(error.code) {
// @ts-ignore
switch (error.code) {
case "EADDRNOTAVAIL":
case "ENODEV":
this.emit(this.onError, new SACNError(`sACN error: Have you configured the right interface IP?`));
break;
case "ERR_SOCKET_DGRAM_NOT_RUNNING":
this.emit(this.onError, new SACNError("sACN error: UDP subsystem not running!"));
break;
default:
this.emit(this.onError, error);
break;
}
} else {
this.emit(this.onError, error);
}
this.stop();
});

this.emit(this.onStatus, new SACNWaiting(universes));

this.watchdogTimeout = setInterval(() => this.watchdog(), this.config.sacn.timeout);
this.watchdogTimeout.unref();

this.running = true;
}
}
}

stop(): void {
if (this.sACNReceiver) {
if (this.sACNReceiver && this.running) {
try {
this.sACNReceiver.close();
} catch (ignored) {
// TODO sentry
}
this.running = false;
this.emit(this.onStatus, new SACNStopped());
}
_.attempt(() => clearInterval(this.watchdogTimeout));
this.emit(this.onStatus, new SACNStopped());
}

private watchdog() {
Expand Down Expand Up @@ -154,6 +181,11 @@ export default class SACNPermission extends EventEmitter implements PermissionIn
return this;
}

withOnErrorHandler(handler: (error: Error) => any) {
this.onError(handler);
return this;
}

protected emit<Args extends any[]>(event: EventBinder<Args>, ...args: Args) {
try {
super.emit(event, ...args);
Expand All @@ -163,6 +195,7 @@ export default class SACNPermission extends EventEmitter implements PermissionIn
}

onStatus = this.registerEvent<(status: SACNStatus) => any>();
onError = this.registerEvent<(error: SACNError) => any>();
}

export class SACNStatus {
Expand All @@ -189,8 +222,22 @@ export class SACNStopped extends SACNStatus {
}
}

export class SACNCorrupt extends SACNStatus {
export class SACNError extends Error {

constructor(message?: string) {
super(message);

Object.setPrototypeOf(this, SACNError.prototype);
this.name = SACNError.name;
}
}

export class SACNCorruptError extends SACNError {
constructor() {
super(null);
super("sACN error: Unexpected packet received. Are you using the \"final\" " +
"protocol version in your MA sACN settings?");

Object.setPrototypeOf(this, SACNCorruptError.prototype);
this.name = SACNCorruptError.name;
}
}

0 comments on commit 58c40cf

Please sign in to comment.