Skip to content

Commit

Permalink
Merge pull request #56 from AlCalzone/connection-errors
Browse files Browse the repository at this point in the history
 Differentiate between the various possible connection and auth errors
  • Loading branch information
AlCalzone authored Mar 15, 2018
2 parents c1a3f0a + 009a839 commit 511fa9e
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 37 deletions.
43 changes: 34 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ import { TradfriClient, Accessory, AccessoryTypes } from "node-tradfri-client";

// connect
const tradfri = new TradfriClient("gw-abcdef012345");
await tradfri.connect(identity, psk);
try {
await tradfri.connect(identity, psk);
} catch (e) {
// handle error - see below for details
}
```

### Make a lightbulb blink
Expand Down Expand Up @@ -194,16 +198,20 @@ try {
```
The returned `identity` and `psk` **have to be stored** for future connections to the gateway. To comply with IKEA's requests, the security code **must not be stored** permanently in your application.

The call throws an error if it wasn't successful which you should handle. The error `e` should be of type `TradfriError` and gives further information why the authentication failed. To check that, add `TradfriError` and `TradfriErrorCodes` to the list of imports and check as follows:
If the authentication was not successful, this method throws (or rather rejects with) an error which you should handle. The error `e` should be of type `TradfriError` and gives further information why the authentication failed. To check that, add `TradfriError` and `TradfriErrorCodes` to the list of imports and check as follows:
```TS
if (e instanceof TradfriError) {
switch (e.code) {
case TradfriErrorCodes.ConnectionFailed: {
// Gateway unreachable or security code wrong
case TradfriErrorCodes.ConnectionTimedOut: {
// The gateway is unreachable or did not respond in time
}
case TradfriErrorCodes.AuthenticationFailed: {
// Something went wrong with the authentication.
// It might be that this library has to be updated to be compatible with a new firmware.
// The security code is wrong or something else went wrong with the authentication.
// Check the error message for details. It might be that this library has to be updated
// to be compatible with a new firmware.
}
case TradfriErrorCodes.ConnectionFailed: {
// An unknown error happened while trying to connect
}
}
}
Expand All @@ -212,9 +220,24 @@ if (e instanceof TradfriError) {
### Connecting to the gateway
When you have a valid identity and psk, you can connect to the gateway using the `connect` method:
```TS
const success = await tradfri.connect(identity, psk);
try {
await tradfri.connect(identity, psk);
} catch (e: TradfriError) {
// handle error
switch (e.code) {
case TradfriErrorCodes.ConnectionTimedOut: {
// The gateway is unreachable or did not respond in time
}
case TradfriErrorCodes.AuthenticationFailed: {
// The provided credentials are not valid. You need to re-authenticate using `authenticate()`.
}
case TradfriErrorCodes.ConnectionFailed: {
// An unknown error happened while trying to connect
}
}
}
```
If the connection was unsuccessful, either the gateway was unreachable or the identity/psk pair isn't valid.
**NOTE:** As of v0.6.0, this no longer resolves with `false` if the connection was unsuccessful. Instead, it throws (or rejects with) a `TradfriError` which contains details about why the connection failed.

### Pinging the gateway
```TS
Expand Down Expand Up @@ -507,7 +530,9 @@ A DeviceInfo object contains general information about a device. It has the foll

## Changelog

#### __WORK IN PROGRESS__
#### __WORK IN PROGRESS__ - WARNING: BREAKING CHANGES!
* (AlCalzone) **BREAKING**: The `connect()` method now either resolves with `true` or rejects with an error detailing why the connection failed.
* (AlCalzone) The error thrown by `authentication()` now correctly reflects why the authentication failed.
* (AlCalzone) Swallow `"DTLS handshake timed out"` promise rejections and emit an `"error"` instead

