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.
- package/README.md +2 -2
- package/package.json +1 -1
- 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
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(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
|
197
|
-
|
|
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 =
|
|
251
|
-
service_object.stopped = true
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
316
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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)
|
|
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(
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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 =
|
|
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 = `
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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
|
-
.
|
|
682
|
+
.slice(0, index_equ)
|
|
634
683
|
.trimRight()
|
|
635
684
|
] = decodeURI(
|
|
636
685
|
cookie
|
|
637
|
-
.
|
|
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].
|
|
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
|
|