homebridge-smartsystem 6.8.13 → 7.0.1
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/.vscode/launch.json +15 -4
- package/MIXINS-GUIDE.md +210 -0
- package/README.md +32 -0
- package/SETTINGS-UPDATE.md +195 -0
- package/TESTING-GUIDE.md +220 -0
- package/WEBSOCKET-API.md +303 -0
- package/accessories/accessory.js +20 -4
- package/accessories/accessory.ts +19 -4
- package/accessories/bulb.js +0 -5
- package/accessories/bulb.ts +1 -5
- package/accessories/dimmer.js +11 -3
- package/accessories/dimmer.ts +11 -4
- package/accessories/garagedoor.js +19 -9
- package/accessories/garagedoor.ts +21 -9
- package/accessories/lock.js +11 -3
- package/accessories/lock.ts +10 -3
- package/accessories/temperature.js +10 -2
- package/accessories/temperature.ts +9 -2
- package/accessories/windowcovering.js +30 -14
- package/accessories/windowcovering.ts +29 -15
- package/config.json +57 -19
- package/duotecno/types.js +48 -2
- package/duotecno/types.ts +75 -1
- package/package.json +12 -4
- package/raspberry-pi-setup.sh +80 -0
- package/server/HA-API.js +4 -1
- package/server/HA-API.ts +4 -1
- package/server/HB.js +6 -1
- package/server/HB.ts +5 -1
- package/server/mDNS.js +168 -0
- package/server/mDNS.ts +198 -0
- package/server/platform.js +7 -0
- package/server/platform.ts +8 -0
- package/server/proxy.js +58 -19
- package/server/proxy.ts +31 -3
- package/server/smartapp.js +397 -18
- package/server/smartapp.ts +432 -20
- package/server/socapp.js +2 -1
- package/server/socapp.ts +2 -1
- package/server/views/assets/Duotecno_logo_black.svg +10 -0
- package/server/views/assets/Duotecno_logo_white.svg +10 -0
- package/server/views/device-details.ejs +139 -0
- package/server/views/device-list.ejs +44 -0
- package/server/views/footer.ejs +0 -1
- package/server/views/head.ejs +9 -0
- package/server/views/homekit.ejs +2 -9
- package/server/views/link-details.ejs +2 -15
- package/server/views/link-list.ejs +2 -9
- package/server/views/login.ejs +8 -9
- package/server/views/master-detail.ejs +3 -9
- package/server/views/master-list.ejs +3 -10
- package/server/views/nav.ejs +44 -11
- package/server/views/node-details.ejs +1 -8
- package/server/views/power-binding.ejs +3 -10
- package/server/views/power-rule.ejs +3 -10
- package/server/views/power.ejs +8 -15
- package/server/views/proxy.ejs +1 -8
- package/server/views/scripts.ejs +10 -0
- package/server/views/service-list.ejs +2 -9
- package/server/views/settings.ejs +61 -22
- package/server/views/style.ejs +49 -5
- package/server/views/switch-details.ejs +2 -9
- package/server/views/switch-list.ejs +2 -9
- package/server/webapp.js +150 -11
- package/server/webapp.ts +166 -14
- package/{index.spec.js → testHB.js} +1 -1
- package/{run.js → testProxy.js} +2 -2
- package/{run.ts → testProxy.ts} +1 -1
- package/testWS.js +137 -0
- package/testWS.ts +174 -0
- package/run9998.js +0 -7
- package/run9998.ts +0 -6
- package/run9999.js +0 -7
- package/run9999.ts +0 -6
- /package/{index.spec.ts → testHB.ts} +0 -0
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<!--Let browser know website is optimized for mobile-->
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
8
|
-
<link rel="shortcut icon" href="/files/favicon.ico" type="image/x-icon" />
|
|
9
|
-
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
10
|
-
|
|
11
|
-
<%- include('/views/style'); -%>
|
|
4
|
+
<%- include('/views/head'); -%>
|
|
12
5
|
</head>
|
|
13
6
|
|
|
14
7
|
<body>
|
|
15
|
-
<%- include('/views/nav'); -%>
|
|
8
|
+
<%- include('/views/nav', this); -%>
|
|
16
9
|
|
|
17
10
|
<form method="POST" action="/settings">
|
|
18
11
|
<input type="hidden" name="daction" value="save">
|
|
19
|
-
<h1>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
<h1>Settings</h1>
|
|
13
|
+
|
|
14
|
+
<% if (typeof message !== 'undefined' && message) { %>
|
|
15
|
+
<div class="row">
|
|
16
|
+
<div class="col s12">
|
|
17
|
+
<div class="card-panel <%= message.includes('successfully') ? 'green lighten-4' : 'orange lighten-4' %>">
|
|
18
|
+
<span class="<%= message.includes('successfully') ? 'green-text text-darken-4' : 'orange-text text-darken-4' %>">
|
|
19
|
+
<i class="material-icons left"><%= message.includes('successfully') ? 'check_circle' : 'info' %></i>
|
|
20
|
+
<%= message %>
|
|
21
|
+
</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<% } %>
|
|
24
26
|
|
|
27
|
+
<!--
|
|
28
|
+
TODO: Network IP configuration is temporarily disabled pending implementation fixes.
|
|
29
|
+
Focus is on testing the Devices WebSocket functionality first.
|
|
30
|
+
These fields will be re-enabled once DHCP configuration is properly implemented.
|
|
31
|
+
-->
|
|
32
|
+
|
|
33
|
+
<!-- NETWORK SETTINGS - TEMPORARILY DISABLED
|
|
25
34
|
<div class="row">
|
|
26
35
|
<div class="input-field col s6">
|
|
27
36
|
<input placeholder="" id="ip1" name="ip1" type="text" value="<%= config.network.ip1 %>">
|
|
@@ -39,17 +48,47 @@
|
|
|
39
48
|
<label for="nameservers">Nameservers</label>
|
|
40
49
|
</div>
|
|
41
50
|
</div>
|
|
51
|
+
END NETWORK SETTINGS -->
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
53
|
+
<h5>mDNS Configuration</h5>
|
|
54
|
+
<div class="row">
|
|
55
|
+
<div class="input-field col s12">
|
|
56
|
+
<input placeholder="duotecno-gateway" id="mdnsName" name="mdnsName" type="text" value="<%= config.mdnsName || 'duotecno-gateway' %>">
|
|
57
|
+
<label for="mdnsName">mDNS Service Name (e.g., duotecno-gateway → duotecno-gateway.local)</label>
|
|
47
58
|
</div>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="row">
|
|
62
|
+
<div class="col s12">
|
|
63
|
+
<button class="btn waves-effect waves-light" type="submit" name="action" value="save">
|
|
64
|
+
<i class="material-icons left">save</i>
|
|
65
|
+
Save Settings
|
|
66
|
+
</button>
|
|
67
|
+
<button class="btn waves-effect waves-light orange" type="submit" name="action" value="restart">
|
|
68
|
+
<i class="material-icons left">refresh</i>
|
|
69
|
+
Restart Server
|
|
70
|
+
</button>
|
|
51
71
|
</div>
|
|
52
|
-
</div
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<!-- SYSTEM ACTIONS - TEMPORARILY DISABLED
|
|
75
|
+
<div class="row">
|
|
76
|
+
<div class="col s12">
|
|
77
|
+
<button class="btn waves-effect waves-light red" type="submit" name="action" value="reboot">
|
|
78
|
+
<i class="material-icons left">power_settings_new</i>
|
|
79
|
+
Reboot System
|
|
80
|
+
</button>
|
|
81
|
+
<button class="btn waves-effect waves-light grey" type="submit" name="action" value="reset">
|
|
82
|
+
<i class="material-icons left">settings_backup_restore</i>
|
|
83
|
+
Reset to DHCP
|
|
84
|
+
</button>
|
|
85
|
+
<button class="btn waves-effect waves-light blue" type="submit" name="action" value="install">
|
|
86
|
+
<i class="material-icons left">system_update</i>
|
|
87
|
+
Install IP Settings
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
END SYSTEM ACTIONS -->
|
|
53
92
|
|
|
54
93
|
</form>
|
|
55
94
|
|
package/server/views/style.ejs
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
<style>
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/* Page layout with sticky footer */
|
|
3
|
+
html, body { height: 100%; margin: 0; display: flex; flex-direction: column }
|
|
4
|
+
body { min-height: 100vh }
|
|
5
|
+
|
|
6
|
+
/* Main content area - grows to fill space */
|
|
7
|
+
body > *:not(nav):not(.page-footer) { flex: 1 0 auto }
|
|
8
|
+
|
|
9
|
+
form { padding-left: 7px; padding-right: 9px; padding-bottom: 80px }
|
|
10
|
+
|
|
11
|
+
nav, nav .nav-wrapper { height: 64px !important; line-height: 64px !important; flex-shrink: 0 }
|
|
12
|
+
nav .brand-logo { left: 10px !important; -webkit-transform: none !important; transform: none !important }
|
|
13
|
+
|
|
4
14
|
h1 { font-size: 30px }
|
|
5
15
|
h1.btn { margin-top: 18px }
|
|
6
16
|
h1 .btn { margin-left: 10px }
|
|
7
|
-
a span { margin-bottom: 7px; display: inline-block
|
|
8
|
-
|
|
17
|
+
a span { margin-bottom: 7px; display: inline-block }
|
|
18
|
+
|
|
19
|
+
/* Sticky footer */
|
|
20
|
+
.page-footer { position: fixed; bottom: 0; left: 0; right: 0; width: 100%; flex-shrink: 0; z-index: 100; }
|
|
21
|
+
.page-footer p { text-align: right; padding-bottom: 4px; margin-right: 6px }
|
|
22
|
+
.page-footer a { float: left; margin-left: 10px; color: white; }
|
|
9
23
|
.page-footer { padding-top: 5px; padding-right: 6px }
|
|
10
24
|
.range-field { margin-bottom: -1em }
|
|
11
25
|
b { color: #ee6e73 }
|
|
@@ -14,7 +28,37 @@
|
|
|
14
28
|
.btn-floating.btn-small { width: 32px; height: 31px }
|
|
15
29
|
.rules p { display: inline-block; margin-bottom: 0px }
|
|
16
30
|
div.select-wrapper { max-width: 212px }
|
|
17
|
-
div.select-wrapper input.select-dropdown { font-size:
|
|
31
|
+
div.select-wrapper input.select-dropdown { font-size: 16px !important; height: 3rem !important; }
|
|
18
32
|
span.used { color: darkgrey }
|
|
19
33
|
.message { margin-left: 10px; font-size: 15px }
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/* Hamburger menu icon - positioned on right side */
|
|
37
|
+
nav .sidenav-trigger {
|
|
38
|
+
position: absolute; right: 10px; top: 0;
|
|
39
|
+
height: 64px; line-height: 64px; padding: 0 15px; margin: 0;
|
|
40
|
+
}
|
|
41
|
+
nav .sidenav-trigger i { line-height: 64px; font-size: 2rem }
|
|
42
|
+
|
|
43
|
+
/* Hide hamburger on desktop, show desktop menu */
|
|
44
|
+
@media only screen and (min-width: 993px) {
|
|
45
|
+
nav .sidenav-trigger { display: none !important; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Show hamburger on mobile, hide desktop menu */
|
|
49
|
+
@media only screen and (max-width: 992px) {
|
|
50
|
+
nav .sidenav-trigger { display: block !important; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Desktop navigation active state */
|
|
54
|
+
nav ul li.active { background-color: rgba(0, 0, 0, 0.1) }
|
|
55
|
+
|
|
56
|
+
/* Sidenav styling */
|
|
57
|
+
.sidenav { width: 280px; }
|
|
58
|
+
.sidenav .user-view { padding: 1px 32px 16px 16px }
|
|
59
|
+
.sidenav .user-view .name { font-size: 18px; font-weight: 500; margin-top: 16px; display: block; }
|
|
60
|
+
.sidenav li > a { height: auto; line-height: 1px; padding: 14px 32px; }
|
|
61
|
+
.sidenav .divider { margin: 8px 0; }
|
|
62
|
+
.sidenav li.active { background-color: rgba(0,0,0,0.05); }
|
|
63
|
+
|
|
20
64
|
</style>
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<!--Let browser know website is optimized for mobile-->
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
8
|
-
<link rel="shortcut icon" href="/files/favicon.ico" type="image/x-icon" />
|
|
9
|
-
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
10
|
-
|
|
11
|
-
<%- include('/views/style'); -%>
|
|
4
|
+
<%- include('/views/head'); -%>
|
|
12
5
|
</head>
|
|
13
6
|
|
|
14
7
|
<body>
|
|
@@ -18,7 +11,7 @@
|
|
|
18
11
|
<input type="hidden" name="daction" value="save">
|
|
19
12
|
<input type="hidden" name="id" value="<%= id %>">
|
|
20
13
|
<h1>
|
|
21
|
-
Switch #<%= id %>
|
|
14
|
+
Switch #<%= id+1 %>
|
|
22
15
|
<button class="right btn waves-effect waves-light" type="submit" name="action" value="cancel">Back</button>
|
|
23
16
|
<button class="right btn waves-effect waves-light" type="submit" name="action" value="delete">Delete</button>
|
|
24
17
|
<button class="right btn waves-effect waves-light" type="submit" name="action" value="change">Save</button>
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<!--Let browser know website is optimized for mobile-->
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
8
|
-
<link rel="shortcut icon" href="/files/favicon.ico" type="image/x-icon" />
|
|
9
|
-
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
10
|
-
|
|
11
|
-
<%- include('/views/style'); -%>
|
|
4
|
+
<%- include('/views/head'); -%>
|
|
12
5
|
</head>
|
|
13
6
|
|
|
14
7
|
<body>
|
|
15
|
-
<%- include('/views/nav'); -%>
|
|
8
|
+
<%- include('/views/nav', this); -%>
|
|
16
9
|
|
|
17
10
|
<form method="POST" action="/switches">
|
|
18
11
|
<h1>
|
package/server/webapp.js
CHANGED
|
@@ -23,6 +23,7 @@ const types = require("../duotecno/types");
|
|
|
23
23
|
const logger = require("../duotecno/logger");
|
|
24
24
|
const logger_1 = require("../duotecno/logger");
|
|
25
25
|
const child_process_1 = require("child_process");
|
|
26
|
+
const mDNS_1 = require("./mDNS");
|
|
26
27
|
;
|
|
27
28
|
const kLogoutHeader = { "Set-Cookie": "ticket=x" };
|
|
28
29
|
//////////////////////
|
|
@@ -48,6 +49,7 @@ class Context {
|
|
|
48
49
|
this.date = types.date;
|
|
49
50
|
this.datetime = types.datetime;
|
|
50
51
|
this.makeInt = types.makeInt;
|
|
52
|
+
this.cap = types.cap;
|
|
51
53
|
const bodyParams = qs.parse(body);
|
|
52
54
|
const urlParts = url.parse(req.url, true);
|
|
53
55
|
this.params = Object.assign(Object.assign({}, bodyParams), urlParts.query);
|
|
@@ -229,10 +231,26 @@ class WebApp extends base_1.Base {
|
|
|
229
231
|
this.token = ""; // "$#@!" + new Date().getTime() + "!@#$"; // should never match
|
|
230
232
|
this.user = "";
|
|
231
233
|
this.password = "";
|
|
234
|
+
this.mdnsService = null; // mDNS service for hostname and service discovery
|
|
232
235
|
this.port = 80;
|
|
233
236
|
(0, logger_1.log)("server", "Creating http server");
|
|
234
237
|
this.files = {};
|
|
235
|
-
this.
|
|
238
|
+
this.servers = [];
|
|
239
|
+
// Note: Server will be fully configured in serve() method
|
|
240
|
+
// We don't create it here anymore since we might need multiple servers
|
|
241
|
+
}
|
|
242
|
+
// Override this in subclasses to control template caching behavior
|
|
243
|
+
// Return true to always reload templates from disk (development mode)
|
|
244
|
+
// Return false to use preloaded cached templates (production mode)
|
|
245
|
+
isDebugMode() {
|
|
246
|
+
return false; // Default: use cached templates for performance
|
|
247
|
+
}
|
|
248
|
+
serve(onConnected) {
|
|
249
|
+
// Handle both single port and multiple ports
|
|
250
|
+
const ports = Array.isArray(this.port) ? this.port : [this.port];
|
|
251
|
+
(0, logger_1.log)("server", "WebApp - Start serving http on port(s): " + ports.join(", "));
|
|
252
|
+
// Create the request handler function (shared across all servers)
|
|
253
|
+
const requestHandler = (req, res) => {
|
|
236
254
|
this.doServe(req, res).then(result => {
|
|
237
255
|
res.writeHead(result.status, Object.assign({ 'Content-Type': result.type || 'text/html' }, result.header));
|
|
238
256
|
res.write(result.data);
|
|
@@ -242,14 +260,135 @@ class WebApp extends base_1.Base {
|
|
|
242
260
|
res.write(reason.toString());
|
|
243
261
|
res.end();
|
|
244
262
|
});
|
|
263
|
+
};
|
|
264
|
+
// Track how many servers are listening for the callback
|
|
265
|
+
let listeningCount = 0;
|
|
266
|
+
const totalPorts = ports.length;
|
|
267
|
+
const onListening = (port) => {
|
|
268
|
+
(0, logger_1.log)("server", `WebApp - Listening on port ${port}`);
|
|
269
|
+
listeningCount++;
|
|
270
|
+
// Call onConnected only after ALL servers are listening
|
|
271
|
+
if (listeningCount === totalPorts && onConnected) {
|
|
272
|
+
onConnected();
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
// Create a server for each port (each http.Server can only listen on ONE port)
|
|
276
|
+
ports.forEach((port) => {
|
|
277
|
+
const server = http.createServer(requestHandler);
|
|
278
|
+
// Set socket options to allow port reuse (prevents EADDRINUSE on restart)
|
|
279
|
+
server.on('listening', () => {
|
|
280
|
+
const address = server.address();
|
|
281
|
+
if (address && typeof address === 'object') {
|
|
282
|
+
onListening(address.port);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
this.servers.push(server);
|
|
286
|
+
// Listen with options to reuse address
|
|
287
|
+
server.listen({
|
|
288
|
+
port: port,
|
|
289
|
+
host: '::',
|
|
290
|
+
exclusive: false // Allow multiple processes to bind (for cluster mode)
|
|
291
|
+
});
|
|
245
292
|
});
|
|
246
293
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
294
|
+
/**
|
|
295
|
+
* Close all HTTP servers and cleanup
|
|
296
|
+
* Call this before restarting or shutting down
|
|
297
|
+
*/
|
|
298
|
+
closeServers() {
|
|
299
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
300
|
+
return new Promise((resolve) => {
|
|
301
|
+
if (this.servers.length === 0) {
|
|
302
|
+
resolve();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
let closedCount = 0;
|
|
306
|
+
const totalServers = this.servers.length;
|
|
307
|
+
const onClosed = () => {
|
|
308
|
+
closedCount++;
|
|
309
|
+
if (closedCount === totalServers) {
|
|
310
|
+
(0, logger_1.log)("server", "All HTTP servers closed");
|
|
311
|
+
this.servers = [];
|
|
312
|
+
resolve();
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
// Close each server
|
|
316
|
+
this.servers.forEach((server, index) => {
|
|
317
|
+
if (server.listening) {
|
|
318
|
+
server.close((error) => {
|
|
319
|
+
if (error) {
|
|
320
|
+
logger.err("server", `Error closing server ${index}: ${error.message}`);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
(0, logger_1.log)("server", `Server ${index} closed`);
|
|
324
|
+
}
|
|
325
|
+
onClosed();
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
onClosed();
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Initialize mDNS service with the given name and version
|
|
337
|
+
* Call this before serve() or during initialization
|
|
338
|
+
*/
|
|
339
|
+
initMDNS(name, version) {
|
|
340
|
+
try {
|
|
341
|
+
const ports = Array.isArray(this.port) ? this.port : [this.port];
|
|
342
|
+
this.mdnsService = new mDNS_1.MDNSService({
|
|
343
|
+
name,
|
|
344
|
+
port: ports,
|
|
345
|
+
version: version || '1.0.0'
|
|
346
|
+
});
|
|
347
|
+
this.mdnsService.init();
|
|
348
|
+
(0, logger_1.log)("webapp", "mDNS service initialized");
|
|
349
|
+
}
|
|
350
|
+
catch (e) {
|
|
351
|
+
(0, logger_1.err)("webapp", `Failed to initialize mDNS: ${e.message}`);
|
|
352
|
+
this.mdnsService = null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Register mDNS service and hostname
|
|
357
|
+
* Call this after server is listening (e.g., in onConnected callback)
|
|
358
|
+
*/
|
|
359
|
+
registerMDNS() {
|
|
360
|
+
if (!this.mdnsService) {
|
|
361
|
+
(0, logger_1.err)("webapp", "mDNS service not initialized, cannot register");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
this.mdnsService.register();
|
|
366
|
+
}
|
|
367
|
+
catch (e) {
|
|
368
|
+
(0, logger_1.err)("webapp", `Failed to register mDNS service: ${e.message}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Stop and cleanup mDNS service
|
|
373
|
+
*/
|
|
374
|
+
destroyMDNS() {
|
|
375
|
+
if (this.mdnsService) {
|
|
376
|
+
try {
|
|
377
|
+
this.mdnsService.destroy();
|
|
378
|
+
this.mdnsService = null;
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
(0, logger_1.err)("webapp", `Failed to destroy mDNS service: ${e.message}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
250
384
|
}
|
|
251
385
|
addFile(name, filename, type, enc) {
|
|
252
|
-
|
|
386
|
+
// Ensure HTML files have charset specified
|
|
387
|
+
let contentType = type || "text/html";
|
|
388
|
+
if (contentType === "text/html" || contentType.startsWith("text/html")) {
|
|
389
|
+
contentType = "text/html; charset=utf-8";
|
|
390
|
+
}
|
|
391
|
+
this.addContent(name, fs.readFileSync(filename, { encoding: enc || 'utf-8' }), contentType, filename);
|
|
253
392
|
}
|
|
254
393
|
addContent(name, content, type, filename) {
|
|
255
394
|
this.files[name] = { content, type, filename };
|
|
@@ -269,17 +408,17 @@ class WebApp extends base_1.Base {
|
|
|
269
408
|
return { status: 200, data: file.content, type: file.type };
|
|
270
409
|
}
|
|
271
410
|
html(html) {
|
|
272
|
-
return { status: 200, data: html, type: "text/html" };
|
|
411
|
+
return { status: 200, data: html, type: "text/html; charset=utf-8" };
|
|
273
412
|
}
|
|
274
413
|
ejs(name, context, objects, header = {}) {
|
|
275
414
|
// types.debug("webapp", context); !! circular ref in socket/http
|
|
276
|
-
//
|
|
277
|
-
let file = this.getFile(name);
|
|
278
|
-
// for non cached files
|
|
415
|
+
// In debug mode, always reload from disk to see changes without restart
|
|
416
|
+
let file = this.isDebugMode() ? null : this.getFile(name);
|
|
417
|
+
// for non cached files or debug mode
|
|
279
418
|
if (!file) {
|
|
280
419
|
try {
|
|
281
420
|
const filename = __dirname + "/views/" + name;
|
|
282
|
-
file = { content: fs.readFileSync(filename, { encoding: 'utf-8' }), type: "text/html", filename };
|
|
421
|
+
file = { content: fs.readFileSync(filename, { encoding: 'utf-8' }), type: "text/html; charset=utf-8", filename };
|
|
283
422
|
}
|
|
284
423
|
catch (e) {
|
|
285
424
|
return this.error(context, e.toString());
|
|
@@ -291,7 +430,7 @@ class WebApp extends base_1.Base {
|
|
|
291
430
|
context[key] = objects[key];
|
|
292
431
|
try {
|
|
293
432
|
const html = ejs.render(file.content, context, { root: __dirname, filename: file.filename });
|
|
294
|
-
return { status: 200, type: file.type, data: html, header };
|
|
433
|
+
return { status: 200, type: file.type || "text/html; charset=utf-8", data: html, header };
|
|
295
434
|
}
|
|
296
435
|
catch (e) {
|
|
297
436
|
return this.error(context, e.toString());
|