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 +5 -2
- package/package.json +3 -2
- package/{server_static → rpc_static}/rpc/rpc.js +32 -16
- package/scaffold/static/404/index.html +16 -0
- package/scaffold/static/index.html +16 -1
- package/scaffold/static/local.sass +11 -0
- package/scaffold/static/local.sass.css +12 -0
- package/tlsd.js +112 -15
- package/server_static/rpc/test.html +0 -11
- package/test.html +0 -4
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
|
|
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.
|
|
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 =
|
|
27
|
+
let DBG = function( ...args ) {
|
|
28
28
|
if( RPC.debug ) {
|
|
29
|
-
|
|
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
|
|
52
|
+
//DBG( "clearInterval", timer );
|
|
51
53
|
clearInterval(timer)
|
|
52
54
|
timer = null
|
|
53
55
|
}
|
|
54
|
-
DBG("forgetting
|
|
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
|
|
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
|
|
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(">>--->
|
|
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:
|
|
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
|
|
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( "<---<<
|
|
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
|
-
|
|
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:
|
|
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>
|
|
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 )
|
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
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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(
|
|
228
|
-
V( "WS: connection request from "+
|
|
239
|
+
wsd.on( "request", function( wsreq ) {
|
|
240
|
+
V( "WS: connection request from "+wsreq.remoteAddress+" "+wsreq.resource )
|
|
229
241
|
|
|
230
|
-
const domain =
|
|
242
|
+
const domain = wsreq.httpRequest.headers[ "host" ];
|
|
231
243
|
|
|
232
|
-
const socket =
|
|
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
|
} );
|
package/test.html
DELETED