tlsd 2.11.0 → 2.11.2
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 +3 -5
- package/package.json +1 -1
- package/tlsd.js +125 -198
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ run it this way:
|
|
|
25
25
|
|
|
26
26
|
npx tlsd
|
|
27
27
|
|
|
28
|
-
This way you can just use it to serve your local project in dev or
|
|
28
|
+
This way you can just use it to serve your local project in dev or TLS mode.
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
## tlsd init
|
|
@@ -65,7 +65,7 @@ Adds a domain to Greenlock so that you can serve site with HTTPS:
|
|
|
65
65
|
Displays the current version of tlsd
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
###
|
|
68
|
+
### TLS Mode
|
|
69
69
|
|
|
70
70
|
In HTTPS mode, you must specify the dir containing links/dirs for domains to serve
|
|
71
71
|
it will run on HTTP on 80 (redirecting to 443), HTTPS on 443, and magically
|
|
@@ -79,9 +79,7 @@ You must also add your domain to greenlock before running nodes or it won't be r
|
|
|
79
79
|
|
|
80
80
|
npx greenlock add --subject example.com --altnames example.com,www.example.com
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
The browser reload feature does not operate in production mode.
|
|
82
|
+
TLS mode serves files with Letsencrypt SSL certs magically.
|
|
85
83
|
|
|
86
84
|
|
|
87
85
|
|
package/package.json
CHANGED
package/tlsd.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
All Rights Reserved
|
|
5
|
-
*/
|
|
2
|
+
//Copyright 2024 Sleepless Software Inc.
|
|
3
|
+
//All Rights Reserved
|
|
6
4
|
|
|
7
5
|
const { path, http, https, fs, crypto, tls, } = require( "allcore" );
|
|
8
6
|
|
|
@@ -10,6 +8,11 @@ const connect = require( "connect" );
|
|
|
10
8
|
const websocket = require( "websocket" );
|
|
11
9
|
const serveStatic = require( "serve-static" )
|
|
12
10
|
|
|
11
|
+
const body_parser = require( "body-parser" ).json( { limit: "10mb" } );
|
|
12
|
+
const compression = require( "compression" )();
|
|
13
|
+
const cors = require( "cors" )();
|
|
14
|
+
const queryString = require( "querystring" );
|
|
15
|
+
|
|
13
16
|
require( "sleepless" ).globalize();
|
|
14
17
|
|
|
15
18
|
const L = log5.mkLog( "TLSD: " );
|
|
@@ -23,7 +26,7 @@ function usage() {
|
|
|
23
26
|
const base = path.basename( module.filename );
|
|
24
27
|
log(
|
|
25
28
|
`Usage:
|
|
26
|
-
|
|
29
|
+
TLS mode:
|
|
27
30
|
DOMAINS_ROOT=./my_domains MAINTAINER_EMAIL=foo@bar.com VERBOSITY=4 node ` + base + `
|
|
28
31
|
Dev mode:
|
|
29
32
|
node ` + base + ` site_root listen_port verbosity
|
|
@@ -33,7 +36,7 @@ function usage() {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
let seq = 0;
|
|
36
|
-
|
|
39
|
+
function next_seq() {
|
|
37
40
|
seq += 1;
|
|
38
41
|
return seq;
|
|
39
42
|
}
|
|
@@ -41,7 +44,7 @@ const next_seq = function() {
|
|
|
41
44
|
|
|
42
45
|
// Handles incoming RPC msgs.
|
|
43
46
|
// Tries to load the rpc handler module from root and if successful, passes the msg to it
|
|
44
|
-
|
|
47
|
+
function rpc_handler( root, msg, xport, _okay, _fail ) {
|
|
45
48
|
|
|
46
49
|
let ll = toInt( msg.log_level );
|
|
47
50
|
if( ll > 0 ) {
|
|
@@ -91,7 +94,7 @@ const rpc_handler = function( root, msg, xport, _okay, _fail ) {
|
|
|
91
94
|
|
|
92
95
|
// Glue function to call rpc handler module and then return response
|
|
93
96
|
// via the websockets msg object
|
|
94
|
-
|
|
97
|
+
function ws_msg_handler( root, msg ) {
|
|
95
98
|
rpc_handler( root, msg.msg, "WS", data => {
|
|
96
99
|
msg.reply( data );
|
|
97
100
|
}, error => {
|
|
@@ -101,115 +104,113 @@ const ws_msg_handler = function( root, msg ) {
|
|
|
101
104
|
};
|
|
102
105
|
|
|
103
106
|
|
|
104
|
-
//
|
|
105
|
-
// XXX This has got to be inefficient at best, as it's creating a
|
|
106
|
-
// connect app on every request. No idea how much of this gets optimized
|
|
107
|
-
// out by V8.
|
|
108
|
-
const basic_handler = function( root ) {
|
|
109
|
-
|
|
110
|
-
const app = connect();
|
|
111
|
-
|
|
112
|
-
app.use( require( "body-parser" ).json( { limit: "10mb" } ) );
|
|
113
|
-
app.use( require( "compression" )() );
|
|
114
|
-
app.use( require( "cors" )() ); // allow requests from other domains
|
|
115
|
-
|
|
116
|
-
// populate req.query
|
|
117
|
-
app.use( function( req, res, next ) {
|
|
118
|
-
req.query = require( "querystring" ).parse( req._parsedUrl.query );
|
|
119
|
-
next();
|
|
120
|
-
} );
|
|
121
|
-
|
|
122
|
-
// logger
|
|
123
|
-
app.use( function( req, res, next ) {
|
|
124
|
-
const host = req.headers[ "host" ];
|
|
125
|
-
let { method, url, query, body } = req;
|
|
126
|
-
url = url.split( "?" ).shift();
|
|
127
|
-
I( host + ": " + method + " " + o2j(url) + " " + o2j( query, null, 2 ) + "/" + o2j( body, null, 2 ) );
|
|
128
|
-
next();
|
|
129
|
-
} );
|
|
130
|
-
|
|
131
|
-
// Intercept and service POST requests for "/rpc"
|
|
132
|
-
app.use( function( req, res, next ) {
|
|
133
|
-
|
|
134
|
-
let { method, url, query, body } = req;
|
|
135
|
-
|
|
136
|
-
url = url.split( "?" ).shift();
|
|
137
|
-
|
|
138
|
-
// if not an rpc request, pass on to static
|
|
139
|
-
if( url != "/rpc" && url != "/rpc/" ) {
|
|
140
|
-
next();
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Only allow POSTS to /rpc in production mode.
|
|
145
|
-
// In dev mode, GET is allowed as well with query args.
|
|
146
|
-
let input = body;
|
|
147
|
-
if( method != "POST" ) {
|
|
148
|
-
if( ! ( method == "GET" && dev_mode ) ) {
|
|
149
|
-
W( "GET /rpc is disallowed in production mode" );
|
|
150
|
-
res.writeHead( 405 ); // Method Not Allowed
|
|
151
|
-
res.end();
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
input = query;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Set up callbacks
|
|
158
|
-
const done = ( error, data ) => {
|
|
159
|
-
res.writeHead( 200, {
|
|
160
|
-
"Content-Type": "application/json",
|
|
161
|
-
"Cache-Control": "no-store",
|
|
162
|
-
});
|
|
163
|
-
res.write( o2j( data ) );
|
|
164
|
-
res.end();
|
|
165
|
-
};
|
|
166
|
-
const okay = ( data ) => { done( null, data ); };
|
|
167
|
-
// XXX wrong
|
|
168
|
-
const fail = ( error, body ) => { done( error, body ); };
|
|
107
|
+
// -----------------------
|
|
169
108
|
|
|
170
|
-
// Summon the rpc handler for the domain root.
|
|
171
|
-
rpc_handler( root, input, "REST", okay, fail );
|
|
172
109
|
|
|
173
|
-
|
|
110
|
+
// Handler that populates the req.query var with what's in query args
|
|
111
|
+
function populate_query( req, res, next ) {
|
|
112
|
+
req.query = queryString.parse( req._parsedUrl.query );
|
|
113
|
+
next();
|
|
114
|
+
}
|
|
174
115
|
|
|
116
|
+
// Simple logging handler
|
|
117
|
+
function logger( req, res, next ) {
|
|
118
|
+
const host = req.headers[ "host" ];
|
|
119
|
+
let { method, url, query, body } = req;
|
|
120
|
+
url = url.split( "?" ).shift();
|
|
121
|
+
I( host + ": " + method + " " + o2j( url ) + " " + o2j( query, null, 2 ) + "/" + o2j( body, null, 2 ) );
|
|
122
|
+
next();
|
|
123
|
+
}
|
|
175
124
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
125
|
+
// Creates and returns a handler function that intercepts and services
|
|
126
|
+
// POST requests to "/rpc" endpoint.
|
|
127
|
+
function rpc_post( root ) {
|
|
128
|
+
return function( req, res, next ) {
|
|
129
|
+
let { method, url, query, body } = req;
|
|
181
130
|
|
|
131
|
+
url = url.split( "?" ).shift();
|
|
132
|
+
if( url != "/rpc" && url != "/rpc/" ) {
|
|
133
|
+
next();
|
|
134
|
+
return
|
|
135
|
+
}
|
|
182
136
|
|
|
183
|
-
|
|
184
|
-
|
|
137
|
+
// Only allow POSTS in TLS mode.
|
|
138
|
+
// In dev mode, GET is allowed as well with query args.
|
|
139
|
+
let input = body;
|
|
140
|
+
if( method != "POST" ) {
|
|
141
|
+
if( ! ( method == "GET" && dev_mode ) ) {
|
|
142
|
+
W( "GET /rpc is disallowed in TLS mode" );
|
|
143
|
+
res.writeHead( 405 ); // Method Not Allowed
|
|
144
|
+
res.end();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
input = query;
|
|
148
|
+
}
|
|
185
149
|
|
|
150
|
+
// Set up callbacks
|
|
151
|
+
const done = ( error, data ) => {
|
|
152
|
+
res.writeHead( 200, {
|
|
153
|
+
"Content-Type": "application/json",
|
|
154
|
+
"Cache-Control": "no-store",
|
|
155
|
+
});
|
|
156
|
+
res.write( o2j( data ) );
|
|
157
|
+
res.end();
|
|
158
|
+
};
|
|
159
|
+
const okay = ( data ) => { done( null, data ); };
|
|
160
|
+
// XXX wrong
|
|
161
|
+
const fail = ( error, body ) => { done( error, body ); };
|
|
162
|
+
|
|
163
|
+
// Summon the rpc handler for the domain root.
|
|
164
|
+
rpc_handler( root, input, "REST", okay, fail );
|
|
165
|
+
};
|
|
166
|
+
}
|
|
186
167
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
168
|
+
// Handler for tlsd's own static files.
|
|
169
|
+
// This is so that "GET /rpc/rpc.js" can return the client-side code for
|
|
170
|
+
// sending WS RPC messages (similar to how socket.io works).
|
|
171
|
+
const rpc_static = serveStatic( __dirname + "/rpc_static" );
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
// Creates and returns a handler that checks for "/404" at root.
|
|
175
|
+
// If found, it redirects to it, otherwise it responds with 404.
|
|
176
|
+
function not_found( root ) {
|
|
177
|
+
return function( req, res, next ) {
|
|
178
|
+
// Can't just do a redirect here because if /404 doesn't exist, it will
|
|
179
|
+
// just go into a redirect loop. So test to see if the dir exists first.
|
|
180
|
+
let path = root + "/static/404";
|
|
181
|
+
is_dir( path, so => {
|
|
182
|
+
if( so ) {
|
|
183
|
+
let p404 = "/404/?original_path=" + encodeURIComponent( req.url );
|
|
184
|
+
res.writeHead( 302, { "Location": p404, } ); // redirect to path
|
|
199
185
|
} else {
|
|
200
|
-
|
|
186
|
+
res.writeHead( 404 );
|
|
201
187
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
188
|
+
res.end();
|
|
189
|
+
} );
|
|
190
|
+
}
|
|
191
|
+
}
|
|
205
192
|
|
|
193
|
+
// Create and return a connect app/function that implements
|
|
194
|
+
// default (basic) functionality.
|
|
195
|
+
function basic_handler( root ) {
|
|
196
|
+
const app = connect();
|
|
197
|
+
app.use( body_parser );
|
|
198
|
+
app.use( compression );
|
|
199
|
+
app.use( cors ); // allow requests from other domains
|
|
200
|
+
app.use( populate_query );
|
|
201
|
+
app.use( logger );
|
|
202
|
+
app.use( rpc_post( root ) );
|
|
203
|
+
app.use( rpc_static );
|
|
204
|
+
app.use( serveStatic( root + "/static" ) ); // static files for domain
|
|
205
|
+
app.use( not_found( root ) );
|
|
206
206
|
return app;
|
|
207
|
-
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
|
|
210
|
+
const cached_basic_handlers = {};
|
|
211
|
+
|
|
211
212
|
// Handle REST calls (as opposed to websocket messages)
|
|
212
|
-
|
|
213
|
+
function rest_handler( root, req, rsp ) {
|
|
213
214
|
|
|
214
215
|
I( req.headers[ "host" ] + ": " + req.method + " " + req.url );
|
|
215
216
|
D( "rest_handler root: " + root );
|
|
@@ -233,21 +234,31 @@ const rest_handler = function( root, req, rsp ) {
|
|
|
233
234
|
D( "Using custom REST handler loaded from "+root );
|
|
234
235
|
call( handler );
|
|
235
236
|
} catch( err ) {
|
|
236
|
-
//
|
|
237
|
-
|
|
237
|
+
// attempt to load custom handler threw an exception
|
|
238
|
+
|
|
239
|
+
let handler = cached_basic_handlers[ root ];
|
|
240
|
+
if( ! handler ) {
|
|
241
|
+
handler = basic_handler( root );
|
|
242
|
+
cached_basic_handlers[ root ] = handler;
|
|
243
|
+
}
|
|
244
|
+
|
|
238
245
|
D( "Using basic REST handler" );
|
|
239
246
|
call( handler );
|
|
240
247
|
}
|
|
241
248
|
};
|
|
242
249
|
|
|
243
250
|
|
|
251
|
+
// -----------------------
|
|
252
|
+
|
|
253
|
+
|
|
244
254
|
// tracked websocket connections
|
|
245
255
|
const ws_connections = {};
|
|
246
256
|
|
|
247
257
|
|
|
248
|
-
|
|
258
|
+
// Associate a websocket message handler (msg_handler) to a webserver instance (server)
|
|
259
|
+
function ws_attach( server, msg_handler ) {
|
|
249
260
|
|
|
250
|
-
const wsd = new websocket.server( { httpServer, autoAcceptConnections: false, } );
|
|
261
|
+
const wsd = new websocket.server( { httpServer: server, autoAcceptConnections: false, } );
|
|
251
262
|
|
|
252
263
|
wsd.on( "request", function( wsreq ) {
|
|
253
264
|
V( "WS: connection request from "+wsreq.remoteAddress+" "+wsreq.resource )
|
|
@@ -256,7 +267,7 @@ const ws_attach = function( httpServer, msg_handler ) {
|
|
|
256
267
|
|
|
257
268
|
const socket = wsreq.accept( null, wsreq.origin );
|
|
258
269
|
|
|
259
|
-
const name = "ws-
|
|
270
|
+
const name = "ws-conn-"+next_seq(); // XXX just use the websocket id
|
|
260
271
|
|
|
261
272
|
// send msg to connected client
|
|
262
273
|
const send = function( msg , cb ) {
|
|
@@ -318,84 +329,6 @@ const ws_attach = function( httpServer, msg_handler ) {
|
|
|
318
329
|
};
|
|
319
330
|
|
|
320
331
|
|
|
321
|
-
|
|
322
|
-
/*
|
|
323
|
-
const build_sass = function( path ) {
|
|
324
|
-
const sass = require( "sass" );
|
|
325
|
-
const result = sass.compile( path );
|
|
326
|
-
fs.writeFileSync( path + ".css", result.css, "utf8" );
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
const auto_builders = [
|
|
330
|
-
[ /\.(sass|scss)$/, build_sass ],
|
|
331
|
-
];
|
|
332
|
-
|
|
333
|
-
// Do auto-build processes (dev mode only)
|
|
334
|
-
const auto_build = function( paths ) {
|
|
335
|
-
|
|
336
|
-
for( let path of paths ) {
|
|
337
|
-
for( const bldr of auto_builders ) {
|
|
338
|
-
if( bldr[ 0 ].test( path ) ) {
|
|
339
|
-
bldr[ 1 ]( path );
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// Engage the filesystem watcher (dev mode only)
|
|
349
|
-
const enable_fs_watch = function( root ) {
|
|
350
|
-
|
|
351
|
-
let paths_noted = {};
|
|
352
|
-
|
|
353
|
-
let hot_reload_tid = null;
|
|
354
|
-
let hot_reload_count = 0;
|
|
355
|
-
|
|
356
|
-
function hot_reload_tick() {
|
|
357
|
-
hot_reload_count -= 1; // reduce count by 1
|
|
358
|
-
if( hot_reload_count <= 0 ) {
|
|
359
|
-
// counter reached 0
|
|
360
|
-
clearInterval( hot_reload_tid ); // turn off the ticker
|
|
361
|
-
hot_reload_tid = null;
|
|
362
|
-
|
|
363
|
-
auto_build( Object.keys( paths_noted ) );
|
|
364
|
-
paths_noted = {};
|
|
365
|
-
|
|
366
|
-
// send RELOAD msg to all client that have an active ws connection
|
|
367
|
-
for( let conn of Object.values( ws_connections ) ) {
|
|
368
|
-
conn.send( { msg: "RELOAD" } ); // send RELOAD msg to browser
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function note( evt, path ) {
|
|
374
|
-
// A change of some sort was seen
|
|
375
|
-
// Hundreds of these can happen very rapidly, so the timer stuff
|
|
376
|
-
// above is used to delay sending the RELOAD command until
|
|
377
|
-
// a second or so has passed without any more calls to this function
|
|
378
|
-
|
|
379
|
-
paths_noted[ path ] = evt; // XXX
|
|
380
|
-
//D( "noted fs change: [" + evt + "] " + path );
|
|
381
|
-
|
|
382
|
-
hot_reload_count = 5;
|
|
383
|
-
if( hot_reload_tid === null ) {
|
|
384
|
-
// ticker isn't running, so start it
|
|
385
|
-
hot_reload_tid = setInterval( hot_reload_tick, 200 );
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
// tell OS to start watching for any changes in root
|
|
391
|
-
fs.watch( root, { recursive: true, }, function( evt, path ) {
|
|
392
|
-
note( evt, root + "/" + path );
|
|
393
|
-
} );
|
|
394
|
-
|
|
395
|
-
};
|
|
396
|
-
*/
|
|
397
|
-
|
|
398
|
-
|
|
399
332
|
// -----------------------
|
|
400
333
|
|
|
401
334
|
const argv = process.argv;
|
|
@@ -407,7 +340,7 @@ if( argv.length == 2 ) {
|
|
|
407
340
|
if( DOMAINS_ROOT && MAINTAINER_EMAIL && VERBOSITY ) {
|
|
408
341
|
L( toInt( VERBOSITY ) );
|
|
409
342
|
|
|
410
|
-
I( "===
|
|
343
|
+
I( "=== TLS MODE ===" );
|
|
411
344
|
V( "DOMAINS_ROOT: " + DOMAINS_ROOT );
|
|
412
345
|
V( "MAINTAINER_EMAIL: " + MAINTAINER_EMAIL );
|
|
413
346
|
V( "VERBOSITY: " + VERBOSITY );
|
|
@@ -417,12 +350,11 @@ if( argv.length == 2 ) {
|
|
|
417
350
|
maintainerEmail: MAINTAINER_EMAIL,
|
|
418
351
|
configDir: "./greenlock.d",
|
|
419
352
|
cluster: false,
|
|
420
|
-
//notify
|
|
421
353
|
} ).ready( glx => {
|
|
422
354
|
|
|
423
|
-
var
|
|
355
|
+
var server = glx.httpsServer();
|
|
424
356
|
|
|
425
|
-
ws_attach(
|
|
357
|
+
ws_attach( server, ( msg, conn, domain ) => {
|
|
426
358
|
const root = path.resolve( DOMAINS_ROOT + "/" + domain );
|
|
427
359
|
ws_msg_handler( root, msg );
|
|
428
360
|
} );
|
|
@@ -456,22 +388,17 @@ if( argv.length == 5 ) {
|
|
|
456
388
|
V( "SITE_ROOT: " + SITE_ROOT );
|
|
457
389
|
V( "PORT: " + PORT );
|
|
458
390
|
|
|
459
|
-
const
|
|
391
|
+
const server = http.createServer( ( req, res ) => {
|
|
460
392
|
rest_handler( SITE_ROOT, req, res );
|
|
461
393
|
} );
|
|
462
394
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
ws_attach( httpd, ( msg, conn, domain ) => {
|
|
467
|
-
ws_msg_handler( SITE_ROOT, msg );
|
|
468
|
-
} );
|
|
395
|
+
ws_attach( server, ( msg, conn, domain ) => {
|
|
396
|
+
ws_msg_handler( SITE_ROOT, msg );
|
|
397
|
+
} );
|
|
469
398
|
|
|
470
|
-
|
|
471
|
-
// enable_fs_watch( SITE_ROOT );
|
|
399
|
+
server.listen( toInt( PORT ), () => {
|
|
472
400
|
|
|
473
401
|
I( "Listening on " + PORT + " & serving from " + SITE_ROOT );
|
|
474
|
-
|
|
475
402
|
} );
|
|
476
403
|
|
|
477
404
|
} else {
|