tlsd 2.15.0 → 2.16.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/README.md CHANGED
@@ -9,7 +9,7 @@ It is *not* designed for production websites.
9
9
 
10
10
  - SSL/TLS via Greenlock
11
11
  - Websockets built in
12
- - Standardised Websockets/RPC protocol for front/back end communcations
12
+ - Standardised Websockets/RPC protocol for front/back end communications
13
13
  - Utility command 'tlsd'
14
14
 
15
15
 
@@ -48,16 +48,16 @@ You should see the pages being delivered and a websocket connection being establ
48
48
  In dev mode, tlsd runs a plain HTTP server without TLS/SSL security.
49
49
  This is for local (your computer) development:
50
50
 
51
- tlsd root_dir port_num log_level
51
+ tlsd run dev root_dir port verbosity
52
52
 
53
53
  All arguments are required.
54
54
 
55
55
 
56
56
  ## tlsd domain
57
57
 
58
- Adds a domain to Greenlock so that you can serve site with HTTPS:
58
+ Adds a domain to Greenlock and links the site's root so it can be served with HTTPS:
59
59
 
60
- tlsd domain example.com
60
+ tlsd domain example.com /absolute/path/to/site_root
61
61
 
62
62
 
63
63
  ## tlsd version
@@ -71,11 +71,15 @@ In HTTPS mode, you must specify the dir containing links/dirs for domains to ser
71
71
  it will run on HTTP on 80 (redirecting to 443), HTTPS on 443, and magically
72
72
  generate certs and serve secure sites like nodes:
73
73
 
74
- DOMAINS_ROOT=./my_domains MAINTAINER_EMAIL=foo@bar.com VERBOSITY=4 node tlsd.js
74
+ DOMAINS_ROOT=./my_domains MAINTAINER_EMAIL=foo@bar.com VERBOSITY=4 node tlsd.js
75
75
 
76
- All env vars and arguments are required.
76
+ All env vars are required.
77
77
 
78
- You must also add your domain to greenlock before running nodes or it won't be recognzied:
78
+ You must also register your domain before running in TLS mode, otherwise it won't be recognized. Either run:
79
+
80
+ tlsd domain example.com /absolute/path/to/site_root
81
+
82
+ or use Greenlock directly:
79
83
 
80
84
  npx greenlock add --subject example.com --altnames example.com,www.example.com
81
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlsd",
3
- "version": "2.15.0",
3
+ "version": "2.16.1",
4
4
  "description": "A server for web app prototyping with HTTPS and Websockets",
5
5
  "main": "tlsd.js",
