tlsd 2.7.1 → 2.9.0

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 CHANGED
@@ -7,8 +7,9 @@ This is a node.js based webserver.
7
7
 
8
8
  - SSL/TLS magically via Greenlock
9
9
  - Websockets built in
10
- - Standardised RPC protocol for front/back end communcations
10
+ - Standardised Websockets/RPC protocol for front/back end communcations
11
11
  - Utility command 'tlsd'
12
+ - Browser reload on src changes
12
13
 
13
14
 
14
15
  ## Installing
@@ -70,7 +71,7 @@ All arguments are required
70
71
 
71
72
  Adds a domain to Greenlock for production.
72
73
 
73
- tlsd domain foo.bar
74
+ tlsd domain example.com
74
75
 
75
76
 
76
77
  ## tlsd version
@@ -94,5 +95,7 @@ You must also add your domain to greenlock before running nodes or it won't be r
94
95
 
95
96
  Production mode serves files with Letsencrypt SSL certs magically.
96
97
 
98
+ The browser reload feature does not operate in production mode.
99
+
97
100
 
98
101
 
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "tlsd",
3
- "version": "2.7.1",
3
+ "version": "2.9.0",
4
4
  "description": "A server for web app prototyping with HTTPS and Websockets",
5
5
  "main": "tlsd.js",
6
6
  "bin": {
7
7
  "tlsd": "tlsd"
8
8
  },
9
9
  "scripts": {
10
+ "dev": "./tlsd run dev scaffold 12345 5",
10
11
  "test": "echo \"Error: no test specified\" && exit 1"
11
12
  },
12
13
  "author": "Joe Hitchens <joe@sleepless.com>",
@@ -19,9 +20,9 @@
19
20
  "cors": "^2.8.5",
20
21
  "g": "^2.0.1",
21
22
  "greenlock-express": "^4.0.3",
23
+ "sass": "^1.69.5",
22
24
  "serve-static": "^1.15.0",
23
25
  "sleepless": "^5.13.0",
24
- "tlsd": "^2.4.1",
25
26
  "websocket": "^1.0.34"
26
27
  }
27
28
  }
@@ -24,9 +24,11 @@
24
24
  const time = function( dt ) { return Math.round( ( new Date() ).getTime() / 1000 ); }
25
25
 
26
26
 
27
- let DBG = msg => {
27
+ let DBG = function( ...args ) {
28
28
  if( RPC.debug ) {
29
- console.log( "RPC: " + o2j( msg ) );
29
+ args.unshift( "RPC:" );
30
+ console.log.apply( this, args );
31
+ //console.log( "RPC: ", args );
30
32
  }
31
33
  };
32
34
 
@@ -47,11 +49,11 @@
47
49
  delete wraps[id]
48
50
  num -= 1
49
51
  if(num == 0) {
50
- DBG( "clearInterval " + timer );
52
+ //DBG( "clearInterval", timer );
51
53
  clearInterval(timer)
52
54
  timer = null
53
55
  }
54
- DBG("forgetting " + id + " " + o2j( p ));
56
+ //DBG("forgetting", id, p );
55
57
  }
56
58
  return p
57
59
  }
@@ -60,7 +62,7 @@
60
62
  // Put a msg into the list
61
63
  // ttl is in secs and should not be less than 10 (default is 60 if not provided)
62
64
  const ins = self.ins = function(p, id, ttl) {
63
- DBG( "remembering " + id + " " + o2j( p ) );
65
+ //DBG( "remembering", id, p );
64
66
  const w = {
65
67
  expire: time() + (ttl || 60),
66
68
  payload: p,
@@ -77,7 +79,7 @@
77
79
  }
78
80
  }
79
81
  }, 10 * 1000);
80
- DBG( "setInterval " + timer );
82
+ //DBG( "setInterval", timer );
81
83
  }
82
84
  }
83
85
 
@@ -99,7 +101,6 @@
99
101
  const waiting = new WaitList()
100
102
 
101
103
  const send = function(m, cb, fail) {
102
- DBG( "send: " + o2j( m ) );
103
104
  if(m.msg_id === undefined) {
104
105
  m.msg_id = "CMID-" + next_seq(); // every message must have an id
105
106
  }
@@ -109,7 +110,7 @@
109
110
  waiting.ins( { msg: m, cb, fail }, m.msg_id );
110
111
  }
111
112
 
112
- DBG(">>---> server "+o2j( m ));
113
+ DBG(">>--->", m );
113
114
  socket.send( o2j( m ) );
114
115
  }
115
116
 
