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