Skip to content

Commit cbdb77f

Browse files
authored
Merge pull request #49 from AlCalzone/swallow-coap-reset
Swallow coap reset rejections and emit an error instead
2 parents a6500d3 + 70c7407 commit cbdb77f

8 files changed

Lines changed: 97 additions & 14 deletions

File tree

README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,9 @@ tradfri.reset();
230230
```
231231
After a connection loss or reboot of another endpoint, the currently active connection params might no longer be valid. In this case, use the reset method to invalidate the stored connection params, so the next request will use a fresh connection.
232232

233-
This causes all requests to be dropped and clears all observations.
233+
This causes all requests to be dropped and clears all observations.
234+
235+
**Note:** Promises belonging to any pending connections, requests or observers will not be fulfilled anymore and you should delete all references to them. In that case, the `"error"` event will be emitted (once or multiple times) with an error with code `TradfriClient.NetworkReset`.
234236

235237
### Closing the connection
236238
```TS
@@ -272,11 +274,22 @@ type SceneUpdatedCallback = (groupId: number, scene: Scene) => void;
272274
type SceneRemovedCallback = (groupId: number, instanceId: number) => void;
273275
```
274276

275-
#### `"error"` - An error occured
277+
### Handle errors
278+
The `"error"` event gets emitted when something unexpected happens. The callback has the following form.
276279
```TS
277280
type ErrorCallback = (e: Error) => void;
278281
```
279-
This doesn't have to be fatal and can be called when an unexpected response code is received.
282+
This doesn't have to be fatal, so you should check which kind of error happened.
283+
Some errors are of the type `TradfriError` and contain a code which provides more information about the nature of the error. To check that, add `TradfriError` and `TradfriErrorCodes` to the list of imports and check as follows:
284+
```TS
285+
if (e instanceof TradfriError) {
286+
// handle the error depending on `e.code`
287+
} else {
288+
// handle the error as you normally would.
289+
}
290+
```
291+
The currently supported error codes are:
292+
* `TradfriErrorCode.NetworkReset`: The `reset()` method was called while some requests or connection attempts were still pending. Those promises will not be fulfilled anymore, and you should delete all references to them.
280293

281294
### Observe a resource
282295
The standard way to receive updates to a Trådfri (or CoAP) resource is by observing it. The TradfriClient provides a couple of methods to observe resources, with the most generic one being
@@ -489,6 +502,9 @@ A DeviceInfo object contains general information about a device. It has the foll
489502

490503
## Changelog
491504

505+
#### NEXT:
506+
* (AlCalzone) Swallow `"CoapClient was reset"` promise rejections and emit an `"error"` instead
507+
492508
#### 0.9.1 (2018-03-09)
493509
* (AlCalzone) Fix properties which are wrongly reported by the gateway
494510

build/lib/tradfri-error.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export declare enum TradfriErrorCodes {
22
ConnectionFailed = 0,
33
AuthenticationFailed = 1,
4+
NetworkReset = 2,
45
}
56
export declare class TradfriError extends Error {
67
readonly message: string;

build/lib/tradfri-error.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var TradfriErrorCodes;
44
(function (TradfriErrorCodes) {
55
TradfriErrorCodes[TradfriErrorCodes["ConnectionFailed"] = 0] = "ConnectionFailed";
66
TradfriErrorCodes[TradfriErrorCodes["AuthenticationFailed"] = 1] = "AuthenticationFailed";
7+
TradfriErrorCodes[TradfriErrorCodes["NetworkReset"] = 2] = "NetworkReset";
78
})(TradfriErrorCodes = exports.TradfriErrorCodes || (exports.TradfriErrorCodes = {}));
89
class TradfriError extends Error {
910
constructor(message, code) {

build/tradfri-client.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,5 @@ export declare class TradfriClient extends EventEmitter implements OperationProv
187187
code: string;
188188
payload: any;
189189
}>;
190+
private swallowInternalCoapRejections<T>(promise);
190191
}

build/tradfri-client.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class TradfriClient extends events_1.EventEmitter {
9494
// request creation of new PSK
9595
let payload = JSON.stringify({ 9090: identity });
9696
payload = Buffer.from(payload);
97-
const response = yield node_coap_client_1.CoapClient.request(`${this.requestBase}${endpoints_1.endpoints.authentication}`, "post", payload);
97+
const response = yield this.swallowInternalCoapRejections(node_coap_client_1.CoapClient.request(`${this.requestBase}${endpoints_1.endpoints.authentication}`, "post", payload));
9898
// check the response
9999
if (response.code.toString() !== "2.01") {
100100
// that didn't work, so the code is wrong
@@ -121,7 +121,7 @@ class TradfriClient extends events_1.EventEmitter {
121121
return false;
122122
// start observing
123123
this.observedPaths.push(observerUrl);
124-
yield node_coap_client_1.CoapClient.observe(observerUrl, "get", callback);
124+
yield this.swallowInternalCoapRejections(node_coap_client_1.CoapClient.observe(observerUrl, "get", callback));
125125
return true;
126126
});
127127
}
@@ -543,7 +543,7 @@ class TradfriClient extends events_1.EventEmitter {
543543
let payload = JSON.stringify(serializedObj);
544544
logger_1.log(`updateResource(${path}) > sending payload: ${payload}`, "debug");
545545
payload = Buffer.from(payload);
546-
yield node_coap_client_1.CoapClient.request(`${this.requestBase}${path}`, "put", payload);
546+
yield this.swallowInternalCoapRejections(node_coap_client_1.CoapClient.request(`${this.requestBase}${path}`, "put", payload));
547547
return true;
548548
});
549549
}
@@ -589,13 +589,35 @@ class TradfriClient extends events_1.EventEmitter {
589589
jsonPayload = Buffer.from(jsonPayload);
590590
}
591591
// wait for the CoAP response and respond to the message
592-
const resp = yield node_coap_client_1.CoapClient.request(`${this.requestBase}${path}`, method, jsonPayload);
592+
const resp = yield this.swallowInternalCoapRejections(node_coap_client_1.CoapClient.request(`${this.requestBase}${path}`, method, jsonPayload));
593593
return {
594594
code: resp.code.toString(),
595595
payload: parsePayload(resp),
596596
};
597597
});
598598
}
599+
swallowInternalCoapRejections(promise) {
600+
// We use the conventional promise pattern here so we can opt to never
601+
// resolve the promise in case we want to redirect it into an emitted error event
602+
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
603+
try {
604+
// try to resolve the promise normally
605+
resolve(yield promise);
606+
}
607+
catch (e) {
608+
if (/coap\s?client was reset/i.test(e.message)) {
609+
// The CoAP client was reset. This happens when the user
610+
// resets the CoAP client while connections or requests
611+
// are still pending. It's not an error per se, so just
612+
// inform the user about what happened.
613+
this.emit("error", new tradfri_error_1.TradfriError("The network stack was reset. Pending promises will not be fulfilled.", tradfri_error_1.TradfriErrorCodes.NetworkReset));
614+
}
615+
else {
616+
reject(e);
617+
}
618+
}
619+
}));
620+
}
599621
}
600622
exports.TradfriClient = TradfriClient;
601623
/** Normalizes the path to a resource, so it can be used for storing the observer */

src/lib/tradfri-error.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export enum TradfriErrorCodes {
22
ConnectionFailed,
33
AuthenticationFailed,
4+
NetworkReset,
45
}
56

67
export class TradfriError extends Error {

src/tradfri-client.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,5 +626,20 @@ describe("tradfri-client => custom requests => ", () => {
626626
fakeCoap.request.resetBehavior();
627627
}
628628
});
629+
630+
it("when the coap client is reset during a pending request, the rejection should be turned into an error event", (done) => {
631+
fakeCoap.request.returns(Promise.reject(new Error("CoapClient was reset")));
632+
633+
tradfri.on("error", err => {
634+
err.should.be.an.instanceof(TradfriError);
635+
(err as TradfriError).code.should.equal(TradfriErrorCodes.NetworkReset);
636+
637+
tradfri.removeAllListeners();
638+
fakeCoap.request.resetBehavior();
639+
done();
640+
});
641+
642+
tradfri.request(null, null);
643+
});
629644
});
630645
});

src/tradfri-client.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,11 @@ export class TradfriClient extends EventEmitter implements OperationProvider {
148148
// request creation of new PSK
149149
let payload: string | Buffer = JSON.stringify({ 9090: identity });
150150
payload = Buffer.from(payload);
151-
const response = await coap.request(
151+
const response = await this.swallowInternalCoapRejections(coap.request(
152152
`${this.requestBase}${coapEndpoints.authentication}`,
153153
"post",
154154
payload,
155-
);
155+
));
156156

157157
// check the response
158158
if (response.code.toString() !== "2.01") {
@@ -183,7 +183,9 @@ export class TradfriClient extends EventEmitter implements OperationProvider {
183183

184184
// start observing
185185
this.observedPaths.push(observerUrl);
186-
await coap.observe(observerUrl, "get", callback);
186+
await this.swallowInternalCoapRejections(
187+
coap.observe(observerUrl, "get", callback),
188+
);
187189
return true;
188190
}
189191

@@ -687,9 +689,9 @@ export class TradfriClient extends EventEmitter implements OperationProvider {
687689
log(`updateResource(${path}) > sending payload: ${payload}`, "debug");
688690
payload = Buffer.from(payload);
689691

690-
await coap.request(
692+
await this.swallowInternalCoapRejections(coap.request(
691693
`${this.requestBase}${path}`, "put", payload,
692-
);
694+
));
693695
return true;
694696
}
695697

@@ -755,16 +757,40 @@ export class TradfriClient extends EventEmitter implements OperationProvider {
755757
}
756758

757759
// wait for the CoAP response and respond to the message
758-
const resp = await coap.request(
760+
const resp = await this.swallowInternalCoapRejections(coap.request(
759761
`${this.requestBase}${path}`,
760762
method,
761763
jsonPayload as Buffer,
762-
);
764+
));
763765
return {
764766
code: resp.code.toString(),
765767
payload: parsePayload(resp),
766768
};
767769
}
770+
771+
private swallowInternalCoapRejections<T>(promise: Promise<T>): Promise<T> {
772+
// We use the conventional promise pattern here so we can opt to never
773+
// resolve the promise in case we want to redirect it into an emitted error event
774+
return new Promise(async (resolve, reject) => {
775+
try {
776+
// try to resolve the promise normally
777+
resolve(await promise);
778+
} catch (e) {
779+
if (/coap\s?client was reset/i.test(e.message)) {
780+
// The CoAP client was reset. This happens when the user
781+
// resets the CoAP client while connections or requests
782+
// are still pending. It's not an error per se, so just
783+
// inform the user about what happened.
784+
this.emit("error", new TradfriError(
785+
"The network stack was reset. Pending promises will not be fulfilled.",
786+
TradfriErrorCodes.NetworkReset,
787+
));
788+
} else {
789+
reject(e);
790+
}
791+
}
792+
});
793+
}
768794
}
769795

770796
/** Normalizes the path to a resource, so it can be used for storing the observer */

0 commit comments

Comments
 (0)