@@ -117,7 +118,7 @@
117
118
  var socket = new WebSocket( ( loc.protocol == "http:" ? "ws:" : "wss:" ) + "//" + loc.host + path )
118
119
 
119
120
  socket.onerror = function( evt ) {
120
- DBG( "error: " + evt.data );
121
+ DBG( "error:", evt.data );
121
122
  cb_ctrl( "error", evt.data );
122
123
  }
123
124
 
@@ -125,7 +126,7 @@
125
126
  DBG( "close" );
126
127
  cb_ctrl( "disconnect", conn.attempt );
127
128
  setTimeout( () => {
128
- DBG( "reconnect atttempt " + conn.attempt );
129
+ DBG( "reconnect atttempt", conn.attempt );
129
130
  ws_connect( cb_msg, cb_ctrl, path );
130
131
  }, 2 * 1000 );
131
132
  }
@@ -137,7 +138,7 @@
137
138
 
138
139
  // handle incoming messages from server
139
140
  socket.onmessage = function( evt ) {
140
- DBG( "<---<< server " + o2j( evt.data ) )
141
+ //DBG( "<---<<", evt.data )
141
142
 
142
143
  var json = evt.data // raw message is a utf8 string
143
144
  var msg_in = j2o( json )
@@ -146,8 +147,10 @@
146
147
  return;
147
148
  }
148
149
 
