rtjscomp 0.8.3 → 0.8.5

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 +2 -2
  2. package/package.json +1 -1
  3. package/rtjscomp.js +124 -75
package/README.md CHANGED
@@ -11,13 +11,13 @@ easy to use http server that allows for using javascript just as php was used ba
11
11
  go into the directory where you want to have your project and run
12
12
 
13
13
  ```console
14
- $ npx rtjscomp
14
+ $ npx --yes rtjscomp@latest
15
15
  ```
16
16
 
17
17
  or in case you prefer [bun](https://bun.sh):
18
18
 
19
19
  ```console
20
- $ bunx rtjscomp
20
+ $ bunx --bun rtjscomp@latest
21
21
  ```
22
22
 
23
23
  and now http://localhost:8080 offers a greeting!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rtjscomp",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "php-like server but with javascript",
5
5
  "repository": {
6
6
  "type": "git",
package/rtjscomp.js CHANGED
@@ -22,6 +22,7 @@ const PATH_PUBLIC = 'public/';
22
22
  const PATH_CONFIG = 'config/';
23
23
  const PATH_DATA = 'data/';
24
24
  const GZIP_OPTIONS = {level: 9};
25
+ const WATCH_OPTIONS = {persistent: true, interval: 1000};
25
26
  const AGENT_CHECK_BOT = /bot|googlebot|crawler|spider|robot|crawling|favicon/i;
26
27
  const AGENT_CHECK_MOBIL = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i;
27
28
  const HTTP_LIST_REG = /,\s*/;
@@ -59,6 +60,29 @@ if (!Object.fromEntries) {
59
60
  };
60
61
  }
61
62
 
63
+ // workaround for bun: https://github.com/oven-sh/bun/issues/18919
64
+ let fs_watch = fs.watch;
65
+ if (typeof Bun !== 'undefined') {
66
+ const fs_watch_original = fs_watch;
67
+ const watch_callbacks = new Map;
68
+ fs_watch = (path, options, callback) => {
69
+ if (!watch_callbacks.has(path)) {
70
+ fs_watch_original(path, options, () => {
71
+ const callback = watch_callbacks.get(path);
72
+ if (callback) {
73
+ callback();
74
+ }
75
+ });
76
+ }
77
+ watch_callbacks.set(path, callback);
78
+ return {
79
+ close: () => (
80
+ watch_callbacks.set(path, null)
81
+ ),
82
+ };
83
+ }
84
+ }
85
+
62
86
  // legacy, will be removed soon!
63
87
  global.globals = rtjscomp;
64
88
  global.actions = rtjscomp.actions;
@@ -141,6 +165,7 @@ actions.module_cache_clear = () => {
141
165
  custom_require_cache.clear();
142
166
  custom_import_cache.clear();
143
167
  }
168
+ const AsyncFunction = custom_import.constructor;
144
169
 
145
170
  const services_active = new Map;
146
171
  const services_loading = new Set;
@@ -161,21 +186,25 @@ const service_start = async path => {
161
186
  handler_stop: null,
162
187
  path,
163
188
  stopped: false,
189
+ watcher: null,
164
190
  };
165
191
 
166
- await file_keep_new(PATH_PUBLIC + path + '.service.js', async file_content => {
167
- if (file_content === null) {
168
- log('[error] service file not found: ' + path);
169
- return service_stop(service_object, true);
170
- }
171
- if (services_loading.size > 0) {
172
- await Promise.all(Array.from(services_loading));
192
+ service_object.watcher = await file_keep_new(
193
+ PATH_PUBLIC + path + '.service.js',
194
+ async file_content => {
195
+ if (file_content === null) {
196
+ log('[error] service file not found: ' + path);
197
+ return service_stop(service_object, true);
198
+ }
199
+ if (services_loading.size > 0) {
200
+ await Promise.all(Array.from(services_loading));
201
+ }
202
+ const start_promise = service_start_inner(path, service_object, file_content);
203
+ services_loading.add(start_promise);
204
+ await start_promise;
205
+ services_loading.delete(start_promise);
173
206
  }
174
- const start_promise = service_start_inner(path, service_object, file_content);
175
- services_loading.add(start_promise);
176
- await start_promise;
177
- services_loading.delete(start_promise);
178
- });
207
+ );
179
208
  }
180
209
  const service_start_inner = async (path, service_object, file_content) => {
181
210
  if (services_active.has(path)) {
@@ -193,14 +222,15 @@ const service_start_inner = async (path, service_object, file_content) => {
193
222
  }
194
223
 
195
224
  try {
196
- const fun = (0, eval)(
197
- `(async function(require,custom_import){const log=a=>rtjscomp.log(${
225
+ const result = await (new AsyncFunction(
226
+ 'require',
227
+ 'custom_import',
228
+ `const log=a=>rtjscomp.log(${
198
229
  JSON.stringify(path + ': ')
199
230
  }+a);${
200
231
  file_content.replace(IMPORT_REG, 'custom_import(') + '\n'
201
- }})`
202
- );
203
- const result = await fun.call(content_object, custom_require, custom_import);
232
+ }`
233
+ )).call(content_object, custom_require, custom_import);
204
234
  if (service_object.stopped) {
205
235
  clearInterval(start_interval);
206
236
  return;
@@ -247,11 +277,12 @@ const services_shutdown = () => (
247
277
  .map(service_object => service_stop(service_object, true))
248
278
  )
249
279
  )
250
- const service_stop = async (service_object, forget) => {
251
- service_object.stopped = true;
252
- if (forget) fs.unwatchFile(PATH_PUBLIC + service_object.path + '.service.js');
253
- await service_stop_handler(service_object);
254
- }
280
+ const service_stop = (service_object, forget) => (
281
+ service_object.stopped = true,
282
+ forget && service_object.watcher &&
283
+ service_object.watcher.close(),
284
+ service_stop_handler(service_object)
285
+ )
255
286
  const service_stop_handler = async service_object => {
256
287
  services_active.delete(service_object.path);
257
288
  log('stop service: ' + service_object.path);
@@ -307,36 +338,34 @@ const map_generate_equ = (map, data) => {
307
338
  }
308
339
  }
309
340
 
310
- const file_compare = (curr, prev, path) => (
311
- curr.mtime > prev.mtime && (
312
- log_verbose && log('file changed: ' + path),
313
- true
314
- )
315
- )
316
- const file_watch = (path, callback) => (
317
- fs.watchFile(path, (curr, prev) => {
318
- if (file_compare(curr, prev, path)) {
319
- fs.unwatchFile(path);
320
- callback();
321
- }
322
- })
323
- )
341
+ const file_watch_once = (path, callback) => {
342
+ const watcher = fs_watch(path, WATCH_OPTIONS, () => (
343
+ watcher.close(),
344
+ log_verbose && log('file updated: ' + path),
345
+ callback()
346
+ ));
347
+ };
324
348
  const file_keep_new = async (path, callback) => {
325
349
  try {
326
350
  const data = await fsp.readFile(path, 'utf8');
327
351
  if (log_verbose) log('load file: ' + path);
328
352
  await callback(data);
329
- fs.watchFile(path, async (curr, prev) => {
330
- if (file_compare(curr, prev, path)) {
331
- await callback(
332
- await fsp.readFile(path, 'utf8').catch(() => null)
333
- );
334
- }
335
- });
336
353
  }
337
354
  catch (err) {
338
355
  await callback(null);
356
+ return null;
339
357
  }
358
+
359
+ let timeout = 0;
360
+ return fs_watch(path, WATCH_OPTIONS, () => (
361
+ clearTimeout(timeout),
362
+ timeout = setTimeout(() => (
363
+ log_verbose && log('file updated: ' + path),
364
+ fsp.readFile(path, 'utf8')
365
+ .catch(() => null)
366
+ .then(callback)
367
+ ), 50)
368
+ ));
340
369
  }
341
370
 
342
371
  let log_history = rtjscomp.log_history = [];
@@ -393,22 +422,23 @@ const request_handle = async (request, response, https) => {
393
422
  try {
394
423
  const request_url_parsed = url.parse(request.url, false);
395
424
 
396
- let path = request_url_parsed.pathname || '';
397
-
398
- // ignore (timeout) many hack attempts
399
- if (path.includes('php') || path.includes('sql')) return;
400
-
401
- // remove leading/trailing /
402
- while (path.charCodeAt(0) === 47) {
403
- path = path.substring(1);
404
- }
405
- while (path.charCodeAt(path.length - 1) === 47) {
406
- path = path.substring(0, path.length - 1);
425
+ let path = request_url_parsed.pathname || '/';
426
+ if (
427
+ path.charCodeAt(0) !== 47 ||
428
+ path.includes('//')
429
+ ) throw 404;
430
+ if (path.length > 1 && path.endsWith('/')) {
431
+ response.setHeader('Location', path.slice(0, -1));
432
+ throw 301;
407
433
  }
434
+ path = path.slice(1);
408
435
 
409
- if (path.includes('..') || path.includes('~')) throw 403;
410
-
411
- if (file_blocks.has(path)) return;
436
+ // ignore (timeout) many hack attempts
437
+ if (
438
+ path.includes('php') ||
439
+ path.includes('sql') ||
440
+ file_blocks.has(path)
441
+ ) return;
412
442
 
413
443
  response.setHeader('Server', 'l3p3 rtjscomp v' + VERSION);
414
444
  response.setHeader('Access-Control-Allow-Origin', '*');
@@ -434,11 +464,18 @@ const request_handle = async (request, response, https) => {
434
464
  const params = {};
435
465
  for (let i = 0; i < template_length; ++i) {
436
466
  if (template[i].charCodeAt(0) === 42) {
437
- if (template[i].length > 1) params[template[i].substr(1)] = path_split[i];
467
+ if (template[i].length > 1) {
468
+ params[template[i].slice(1)] = path_split[i];
469
+ }
470
+ }
471
+ else if (template[i] !== path_split[i]) {
472
+ continue template;
438
473
  }
439
- else if (template[i] !== path_split[i]) continue template;
440
474
  }
441
- response.setHeader('Content-Location', path = template_pair[1]);
475
+ response.setHeader(
476
+ 'Content-Location',
477
+ path = template_pair[1]
478
+ );
442
479
  path_params = params;
443
480
  break;
444
481
  }
@@ -446,9 +483,13 @@ const request_handle = async (request, response, https) => {
446
483
  }
447
484
 
448
485
  const file_type_index = path.lastIndexOf('.');
449
- // no type ending -> dir?
450
- if (file_type_index <= path.lastIndexOf('/')) throw 404;
451
- const file_type = path.substring(
486
+ if (
487
+ path.includes('..') ||
488
+ path.includes('~') ||
489
+ // no type ending -> dir?
490
+ file_type_index <= path.lastIndexOf('/')
491
+ ) throw 404;
492
+ const file_type = path.slice(
452
493
  file_type_index + 1
453
494
  ).toLowerCase();
454
495
 
@@ -510,7 +551,7 @@ const request_handle = async (request, response, https) => {
510
551
  if (file_stat.isDirectory()) throw 403;
511
552
 
512
553
  if (file_dyn_enabled) { // compile file
513
- const file_content = fs.readFileSync(path_real, 'utf8');
554
+ const file_content = await fsp.readFile(path_real, 'utf8');
514
555
  try {
515
556
  if (file_content.includes('\r')) {
516
557
  throw 'illegal line break, must be unix';
@@ -520,7 +561,7 @@ const request_handle = async (request, response, https) => {
520
561
  }
521
562
  const file_content_length = file_content.length;
522
563
 
523
- let code = `async (input,output,request,response,require,custom_import)=>{const log=a=>rtjscomp.log(${
564
+ let code = `const log=a=>rtjscomp.log(${
524
565
  JSON.stringify(path + ': ')
525
566
  }+a);`;
526
567
 
@@ -546,7 +587,7 @@ const request_handle = async (request, response, https) => {
546
587
  if (file_content.charCodeAt(index_start) !== 61) {
547
588
  code += (
548
589
  file_content
549
- .substring(
590
+ .slice(
550
591
  index_start,
551
592
  index_end
552
593
  )
@@ -557,7 +598,7 @@ const request_handle = async (request, response, https) => {
557
598
  else { // `<?=`?
558
599
  code += `output.write(''+(${
559
600
  file_content
560
- .substring(
601
+ .slice(
561
602
  ++index_start,
562
603
  index_end
563
604
  )
@@ -588,7 +629,7 @@ const request_handle = async (request, response, https) => {
588
629
  if (index_start < index_end) {
589
630
  code += `output.write(${
590
631
  JSON.stringify(
591
- file_content.substring(index_start, index_end)
632
+ file_content.slice(index_start, index_end)
592
633
  )
593
634
  });`;
594
635
  }
@@ -596,7 +637,15 @@ const request_handle = async (request, response, https) => {
596
637
  }
597
638
 
598
639
  try {
599
- file_function = (0, eval)(code += '}');
640
+ file_function = new AsyncFunction(
641
+ 'input',
642
+ 'output',
643
+ 'request',
644
+ 'response',
645
+ 'require',
646
+ 'custom_import',
647
+ code
648
+ );
600
649
  }
601
650
  catch (err) {
602
651
  throw err.message;
@@ -608,9 +657,9 @@ const request_handle = async (request, response, https) => {
608
657
  }
609
658
 
610
659
  file_cache_functions.set(path, file_function);
611
- file_watch(path_real, () => {
612
- file_cache_functions.delete(path);
613
- });
660
+ file_watch_once(path_real, () => (
661
+ file_cache_functions.delete(path)
662
+ ));
614
663
  }
615
664
  }
616
665
 
@@ -630,11 +679,11 @@ const request_handle = async (request, response, https) => {
630
679
  if (index_equ > 0) {
631
680
  file_function_input[
632
681
  cookie
633
- .substring(0, index_equ)
682
+ .slice(0, index_equ)
634
683
  .trimRight()
635
684
  ] = decodeURI(
636
685
  cookie
637
- .substr(index_equ + 1)
686
+ .slice(index_equ + 1)
638
687
  .trimLeft()
639
688
  );
640
689
  }
@@ -729,7 +778,7 @@ const request_handle = async (request, response, https) => {
729
778
  .map(e => e[0] === 'password' ? [e[0], '***'] : e)
730
779
  .map(e => e[0] === 'file' ? [e[0], '...'] : e)
731
780
  .map(e => (typeof e[1] === 'object' && !e[1].length) ? [e[0], Object.keys(e[1]).slice(0, 20)] : e)
732
- .map(e => (e[0] !== 'user_agent' && typeof e[1] === 'string' && e[1].length > 20) ? [e[0], e[1].substr(0, 20) + '...'] : e)
781
+ .map(e => (e[0] !== 'user_agent' && typeof e[1] === 'string' && e[1].length > 20) ? [e[0], e[1].slice(0, 20) + '...'] : e)
733
782
  )
734
783
  ]);
735
784