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.
Files changed (3) hide show
  1. package/README.md +3 -5
  2. package/package.json +1 -1
  3. 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 production mode.
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
- ### Production Mode
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
- Production mode serves files with Letsencrypt SSL certs magically.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlsd",
3
- "version": "2.11.0",
3
+ "version": "2.11.2",
4
4
  "description": "A server for web app prototyping with HTTPS and Websockets",
5
5
  "main": "tlsd.js",
6
6
  "bin": {
package/tlsd.js CHANGED
@@ -1,8 +1,6 @@
1
1
 
2
- /*
3
- Copyright 2023 Sleepless Software Inc.
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
- Production mode:
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
- const next_seq = function() {
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
- const rpc_handler = function( root, msg, xport, _okay, _fail ) {
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
- const ws_msg_handler = function( root, msg ) {
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
- // Return a handler function that implements default (basic) functionality.
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
- // Serve my (tlsd's) own static files
177
- // This is primarily so that GET /rpc/rpc.js can return the client-side code for
178
- // sending WS RPC messages (similar to how socket.io works) and providing the
179
- // browser hot-reload feature (which requires WS RPC)
180
- app.use( serveStatic( __dirname + "/rpc_static" ) );
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
- // Serve static files for the domain
184
- app.use( serveStatic( root + "/static" ) );
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
- // finally, if serveStatic can't service the request,
188
- // look for a 404/ dir and redirect to that if present
189
- app.use( function( req, res, next ) {
190
- // I can't just return a redirect here because if the /404 doesn't exist,
191
- // I will just go into a redirect loop, so I have to test to see if the
192
- // dir exists, and then if so, redirect to it, otherwise, just return 404.
193
- let { url, } = req;
194
- let p = root + "/static/404";
195
- is_dir( p, so => {
196
- if( so ) {
197
- let p404 = "/404/?original_path=" + encodeURIComponent( url );
198
- res.writeHead( 302, { "Location": p404, } );
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
- res.writeHead( 404 );
186
+ res.writeHead( 404 );
201
187
  }
202
- res.end();
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
- const rest_handler = function( root, req, rsp ) {
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
- // custom handler load attempt threw an exception
237
- const handler = basic_handler( root );
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
- const ws_attach = function( httpServer, msg_handler ) {
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-client-"+next_seq(); // XXX just use the websocket id
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( "=== PRODUCTION MODE ===" );
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 httpd = glx.httpsServer();
355
+ var server = glx.httpsServer();
424
356
 
425
- ws_attach( httpd, ( msg, conn, domain ) => {
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 httpd = http.createServer( ( req, res ) => {
391
+ const server = http.createServer( ( req, res ) => {
460
392
  rest_handler( SITE_ROOT, req, res );
461
393
  } );
462
394
 
463
- // XXX call ws_attach *before* calling listen() ?
464
- httpd.listen( toInt( PORT ), () => {
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
- // enable the file system watch, which handles hot-reload and auto-build
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 {