150
+ DBG( "<---<<", msg_in )
151
+
149
152
  if(typeof msg_in.msg !== "undefined") {
150
- // server initiated msg (not a reply)
153
+ // server initiated msg (not a reply to a client msg)
151
154
 
152
155
  msg_in.reply = function( response ) {
153
156
  send( { msg_id: msg_in.msg_id, response, } );
@@ -181,7 +184,7 @@
181
184
  }
182
185
 
183
186
  if( typeof msg_in.response !== "undefined" ) {
184
- // response to a client initiated msg
187
+ // normal response to a client initiated msg
185
188
 
186
189
  var x = waiting.rem( msg_in.msg_id );
187
190
  if( ! x ) {
@@ -257,14 +260,27 @@
257
260
 
258
261
  ws_connect( msg => {
259
262
  // msg initiated by server
260
- DBG( "Server says: " + msg );
263
+
264
+ // If there's a handler set for this, call that
261
265
  const fn = RPC[ "onmessage" ];
262
266
  if( typeof fn == "function" ) {
263
- fn( msg );
267
+ if( fn( msg ) ) {
268
+ // returning true means it was handled by client
269
+ return;
270
+ }
264
271
  }
272
+
273
+ // Server is telling browser to reload the page
274
+ // this is due to the dev-mode hot-reload feature
275
+ // server sends this when it detects any changes to the
276
+ // site root dir, which includes the static/ and rpc/ dirs.
277
+ if( msg.msg === "RELOAD" ) {
278
+ location.reload();
279
+ }
280
+
265
281
  }, ( evt, detail ) => {
266
282
  // event occurred
267
- DBG( "Event: " + evt );
283
+ DBG( "Event:", evt );
268
284
  if( evt === "connect" ) {
269
285
  RPC.connected = true;
270
286
  }
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Page Not Found </title>
5
+ </head>
6
+ <body>
7
+ <h1>Sorry, nothing there, mate!</h1>
8
+ <p>
9
+ <script>
10
+ let url = decodeURIComponent( document.location.search.split( "=" ).pop() );
11
+ document.write( "Requested URL: " + url );
12
+ </script>
13
+ </p>
14
+ </body>
15
+ </html>
16
+
@@ -1,7 +1,13 @@
1
1
  <html>
2
+ <head>
3
+ <link href="/local.sass.css" rel="stylesheet">
4
+ </head>
2
5
  <body>
3
6
 
4
- <h1> Hello </h1>
7
+ <h1>TLSD Test Page</h1>
8
+ <script>
9
+ document.write( Date() );
10
+ </script>
5
11
 
6
12
  <script src="/rpc/rpc.js"></script>
7
13
 
@@ -15,6 +21,15 @@
15
21
  RPC.POST() or RPC.WS().
16
22
  */
17
23
 
24
+ RPC.debug = true; // enables debug output to browser console
25
+
26
+ RPC.onmessage = msg => {
27
+ // Message originating from server (not a response to client message)
28
+ // returning true will prevent tlsd from doing anything further
29
+ // with the message, such as try to detect hot-reload messages, etc.
30
+ return false;
31
+ };
32
+
18
33
  RPC.on_connect = function( evt ) {
19
34
  // RPC is ready to be used
20
35
  RPC( { action: "hello" }, console.log, alert )
@@ -0,0 +1,11 @@
1
+
2
+ body
3
+ margin: 2em 2vw
4
+ font-family: sans-serif
5
+ text-align: center
6
+
7
+ h1
8
+ font-size: 40px
9
+ a
10
+ font-style: italic
11
+
@@ -0,0 +1,12 @@
1
+ body {
2
+ margin: 2em 2vw;
3
+ font-family: sans-serif;
4
+ text-align: center;
5
+ }
6
+
7
+ h1 {
8
+ font-size: 40px;
9
+ }
10
+ h1 a {
11
+ font-style: italic;
12
+ }
package/tlsd.js CHANGED
@@ -159,25 +159,33 @@ const basic_handler = function( root ) {
159
159
 
160
160
  } );
161
161
 
162
- // Serve my own static files
163
- // this is primarily so that GET /rpc/rpc.js can return the client-side code for
164
- // sending WS and RPC messages (similar to how socket.io works)
165
- app.use( serveStatic( __dirname + "/server_static" ) );
166
162
 
167
- // Serve static files for domain
163
+ // Serve my (tlsd's) own static files
164
+ // This is primarily so that GET /rpc/rpc.js can return the client-side code for
165
+ // sending WS RPC messages (similar to how socket.io works) and providing the
166
+ // browser hot-reload feature (which requires WS RPC)
167
+ app.use( serveStatic( __dirname + "/rpc_static" ) );
168
+
169
+
170
+ // Serve static files for the domain
168
171
  app.use( serveStatic( root + "/static" ) );
169
172
 
173
+
170
174
  // finally, if serveStatic can't service the request,
171
175
  // look for a 404/ dir and redirect to that if present
172
176
  app.use( function( req, res, next ) {
173
177
  // I can't just return a redirect here because if the /404 doesn't exist,
174
178
  // I will just go into a redirect loop, so I have to test to see if the
175
179
  // dir exists, and then if so, redirect to it, otherwise, just return 404.
176
- is_file( root + "/404", so => {
177
- if( so )
178
- res.writeHead( 302, { "Location": "/404/", });
179
- else
180
+ let { url, } = req;
181
+ let p = root + "/static/404";
182
+ is_dir( p, so => {
183
+ if( so ) {
184
+ let p404 = "/404/?original_path=" + encodeURIComponent( url );
185
+ res.writeHead( 302, { "Location": p404, } );
186
+ } else {
180
187
  res.writeHead( 404 );
188
+ }
181
189
  res.end();
182
190
  } );
183
191
  } );
@@ -220,18 +228,22 @@ const rest_handler = function( root, req, rsp ) {
220
228
  };
221
229
 
222
230
 
231
+ // tracked websocket connections
232
+ const ws_connections = {};
233
+
234
+
223
235
  const ws_attach = function( httpServer, msg_handler ) {
224
236
 
225
237
  const wsd = new websocket.server( { httpServer, autoAcceptConnections: false, } );
226
238
 
227
- wsd.on( "request", function( req ) {
228
- V( "WS: connection request from "+req.remoteAddress+" "+req.resource )
239
+ wsd.on( "request", function( wsreq ) {
240
+ V( "WS: connection request from "+wsreq.remoteAddress+" "+wsreq.resource )
229
241
 
230
- const domain = req.httpRequest.headers[ "host" ];
242
+ const domain = wsreq.httpRequest.headers[ "host" ];
231
243
 
232
- const socket = req.accept( null, req.origin );
244
+ const socket = wsreq.accept( null, wsreq.origin );
233
245
 
234
- const name = "ws-client-"+next_seq();
246
+ const name = "ws-client-"+next_seq(); // XXX just use the websocket id
235
247
 
236
248
  // send msg to connected client
237
249
  const send = function( msg , cb ) {
@@ -250,6 +262,7 @@ const ws_attach = function( httpServer, msg_handler ) {
250
262
 
251
263
  socket.on("close", function() {
252
264
  D( "WS: disconnect" );
265
+ delete ws_connections[ name ]; // remove from tracked connections
253
266
  });
254
267
 
255
268
  // incoming msgs from client come through here
@@ -281,6 +294,8 @@ const ws_attach = function( httpServer, msg_handler ) {
281
294
 
282
295
  D( "WS: connected: "+name );
283
296
 
297
+ ws_connections[ name ] = conn; // add to tracked connections
298
+
284
299
  } );
285
300
 
286
301
  D( "WS: initialized" );
@@ -290,6 +305,84 @@ const ws_attach = function( httpServer, msg_handler ) {
290
305
  };
291
306
 
292
307
 
308
+
309
+ const build_sass = function( path ) {
310
+ //D( "_________ auto_build sass " + path );
311
+ const sass = require( "sass" );
312
+ const result = sass.compile( path );
313
+ fs.writeFileSync( path + ".css", result.css, "utf8" );
314
+ };
315
+
316
+ const auto_builders = [
317
+ [ /\.(sass|scss)$/, build_sass ],
318
+ ];
319
+
320
+ // Do auto-build processes (dev mode only)
321
+ const auto_build = function( paths ) {
322
+
323
+ for( let path of paths ) {
324
+ // D( "___ path? " + path );
325
+ for( const bldr of auto_builders ) {
326
+ if( bldr[ 0 ].test( path ) ) {
327
+ bldr[ 1 ]( path );
328
+ }
329
+ }
330
+
331
+ }
332
+
333
+ };
334
+
335
+
336
+ // Engage the filesystem watcher (dev mode only)
337
+ const enable_fs_watch = function( root ) {
338
+
339
+ let paths_noted = {};
340
+
341
+ let hot_reload_tid = null;
342
+ let hot_reload_count = 0;
343
+
344
+ function hot_reload_tick() {
345
+ hot_reload_count -= 1; // reduce count by 1
346
+ if( hot_reload_count <= 0 ) {
347
+ // counter reached 0
348
+ clearInterval( hot_reload_tid ); // turn off the ticker
349
+ hot_reload_tid = null;
350
+
351
+ auto_build( Object.keys( paths_noted ) );
352
+ paths_noted = {};
353
+
354
+ // send RELOAD msg to all client that have an active ws connection
355
+ for( let conn of Object.values( ws_connections ) ) {
356
+ conn.send( { msg: "RELOAD" } ); // send RELOAD msg to browser
357
+ }
358
+ }
359
+ }
360
+
361
+ function note( evt, path ) {
362
+ // A change of some sort was seen
363
+ // Hundreds of these can happen very rapidly, so the timer stuff
364
+ // above is used to delay sending the RELOAD command until
365
+ // a second or so has passed without any more calls to this function
366
+
367
+ paths_noted[ path ] = evt; // XXX
368
+ //D( "noted fs change: [" + evt + "] " + path );
369
+
370
+ hot_reload_count = 5;
371
+ if( hot_reload_tid === null ) {
372
+ // ticker isn't running, so start it
373
+ hot_reload_tid = setInterval( hot_reload_tick, 200 );
374
+ }
375
+
376
+ };
377
+
378
+ // tell OS to start watching for any changes in root
379
+ fs.watch( root, { recursive: true, }, function( evt, path ) {
380
+ note( evt, root + "/" + path );
381
+ } );
382
+
383
+ };
384
+
385
+
293
386
  // -----------------------
294
387
 
295
388
  const argv = process.argv;
@@ -319,7 +412,7 @@ if( argv.length == 2 ) {
319
412
  ws_attach( httpd, ( msg, conn, domain ) => {
320
413
  const root = path.resolve( DOMAINS_ROOT + "/" + domain );
321
414
  ws_msg_handler( root, msg );
322
- } );
415
+ } );
323
416
 
324
417
  glx.serveApp( ( req, res ) => {
325
418
  const root = path.resolve( DOMAINS_ROOT + "/" + req.headers[ "host" ] );
@@ -354,12 +447,16 @@ if( argv.length == 5 ) {
354
447
  rest_handler( SITE_ROOT, req, res );
355
448
  } );
356
449
 
450
+ // XXX call ws_attach *before* calling listen() ?
357
451
  httpd.listen( toInt( PORT ), () => {
358
452
 
359
453
  ws_attach( httpd, ( msg, conn, domain ) => {
360
454
  ws_msg_handler( SITE_ROOT, msg );
361
455
  } );
362
456
 
457
+ // enable the file system watch, which handles hot-reload and auto-build
458
+ enable_fs_watch( SITE_ROOT );
459
+
363
460
  I( "Listening on " + PORT + " & serving from " + SITE_ROOT );
364
461
 
365
462
  } );
@@ -1,11 +0,0 @@
1
- <html>
2
- <body>
3
-
4
- <script src="/rpc/rpc.js"></script>
5
-
6
- <script>
7
- RPC.debug = true;
8
- RPC.connect( function() {
9
- RPC( { action: "ping" }, console.log, alert );
10
- } );
11
- </script>
package/test.html DELETED
@@ -1,4 +0,0 @@
1
- <html>
2
- <head>
3
- #include head.html
4
- </head>