#### 0.10.1 (2018-03-15)
Expand Down
2 changes: 1 addition & 1 deletion build/tradfri-client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export declare class TradfriClient extends EventEmitter implements OperationProv
* @param identity A previously negotiated identity.
* @param psk The pre-shared key belonging to the identity.
*/
connect(identity: string, psk: string): Promise<boolean>;
connect(identity: string, psk: string): Promise<true>;
/**
* Try to establish a connection to the configured gateway.
* @param identity The DTLS identity to use
Expand Down
24 changes: 19 additions & 5 deletions build/tradfri-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,14 @@ class TradfriClient extends events_1.EventEmitter {
* @param psk The pre-shared key belonging to the identity.
*/
connect(identity, psk) {
return this.tryToConnect(identity, psk);
return __awaiter(this, void 0, void 0, function* () {
switch (yield this.tryToConnect(identity, psk)) {
case true: return true;
case "auth failed": throw new tradfri_error_1.TradfriError("The provided credentials are not valid. Please re-authenticate!", tradfri_error_1.TradfriErrorCodes.AuthenticationFailed);
case "timeout": throw new tradfri_error_1.TradfriError("The gateway did not respond in time.", tradfri_error_1.TradfriErrorCodes.ConnectionTimedOut);
case "error": throw new tradfri_error_1.TradfriError("An unknown error occured while connecting to the gateway", tradfri_error_1.TradfriErrorCodes.ConnectionFailed);
}
});
}
/**
* Try to establish a connection to the configured gateway.
Expand All @@ -71,7 +78,12 @@ class TradfriClient extends events_1.EventEmitter {
});
logger_1.log(`Attempting connection. Identity = ${identity}, psk = ${psk}`, "debug");
const result = yield node_coap_client_1.CoapClient.tryToConnect(this.requestBase);
logger_1.log(`Connection ${result ? "" : "un"}successful`, "debug");
if (result === true) {
logger_1.log("Connection successful", "debug");
}
else {
logger_1.log("Connection failed. Reason: " + result, "debug");
}
return result;
});
}
Expand All @@ -85,9 +97,11 @@ class TradfriClient extends events_1.EventEmitter {
return __awaiter(this, void 0, void 0, function* () {
// first, check try to connect with the security code
logger_1.log("authenticate() > trying to connect with the security code", "debug");
if (!(yield this.tryToConnect("Client_identity", securityCode))) {
// that didn't work, so the code is wrong
throw new tradfri_error_1.TradfriError("The security code is wrong", tradfri_error_1.TradfriErrorCodes.ConnectionFailed);
switch (yield this.tryToConnect("Client_identity", securityCode)) {
case true: break; // all good
case "auth failed": throw new tradfri_error_1.TradfriError("The security code is wrong", tradfri_error_1.TradfriErrorCodes.AuthenticationFailed);
case "timeout": throw new tradfri_error_1.TradfriError("The gateway did not respond in time.", tradfri_error_1.TradfriErrorCodes.ConnectionTimedOut);
case "error": throw new tradfri_error_1.TradfriError("An unknown error occured while connecting to the gateway", tradfri_error_1.TradfriErrorCodes.ConnectionFailed);
}
// generate a new identity
const identity = `tradfri_${Date.now()}`;
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-tradfri-client",
"version": "0.10.1",
"version": "0.11.0",
"description": "Library to talk to IKEA Trådfri Gateways without external binaries",
"keywords": [
"coap",
Expand Down Expand Up @@ -63,7 +63,7 @@
"dependencies": {
"bonjour": "^3.5.0",
"debug": "^3.1.0",
"node-coap-client": "^0.5.5",
"node-coap-client": "^0.6.0",
"reflect-metadata": "^0.1.12"
},
"scripts": {
Expand Down Expand Up @@ -104,4 +104,4 @@
"instrument": true
},
"readme": "README.md"
}
}
66 changes: 55 additions & 11 deletions src/tradfri-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,10 @@ describe("tradfri-client => infrastructure => ", () => {
const identity = "IDENTITY";
const psk = "PSK";

it("should reset the CoAP client, provide new security params and return the result from tryToConnect", async () => {
// test if both possible responses are passed through
it("should reset the CoAP client, provide new security params and resolve with true on success", async () => {
fakeCoap.tryToConnect.returns(Promise.resolve(true));
await tradfri.connect(identity, psk).should.become(true);

fakeCoap.tryToConnect.returns(Promise.resolve(false));
await tradfri.connect(identity, psk).should.become(false);

fakeCoap.reset.should.have.been.called;
fakeCoap.setSecurityParams.should.have.been.called;
fakeCoap.setSecurityParams.getCall(0).args[1].should.deep.equal({
Expand All @@ -76,6 +72,33 @@ describe("tradfri-client => infrastructure => ", () => {

fakeCoap.tryToConnect.resetBehavior();
});

it("should reject with a `TradfriError` with code ConnectionTimedOut when the connection times out", async () => {
fakeCoap.tryToConnect.returns(Promise.resolve("timeout"));
await tradfri.connect(identity, psk).should.be.rejected.then(err => {
expect(err).to.be.an.instanceof(TradfriError);
expect(err.code).to.equal(TradfriErrorCodes.ConnectionTimedOut);
});
fakeCoap.tryToConnect.resetBehavior();
});

it("should reject with a `TradfriError` with code AuthenticationFailed when the credentials are wrong", async () => {
fakeCoap.tryToConnect.returns(Promise.resolve("auth failed"));
await tradfri.connect(identity, psk).should.be.rejected.then(err => {
expect(err).to.be.an.instanceof(TradfriError);
expect(err.code).to.equal(TradfriErrorCodes.AuthenticationFailed);
});
fakeCoap.tryToConnect.resetBehavior();
});

it("should reject with a `TradfriError` with code ConnectionFailed when some other error happens", async () => {
fakeCoap.tryToConnect.returns(Promise.resolve("error"));
await tradfri.connect(identity, psk).should.be.rejected.then(err => {
expect(err).to.be.an.instanceof(TradfriError);
expect(err.code).to.equal(TradfriErrorCodes.ConnectionFailed);
});
fakeCoap.tryToConnect.resetBehavior();
});
});

describe("authenticate => ", () => {
Expand All @@ -90,11 +113,6 @@ describe("tradfri-client => infrastructure => ", () => {
fakeCoap.request.resetBehavior();
});

it(`detects failure to connect with "Client_identity" as a wrong security code`, async () => {
fakeCoap.tryToConnect.returns(Promise.resolve(false));
await tradfri.authenticate(null).should.be.rejectedWith("security code");
});

it(`should call coap.request with the correct endpoint and payload and return the identity and psk`, async () => {
fakeCoap.tryToConnect.returns(Promise.resolve(true));
fakeCoap.request.returns(Promise.resolve(authResponse));
Expand All @@ -114,6 +132,33 @@ describe("tradfri-client => infrastructure => ", () => {
);
});

it("should reject with a `TradfriError` with code ConnectionTimedOut when the authentication times out", async () => {
fakeCoap.tryToConnect.returns(Promise.resolve("timeout"));
await tradfri.authenticate(dummyIdentity).should.be.rejected.then(err => {
expect(err).to.be.an.instanceof(TradfriError);
expect(err.code).to.equal(TradfriErrorCodes.ConnectionTimedOut);
});
fakeCoap.tryToConnect.resetBehavior();
});

it("should reject with a `TradfriError` with code AuthenticationFailed when the security code was wrong", async () => {
fakeCoap.tryToConnect.returns(Promise.resolve("auth failed"));
await tradfri.authenticate(dummyIdentity).should.be.rejected.then(err => {
expect(err).to.be.an.instanceof(TradfriError);
expect(err.code).to.equal(TradfriErrorCodes.AuthenticationFailed);
});
fakeCoap.tryToConnect.resetBehavior();
});

it("should reject with a `TradfriError` with code ConnectionFailed when some other error happens", async () => {
fakeCoap.tryToConnect.returns(Promise.resolve("error"));
await tradfri.authenticate(dummyIdentity).should.be.rejected.then(err => {
expect(err).to.be.an.instanceof(TradfriError);
expect(err.code).to.equal(TradfriErrorCodes.ConnectionFailed);
});
fakeCoap.tryToConnect.resetBehavior();
});

it(`if coap.request returns an error, throw AuthenticationFailed`, async () => {
fakeCoap.tryToConnect.returns(Promise.resolve(true));
fakeCoap.request.returns(Promise.resolve(failedAuthResponse));
Expand All @@ -122,7 +167,6 @@ describe("tradfri-client => infrastructure => ", () => {
expect((err as TradfriError).code).to.equal(TradfriErrorCodes.AuthenticationFailed);
});
});

});

describe("ping =>", () => {
Expand Down
47 changes: 39 additions & 8 deletions src/tradfri-client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// load external modules
import { EventEmitter } from "events";
import { CoapClient as coap, CoapResponse, RequestMethod } from "node-coap-client";
import { CoapClient as coap, CoapResponse, ConnectionResult, RequestMethod } from "node-coap-client";

// load internal modules
import { Accessory, AccessoryTypes } from "./lib/accessory";
Expand Down Expand Up @@ -105,8 +105,22 @@ export class TradfriClient extends EventEmitter implements OperationProvider {
* @param identity A previously negotiated identity.
* @param psk The pre-shared key belonging to the identity.
*/
public connect(identity: string, psk: string): Promise<boolean> {
return this.tryToConnect(identity, psk);
public async connect(identity: string, psk: string): Promise<true> {
switch (await this.tryToConnect(identity, psk)) {
case true: return true;
case "auth failed": throw new TradfriError(
"The provided credentials are not valid. Please re-authenticate!",
TradfriErrorCodes.AuthenticationFailed,
);
case "timeout": throw new TradfriError(
"The gateway did not respond in time.",
TradfriErrorCodes.ConnectionTimedOut,
);
case "error": throw new TradfriError(
"An unknown error occured while connecting to the gateway",
TradfriErrorCodes.ConnectionFailed,
);
}
}

/**
Expand All @@ -115,7 +129,7 @@ export class TradfriClient extends EventEmitter implements OperationProvider {
* @param psk The pre-shared key to use
* @returns true if the connection attempt was successful, otherwise false.
*/
private async tryToConnect(identity: string, psk: string): Promise<boolean> {
private async tryToConnect(identity: string, psk: string): Promise<ConnectionResult> {

// initialize CoAP client
coap.reset();
Expand All @@ -125,7 +139,11 @@ export class TradfriClient extends EventEmitter implements OperationProvider {

log(`Attempting connection. Identity = ${identity}, psk = ${psk}`, "debug");
const result = await coap.tryToConnect(this.requestBase);
log(`Connection ${result ? "" : "un"}successful`, "debug");
if (result === true) {
log("Connection successful", "debug");
} else {
log("Connection failed. Reason: " + result, "debug");
}
return result;
}

Expand All @@ -138,10 +156,23 @@ export class TradfriClient extends EventEmitter implements OperationProvider {
public async authenticate(securityCode: string): Promise<{identity: string, psk: string}> {
// first, check try to connect with the security code
log("authenticate() > trying to connect with the security code", "debug");
if (!await this.tryToConnect("Client_identity", securityCode)) {
// that didn't work, so the code is wrong
throw new TradfriError("The security code is wrong", TradfriErrorCodes.ConnectionFailed);
switch (await this.tryToConnect("Client_identity", securityCode)) {
case true: break; // all good

case "auth failed": throw new TradfriError(
"The security code is wrong",
TradfriErrorCodes.AuthenticationFailed,
);
case "timeout": throw new TradfriError(
"The gateway did not respond in time.",
TradfriErrorCodes.ConnectionTimedOut,
);
case "error": throw new TradfriError(
"An unknown error occured while connecting to the gateway",
TradfriErrorCodes.ConnectionFailed,
);
}

// generate a new identity
const identity = `tradfri_${Date.now()}`;
log(`authenticating with identity "${identity}"`, "debug");
Expand Down

0 comments on commit 511fa9e

Please sign in to comment.