6
6
  "bin": {
@@ -0,0 +1,214 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⚙️</text></svg>">
7
+ <title>RPC Tool</title>
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ height: 100vh;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .grid-container {
22
+ display: grid;
23
+ grid-template-columns: 1fr 1fr;
24
+ grid-template-rows: 1fr 1fr;
25
+ height: 100vh;
26
+ gap: 10px;
27
+ padding: 10px;
28
+ }
29
+
30
+ .textarea-group {
31
+ display: flex;
32
+ flex-direction: column;
33
+ height: 100%;
34
+ }
35
+
36
+ .textarea-group:first-child {
37
+ position: relative;
38
+ }
39
+
40
+ .send-button {
41
+ position: absolute;
42
+ top: 0;
43
+ right: 0;
44
+ background-color: #007bff;
45
+ color: white;
46
+ border: none;
47
+ border-radius: 4px;
48
+ padding: 6px 12px;
49
+ font-size: 12px;
50
+ font-weight: bold;
51
+ cursor: pointer;
52
+ transition: background-color 0.2s;
53
+ }
54
+
55
+ .send-button:hover {
56
+ background-color: #0056b3;
57
+ }
58
+
59
+ .send-button:active {
60
+ background-color: #004085;
61
+ }
62
+
63
+ .textarea-label {
64
+ font-weight: bold;
65
+ margin-bottom: 5px;
66
+ color: #333;
67
+ font-size: 14px;
68
+ }
69
+
70
+ .textarea-input {
71
+ flex: 1;
72
+ resize: none;
73
+ border: 2px solid #ddd;
74
+ border-radius: 5px;
75
+ padding: 10px;
76
+ font-family: monospace;
77
+ font-size: 14px;
78
+ line-height: 1.4;
79
+ background-color: #f9f9f9;
80
+ }
81
+
82
+ .textarea-input:focus {
83
+ outline: none;
84
+ border-color: #007bff;
85
+ background-color: white;
86
+ }
87
+
88
+ .textarea-input:hover {
89
+ border-color: #007bff;
90
+ }
91
+ </style>
92
+ <script src="/rpc/rpc.js"></script>
93
+ </head>
94
+ <body>
95
+ <div class="grid-container">
96
+ <div class="textarea-group">
97
+ <label class="textarea-label">Message Data</label>
98
+ <button class="send-button">Send</button>
99
+ <textarea class="textarea-input" id="e_input" ></textarea>
100
+ </div>
101
+ <div class="textarea-group">
102
+ <label class="textarea-label">Preview</label>
103
+ <textarea class="textarea-input" id="e_preview" ></textarea>
104
+ </div>
105
+ <div class="textarea-group">
106
+ <label class="textarea-label">Sent to Server →</label>
107
+ <textarea class="textarea-input" id="e_sent" ></textarea>
108
+ </div>
109
+ <div class="textarea-group">
110
+ <label class="textarea-label">← Received from Server</label>
111
+ <textarea class="textarea-input" id="e_received" ></textarea>
112
+ </div>
113
+ </div>
114
+
115
+ <script>
116
+
117
+ // Get the send button and first textarea
118
+ const sendButton = document.querySelector('.send-button');
119
+ const e_input = document.querySelector('#e_input');
120
+ const e_preview = document.querySelector('#e_preview');
121
+ const e_sent = document.querySelector('#e_sent');
122
+ const e_received = document.querySelector('#e_received');
123
+
124
+ // Add click event listener to the send button
125
+ sendButton.addEventListener('click', function() {
126
+ // Get the content from the first textarea
127
+ const textareaContent = e_input.value;
128
+
129
+ // Store the content in local storage
130
+ localStorage.setItem('textareaContent', textareaContent);
131
+ let data = parse_input( textareaContent );
132
+
133
+ let json_out = o2j( data, null, 2 );
134
+ e_sent.value = json_out;
135
+
136
+ let x_data = j2o( o2j( data ) );
137
+ delete x_data.action;
138
+ let x_json = o2j( x_data, null, 4 );
139
+
140
+ e_received.value = "...";
141
+
142
+ RPC( data, function( rsp ) {
143
+ e_received.value = o2j( rsp, null, 2 );
144
+ }, function( err ) {
145
+ e_received.value = "ERROR:\n" + o2j( err, null, 2 );
146
+ });
147
+
148
+ });
149
+
150
+ // Load content from local storage when page loads
151
+ window.addEventListener('pageshow', function() {
152
+ const savedContent = localStorage.getItem('textareaContent');
153
+ if (savedContent) {
154
+ e_input.value = savedContent;
155
+ }
156
+ });
157
+
158
+ function toInt( v ) {
159
+ return parseInt( v );
160
+ }
161
+
162
+ function j2o( v ) {
163
+ return JSON.parse( v );
164
+ }
165
+
166
+ function o2j( v, null_to_empty, indent ) {
167
+ return JSON.stringify( v, null, indent );
168
+ }
169
+
170
+ // parse the input into a data object
171
+ function parse_input( input ) {
172
+ let data = {};
173
+ let action = null;
174
+ let lines = input.trim().split( /\n+/ ).filter( ln => ln );
175
+ for( let line of lines ) {
176
+ let words = line.split( /\s+/ );
177
+ let k = words.shift();
178
+ let v = words.join( " " );
179
+ if( k == "action" ) {
180
+ action = v;
181
+ }
182
+ if( k[ 0 ] == "#" ) {
183
+ continue; // ignore
184
+ }
185
+ else
186
+ if( v[ 0 ] == "{" || v[ 0 ] == "[" ) {
187
+ v = j2o( v ) || v;
188
+ }
189
+ else
190
+ if( /^[\d]+$/.test( v ) ) {
191
+ v = toInt( v );
192
+ }
193
+ else
194
+ if( v == "true" ) {
195
+ v = true;
196
+ }
197
+ else
198
+ if( v == "false" ) {
199
+ v = false;
200
+ }
201
+ data[ k ] = v;
202
+ }
203
+ return data;
204
+ }
205
+
206
+ setInterval( function() {
207
+ let data = parse_input( e_input.value );
208
+ let json_out = o2j( data, null, 2 );
209
+ e_preview.value = json_out;
210
+ }, 500 );
211
+
212
+ </script>
213
+ </body>
214
+ </html>
@@ -1,20 +1,15 @@
1
1
 
2
- // This is the back end script that handles messages from the front end.
3
- // The "input" object is exactly the same thing that was given to RPC()
4
- // on the front end.
5
- // Use okay() to return a normal response, or fail() to return an error.
6
- // These correspond to the same okay()/fail() functions given to RPC() on
7
- // the front end.
2
+ delete require.cache[ module.filename ];
8
3
 
9
- module.exports = ( input, okay, fail ) => {
4
+ module.exports = ( input, okay, fail, context ) => {
10
5
 
11
6
  const action = input.action;
12
7
 
13
8
  if( action == "hello" ) {
14
- return okay( "welcome" );
9
+ return okay( { message: "Welcome!" } );
15
10
  }
16
11
 
17
- fail( "Invalid action: "+action );
12
+ okay( { error: "Invalid action: "+action } );
18
13
 
19
14
  };
20
15
 
@@ -1,3 +1,4 @@
1
+ <!DOCTYPE html>
1
2
  <html>
2
3
  <head>
3
4
  <title>TLSD Test Page</title>
@@ -14,6 +15,10 @@
14
15
 
15
16
  <h3 id=connect_status>...</h3>
16
17
 
18
+ <div id="drop_zone">
19
+ <h4>🎯 Drop files here to upload</h4>
20
+ </div>
21
+
17
22
  <script src="/rpc/rpc.js"></script>
18
23
 
19
24
  <script>
@@ -23,26 +28,38 @@
23
28
  // Websocket or REST based on the size of the JSON encoded msg.
24
29
  // Otherwise, you can explicitly choose the transport by using
25
30
  // RPC.POST() or RPC.WS().
26
-
27
- RPC.onmessage = msg => {
28
- // Message originating from server (not a response to client message).
29
- // Returning true will prevent tlsd from doing anything further
30
- // with the message.
31
- return false;
32
- };
33
-
34
- RPC.on_connect = function( evt ) {
31
+ RPC.connect( function( evt ) {
35
32
  // RPC is ready to be used (websocket is connected)
36
- RPC( { action: "hello" }, msg => {
37
- document.getElementById( "connect_status" ).innerText = msg;
33
+ RPC( { action: "hello" }, response => {
34
+ document.getElementById( "connect_status" ).innerText = response.message;
38
35
  } );
39
- }
36
+ } );
37
+
38
+ // Drag/drop upload
39
+ const dropZone = document.getElementById( "drop_zone" );
40
+ dropZone.addEventListener( 'dragover', ( e ) => e.preventDefault() );
41
+ dropZone.addEventListener( 'drop', function( e ) {
42
+ e.preventDefault();
43
+ const file = e.dataTransfer.files[ 0 ];
44
+ const xhr = new XMLHttpRequest();
45
+ const uploadURL = '/';
46
+ xhr.open( 'PUT', uploadURL, true );
47
+ xhr.setRequestHeader( 'Content-Type', file.type || 'application/octet-stream' );
48
+ xhr.onload = () => {
49
+ if( xhr.status === 200 ) {
50
+ let result = JSON.parse( xhr.responseText );
51
+ alert( "Successfully uploaded file. Hash=" + result.hash );
52
+ } else {
53
+ alert( "Upload failed with status: " + xhr.status );
54
+ }
55
+ };
56
+
57
+ xhr.onerror = () => {
58
+ alert( "Upload failed" );
59
+ };
40
60
 
41
- RPC.on_disconnect = function( evt ) {
42
- // The websocket connection was lost for some reason.
43
- // The RPC code will reconnect automatically.
44
- document.getElementById( "connect_status" ).innerText = "[ DISCONNECTED ]";
45
- }
61
+ xhr.send( file );
62
+ } );
46
63
 
47
64
  </script>
48
65
 
package/tlsd CHANGED
@@ -52,8 +52,8 @@ if [ "$cmd" = "run" ] ; then
52
52
  node "$home/tlsd.js" "$root_dir" "$port" "$verbosity"
53
53
  if [ $? != 0 ] ; then
54
54
  echo
55
- echo "(( restart in 15 seconds ))"
56
- sleep 15
55
+ echo "(( restart in 5 seconds ))"
56
+ sleep 5
57
57
  fi
58
58
 
59
59
  done
package/tlsd.js CHANGED
@@ -2,30 +2,33 @@
2
2
  // Copyright 2025 Sleepless Software Inc.
3
3
  // All Rights Reserved
4
4
 
5
- const { path, http, https, fs, crypto, tls, } = require( "allcore" );
5
+ const { path, http, fs, } = require( "allcore" );
6
6
 
7
7
  const connect = require( "connect" );
8
8
  const websocket = require( "websocket" );
9
- const serveStatic = require( "serve-static" )
9
+ const serveStatic = require( "serve-static" );
10
10
 
11
11
  const body_parser = require( "body-parser" ).json( { limit: "10mb" } );
12
12
  const compression = require( "compression" )();
13
13
  const cors = require( "cors" )();
14
14
  const queryString = require( "querystring" );
15
15
 
16
- const { log5, o2j, j2o, toInt, is_dir } = require( "sleepless" );
16
+ const { log5, o2j, j2o, toInt, is_dir, sha1 } = require( "sleepless" );
17
17
  const L = log5.mkLog( "TLSD: " );
18
18
  const { D, V, I, W, E } = L;
19
19
 
20
20
 
21
+ const UPLOAD_DIR = process.env.UPLOAD_DIR || "/tmp/tlsd-put-files/";
22
+ const UPLOAD_MAX_BYTES = toInt( process.env.UPLOAD_MAX_BYTES ) || ( 200 * 1024 * 1024 );
23
+
21
24
  let dev_mode = false;
22
25
 
23
26
 
24
27
  function usage() {
25
28
  const base = path.basename( module.filename );
26
- log(
29
+ I(
27
30
  `Usage:
28
- TLS mode:
31
+ Production/TLS mode:
29
32
  DOMAINS_ROOT=./my_domains MAINTAINER_EMAIL=foo@bar.com VERBOSITY=4 node ` + base + `
30
33
  Dev mode:
31
34
  node ` + base + ` site_root listen_port verbosity
@@ -42,13 +45,17 @@ function next_seq() {
42
45
  }
43
46
 
44
47
 
45
- // Handles incoming RPC msgs.
46
- // Tries to load the rpc handler module from root and if successful, passes the msg to it
47
- // transport =
48
- // { type: "WS", connection: { ... } }
49
- // { type: "GET", connection: { req, res } }
50
- // { type: "POST", connection: { req, res } }
51
- function rpc_handler( root, msg, transport, _okay, _fail ) {
48
+ // Handles incoming RPC messages.
49
+ // Load order for RPC handlers under the provided root:
50
+ // 1) Tries explicit CommonJS module at "root/rpc/index.cjs"
51
+ // 2) Falls back to legacy directory loader at "root/rpc"
52
+ // If loading succeeds, passes the message to the handler.
53
+ // context may be one of:
54
+ // { transport_type: "WS", connection: { ... } }
55
+ // { transport_type: "GET", connection: { req, res } }
56
+ // { transport_type: "POST", connection: { req, res } }
57
+ // Additionally, context.upload_dir is set to the server's upload directory.
58
+ function rpc_handler( root, msg, context, _okay, _fail ) {
52
59
 
53
60
  V( "RPC " + o2j( msg ).abbr( 70 ) );
54
61
 
@@ -64,13 +71,14 @@ function rpc_handler( root, msg, transport, _okay, _fail ) {
64
71
  _fail();
65
72
  };
66
73
 
74
+ context.upload_dir = UPLOAD_DIR;
75
+
67
76
  try {
68
77
  // try loading explicit common js module
69
- const path = root + "/rpc/index.cjs";
70
- //D( "path=" + path );
71
- const mod = require( path );
78
+ const mod_path = root + "/rpc/index.cjs";
79
+ const mod = require( mod_path );
72
80
  try {
73
- mod( msg, okay, fail, transport );
81
+ mod( msg, okay, fail, context );
74
82
  } catch( err ) {
75
83
  fail( err );
76
84
  }
@@ -79,11 +87,10 @@ function rpc_handler( root, msg, transport, _okay, _fail ) {
79
87
 
80
88
  // try loading it the old way using the dir
81
89
  try {
82
- const path = root + "/rpc";
83
- //D( "path=" + path );
84
- const mod = require( path );
90
+ const mod_path = root + "/rpc";
91
+ const mod = require( mod_path );
85
92
  try {
86
- mod( msg, okay, fail, transport );
93
+ mod( msg, okay, fail, context );
87
94
  } catch( err ) {
88
95
  fail( err );
89
96
  }
@@ -99,7 +106,7 @@ function rpc_handler( root, msg, transport, _okay, _fail ) {
99
106
  // Glue function to call rpc handler module and then return response
100
107
  // via the websockets msg object
101
108
  function ws_msg_handler( root, msg, host, connection ) {
102
- rpc_handler( root, msg.msg, { type: "WS", host, connection }, data => {
109
+ rpc_handler( root, msg.msg, { transport_type: "WS", host, connection }, data => {
103
110
  msg.reply( data );
104
111
  }, err => {
105
112
  msg.error( err || "Unspecified Error" );
@@ -124,8 +131,9 @@ function logger( req, res, next ) {
124
131
  }
125
132
 
126
133
 
127
- // Creates and returns a handler function that intercepts and services
128
- // POST requests to "/rpc" endpoint.
134
+ // Creates and returns a handler that intercepts and services requests to
135
+ // the "/rpc" endpoint. In TLS mode only POST is allowed; in dev mode
136
+ // GET is also allowed with query-string payloads.
129
137
  function rpc_post( root ) {
130
138
  return function( req, res, next ) {
131
139
  let { method, url, query, body } = req;
@@ -133,7 +141,7 @@ function rpc_post( root ) {
133
141
  url = url.split( "?" ).shift();
134
142
  if( url != "/rpc" && url != "/rpc/" ) {
135
143
  next();
136
- return
144
+ return;
137
145
  }
138
146
 
139
147
  // Only allow POSTS in TLS mode.
@@ -152,7 +160,7 @@ function rpc_post( root ) {
152
160
  D( method + " >------> " + o2j( input, null, 2 ) );
153
161
 
154
162
  // Summon the rpc handler for the domain root.
155
- rpc_handler( root, input, { type: method, connection: { host: req.headers[ "host" ], req, res, } } , output => {
163
+ rpc_handler( root, input, { transport_type: method, connection: { host: req.headers[ "host" ], req, res, } } , output => {
156
164
  res.writeHead( 200, {
157
165
  "Content-Type": "application/json",
158
166
  "Cache-Control": "no-store",
@@ -183,8 +191,8 @@ function not_found( root ) {
183
191
  return function( req, res, next ) {
184
192
  // Can't just do a redirect here because if /404 doesn't exist, it will
185
193
  // just go into a redirect loop. So test to see if the dir exists first.
186
- let path = root + "/static/404";
187
- is_dir( path, so => {
194
+ let mod_path = root + "/static/404";
195
+ is_dir( mod_path, so => {
188
196
  if( so ) {
189
197
  let p404 = "/404/?original_path=" + encodeURIComponent( req.url );
190
198
  res.writeHead( 302, { "Location": p404, } ); // redirect to path
@@ -196,6 +204,87 @@ function not_found( root ) {
196
204
  }
197
205
  }
198
206
 
207
+ function put_handler( req, res, next ) {
208
+
209
+ if( req.method != "PUT" ) {
210
+ next();
211
+ return;
212
+ }
213
+
214
+ function fail( error, stack ) {
215
+ E( error, stack );
216
+ res.writeHead( 500, { "Content-Type": "application/json" } );
217
+ res.write( o2j( { error } ) );
218
+ res.end();
219
+ }
220
+
221
+ try {
222
+ I( "PUT " + req.url );
223
+
224
+ // Preflight size check from Content-Length if provided
225
+ const content_length = toInt( req.headers[ "content-length" ] );
226
+ if( content_length && content_length > UPLOAD_MAX_BYTES ) {
227
+ W( "PUT: Content-Length " + content_length + " exceeds cap " + UPLOAD_MAX_BYTES );
228
+ res.writeHead( 413, { "Content-Type": "application/json" } );
229
+ res.write( o2j( { error: "Payload too large" } ) );
230
+ res.end();
231
+ return;
232
+ }
233
+
234
+ let local_path = UPLOAD_DIR;
235
+ fs.mkdirSync( local_path, { recursive: true } );
236
+
237
+ // generate random hash to store file under locally
238
+ const hash = sha1( "" + ( Date.now() + Math.random() ) );
239
+ local_path += "/" + hash;
240
+
241
+ D( "PUT: " + local_path );
242
+
243
+ const writeStream = fs.createWriteStream( local_path );
244
+
245
+ let responded = false;
246
+ let received_bytes = 0;
247
+ const abort_too_large = function( ) {
248
+ if( responded ) return;
249
+ responded = true;
250
+ try { req.unpipe( writeStream ); } catch( _e ) {}
251
+ try { writeStream.destroy(); } catch( _e ) {}
252
+ try { req.destroy(); } catch( _e ) {}
253
+ try { fs.unlink( local_path, function( ){} ); } catch( _e ) {}
254
+ res.writeHead( 413, { "Content-Type": "application/json" } );
255
+ res.write( o2j( { error: "Payload too large" } ) );
256
+ res.end();
257
+ };
258
+
259
+ // Streaming size guard
260
+ req.on( "data", function( chunk ) {
261
+ received_bytes += chunk.length;
262
+ if( received_bytes > UPLOAD_MAX_BYTES ) {
263
+ W( "PUT: stream exceeded cap: " + received_bytes + " > " + UPLOAD_MAX_BYTES );
264
+ abort_too_large( );
265
+ }
266
+ } );
267
+
268
+ req.pipe( writeStream );
269
+
270
+ writeStream.on( "finish", ( ) => {
271
+ if( responded ) return;
272
+ I( "PUT: " + local_path );
273
+ res.writeHead( 200, { "Content-Type": "application/json" } );
274
+ res.write( o2j( { hash } ) );
275
+ res.end();
276
+ } );
277
+
278
+ writeStream.on( "error", ( error ) => {
279
+ if( responded ) return;
280
+ fail( "PUT: " + local_path + " failed during stream", error.stack );
281
+ } );
282
+ } catch ( error ) {
283
+ fail( "PUT: failed", error.stack );
284
+ }
285
+
286
+ }
287
+
199
288
 
200
289
  // Create and return a connect app/function that implements
201
290
  // default (basic) functionality.
@@ -206,6 +295,7 @@ function basic_handler( root ) {
206
295
  app.use( cors ); // allow requests from other domains
207
296
  app.use( populate_query );
208
297
  app.use( logger );
298
+ app.use( put_handler )
209
299
  app.use( rpc_post( root ) );
210
300
  app.use( rpc_static );
211
301
  app.use( serveStatic( root + "/static" ) ); // static files for domain
@@ -220,7 +310,8 @@ const cached_basic_handlers = {};
220
310
  // Handle REST calls (as opposed to websocket messages)
221
311
  function rest_handler( root, req, rsp ) {
222
312
 
223
- I( req.headers[ "host" ] + ": " + req.method + " " + req.url );
313
+ const remote_ip = req.socket.remoteAddress;
314
+ I( remote_ip + " " + req.headers[ "host" ] + ": " + req.method + " " + req.url );
224
315
  D( "rest_handler root: " + root );
225
316
 
226
317
  const call = function( handler ) {
@@ -237,14 +328,15 @@ function rest_handler( root, req, rsp ) {
237
328
  }
238
329
 
239
330
  // try loading custom handler first
331
+ let handler;
240
332
  try {
241
333
  handler = require( root );
242
- D( "Using custom REST handler loaded from "+root );
334
+ D( "Using custom REST handler loaded from " + root );
243
335
  call( handler );
244
336
  } catch( err ) {
245
337
  // attempt to load custom handler threw an exception
246
338
 
247
- let handler = cached_basic_handlers[ root ];
339
+ handler = cached_basic_handlers[ root ];
248
340
  if( ! handler ) {
249
341
  handler = basic_handler( root );
250
342
  cached_basic_handlers[ root ] = handler;
@@ -267,11 +359,12 @@ function ws_attach( server, msg_handler ) {
267
359
 
268
360
  const host = wsreq.httpRequest.headers[ "host" ];
269
361
 
270
- const socket = wsreq.accept( null, wsreq.origin );
362
+ const socket = wsreq.accept( null, wsreq.origin || "*" );
271
363
 
272
364
  const name = "ws-conn-" + next_seq(); // XXX just use the websocket id
273
365
 
274
- // send msg to connected client
366
+ // Send a message to the connected client.
367
+ // XXX "cb" is currently unused - not sure it should be that way
275
368
  const send = function( msg, cb ) {
276
369
  if( msg.msg_id === undefined ) {
277
370
  msg.msg_id = "msg-id-" + next_seq(); // every message must have an id
@@ -336,7 +429,7 @@ if( argv.length == 2 ) {
336
429
 
337
430
  VERBOSITY = toInt( VERBOSITY );
338
431
 
339
- if( DOMAINS_ROOT && MAINTAINER_EMAIL && VERBOSITY ) {
432
+ if( DOMAINS_ROOT && MAINTAINER_EMAIL && VERBOSITY >= 0 ) {
340
433
 
341
434
  L( toInt( VERBOSITY ) );
342
435
 
@@ -374,14 +467,14 @@ if( argv.length == 2 ) {
374
467
  else
375
468
  if( argv.length == 5 ) {
376
469
 
377
- let [ exe, script, SITE_ROOT, PORT, VERBOSITY, ] = argv;
470
+ let [ _exe, _script, SITE_ROOT, PORT, VERBOSITY, ] = argv;
378
471
 
379
472
  VERBOSITY = toInt( VERBOSITY );
380
473
  PORT = toInt( PORT );
381
474
 
382
475
  dev_mode = true;
383
476
 
384
- if( SITE_ROOT && PORT && VERBOSITY ) {
477
+ if( SITE_ROOT && PORT && VERBOSITY >= 0 ) {
385
478
 
386
479
  L( toInt( VERBOSITY ) );
387
480
 
@@ -415,7 +508,7 @@ if( argv.length == 3 && argv[ 2 ] == "-v" ) {
415
508
 
416
509
  // pull package version from package.json and print it
417
510
  const p = require( __dirname + "/package.json" );
418
- log( p.version );
511
+ I( p.version );
419
512
 
420
513
  }
421
514
  else {