homebridge-smartsystem 6.8.4 → 6.8.5
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 +2 -0
- package/package.json +2 -2
- package/server/proxy.js +94 -49
- package/server/proxy.ts +114 -57
package/README.md
CHANGED
|
@@ -307,6 +307,8 @@ Homebridge UI version
|
|
|
307
307
|
- 1: max/min values for hbValues
|
|
308
308
|
- 2: HB up/down -> don't respond to motor unit's, only macro
|
|
309
309
|
- 3: remember request before the login kicked in (and redirect to that one, once the login was succesful)
|
|
310
|
+
- 4: now using only TCP sockets to the server
|
|
311
|
+
- 5: making it more robust / inline with repo-service version
|
|
310
312
|
|
|
311
313
|
## Hardware
|
|
312
314
|
A Raspberry Pi that connects to a Duotecno IP Node
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-smartsystem",
|
|
3
3
|
"displayname": "Duotecno Bridge",
|
|
4
|
-
"version": "6.8.
|
|
5
|
-
"description": "SmartServer (Proxy
|
|
4
|
+
"version": "6.8.5",
|
|
5
|
+
"description": "SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Johan Coppieters",
|
|
8
8
|
"homepage": "http://www.duotecno.be",
|
package/server/proxy.js
CHANGED
|
@@ -63,7 +63,7 @@ function error(str) {
|
|
|
63
63
|
if (config.uniqueId) {
|
|
64
64
|
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
65
65
|
}
|
|
66
|
-
function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId) {
|
|
66
|
+
function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId, count = 0) {
|
|
67
67
|
const cloudSocket = new net.Socket();
|
|
68
68
|
cloudSocket.connect(serverPort, server, () => {
|
|
69
69
|
log(`[PROXY] → [CLOUD] Connected to Cloud at ${server}:${serverPort}`);
|
|
@@ -75,27 +75,23 @@ function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId
|
|
|
75
75
|
log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
|
|
76
76
|
});
|
|
77
77
|
cloudSocket.on('error', (err) => {
|
|
78
|
-
error(`[PROXY] → [CLOUD] Failed to connect to Cloud: ${err.message}`);
|
|
78
|
+
error(`[PROXY] → [CLOUD] Failed to connect ${count + 1} times to the Cloud server: ${err.message}`);
|
|
79
|
+
if (count < 3) {
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId, count + 1);
|
|
82
|
+
}, 1000);
|
|
83
|
+
}
|
|
79
84
|
});
|
|
80
|
-
log(`[PROXY] → [CLOUD]
|
|
85
|
+
log(`[PROXY] → [CLOUD] Attempt ${count + 1} to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
|
|
81
86
|
}
|
|
82
87
|
exports.makeNewCloudConnection = makeNewCloudConnection;
|
|
83
88
|
////////////////
|
|
84
89
|
// Restarting //
|
|
85
90
|
////////////////
|
|
86
91
|
function cleanStart(restart = false) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
context.deviceSocket.end();
|
|
91
|
-
context.deviceSocket = null;
|
|
92
|
-
}
|
|
93
|
-
context.cloudSocket.end();
|
|
94
|
-
context.cloudSocket = null;
|
|
95
|
-
});
|
|
96
|
-
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
97
|
-
log(`[PROXY] → [CLOUD] Cleaned up all cloud connections.`);
|
|
98
|
-
}
|
|
92
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
93
|
+
log(`[PROXY] → [CLOUD] Cleaned up all ${gCloudConnections.length} cloud connections.`);
|
|
94
|
+
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
99
95
|
if (restart) {
|
|
100
96
|
if (config.uniqueId) {
|
|
101
97
|
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
@@ -122,7 +118,7 @@ class Context {
|
|
|
122
118
|
}
|
|
123
119
|
setupCloudSocket() {
|
|
124
120
|
// set up handlers for the cloud socket
|
|
125
|
-
this.cloudSocket.on('data', data => {
|
|
121
|
+
this.cloudSocket.on('data', (data) => {
|
|
126
122
|
this.handleDataFromCloud(data);
|
|
127
123
|
});
|
|
128
124
|
this.cloudSocket.on('close', () => {
|
|
@@ -165,8 +161,8 @@ class Context {
|
|
|
165
161
|
}
|
|
166
162
|
}
|
|
167
163
|
else if (this.deviceSocket.readyState === 'open') {
|
|
168
|
-
this.deviceSocket.write(
|
|
169
|
-
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().
|
|
164
|
+
this.deviceSocket.write(data);
|
|
165
|
+
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().trim()}`);
|
|
170
166
|
}
|
|
171
167
|
else {
|
|
172
168
|
warning(`[PROXY] → [DEVICE] Device socket is not open yet, waiting for connection... what to do with the data??`);
|
|
@@ -185,13 +181,13 @@ class Context {
|
|
|
185
181
|
}
|
|
186
182
|
log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
|
|
187
183
|
this.setUpDeviceSocket();
|
|
188
|
-
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().
|
|
189
|
-
this.deviceSocket.write(
|
|
184
|
+
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().trim()}`);
|
|
185
|
+
this.deviceSocket.write(data);
|
|
190
186
|
});
|
|
191
187
|
}
|
|
192
188
|
else if (this.deviceSocket.readyState === 'open') {
|
|
193
189
|
warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`);
|
|
194
|
-
this.deviceSocket.write(
|
|
190
|
+
this.deviceSocket.write(data);
|
|
195
191
|
}
|
|
196
192
|
else {
|
|
197
193
|
error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`);
|
|
@@ -204,8 +200,8 @@ class Context {
|
|
|
204
200
|
}
|
|
205
201
|
this.deviceSocket.on('data', (data) => {
|
|
206
202
|
const message = data.toString('utf-8');
|
|
207
|
-
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.
|
|
208
|
-
this.cloudSocket.write(
|
|
203
|
+
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.trim()}`);
|
|
204
|
+
this.cloudSocket.write(data);
|
|
209
205
|
});
|
|
210
206
|
this.deviceSocket.on('close', () => {
|
|
211
207
|
log(`[DEVICE] → [PROXY] Device socket closed`);
|
|
@@ -216,33 +212,43 @@ class Context {
|
|
|
216
212
|
this.removeConnection();
|
|
217
213
|
});
|
|
218
214
|
}
|
|
219
|
-
|
|
220
|
-
// Clean up device socket
|
|
215
|
+
cleanupSockets() {
|
|
216
|
+
// Clean up the device socket
|
|
221
217
|
if (this.deviceSocket) {
|
|
222
218
|
if (!this.deviceSocket.destroyed) {
|
|
219
|
+
this.deviceSocket.removeAllListeners();
|
|
223
220
|
this.deviceSocket.end();
|
|
221
|
+
this.deviceSocket.destroy();
|
|
224
222
|
}
|
|
225
223
|
this.deviceSocket = null;
|
|
226
224
|
}
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
// Clean up the cloud socket
|
|
226
|
+
if (this.cloudSocket) {
|
|
227
|
+
if (!this.cloudSocket.destroyed) {
|
|
228
|
+
this.cloudSocket.removeAllListeners();
|
|
229
|
+
this.cloudSocket.end();
|
|
230
|
+
this.cloudSocket.destroy();
|
|
231
|
+
}
|
|
231
232
|
this.cloudSocket = null;
|
|
232
|
-
gCloudConnections.splice(index, 1);
|
|
233
|
-
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
234
233
|
}
|
|
234
|
+
}
|
|
235
|
+
removeConnection() {
|
|
236
|
+
const index = gCloudConnections.indexOf(this);
|
|
237
|
+
if (index !== -1)
|
|
238
|
+
gCloudConnections.splice(index, 1);
|
|
239
|
+
this.cleanupSockets();
|
|
240
|
+
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
235
241
|
if (gCloudConnections.length === 0) {
|
|
236
242
|
log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
|
|
237
243
|
// just to be sure: restart...
|
|
238
244
|
if (config.kind === "pm2") {
|
|
239
245
|
// PM2 should restart us.
|
|
240
246
|
log(`[PROXY] → [CLOUD] PM2 should restart us, exiting now.`);
|
|
241
|
-
process.exit();
|
|
247
|
+
setTimeout(() => process.exit(0), 100);
|
|
242
248
|
}
|
|
243
249
|
else if (config.kind === "solo") {
|
|
244
250
|
log(`[PROXY] → [CLOUD] Running solo, restarting proxy.`);
|
|
245
|
-
|
|
251
|
+
restart();
|
|
246
252
|
}
|
|
247
253
|
else {
|
|
248
254
|
log(`[PROXY] → [CLOUD] Running under gateway/homebridge, not restarting... not sure what to do here.`);
|
|
@@ -252,25 +258,18 @@ class Context {
|
|
|
252
258
|
///////////////
|
|
253
259
|
// Utilities //
|
|
254
260
|
///////////////
|
|
255
|
-
restart() {
|
|
256
|
-
console.log('🔄 Restarting...');
|
|
257
|
-
(0, child_process_1.spawn)(process_1.execPath, process_1.argv.slice(1), {
|
|
258
|
-
cwd: (0, process_1.cwd)(),
|
|
259
|
-
detached: true,
|
|
260
|
-
stdio: 'inherit',
|
|
261
|
-
});
|
|
262
|
-
process.exit();
|
|
263
|
-
}
|
|
264
261
|
// check if it's a heartbeat request from the server
|
|
265
262
|
isHeartbeatRequest(data) {
|
|
263
|
+
const msg = data.toString('utf-8').trim();
|
|
264
|
+
return msg === '[215,3]';
|
|
266
265
|
// hearbeatKind = (poll = 1, poll_old = 2, serve = 3)
|
|
267
|
-
return ((data[0] === 91) &&
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
266
|
+
// return ((data[0] === 91) && // [
|
|
267
|
+
// (data[1] === 50) && // 2
|
|
268
|
+
// (data[2] === 49) && // 1
|
|
269
|
+
// (data[3] === 53) && // 5
|
|
270
|
+
// (data[4] === 44) && // ,
|
|
271
|
+
// (data[5] === 51) && // 3
|
|
272
|
+
// (data[6] === 93)); // ]
|
|
274
273
|
}
|
|
275
274
|
// check if it's a server response after connecting and sending our uniqueID
|
|
276
275
|
isConnectionResponse(data) {
|
|
@@ -280,4 +279,50 @@ class Context {
|
|
|
280
279
|
}
|
|
281
280
|
}
|
|
282
281
|
exports.Context = Context;
|
|
282
|
+
function restart() {
|
|
283
|
+
console.log('🔄 Restarting...');
|
|
284
|
+
setTimeout(() => {
|
|
285
|
+
(0, child_process_1.spawn)(process_1.execPath, process_1.argv.slice(1), {
|
|
286
|
+
cwd: (0, process_1.cwd)(),
|
|
287
|
+
detached: true,
|
|
288
|
+
stdio: 'inherit',
|
|
289
|
+
});
|
|
290
|
+
process.exit(0);
|
|
291
|
+
}, 100);
|
|
292
|
+
}
|
|
293
|
+
// Graceful shutdown handler
|
|
294
|
+
function handleShutdown(signal) {
|
|
295
|
+
log(`[PROXY] → [SYSTEM] Received ${signal}, shutting down gracefully...`);
|
|
296
|
+
// Close all cloud connections
|
|
297
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
298
|
+
gCloudConnections.splice(0, gCloudConnections.length);
|
|
299
|
+
log(`[PROXY] → [SYSTEM] All connections closed. Exiting.`);
|
|
300
|
+
if (config.kind === "solo") {
|
|
301
|
+
log(`[PROXY] → [SYSTEM] Restarting proxy in solo mode...`);
|
|
302
|
+
restart();
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
log(`[PROXY] → [SYSTEM] Exiting without restart.`);
|
|
306
|
+
setTimeout(() => process.exit(0), 100);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Listen for termination signals
|
|
310
|
+
process.on('SIGINT', () => handleShutdown('SIGINT'));
|
|
311
|
+
process.on('SIGTERM', () => handleShutdown('SIGTERM'));
|
|
312
|
+
process.on('SIGINT', (err) => {
|
|
313
|
+
error(`[SYSTEM] received SIGINT`);
|
|
314
|
+
handleShutdown('SIGINT');
|
|
315
|
+
});
|
|
316
|
+
process.on('SIGTERM', (err) => {
|
|
317
|
+
error(`[SYSTEM] received SIGTERM`);
|
|
318
|
+
handleShutdown('SIGTERM');
|
|
319
|
+
});
|
|
320
|
+
process.on('uncaughtException', (err) => {
|
|
321
|
+
error(`[SYSTEM] Uncaught Exception: ${err.message}`);
|
|
322
|
+
handleShutdown('uncaughtException');
|
|
323
|
+
});
|
|
324
|
+
process.on('unhandledRejection', (reason) => {
|
|
325
|
+
error(`[SYSTEM] Unhandled Rejection: ${reason}`);
|
|
326
|
+
handleShutdown('unhandledRejection');
|
|
327
|
+
});
|
|
283
328
|
//# sourceMappingURL=proxy.js.map
|
package/server/proxy.ts
CHANGED
|
@@ -44,7 +44,7 @@ export function setProxyConfig(c?: ProxyConfig): ProxyConfig {
|
|
|
44
44
|
|
|
45
45
|
let config: ProxyConfig;
|
|
46
46
|
try {
|
|
47
|
-
setProxyConfig(require('../config-proxy.json'))
|
|
47
|
+
setProxyConfig(require('../config-proxy.json'));
|
|
48
48
|
} catch (e) {
|
|
49
49
|
setProxyConfig();
|
|
50
50
|
console.log("No config-proxy.json found, using default values from homebridge or other.");
|
|
@@ -82,7 +82,7 @@ if (config.uniqueId) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
export function makeNewCloudConnection(master: string, masterPort: number, server: string, serverPort: number, uniqueId: string) {
|
|
85
|
+
export function makeNewCloudConnection(master: string, masterPort: number, server: string, serverPort: number, uniqueId: string, count = 0) {
|
|
86
86
|
const cloudSocket = new net.Socket();
|
|
87
87
|
|
|
88
88
|
cloudSocket.connect(serverPort, server, () => {
|
|
@@ -98,10 +98,15 @@ export function makeNewCloudConnection(master: string, masterPort: number, serve
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
cloudSocket.on('error', (err) => {
|
|
101
|
-
error(`[PROXY] → [CLOUD] Failed to connect to Cloud: ${err.message}`);
|
|
101
|
+
error(`[PROXY] → [CLOUD] Failed to connect ${count+1} times to the Cloud server: ${err.message}`);
|
|
102
|
+
if (count < 3) {
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId, count + 1);
|
|
105
|
+
}, 1000);
|
|
106
|
+
}
|
|
102
107
|
});
|
|
103
108
|
|
|
104
|
-
log(`[PROXY] → [CLOUD]
|
|
109
|
+
log(`[PROXY] → [CLOUD] Attempt ${count+1} to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
|
|
@@ -109,18 +114,10 @@ export function makeNewCloudConnection(master: string, masterPort: number, serve
|
|
|
109
114
|
// Restarting //
|
|
110
115
|
////////////////
|
|
111
116
|
export function cleanStart(restart: boolean = false) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
context.deviceSocket = null;
|
|
117
|
-
}
|
|
118
|
-
context.cloudSocket.end();
|
|
119
|
-
context.cloudSocket = null;
|
|
120
|
-
});
|
|
121
|
-
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
122
|
-
log(`[PROXY] → [CLOUD] Cleaned up all cloud connections.`);
|
|
123
|
-
}
|
|
117
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
118
|
+
log(`[PROXY] → [CLOUD] Cleaned up all ${gCloudConnections.length} cloud connections.`);
|
|
119
|
+
|
|
120
|
+
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
124
121
|
|
|
125
122
|
if (restart) {
|
|
126
123
|
if (config.uniqueId) {
|
|
@@ -135,11 +132,11 @@ export function cleanStart(restart: boolean = false) {
|
|
|
135
132
|
|
|
136
133
|
|
|
137
134
|
export class Context {
|
|
138
|
-
deviceSocket: net.Socket;
|
|
135
|
+
deviceSocket: net.Socket | null;
|
|
139
136
|
master: string;
|
|
140
137
|
masterPort: number;
|
|
141
138
|
|
|
142
|
-
cloudSocket: net.Socket;
|
|
139
|
+
cloudSocket: net.Socket | null;
|
|
143
140
|
server: string;
|
|
144
141
|
serverPort: number;
|
|
145
142
|
|
|
@@ -162,8 +159,8 @@ export class Context {
|
|
|
162
159
|
|
|
163
160
|
setupCloudSocket() {
|
|
164
161
|
// set up handlers for the cloud socket
|
|
165
|
-
this.cloudSocket.on('data', data => {
|
|
166
|
-
this.handleDataFromCloud(data
|
|
162
|
+
this.cloudSocket.on('data', (data: Buffer) => {
|
|
163
|
+
this.handleDataFromCloud(data);
|
|
167
164
|
});
|
|
168
165
|
|
|
169
166
|
this.cloudSocket.on('close', () => {
|
|
@@ -210,8 +207,8 @@ export class Context {
|
|
|
210
207
|
}
|
|
211
208
|
|
|
212
209
|
} else if (this.deviceSocket.readyState === 'open') {
|
|
213
|
-
this.deviceSocket.write(
|
|
214
|
-
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().
|
|
210
|
+
this.deviceSocket.write(data);
|
|
211
|
+
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().trim()}`);
|
|
215
212
|
|
|
216
213
|
} else {
|
|
217
214
|
warning(`[PROXY] → [DEVICE] Device socket is not open yet, waiting for connection... what to do with the data??`);
|
|
@@ -236,14 +233,14 @@ export class Context {
|
|
|
236
233
|
log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
|
|
237
234
|
|
|
238
235
|
this.setUpDeviceSocket();
|
|
239
|
-
|
|
240
|
-
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().
|
|
241
|
-
this.deviceSocket.write(
|
|
236
|
+
|
|
237
|
+
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().trim()}`);
|
|
238
|
+
this.deviceSocket.write(data);
|
|
242
239
|
});
|
|
243
240
|
|
|
244
241
|
} else if (this.deviceSocket.readyState === 'open') {
|
|
245
242
|
warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`);
|
|
246
|
-
this.deviceSocket.write(
|
|
243
|
+
this.deviceSocket.write(data);
|
|
247
244
|
|
|
248
245
|
} else {
|
|
249
246
|
error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`);
|
|
@@ -258,8 +255,8 @@ export class Context {
|
|
|
258
255
|
|
|
259
256
|
this.deviceSocket.on('data', (data: Buffer) => {
|
|
260
257
|
const message = data.toString('utf-8');
|
|
261
|
-
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.
|
|
262
|
-
this.cloudSocket.write(
|
|
258
|
+
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.trim()}`);
|
|
259
|
+
this.cloudSocket.write(data);
|
|
263
260
|
});
|
|
264
261
|
|
|
265
262
|
this.deviceSocket.on('close', () => {
|
|
@@ -273,23 +270,34 @@ export class Context {
|
|
|
273
270
|
});
|
|
274
271
|
}
|
|
275
272
|
|
|
276
|
-
|
|
277
|
-
// Clean up device socket
|
|
273
|
+
cleanupSockets() {
|
|
274
|
+
// Clean up the device socket
|
|
278
275
|
if (this.deviceSocket) {
|
|
279
276
|
if (!this.deviceSocket.destroyed) {
|
|
277
|
+
this.deviceSocket.removeAllListeners();
|
|
280
278
|
this.deviceSocket.end();
|
|
279
|
+
this.deviceSocket.destroy();
|
|
281
280
|
}
|
|
282
281
|
this.deviceSocket = null;
|
|
283
282
|
}
|
|
284
283
|
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
284
|
+
// Clean up the cloud socket
|
|
285
|
+
if (this.cloudSocket) {
|
|
286
|
+
if (!this.cloudSocket.destroyed) {
|
|
287
|
+
this.cloudSocket.removeAllListeners();
|
|
288
|
+
this.cloudSocket.end();
|
|
289
|
+
this.cloudSocket.destroy();
|
|
290
|
+
}
|
|
289
291
|
this.cloudSocket = null;
|
|
290
|
-
gCloudConnections.splice(index, 1);
|
|
291
|
-
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
292
292
|
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
removeConnection() {
|
|
296
|
+
const index = gCloudConnections.indexOf(this);
|
|
297
|
+
if (index !== -1) gCloudConnections.splice(index, 1);
|
|
298
|
+
|
|
299
|
+
this.cleanupSockets();
|
|
300
|
+
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
293
301
|
|
|
294
302
|
if (gCloudConnections.length === 0) {
|
|
295
303
|
log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
|
|
@@ -297,11 +305,11 @@ export class Context {
|
|
|
297
305
|
if (config.kind === "pm2") {
|
|
298
306
|
// PM2 should restart us.
|
|
299
307
|
log(`[PROXY] → [CLOUD] PM2 should restart us, exiting now.`);
|
|
300
|
-
process.exit();
|
|
308
|
+
setTimeout(() => process.exit(0), 100);
|
|
301
309
|
|
|
302
310
|
} else if (config.kind === "solo"){
|
|
303
311
|
log(`[PROXY] → [CLOUD] Running solo, restarting proxy.`);
|
|
304
|
-
|
|
312
|
+
restart();
|
|
305
313
|
|
|
306
314
|
} else {
|
|
307
315
|
log(`[PROXY] → [CLOUD] Running under gateway/homebridge, not restarting... not sure what to do here.`);
|
|
@@ -314,28 +322,18 @@ export class Context {
|
|
|
314
322
|
// Utilities //
|
|
315
323
|
///////////////
|
|
316
324
|
|
|
317
|
-
restart() {
|
|
318
|
-
console.log('🔄 Restarting...');
|
|
319
|
-
|
|
320
|
-
spawn(execPath, argv.slice(1), {
|
|
321
|
-
cwd: cwd(),
|
|
322
|
-
detached: true,
|
|
323
|
-
stdio: 'inherit',
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
process.exit();
|
|
327
|
-
}
|
|
328
|
-
|
|
329
325
|
// check if it's a heartbeat request from the server
|
|
330
326
|
isHeartbeatRequest(data: Buffer): boolean {
|
|
327
|
+
const msg = data.toString('utf-8').trim();
|
|
328
|
+
return msg === '[215,3]';
|
|
331
329
|
// hearbeatKind = (poll = 1, poll_old = 2, serve = 3)
|
|
332
|
-
return ((data[0] === 91) && // [
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
330
|
+
// return ((data[0] === 91) && // [
|
|
331
|
+
// (data[1] === 50) && // 2
|
|
332
|
+
// (data[2] === 49) && // 1
|
|
333
|
+
// (data[3] === 53) && // 5
|
|
334
|
+
// (data[4] === 44) && // ,
|
|
335
|
+
// (data[5] === 51) && // 3
|
|
336
|
+
// (data[6] === 93)); // ]
|
|
339
337
|
}
|
|
340
338
|
|
|
341
339
|
// check if it's a server response after connecting and sending our uniqueID
|
|
@@ -346,3 +344,62 @@ export class Context {
|
|
|
346
344
|
return (message.startsWith('[OK')) || (message.startsWith('[ERROR'));
|
|
347
345
|
}
|
|
348
346
|
}
|
|
347
|
+
|
|
348
|
+
function restart() {
|
|
349
|
+
console.log('🔄 Restarting...');
|
|
350
|
+
|
|
351
|
+
setTimeout(() => {
|
|
352
|
+
spawn(execPath, argv.slice(1), {
|
|
353
|
+
cwd: cwd(),
|
|
354
|
+
detached: true,
|
|
355
|
+
stdio: 'inherit',
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
process.exit(0);
|
|
359
|
+
}, 100);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
// Graceful shutdown handler
|
|
364
|
+
function handleShutdown(signal: string) {
|
|
365
|
+
log(`[PROXY] → [SYSTEM] Received ${signal}, shutting down gracefully...`);
|
|
366
|
+
|
|
367
|
+
// Close all cloud connections
|
|
368
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
369
|
+
gCloudConnections.splice(0, gCloudConnections.length);
|
|
370
|
+
|
|
371
|
+
log(`[PROXY] → [SYSTEM] All connections closed. Exiting.`);
|
|
372
|
+
|
|
373
|
+
if (config.kind === "solo") {
|
|
374
|
+
log(`[PROXY] → [SYSTEM] Restarting proxy in solo mode...`);
|
|
375
|
+
restart();
|
|
376
|
+
} else {
|
|
377
|
+
log(`[PROXY] → [SYSTEM] Exiting without restart.`);
|
|
378
|
+
setTimeout(() => process.exit(0), 100);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Listen for termination signals
|
|
383
|
+
process.on('SIGINT', () => handleShutdown('SIGINT'));
|
|
384
|
+
process.on('SIGTERM', () => handleShutdown('SIGTERM'));
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
process.on('SIGINT', (err) => {
|
|
388
|
+
error(`[SYSTEM] received SIGINT`);
|
|
389
|
+
handleShutdown('SIGINT');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
process.on('SIGTERM', (err) => {
|
|
393
|
+
error(`[SYSTEM] received SIGTERM`);
|
|
394
|
+
handleShutdown('SIGTERM');
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
process.on('uncaughtException', (err) => {
|
|
398
|
+
error(`[SYSTEM] Uncaught Exception: ${err.message}`);
|
|
399
|
+
handleShutdown('uncaughtException');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
process.on('unhandledRejection', (reason) => {
|
|
403
|
+
error(`[SYSTEM] Unhandled Rejection: ${reason}`);
|
|
404
|
+
handleShutdown('unhandledRejection');
|
|
405
|
+
});
|