hap-nodejs 1.0.0-beta.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/accessories/AirConditioner_accessory.js +24 -24
- package/dist/accessories/AirConditioner_accessory.js.map +1 -1
- package/dist/accessories/AppleTVRemote_accessory.js +23 -23
- package/dist/accessories/AppleTVRemote_accessory.js.map +1 -1
- package/dist/accessories/Camera_accessory.js +295 -380
- package/dist/accessories/Camera_accessory.js.map +1 -1
- package/dist/accessories/Fan_accessory.js +15 -21
- package/dist/accessories/Fan_accessory.js.map +1 -1
- package/dist/accessories/GarageDoorOpener_accessory.js +12 -12
- package/dist/accessories/GarageDoorOpener_accessory.js.map +1 -1
- package/dist/accessories/Light-AdaptiveLighting_accessory.js +31 -21
- package/dist/accessories/Light-AdaptiveLighting_accessory.js.map +1 -1
- package/dist/accessories/Light_accessory.js +45 -48
- package/dist/accessories/Light_accessory.js.map +1 -1
- package/dist/accessories/Lock_accessory.js +11 -11
- package/dist/accessories/Lock_accessory.js.map +1 -1
- package/dist/accessories/MotionSensor_accessory.js +8 -8
- package/dist/accessories/MotionSensor_accessory.js.map +1 -1
- package/dist/accessories/Outlet_accessory.js +10 -10
- package/dist/accessories/Outlet_accessory.js.map +1 -1
- package/dist/accessories/SmartSpeaker_accessory.js +11 -11
- package/dist/accessories/SmartSpeaker_accessory.js.map +1 -1
- package/dist/accessories/Sprinkler_accessory.js +19 -19
- package/dist/accessories/Sprinkler_accessory.js.map +1 -1
- package/dist/accessories/TV_accessory.js +17 -17
- package/dist/accessories/TV_accessory.js.map +1 -1
- package/dist/accessories/TemperatureSensor_accessory.js +6 -6
- package/dist/accessories/TemperatureSensor_accessory.js.map +1 -1
- package/dist/accessories/Wi-FiRouter_accessory.js +3 -3
- package/dist/accessories/Wi-FiRouter_accessory.js.map +1 -1
- package/dist/accessories/Wi-FiSatellite_accessory.js +4 -4
- package/dist/accessories/Wi-FiSatellite_accessory.js.map +1 -1
- package/dist/accessories/gstreamer-audioProducer.js +45 -55
- package/dist/accessories/gstreamer-audioProducer.js.map +1 -1
- package/dist/accessories/types.js +2 -2
- package/dist/accessories/types.js.map +1 -1
- package/dist/index.d.ts +0 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -31
- package/dist/index.js.map +1 -1
- package/dist/internal-types.d.ts +4 -4
- package/dist/internal-types.d.ts.map +1 -1
- package/dist/internal-types.js +10 -6
- package/dist/internal-types.js.map +1 -1
- package/dist/lib/Accessory.d.ts +19 -74
- package/dist/lib/Accessory.d.ts.map +1 -1
- package/dist/lib/Accessory.js +775 -1155
- package/dist/lib/Accessory.js.map +1 -1
- package/dist/lib/Advertiser.d.ts +5 -6
- package/dist/lib/Advertiser.d.ts.map +1 -1
- package/dist/lib/Advertiser.js +402 -521
- package/dist/lib/Advertiser.js.map +1 -1
- package/dist/lib/Bridge.js +6 -10
- package/dist/lib/Bridge.js.map +1 -1
- package/dist/lib/Characteristic.d.ts +31 -93
- package/dist/lib/Characteristic.d.ts.map +1 -1
- package/dist/lib/Characteristic.js +1474 -668
- package/dist/lib/Characteristic.js.map +1 -1
- package/dist/lib/HAPServer.d.ts +10 -22
- package/dist/lib/HAPServer.d.ts.map +1 -1
- package/dist/lib/HAPServer.js +224 -286
- package/dist/lib/HAPServer.js.map +1 -1
- package/dist/lib/Service.d.ts +9 -27
- package/dist/lib/Service.d.ts.map +1 -1
- package/dist/lib/Service.js +476 -314
- package/dist/lib/Service.js.map +1 -1
- package/dist/lib/camera/RTPProxy.d.ts +0 -2
- package/dist/lib/camera/RTPProxy.d.ts.map +1 -1
- package/dist/lib/camera/RTPProxy.js +112 -104
- package/dist/lib/camera/RTPProxy.js.map +1 -1
- package/dist/lib/camera/RTPStreamManagement.d.ts +2 -67
- package/dist/lib/camera/RTPStreamManagement.d.ts.map +1 -1
- package/dist/lib/camera/RTPStreamManagement.js +272 -292
- package/dist/lib/camera/RTPStreamManagement.js.map +1 -1
- package/dist/lib/camera/RecordingManagement.d.ts +0 -1
- package/dist/lib/camera/RecordingManagement.d.ts.map +1 -1
- package/dist/lib/camera/RecordingManagement.js +325 -392
- package/dist/lib/camera/RecordingManagement.js.map +1 -1
- package/dist/lib/camera/index.d.ts +0 -1
- package/dist/lib/camera/index.d.ts.map +1 -1
- package/dist/lib/camera/index.js +1 -2
- package/dist/lib/camera/index.js.map +1 -1
- package/dist/lib/controller/AdaptiveLightingController.d.ts +19 -4
- package/dist/lib/controller/AdaptiveLightingController.d.ts.map +1 -1
- package/dist/lib/controller/AdaptiveLightingController.js +220 -221
- package/dist/lib/controller/AdaptiveLightingController.js.map +1 -1
- package/dist/lib/controller/CameraController.d.ts +0 -6
- package/dist/lib/controller/CameraController.d.ts.map +1 -1
- package/dist/lib/controller/CameraController.js +192 -258
- package/dist/lib/controller/CameraController.js.map +1 -1
- package/dist/lib/controller/Controller.d.ts +1 -1
- package/dist/lib/controller/Controller.d.ts.map +1 -1
- package/dist/lib/controller/Controller.js +3 -3
- package/dist/lib/controller/Controller.js.map +1 -1
- package/dist/lib/controller/DoorbellController.js +38 -39
- package/dist/lib/controller/DoorbellController.js.map +1 -1
- package/dist/lib/controller/RemoteController.d.ts +4 -20
- package/dist/lib/controller/RemoteController.d.ts.map +1 -1
- package/dist/lib/controller/RemoteController.js +351 -424
- package/dist/lib/controller/RemoteController.js.map +1 -1
- package/dist/lib/controller/index.js +1 -1
- package/dist/lib/datastream/DataStreamManagement.js +57 -58
- package/dist/lib/datastream/DataStreamManagement.js.map +1 -1
- package/dist/lib/datastream/DataStreamParser.d.ts +0 -1
- package/dist/lib/datastream/DataStreamParser.d.ts.map +1 -1
- package/dist/lib/datastream/DataStreamParser.js +261 -306
- package/dist/lib/datastream/DataStreamParser.js.map +1 -1
- package/dist/lib/datastream/DataStreamServer.d.ts +0 -8
- package/dist/lib/datastream/DataStreamServer.d.ts.map +1 -1
- package/dist/lib/datastream/DataStreamServer.js +262 -275
- package/dist/lib/datastream/DataStreamServer.js.map +1 -1
- package/dist/lib/datastream/index.js +1 -1
- package/dist/lib/definitions/CharacteristicDefinitions.d.ts +50 -40
- package/dist/lib/definitions/CharacteristicDefinitions.d.ts.map +1 -1
- package/dist/lib/definitions/CharacteristicDefinitions.js +2059 -2754
- package/dist/lib/definitions/CharacteristicDefinitions.js.map +1 -1
- package/dist/lib/definitions/ServiceDefinitions.d.ts +14 -8
- package/dist/lib/definitions/ServiceDefinitions.d.ts.map +1 -1
- package/dist/lib/definitions/ServiceDefinitions.js +834 -1053
- package/dist/lib/definitions/ServiceDefinitions.js.map +1 -1
- package/dist/lib/definitions/generate-definitions.js +383 -679
- package/dist/lib/definitions/generate-definitions.js.map +1 -1
- package/dist/lib/definitions/generator-configuration.d.ts.map +1 -1
- package/dist/lib/definitions/generator-configuration.js +163 -35
- package/dist/lib/definitions/generator-configuration.js.map +1 -1
- package/dist/lib/definitions/index.js +1 -1
- package/dist/lib/model/AccessoryInfo.d.ts +0 -1
- package/dist/lib/model/AccessoryInfo.d.ts.map +1 -1
- package/dist/lib/model/AccessoryInfo.js +102 -137
- package/dist/lib/model/AccessoryInfo.js.map +1 -1
- package/dist/lib/model/ControllerStorage.js +86 -89
- package/dist/lib/model/ControllerStorage.js.map +1 -1
- package/dist/lib/model/HAPStorage.js +15 -16
- package/dist/lib/model/HAPStorage.js.map +1 -1
- package/dist/lib/model/IdentifierCache.js +49 -49
- package/dist/lib/model/IdentifierCache.js.map +1 -1
- package/dist/lib/tv/AccessControlManagement.d.ts +0 -1
- package/dist/lib/tv/AccessControlManagement.d.ts.map +1 -1
- package/dist/lib/tv/AccessControlManagement.js +43 -46
- package/dist/lib/tv/AccessControlManagement.js.map +1 -1
- package/dist/lib/util/checkName.d.ts +8 -0
- package/dist/lib/util/checkName.d.ts.map +1 -0
- package/dist/lib/util/checkName.js +17 -0
- package/dist/lib/util/checkName.js.map +1 -0
- package/dist/lib/util/clone.js +6 -29
- package/dist/lib/util/clone.js.map +1 -1
- package/dist/lib/util/color-utils.js +8 -12
- package/dist/lib/util/color-utils.js.map +1 -1
- package/dist/lib/util/eventedhttp.d.ts +5 -9
- package/dist/lib/util/eventedhttp.d.ts.map +1 -1
- package/dist/lib/util/eventedhttp.js +306 -405
- package/dist/lib/util/eventedhttp.js.map +1 -1
- package/dist/lib/util/hapCrypto.d.ts +0 -1
- package/dist/lib/util/hapCrypto.d.ts.map +1 -1
- package/dist/lib/util/hapCrypto.js +39 -41
- package/dist/lib/util/hapCrypto.js.map +1 -1
- package/dist/lib/util/hapStatusError.js +9 -12
- package/dist/lib/util/hapStatusError.js.map +1 -1
- package/dist/lib/util/net-utils.d.ts.map +1 -1
- package/dist/lib/util/net-utils.js +34 -54
- package/dist/lib/util/net-utils.js.map +1 -1
- package/dist/lib/util/once.js +4 -10
- package/dist/lib/util/once.js.map +1 -1
- package/dist/lib/util/promise-utils.d.ts +0 -1
- package/dist/lib/util/promise-utils.d.ts.map +1 -1
- package/dist/lib/util/promise-utils.js +10 -16
- package/dist/lib/util/promise-utils.js.map +1 -1
- package/dist/lib/util/request-util.js +8 -10
- package/dist/lib/util/request-util.js.map +1 -1
- package/dist/lib/util/time.d.ts +0 -1
- package/dist/lib/util/time.d.ts.map +1 -1
- package/dist/lib/util/time.js +7 -8
- package/dist/lib/util/time.js.map +1 -1
- package/dist/lib/util/tlv.d.ts +0 -28
- package/dist/lib/util/tlv.d.ts.map +1 -1
- package/dist/lib/util/tlv.js +83 -126
- package/dist/lib/util/tlv.js.map +1 -1
- package/dist/lib/util/uuid.d.ts +0 -10
- package/dist/lib/util/uuid.d.ts.map +1 -1
- package/dist/lib/util/uuid.js +22 -40
- package/dist/lib/util/uuid.js.map +1 -1
- package/dist/types.d.ts +0 -35
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +29 -28
- package/dist/lib/AccessoryLoader.d.ts +0 -28
- package/dist/lib/AccessoryLoader.d.ts.map +0 -1
- package/dist/lib/AccessoryLoader.js +0 -167
- package/dist/lib/AccessoryLoader.js.map +0 -1
- package/dist/lib/camera/Camera.d.ts +0 -44
- package/dist/lib/camera/Camera.d.ts.map +0 -1
- package/dist/lib/camera/Camera.js +0 -36
- package/dist/lib/camera/Camera.js.map +0 -1
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HAPConnection = exports.HAPConnectionEvent = exports.HAPConnectionState = exports.EventedHTTPServer = exports.EventedHTTPServerEvent = exports.HAPEncryption = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const domain_formatter_1 = require("@homebridge/ciao/lib/util/domain-formatter");
|
|
6
|
+
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
7
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
8
|
+
const events_1 = require("events");
|
|
9
|
+
const http_1 = tslib_1.__importDefault(require("http"));
|
|
10
|
+
const net_1 = tslib_1.__importDefault(require("net"));
|
|
11
|
+
const os_1 = tslib_1.__importDefault(require("os"));
|
|
12
|
+
const hapCrypto = tslib_1.__importStar(require("./hapCrypto"));
|
|
13
|
+
const net_utils_1 = require("./net-utils");
|
|
14
|
+
const uuid = tslib_1.__importStar(require("./uuid"));
|
|
15
|
+
const debug = (0, debug_1.default)("HAP-NodeJS:EventedHTTPServer");
|
|
16
|
+
const debugCon = (0, debug_1.default)("HAP-NodeJS:EventedHTTPServer:Connection");
|
|
17
|
+
const debugEvents = (0, debug_1.default)("HAP-NodeJS:EventEmitter");
|
|
18
18
|
/**
|
|
19
19
|
* Simple struct to hold vars needed to support HAP encryption.
|
|
20
20
|
*
|
|
21
21
|
* @group Cryptography
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
class HAPEncryption {
|
|
24
|
+
clientPublicKey;
|
|
25
|
+
secretKey;
|
|
26
|
+
publicKey;
|
|
27
|
+
sharedSecret;
|
|
28
|
+
hkdfPairEncryptionKey;
|
|
29
|
+
accessoryToControllerCount = 0;
|
|
30
|
+
controllerToAccessoryCount = 0;
|
|
31
|
+
accessoryToControllerKey;
|
|
32
|
+
controllerToAccessoryKey;
|
|
33
|
+
incompleteFrame;
|
|
34
|
+
constructor(clientPublicKey, secretKey, publicKey, sharedSecret, hkdfPairEncryptionKey) {
|
|
27
35
|
this.clientPublicKey = clientPublicKey;
|
|
28
36
|
this.secretKey = secretKey;
|
|
29
37
|
this.publicKey = publicKey;
|
|
@@ -32,8 +40,7 @@ var HAPEncryption = /** @class */ (function () {
|
|
|
32
40
|
this.accessoryToControllerKey = Buffer.alloc(0);
|
|
33
41
|
this.controllerToAccessoryKey = Buffer.alloc(0);
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
}());
|
|
43
|
+
}
|
|
37
44
|
exports.HAPEncryption = HAPEncryption;
|
|
38
45
|
/**
|
|
39
46
|
* @group HAP Accessory Server
|
|
@@ -44,7 +51,7 @@ var EventedHTTPServerEvent;
|
|
|
44
51
|
EventedHTTPServerEvent["CONNECTION_OPENED"] = "connection-opened";
|
|
45
52
|
EventedHTTPServerEvent["REQUEST"] = "request";
|
|
46
53
|
EventedHTTPServerEvent["CONNECTION_CLOSED"] = "connection-closed";
|
|
47
|
-
})(EventedHTTPServerEvent
|
|
54
|
+
})(EventedHTTPServerEvent || (exports.EventedHTTPServerEvent = EventedHTTPServerEvent = {}));
|
|
48
55
|
/**
|
|
49
56
|
* EventedHTTPServer provides an HTTP-like server that supports HAP "extensions" for security and events.
|
|
50
57
|
*
|
|
@@ -64,75 +71,67 @@ var EventedHTTPServerEvent;
|
|
|
64
71
|
*
|
|
65
72
|
* @group HAP Accessory Server
|
|
66
73
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
75
|
+
class EventedHTTPServer extends events_1.EventEmitter {
|
|
76
|
+
static CONNECTION_TIMEOUT_LIMIT = 16; // if we have more (or equal) # connections we start the timeout
|
|
77
|
+
static MAX_CONNECTION_IDLE_TIME = 60 * 60 * 1000; // 1h
|
|
78
|
+
tcpServer;
|
|
79
|
+
/**
|
|
80
|
+
* Set of all currently connected HAP connections.
|
|
81
|
+
*/
|
|
82
|
+
connections = new Set();
|
|
83
|
+
/**
|
|
84
|
+
* Session dictionary indexed by username/identifier. The username uniquely identifies every person added to the home.
|
|
85
|
+
* So there can be multiple sessions open for a single username (multiple devices connected to the same Apple ID).
|
|
86
|
+
*/
|
|
87
|
+
connectionsByUsername = new Map();
|
|
88
|
+
connectionIdleTimeout;
|
|
89
|
+
connectionLoggingInterval;
|
|
90
|
+
constructor() {
|
|
91
|
+
super();
|
|
92
|
+
this.tcpServer = net_1.default.createServer();
|
|
93
|
+
}
|
|
94
|
+
scheduleNextConnectionIdleTimeout() {
|
|
85
95
|
this.connectionIdleTimeout = undefined;
|
|
86
96
|
if (!this.tcpServer.listening) {
|
|
87
97
|
return;
|
|
88
98
|
}
|
|
89
99
|
debug("Running idle timeout timer...");
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
debug("[%s] Closing connection as it was inactive for " + timeDelta + "ms");
|
|
98
|
-
connection.close();
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
nextTimeout = Math.max(nextTimeout, EventedHTTPServer.MAX_CONNECTION_IDLE_TIME - timeDelta);
|
|
102
|
-
}
|
|
100
|
+
const currentTime = new Date().getTime();
|
|
101
|
+
let nextTimeout = -1;
|
|
102
|
+
for (const connection of this.connections) {
|
|
103
|
+
const timeDelta = currentTime - connection.lastSocketOperation;
|
|
104
|
+
if (timeDelta >= EventedHTTPServer.MAX_CONNECTION_IDLE_TIME) {
|
|
105
|
+
debug("[%s] Closing connection as it was inactive for " + timeDelta + "ms");
|
|
106
|
+
connection.close();
|
|
103
107
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
finally {
|
|
107
|
-
try {
|
|
108
|
-
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
108
|
+
else {
|
|
109
|
+
nextTimeout = Math.max(nextTimeout, EventedHTTPServer.MAX_CONNECTION_IDLE_TIME - timeDelta);
|
|
109
110
|
}
|
|
110
|
-
finally { if (e_1) throw e_1.error; }
|
|
111
111
|
}
|
|
112
112
|
if (this.connections.size >= EventedHTTPServer.CONNECTION_TIMEOUT_LIMIT) {
|
|
113
113
|
this.connectionIdleTimeout = setTimeout(this.scheduleNextConnectionIdleTimeout.bind(this), nextTimeout);
|
|
114
114
|
}
|
|
115
|
-
}
|
|
116
|
-
|
|
115
|
+
}
|
|
116
|
+
address() {
|
|
117
117
|
return this.tcpServer.address();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
}
|
|
119
|
+
listen(targetPort, hostname) {
|
|
120
|
+
this.tcpServer.listen(targetPort, hostname, () => {
|
|
121
|
+
const address = this.tcpServer.address(); // address() is only a string when listening to unix domain sockets
|
|
122
|
+
debug("Server listening on %s:%s", address.family === "IPv6" ? `[${address.address}]` : address.address, address.port);
|
|
123
|
+
this.connectionLoggingInterval = setInterval(() => {
|
|
124
|
+
const connectionInformation = [...this.connections]
|
|
125
|
+
.map(connection => `${connection.remoteAddress}:${connection.remotePort}`)
|
|
126
126
|
.join(", ");
|
|
127
|
-
debug("Currently %d hap connections open: %s",
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
debug("Currently %d hap connections open: %s", this.connections.size, connectionInformation);
|
|
128
|
+
}, 60_000);
|
|
129
|
+
this.connectionLoggingInterval.unref();
|
|
130
|
+
this.emit("listening" /* EventedHTTPServerEvent.LISTENING */, address.port, address.address);
|
|
131
131
|
});
|
|
132
132
|
this.tcpServer.on("connection", this.onConnection.bind(this));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
var e_2, _a;
|
|
133
|
+
}
|
|
134
|
+
stop() {
|
|
136
135
|
if (this.connectionLoggingInterval != null) {
|
|
137
136
|
clearInterval(this.connectionLoggingInterval);
|
|
138
137
|
this.connectionLoggingInterval = undefined;
|
|
@@ -142,24 +141,14 @@ var EventedHTTPServer = /** @class */ (function (_super) {
|
|
|
142
141
|
this.connectionIdleTimeout = undefined;
|
|
143
142
|
}
|
|
144
143
|
this.tcpServer.close();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
var connection = _c.value;
|
|
148
|
-
connection.close();
|
|
149
|
-
}
|
|
144
|
+
for (const connection of this.connections) {
|
|
145
|
+
connection.close();
|
|
150
146
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
155
|
-
}
|
|
156
|
-
finally { if (e_2) throw e_2.error; }
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
EventedHTTPServer.prototype.destroy = function () {
|
|
147
|
+
}
|
|
148
|
+
destroy() {
|
|
160
149
|
this.stop();
|
|
161
150
|
this.removeAllListeners();
|
|
162
|
-
}
|
|
151
|
+
}
|
|
163
152
|
/**
|
|
164
153
|
* Send an event notification for given characteristic and changed value to all connected clients.
|
|
165
154
|
* If `originator` is specified, the given {@link HAPConnection} will be excluded from the broadcast.
|
|
@@ -171,32 +160,20 @@ var EventedHTTPServer = /** @class */ (function (_super) {
|
|
|
171
160
|
* @param immediateDelivery - The HAP spec requires some characteristics to be delivery immediately.
|
|
172
161
|
* Namely, for the {@link Characteristic.ButtonEvent} and the {@link Characteristic.ProgrammableSwitchEvent} characteristics.
|
|
173
162
|
*/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (connection === originator) {
|
|
180
|
-
debug("[%s] Muting event '%s' notification for this connection since it originated here.", connection.remoteAddress, aid + "." + iid);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
connection.sendEvent(aid, iid, value, immediateDelivery);
|
|
163
|
+
broadcastEvent(aid, iid, value, originator, immediateDelivery) {
|
|
164
|
+
for (const connection of this.connections) {
|
|
165
|
+
if (connection === originator) {
|
|
166
|
+
debug("[%s] Muting event '%s' notification for this connection since it originated here.", connection.remoteAddress, aid + "." + iid);
|
|
167
|
+
continue;
|
|
184
168
|
}
|
|
169
|
+
connection.sendEvent(aid, iid, value, immediateDelivery);
|
|
185
170
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
190
|
-
}
|
|
191
|
-
finally { if (e_3) throw e_3.error; }
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
EventedHTTPServer.prototype.onConnection = function (socket) {
|
|
195
|
-
var _this = this;
|
|
171
|
+
}
|
|
172
|
+
onConnection(socket) {
|
|
196
173
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
197
|
-
|
|
198
|
-
connection.on("request" /* HAPConnectionEvent.REQUEST */,
|
|
199
|
-
|
|
174
|
+
const connection = new HAPConnection(this, socket);
|
|
175
|
+
connection.on("request" /* HAPConnectionEvent.REQUEST */, (request, response) => {
|
|
176
|
+
this.emit("request" /* EventedHTTPServerEvent.REQUEST */, connection, request, response);
|
|
200
177
|
});
|
|
201
178
|
connection.on("authenticated" /* HAPConnectionEvent.AUTHENTICATED */, this.handleConnectionAuthenticated.bind(this, connection));
|
|
202
179
|
connection.on("closed" /* HAPConnectionEvent.CLOSED */, this.handleConnectionClose.bind(this, connection));
|
|
@@ -206,23 +183,23 @@ var EventedHTTPServer = /** @class */ (function (_super) {
|
|
|
206
183
|
if (this.connections.size >= EventedHTTPServer.CONNECTION_TIMEOUT_LIMIT && !this.connectionIdleTimeout) {
|
|
207
184
|
this.scheduleNextConnectionIdleTimeout();
|
|
208
185
|
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
186
|
+
}
|
|
187
|
+
handleConnectionAuthenticated(connection, username) {
|
|
188
|
+
const connections = this.connectionsByUsername.get(username);
|
|
212
189
|
if (!connections) {
|
|
213
190
|
this.connectionsByUsername.set(username, [connection]);
|
|
214
191
|
}
|
|
215
192
|
else if (!connections.includes(connection)) { // ensure this doesn't get added more than one time
|
|
216
193
|
connections.push(connection);
|
|
217
194
|
}
|
|
218
|
-
}
|
|
219
|
-
|
|
195
|
+
}
|
|
196
|
+
handleConnectionClose(connection) {
|
|
220
197
|
this.emit("connection-closed" /* EventedHTTPServerEvent.CONNECTION_CLOSED */, connection);
|
|
221
198
|
this.connections.delete(connection);
|
|
222
199
|
if (connection.username) { // aka connection was authenticated
|
|
223
|
-
|
|
200
|
+
const connections = this.connectionsByUsername.get(connection.username);
|
|
224
201
|
if (connections) {
|
|
225
|
-
|
|
202
|
+
const index = connections.indexOf(connection);
|
|
226
203
|
if (index !== -1) {
|
|
227
204
|
connections.splice(index, 1);
|
|
228
205
|
}
|
|
@@ -231,7 +208,7 @@ var EventedHTTPServer = /** @class */ (function (_super) {
|
|
|
231
208
|
}
|
|
232
209
|
}
|
|
233
210
|
}
|
|
234
|
-
}
|
|
211
|
+
}
|
|
235
212
|
/**
|
|
236
213
|
* This method is to be called when a given {@link HAPConnection} performs a request that should result in the disconnection
|
|
237
214
|
* of all other {@link HAPConnection} with the same {@link HAPUsername}.
|
|
@@ -242,29 +219,15 @@ var EventedHTTPServer = /** @class */ (function (_super) {
|
|
|
242
219
|
* @param initiator - The connection that requested to disconnect all connections of the same username.
|
|
243
220
|
* @param username - The username for which all connections shall be closed.
|
|
244
221
|
*/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
var connections = initiator.server.connectionsByUsername.get(username);
|
|
222
|
+
static destroyExistingConnectionsAfterUnpair(initiator, username) {
|
|
223
|
+
const connections = initiator.server.connectionsByUsername.get(username);
|
|
248
224
|
if (connections) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
var connection = connections_1_1.value;
|
|
252
|
-
connection.closeConnectionAsOfUnpair(initiator);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
256
|
-
finally {
|
|
257
|
-
try {
|
|
258
|
-
if (connections_1_1 && !connections_1_1.done && (_a = connections_1.return)) _a.call(connections_1);
|
|
259
|
-
}
|
|
260
|
-
finally { if (e_4) throw e_4.error; }
|
|
225
|
+
for (const connection of connections) {
|
|
226
|
+
connection.closeConnectionAsOfUnpair(initiator);
|
|
261
227
|
}
|
|
262
228
|
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
EventedHTTPServer.MAX_CONNECTION_IDLE_TIME = 60 * 60 * 1000; // 1h
|
|
266
|
-
return EventedHTTPServer;
|
|
267
|
-
}(events_1.EventEmitter));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
268
231
|
exports.EventedHTTPServer = EventedHTTPServer;
|
|
269
232
|
/**
|
|
270
233
|
* @private
|
|
@@ -280,7 +243,7 @@ var HAPConnectionState;
|
|
|
280
243
|
HAPConnectionState[HAPConnectionState["TO_BE_TEARED_DOWN"] = 3] = "TO_BE_TEARED_DOWN";
|
|
281
244
|
HAPConnectionState[HAPConnectionState["CLOSING"] = 4] = "CLOSING";
|
|
282
245
|
HAPConnectionState[HAPConnectionState["CLOSED"] = 5] = "CLOSED";
|
|
283
|
-
})(HAPConnectionState
|
|
246
|
+
})(HAPConnectionState || (exports.HAPConnectionState = HAPConnectionState = {}));
|
|
284
247
|
/**
|
|
285
248
|
* @group HAP Accessory Server
|
|
286
249
|
*/
|
|
@@ -289,87 +252,106 @@ var HAPConnectionEvent;
|
|
|
289
252
|
HAPConnectionEvent["REQUEST"] = "request";
|
|
290
253
|
HAPConnectionEvent["AUTHENTICATED"] = "authenticated";
|
|
291
254
|
HAPConnectionEvent["CLOSED"] = "closed";
|
|
292
|
-
})(HAPConnectionEvent
|
|
255
|
+
})(HAPConnectionEvent || (exports.HAPConnectionEvent = HAPConnectionEvent = {}));
|
|
293
256
|
/**
|
|
294
257
|
* Manages a single iOS-initiated HTTP connection during its lifetime.
|
|
295
258
|
* @group HAP Accessory Server
|
|
296
259
|
*/
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
261
|
+
class HAPConnection extends events_1.EventEmitter {
|
|
262
|
+
/**
|
|
263
|
+
* @private file-private API
|
|
264
|
+
*/
|
|
265
|
+
server;
|
|
266
|
+
sessionID; // uuid unique to every HAP connection
|
|
267
|
+
state = 0 /* HAPConnectionState.CONNECTING */;
|
|
268
|
+
localAddress;
|
|
269
|
+
remoteAddress; // cache because it becomes undefined in 'onClientSocketClose'
|
|
270
|
+
remotePort;
|
|
271
|
+
networkInterface;
|
|
272
|
+
tcpSocket;
|
|
273
|
+
internalHttpServer;
|
|
274
|
+
httpSocket; // set when in state FULLY_SET_UP
|
|
275
|
+
internalHttpServerPort;
|
|
276
|
+
internalHttpServerAddress;
|
|
277
|
+
lastSocketOperation = new Date().getTime();
|
|
278
|
+
pendingClientSocketData = Buffer.alloc(0); // data received from client before HTTP proxy is fully setup
|
|
279
|
+
handlingRequest = false; // true while we are composing an HTTP response (so events can wait)
|
|
280
|
+
username; // username is unique to every user in the home, basically identifies an Apple ID
|
|
281
|
+
encryption; // created in handlePairVerifyStepOne
|
|
282
|
+
srpServer;
|
|
283
|
+
_pairSetupState; // TODO ensure those two states are always correctly reset?
|
|
284
|
+
_pairVerifyState;
|
|
285
|
+
registeredEvents = new Set();
|
|
286
|
+
eventsTimer;
|
|
287
|
+
queuedEvents = [];
|
|
288
|
+
/**
|
|
289
|
+
* If true, the above {@link queuedEvents} contains events which are set to be delivered immediately!
|
|
290
|
+
*/
|
|
291
|
+
eventsQueuedForImmediateDelivery = false;
|
|
292
|
+
timedWritePid;
|
|
293
|
+
timedWriteTimeout;
|
|
294
|
+
constructor(server, clientSocket) {
|
|
295
|
+
super();
|
|
296
|
+
this.server = server;
|
|
297
|
+
this.sessionID = uuid.generate(clientSocket.remoteAddress + ":" + clientSocket.remotePort);
|
|
298
|
+
this.localAddress = clientSocket.localAddress;
|
|
299
|
+
this.remoteAddress = clientSocket.remoteAddress; // cache because it becomes undefined in 'onClientSocketClose'
|
|
300
|
+
this.remotePort = clientSocket.remotePort;
|
|
301
|
+
this.networkInterface = HAPConnection.getLocalNetworkInterface(clientSocket);
|
|
317
302
|
// clientSocket is the socket connected to the actual iOS device
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
303
|
+
this.tcpSocket = clientSocket;
|
|
304
|
+
this.tcpSocket.on("data", this.onTCPSocketData.bind(this));
|
|
305
|
+
this.tcpSocket.on("close", this.onTCPSocketClose.bind(this));
|
|
321
306
|
// we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely.
|
|
322
|
-
|
|
323
|
-
|
|
307
|
+
this.tcpSocket.on("error", this.onTCPSocketError.bind(this));
|
|
308
|
+
this.tcpSocket.setNoDelay(true); // disable Nagle algorithm
|
|
324
309
|
// "HAP accessory servers must not use keepalive messages, which periodically wake up iOS devices".
|
|
325
310
|
// Thus, we don't configure any tcp keepalive
|
|
326
311
|
// create our internal HTTP server for this connection that we will proxy data to and from
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
312
|
+
this.internalHttpServer = http_1.default.createServer();
|
|
313
|
+
this.internalHttpServer.timeout = 0; // clients expect to hold connections open as long as they want
|
|
314
|
+
this.internalHttpServer.keepAliveTimeout = 0; // workaround for https://github.com/nodejs/node/issues/13391
|
|
315
|
+
this.internalHttpServer.on("listening", this.onHttpServerListening.bind(this));
|
|
316
|
+
this.internalHttpServer.on("request", this.handleHttpServerRequest.bind(this));
|
|
317
|
+
this.internalHttpServer.on("error", this.onHttpServerError.bind(this));
|
|
333
318
|
// close event is added later on the "connect" event as possible listen retries would throw unnecessary close events
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
var eventCount = this.listeners(event).length;
|
|
342
|
-
var tabs1 = event === "authenticated" /* HAPConnectionEvent.AUTHENTICATED */ ? "\t" : "\t\t";
|
|
343
|
-
var tabs2 = !registration ? "\t" : "\t\t";
|
|
319
|
+
this.internalHttpServer.listen(0, this.internalHttpServerAddress = (0, net_utils_1.getOSLoopbackAddressIfAvailable)());
|
|
320
|
+
}
|
|
321
|
+
debugListenerRegistration(event, registration = true, beforeCount = -1) {
|
|
322
|
+
const stackTrace = new Error().stack.split("\n")[3];
|
|
323
|
+
const eventCount = this.listeners(event).length;
|
|
324
|
+
const tabs1 = event === "authenticated" /* HAPConnectionEvent.AUTHENTICATED */ ? "\t" : "\t\t";
|
|
325
|
+
const tabs2 = !registration ? "\t" : "\t\t";
|
|
344
326
|
// eslint-disable-next-line max-len
|
|
345
|
-
debugEvents(
|
|
346
|
-
}
|
|
327
|
+
debugEvents(`[${this.remoteAddress}] ${registration ? "Registered" : "Unregistered"} event '${String(event).toUpperCase()}' ${tabs1}(total: ${eventCount}${!registration ? " Before: " + beforeCount : ""}) ${tabs2}${stackTrace}`);
|
|
328
|
+
}
|
|
347
329
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
348
|
-
|
|
349
|
-
|
|
330
|
+
on(event, listener) {
|
|
331
|
+
const result = super.on(event, listener);
|
|
350
332
|
this.debugListenerRegistration(event);
|
|
351
333
|
return result;
|
|
352
|
-
}
|
|
334
|
+
}
|
|
353
335
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
354
|
-
|
|
355
|
-
|
|
336
|
+
addListener(event, listener) {
|
|
337
|
+
const result = super.addListener(event, listener);
|
|
356
338
|
this.debugListenerRegistration(event);
|
|
357
339
|
return result;
|
|
358
|
-
}
|
|
340
|
+
}
|
|
359
341
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
342
|
+
removeListener(event, listener) {
|
|
343
|
+
const beforeCount = this.listeners(event).length;
|
|
344
|
+
const result = super.removeListener(event, listener);
|
|
363
345
|
this.debugListenerRegistration(event, false, beforeCount);
|
|
364
346
|
return result;
|
|
365
|
-
}
|
|
347
|
+
}
|
|
366
348
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
349
|
+
off(event, listener) {
|
|
350
|
+
const result = super.off(event, listener);
|
|
351
|
+
const beforeCount = this.listeners(event).length;
|
|
370
352
|
this.debugListenerRegistration(event, false, beforeCount);
|
|
371
353
|
return result;
|
|
372
|
-
}
|
|
354
|
+
}
|
|
373
355
|
/**
|
|
374
356
|
* This method is called once the connection has gone through pair-verify.
|
|
375
357
|
* As any HomeKit controller will initiate a pair-verify after the pair-setup procedure, this method gets
|
|
@@ -377,22 +359,22 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
377
359
|
*
|
|
378
360
|
* Once this method has been called, the connection is authenticated and encryption is turned on.
|
|
379
361
|
*/
|
|
380
|
-
|
|
362
|
+
connectionAuthenticated(username) {
|
|
381
363
|
this.state = 2 /* HAPConnectionState.AUTHENTICATED */;
|
|
382
364
|
this.username = username;
|
|
383
365
|
this.emit("authenticated" /* HAPConnectionEvent.AUTHENTICATED */, username);
|
|
384
|
-
}
|
|
385
|
-
|
|
366
|
+
}
|
|
367
|
+
isAuthenticated() {
|
|
386
368
|
return this.state === 2 /* HAPConnectionState.AUTHENTICATED */;
|
|
387
|
-
}
|
|
388
|
-
|
|
369
|
+
}
|
|
370
|
+
close() {
|
|
389
371
|
if (this.state >= 4 /* HAPConnectionState.CLOSING */) {
|
|
390
372
|
return; // already closed/closing
|
|
391
373
|
}
|
|
392
374
|
this.state = 4 /* HAPConnectionState.CLOSING */;
|
|
393
375
|
this.tcpSocket.destroy();
|
|
394
|
-
}
|
|
395
|
-
|
|
376
|
+
}
|
|
377
|
+
closeConnectionAsOfUnpair(initiator) {
|
|
396
378
|
if (this === initiator) {
|
|
397
379
|
// the initiator of the unpair request is this connection, meaning it unpaired itself.
|
|
398
380
|
// we still need to send the response packet to the unpair request.
|
|
@@ -402,16 +384,16 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
402
384
|
// as HomeKit requires it, destroy any active session which got unpaired
|
|
403
385
|
this.close();
|
|
404
386
|
}
|
|
405
|
-
}
|
|
406
|
-
|
|
387
|
+
}
|
|
388
|
+
sendEvent(aid, iid, value, immediateDelivery) {
|
|
407
389
|
(0, assert_1.default)(aid != null, "HAPConnection.sendEvent: aid must be defined!");
|
|
408
390
|
(0, assert_1.default)(iid != null, "HAPConnection.sendEvent: iid must be defined!");
|
|
409
|
-
|
|
391
|
+
const eventName = aid + "." + iid;
|
|
410
392
|
if (!this.registeredEvents.has(eventName)) {
|
|
411
393
|
// non verified connections can't register events, so this case is covered!
|
|
412
394
|
return;
|
|
413
395
|
}
|
|
414
|
-
|
|
396
|
+
const event = {
|
|
415
397
|
aid: aid,
|
|
416
398
|
iid: iid,
|
|
417
399
|
value: value,
|
|
@@ -431,8 +413,8 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
431
413
|
// we search the list of queued events in reverse order.
|
|
432
414
|
// if the last element with the same aid and iid has the same value we don't want to send the event notification twice.
|
|
433
415
|
// BUT, we do not want to override previous event notifications which have a different value. Automations must be executed!
|
|
434
|
-
for (
|
|
435
|
-
|
|
416
|
+
for (let i = this.queuedEvents.length - 1; i >= 0; i--) {
|
|
417
|
+
const queuedEvent = this.queuedEvents[i];
|
|
436
418
|
if (queuedEvent.aid === aid && queuedEvent.iid === iid) {
|
|
437
419
|
if (queuedEvent.value === value) {
|
|
438
420
|
return; // the same event was already queued. do not add it again!
|
|
@@ -446,17 +428,16 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
446
428
|
this.eventsTimer = setTimeout(this.handleEventsTimeout.bind(this), 250);
|
|
447
429
|
this.eventsTimer.unref();
|
|
448
430
|
}
|
|
449
|
-
}
|
|
450
|
-
|
|
431
|
+
}
|
|
432
|
+
handleEventsTimeout() {
|
|
451
433
|
this.eventsTimer = undefined;
|
|
452
434
|
if (this.state > 2 /* HAPConnectionState.AUTHENTICATED */) {
|
|
453
435
|
// connection is closed or about to be closed. no need to send any further events
|
|
454
436
|
return;
|
|
455
437
|
}
|
|
456
438
|
this.writeQueuedEventNotifications();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
var e_5, _a;
|
|
439
|
+
}
|
|
440
|
+
writeQueuedEventNotifications() {
|
|
460
441
|
if (this.queuedEvents.length === 0 || this.handlingRequest) {
|
|
461
442
|
return; // don't send empty event notifications or if we are currently handling a request
|
|
462
443
|
}
|
|
@@ -465,29 +446,19 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
465
446
|
clearTimeout(this.eventsTimer);
|
|
466
447
|
this.eventsTimer = undefined;
|
|
467
448
|
}
|
|
468
|
-
|
|
449
|
+
const eventData = {
|
|
469
450
|
characteristics: [],
|
|
470
451
|
};
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (!this.registeredEvents.has(queuedEvent.aid + "." + queuedEvent.iid)) {
|
|
475
|
-
continue; // client unregistered that event in the meantime
|
|
476
|
-
}
|
|
477
|
-
eventData.characteristics.push(queuedEvent);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
481
|
-
finally {
|
|
482
|
-
try {
|
|
483
|
-
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
452
|
+
for (const queuedEvent of this.queuedEvents) {
|
|
453
|
+
if (!this.registeredEvents.has(queuedEvent.aid + "." + queuedEvent.iid)) {
|
|
454
|
+
continue; // client unregistered that event in the meantime
|
|
484
455
|
}
|
|
485
|
-
|
|
456
|
+
eventData.characteristics.push(queuedEvent);
|
|
486
457
|
}
|
|
487
458
|
this.queuedEvents.splice(0, this.queuedEvents.length);
|
|
488
459
|
this.eventsQueuedForImmediateDelivery = false;
|
|
489
460
|
this.writeEventNotification(eventData);
|
|
490
|
-
}
|
|
461
|
+
}
|
|
491
462
|
/**
|
|
492
463
|
* This will create an EVENT/1.0 notification header with the provided event notification.
|
|
493
464
|
* If currently an HTTP request is in progress the assembled packet will be
|
|
@@ -495,36 +466,36 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
495
466
|
*
|
|
496
467
|
* @param notification - The event which should be sent out
|
|
497
468
|
*/
|
|
498
|
-
|
|
469
|
+
writeEventNotification(notification) {
|
|
499
470
|
debugCon("[%s] Sending HAP event notifications %o", this.remoteAddress, notification.characteristics);
|
|
500
471
|
(0, assert_1.default)(!this.handlingRequest, "Can't write event notifications while handling a request!");
|
|
501
472
|
// Apple backend processes events in reverse order, so we need to reverse the array
|
|
502
473
|
// so that events are processed in chronological order.
|
|
503
474
|
notification.characteristics.reverse();
|
|
504
|
-
|
|
505
|
-
|
|
475
|
+
const dataBuffer = Buffer.from(JSON.stringify(notification), "utf8");
|
|
476
|
+
const header = Buffer.from("EVENT/1.0 200 OK\r\n" +
|
|
506
477
|
"Content-Type: application/hap+json\r\n" +
|
|
507
478
|
"Content-Length: " + dataBuffer.length + "\r\n" +
|
|
508
479
|
"\r\n", "utf8");
|
|
509
|
-
|
|
480
|
+
const buffer = Buffer.concat([header, dataBuffer]);
|
|
510
481
|
this.tcpSocket.write(this.encrypt(buffer), this.handleTCPSocketWriteFulfilled.bind(this));
|
|
511
|
-
}
|
|
512
|
-
|
|
482
|
+
}
|
|
483
|
+
enableEventNotifications(aid, iid) {
|
|
513
484
|
this.registeredEvents.add(aid + "." + iid);
|
|
514
|
-
}
|
|
515
|
-
|
|
485
|
+
}
|
|
486
|
+
disableEventNotifications(aid, iid) {
|
|
516
487
|
this.registeredEvents.delete(aid + "." + iid);
|
|
517
|
-
}
|
|
518
|
-
|
|
488
|
+
}
|
|
489
|
+
hasEventNotifications(aid, iid) {
|
|
519
490
|
return this.registeredEvents.has(aid + "." + iid);
|
|
520
|
-
}
|
|
521
|
-
|
|
491
|
+
}
|
|
492
|
+
getRegisteredEvents() {
|
|
522
493
|
return this.registeredEvents;
|
|
523
|
-
}
|
|
524
|
-
|
|
494
|
+
}
|
|
495
|
+
clearRegisteredEvents() {
|
|
525
496
|
this.registeredEvents.clear();
|
|
526
|
-
}
|
|
527
|
-
|
|
497
|
+
}
|
|
498
|
+
encrypt(data) {
|
|
528
499
|
// if accessoryToControllerKey is not empty, then encryption is enabled for this connection. However, we'll
|
|
529
500
|
// need to be careful to ensure that we don't encrypt the last few bytes of the response from handlePairVerifyStepTwo.
|
|
530
501
|
// Since all communication calls are asynchronous, we could easily receive this 'encrypt' event for those bytes.
|
|
@@ -533,18 +504,17 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
533
504
|
return hapCrypto.layerEncrypt(data, this.encryption);
|
|
534
505
|
}
|
|
535
506
|
return data; // otherwise, we don't encrypt and return plaintext
|
|
536
|
-
}
|
|
537
|
-
|
|
507
|
+
}
|
|
508
|
+
decrypt(data) {
|
|
538
509
|
if (this.encryption && this.encryption.controllerToAccessoryKey.length > 0) {
|
|
539
510
|
// below call may throw an error if decryption failed
|
|
540
511
|
return hapCrypto.layerDecrypt(data, this.encryption);
|
|
541
512
|
}
|
|
542
513
|
return data; // otherwise, we don't decrypt and return plaintext
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
var addressString = addressInfo.family === "IPv6" ? "[".concat(addressInfo.address, "]") : addressInfo.address;
|
|
514
|
+
}
|
|
515
|
+
onHttpServerListening() {
|
|
516
|
+
const addressInfo = this.internalHttpServer.address(); // address() is only a string when listening to unix domain sockets
|
|
517
|
+
const addressString = addressInfo.family === "IPv6" ? `[${addressInfo.address}]` : addressInfo.address;
|
|
548
518
|
this.internalHttpServerPort = addressInfo.port;
|
|
549
519
|
debugCon("[%s] Internal HTTP server listening on %s:%s", this.remoteAddress, addressString, addressInfo.port);
|
|
550
520
|
this.internalHttpServer.on("close", this.onHttpServerClose.bind(this));
|
|
@@ -555,25 +525,25 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
555
525
|
// we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely.
|
|
556
526
|
this.httpSocket.on("error", this.onHttpSocketError.bind(this));
|
|
557
527
|
this.httpSocket.on("close", this.onHttpSocketClose.bind(this));
|
|
558
|
-
this.httpSocket.on("connect",
|
|
528
|
+
this.httpSocket.on("connect", () => {
|
|
559
529
|
// we are now fully set up:
|
|
560
530
|
// - clientSocket is connected to the iOS device
|
|
561
531
|
// - serverSocket is connected to the httpServer
|
|
562
532
|
// - ready to proxy data!
|
|
563
|
-
|
|
564
|
-
debugCon("[%s] Internal HTTP socket connected. HAPConnection now fully set up!",
|
|
533
|
+
this.state = 1 /* HAPConnectionState.FULLY_SET_UP */;
|
|
534
|
+
debugCon("[%s] Internal HTTP socket connected. HAPConnection now fully set up!", this.remoteAddress);
|
|
565
535
|
// start by flushing any pending buffered data received from the client while we were setting up
|
|
566
|
-
if (
|
|
567
|
-
|
|
536
|
+
if (this.pendingClientSocketData && this.pendingClientSocketData.length > 0) {
|
|
537
|
+
this.httpSocket.write(this.pendingClientSocketData);
|
|
568
538
|
}
|
|
569
|
-
|
|
539
|
+
this.pendingClientSocketData = undefined;
|
|
570
540
|
});
|
|
571
|
-
}
|
|
541
|
+
}
|
|
572
542
|
/**
|
|
573
543
|
* This event handler is called when we receive data from a HomeKit controller on our tcp socket.
|
|
574
544
|
* We store the data if the internal http server is not read yet, or forward it to the http server.
|
|
575
545
|
*/
|
|
576
|
-
|
|
546
|
+
onTCPSocketData(data) {
|
|
577
547
|
if (this.state > 2 /* HAPConnectionState.AUTHENTICATED */) {
|
|
578
548
|
// don't accept data of a connection which is about to be closed or already closed
|
|
579
549
|
return;
|
|
@@ -594,13 +564,13 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
594
564
|
else {
|
|
595
565
|
this.httpSocket.write(data); // proxy it along to the HTTP server
|
|
596
566
|
}
|
|
597
|
-
}
|
|
567
|
+
}
|
|
598
568
|
/**
|
|
599
569
|
* This event handler is called when the internal http server receives a request.
|
|
600
570
|
* Meaning we received data from the HomeKit controller in {@link onTCPSocketData}, which then send the
|
|
601
571
|
* data unencrypted to the internal http server. And now it landed here, fully parsed as a http request.
|
|
602
572
|
*/
|
|
603
|
-
|
|
573
|
+
handleHttpServerRequest(request, response) {
|
|
604
574
|
if (this.state > 2 /* HAPConnectionState.AUTHENTICATED */) {
|
|
605
575
|
// don't accept data of a connection which is about to be closed or already closed
|
|
606
576
|
return;
|
|
@@ -608,20 +578,19 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
608
578
|
debugCon("[%s] HTTP request: %s", this.remoteAddress, request.url);
|
|
609
579
|
request.socket.setNoDelay(true);
|
|
610
580
|
this.emit("request" /* HAPConnectionEvent.REQUEST */, request, response);
|
|
611
|
-
}
|
|
581
|
+
}
|
|
612
582
|
/**
|
|
613
583
|
* This event handler is called by the socket which is connected to our internal http server.
|
|
614
584
|
* It is called with the response returned from the http server.
|
|
615
585
|
* In this method we have to encrypt and forward the message back to the HomeKit controller.
|
|
616
586
|
*/
|
|
617
|
-
|
|
618
|
-
var _this = this;
|
|
587
|
+
handleHttpServerResponse(data) {
|
|
619
588
|
data = this.encrypt(data);
|
|
620
589
|
this.tcpSocket.write(data, this.handleTCPSocketWriteFulfilled.bind(this));
|
|
621
590
|
debugCon("[%s] HTTP Response is finished", this.remoteAddress);
|
|
622
591
|
this.handlingRequest = false;
|
|
623
592
|
if (this.state === 3 /* HAPConnectionState.TO_BE_TEARED_DOWN */) {
|
|
624
|
-
setTimeout(
|
|
593
|
+
setTimeout(() => this.close(), 10);
|
|
625
594
|
}
|
|
626
595
|
else if (this.state < 3 /* HAPConnectionState.TO_BE_TEARED_DOWN */) {
|
|
627
596
|
if (!this.eventsTimer || this.eventsQueuedForImmediateDelivery) {
|
|
@@ -630,15 +599,15 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
630
599
|
this.writeQueuedEventNotifications();
|
|
631
600
|
}
|
|
632
601
|
}
|
|
633
|
-
}
|
|
634
|
-
|
|
602
|
+
}
|
|
603
|
+
handleTCPSocketWriteFulfilled() {
|
|
635
604
|
this.lastSocketOperation = new Date().getTime();
|
|
636
|
-
}
|
|
637
|
-
|
|
605
|
+
}
|
|
606
|
+
onTCPSocketError(err) {
|
|
638
607
|
debugCon("[%s] Client connection error: %s", this.remoteAddress, err.message);
|
|
639
608
|
// onTCPSocketClose will be called next
|
|
640
|
-
}
|
|
641
|
-
|
|
609
|
+
}
|
|
610
|
+
onTCPSocketClose() {
|
|
642
611
|
this.state = 5 /* HAPConnectionState.CLOSED */;
|
|
643
612
|
debugCon("[%s] Client connection closed", this.remoteAddress);
|
|
644
613
|
if (this.httpSocket) {
|
|
@@ -647,159 +616,91 @@ var HAPConnection = /** @class */ (function (_super) {
|
|
|
647
616
|
this.internalHttpServer.close();
|
|
648
617
|
this.emit("closed" /* HAPConnectionEvent.CLOSED */); // sending final closed event
|
|
649
618
|
this.removeAllListeners(); // cleanup listeners, we are officially dead now
|
|
650
|
-
}
|
|
651
|
-
|
|
619
|
+
}
|
|
620
|
+
onHttpServerError(err) {
|
|
652
621
|
debugCon("[%s] HTTP server error: %s", this.remoteAddress, err.message);
|
|
653
622
|
if (err.code === "EADDRINUSE") {
|
|
654
623
|
this.internalHttpServerPort = undefined;
|
|
655
624
|
this.internalHttpServer.close();
|
|
656
625
|
this.internalHttpServer.listen(0, this.internalHttpServerAddress = (0, net_utils_1.getOSLoopbackAddressIfAvailable)());
|
|
657
626
|
}
|
|
658
|
-
}
|
|
659
|
-
|
|
627
|
+
}
|
|
628
|
+
onHttpServerClose() {
|
|
660
629
|
debugCon("[%s] HTTP server was closed", this.remoteAddress);
|
|
661
630
|
// make sure the iOS side is closed as well
|
|
662
631
|
this.close();
|
|
663
|
-
}
|
|
664
|
-
|
|
632
|
+
}
|
|
633
|
+
onHttpSocketError(err) {
|
|
665
634
|
debugCon("[%s] HTTP connection error: ", this.remoteAddress, err.message);
|
|
666
635
|
// onHttpSocketClose will be called next
|
|
667
|
-
}
|
|
668
|
-
|
|
636
|
+
}
|
|
637
|
+
onHttpSocketClose() {
|
|
669
638
|
debugCon("[%s] HTTP connection was closed", this.remoteAddress);
|
|
670
639
|
// we only support a single long-lived connection to our internal HTTP server. Since it's closed,
|
|
671
640
|
// we'll need to shut it down entirely.
|
|
672
641
|
this.internalHttpServer.close();
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
var infos = (_c = os_1.default.networkInterfaces()[this.networkInterface]) !== null && _c !== void 0 ? _c : [];
|
|
678
|
-
if (ipVersion === "ipv4") {
|
|
679
|
-
try {
|
|
680
|
-
for (var infos_1 = tslib_1.__values(infos), infos_1_1 = infos_1.next(); !infos_1_1.done; infos_1_1 = infos_1.next()) {
|
|
681
|
-
var info = infos_1_1.value;
|
|
682
|
-
// @ts-expect-error Nodejs 18+ uses the number 4 the string "IPv4"
|
|
683
|
-
if (info.family === "IPv4" || info.family === 4) {
|
|
684
|
-
return info.address;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
689
|
-
finally {
|
|
690
|
-
try {
|
|
691
|
-
if (infos_1_1 && !infos_1_1.done && (_a = infos_1.return)) _a.call(infos_1);
|
|
692
|
-
}
|
|
693
|
-
finally { if (e_6) throw e_6.error; }
|
|
694
|
-
}
|
|
642
|
+
}
|
|
643
|
+
getLocalAddress(ipVersion) {
|
|
644
|
+
const interfaceDetails = os_1.default.networkInterfaces()[this.networkInterface];
|
|
645
|
+
if (!interfaceDetails) {
|
|
695
646
|
throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
|
|
696
647
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
// @ts-expect-error Nodejs 18+ uses the number 6 instead of the string "IPv6"
|
|
703
|
-
if (info.family === "IPv6" || info.family === 6) {
|
|
704
|
-
if (!info.scopeid) {
|
|
705
|
-
return info.address;
|
|
706
|
-
}
|
|
707
|
-
else if (!localUniqueAddress) {
|
|
708
|
-
localUniqueAddress = info.address;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
714
|
-
finally {
|
|
715
|
-
try {
|
|
716
|
-
if (infos_2_1 && !infos_2_1.done && (_b = infos_2.return)) _b.call(infos_2);
|
|
717
|
-
}
|
|
718
|
-
finally { if (e_7) throw e_7.error; }
|
|
719
|
-
}
|
|
720
|
-
if (!localUniqueAddress) {
|
|
721
|
-
throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
|
|
722
|
-
}
|
|
723
|
-
return localUniqueAddress;
|
|
724
|
-
}
|
|
725
|
-
};
|
|
726
|
-
HAPConnection.getLocalNetworkInterface = function (socket) {
|
|
727
|
-
var e_8, _a, e_9, _b, e_10, _c, e_11, _d;
|
|
728
|
-
var localAddress = socket.localAddress;
|
|
729
|
-
if (localAddress.startsWith("::ffff:")) { // IPv4-Mapped IPv6 Address https://tools.ietf.org/html/rfc4291#section-2.5.5.2
|
|
730
|
-
localAddress = localAddress.substring(7);
|
|
731
|
-
}
|
|
732
|
-
else {
|
|
733
|
-
var index = localAddress.indexOf("%");
|
|
734
|
-
if (index !== -1) { // link-local ipv6
|
|
735
|
-
localAddress = localAddress.substring(0, index);
|
|
648
|
+
// Find our first local IPv4 address.
|
|
649
|
+
if (ipVersion === "ipv4") {
|
|
650
|
+
const ipv4Info = interfaceDetails.find(info => info.family === "IPv4");
|
|
651
|
+
if (ipv4Info) {
|
|
652
|
+
return ipv4Info.address;
|
|
736
653
|
}
|
|
654
|
+
throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface + ".");
|
|
737
655
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
try {
|
|
743
|
-
for (var _h = (e_9 = void 0, tslib_1.__values(infos !== null && infos !== void 0 ? infos : [])), _j = _h.next(); !_j.done; _j = _h.next()) {
|
|
744
|
-
var info = _j.value;
|
|
745
|
-
if (info.address === localAddress) {
|
|
746
|
-
return name;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
catch (e_9_1) { e_9 = { error: e_9_1 }; }
|
|
751
|
-
finally {
|
|
752
|
-
try {
|
|
753
|
-
if (_j && !_j.done && (_b = _h.return)) _b.call(_h);
|
|
754
|
-
}
|
|
755
|
-
finally { if (e_9) throw e_9.error; }
|
|
756
|
-
}
|
|
656
|
+
let localUniqueAddress;
|
|
657
|
+
for (const v6entry of interfaceDetails.filter(entry => entry.family === "IPv6")) {
|
|
658
|
+
if (!v6entry.scopeid) {
|
|
659
|
+
return v6entry.address;
|
|
757
660
|
}
|
|
661
|
+
localUniqueAddress ??= v6entry.address;
|
|
758
662
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
try {
|
|
762
|
-
if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
|
|
763
|
-
}
|
|
764
|
-
finally { if (e_8) throw e_8.error; }
|
|
663
|
+
if (localUniqueAddress) {
|
|
664
|
+
return localUniqueAddress;
|
|
765
665
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
666
|
+
throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
|
|
667
|
+
}
|
|
668
|
+
static getLocalNetworkInterface(socket) {
|
|
669
|
+
let localAddress = socket.localAddress;
|
|
670
|
+
// Grab the list of network interfaces.
|
|
671
|
+
const interfaces = os_1.default.networkInterfaces();
|
|
672
|
+
// Default to the first non-loopback interface we see.
|
|
673
|
+
const defaultInterface = () => Object.entries(interfaces).find(([, addresses]) => addresses?.some(address => !address.internal))?.[0] ?? "unknown";
|
|
674
|
+
// No local address return our default.
|
|
675
|
+
if (!localAddress) {
|
|
676
|
+
return defaultInterface();
|
|
677
|
+
}
|
|
678
|
+
// Handle IPv4-mapped IPv6 addresses.
|
|
679
|
+
localAddress = localAddress.replace(/^::ffff:/i, "");
|
|
680
|
+
// Handle edge cases where we have an IPv4-mapped IPv6 address without the requisite prefix.
|
|
681
|
+
if (/^::(?:\d{1,3}\.){3}\d{1,3}$/.test(localAddress)) {
|
|
682
|
+
localAddress = localAddress.replace(/^::/, "");
|
|
683
|
+
}
|
|
684
|
+
// Handle link-local IPv6 addresses.
|
|
685
|
+
localAddress = localAddress.split("%")[0];
|
|
686
|
+
// Let's find an exact match using the IP.
|
|
687
|
+
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
688
|
+
if (addresses?.some(({ address }) => address === localAddress)) {
|
|
689
|
+
return name;
|
|
790
690
|
}
|
|
791
691
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
692
|
+
// We couldn't find an interface to match the address from above, so we attempt to match subnets (see https://github.com/homebridge/HAP-NodeJS/issues/847).
|
|
693
|
+
const family = net_1.default.isIPv4(localAddress) ? "IPv4" : "IPv6";
|
|
694
|
+
// Let's find a match based on the subnet.
|
|
695
|
+
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
696
|
+
if (addresses?.some(entry => entry.family === family && (0, domain_formatter_1.getNetAddress)(localAddress, entry.netmask) === (0, domain_formatter_1.getNetAddress)(entry.address, entry.netmask))) {
|
|
697
|
+
return name;
|
|
796
698
|
}
|
|
797
|
-
finally { if (e_10) throw e_10.error; }
|
|
798
699
|
}
|
|
799
|
-
console.log("WARNING
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
700
|
+
console.log("WARNING: unable to determine which interface to use for socket coming from " + socket.remoteAddress + ":" + socket.remotePort + " to " +
|
|
701
|
+
socket.localAddress + ".");
|
|
702
|
+
return defaultInterface();
|
|
703
|
+
}
|
|
704
|
+
}
|
|
804
705
|
exports.HAPConnection = HAPConnection;
|
|
805
706
|
//# sourceMappingURL=eventedhttp.js.map
|