@jsenv/core 36.0.2 → 36.1.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.
@@ -1,1153 +0,0 @@
1
- window.__supervisor__ = (() => {
2
- const notImplemented = () => {
3
- throw new Error(`window.__supervisor__.setup() not called`);
4
- };
5
- const supervisor = {
6
- reportException: notImplemented,
7
- superviseScript: notImplemented,
8
- superviseScriptTypeModule: notImplemented,
9
- reloadSupervisedScript: notImplemented,
10
- getDocumentExecutionResult: notImplemented,
11
- };
12
-
13
- const executionResults = {};
14
- let documentExecutionStartTime;
15
- try {
16
- documentExecutionStartTime = window.performance.timing.navigationStart;
17
- } catch (e) {
18
- documentExecutionStartTime = Date.now();
19
- }
20
- let documentExecutionEndTime;
21
- supervisor.setup = ({
22
- rootDirectoryUrl,
23
- scriptInfos,
24
- serverIsJsenvDevServer,
25
- logs,
26
- errorOverlay,
27
- errorBaseUrl,
28
- openInEditor,
29
- }) => {
30
- const executions = {};
31
- const promises = [];
32
- let remainingScriptCount = scriptInfos.length;
33
-
34
- // respect execution order
35
- // - wait for classic scripts to be done (non async)
36
- // - wait module script previous execution (non async)
37
- // see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
38
- const executionQueue = [];
39
- let executing = false;
40
- const addToExecutionQueue = async (execution) => {
41
- if (execution.async) {
42
- execution.execute();
43
- return;
44
- }
45
- if (executing) {
46
- executionQueue.push(execution);
47
- return;
48
- }
49
- execThenDequeue(execution);
50
- };
51
- const execThenDequeue = async (execution) => {
52
- executing = true;
53
- // start next js module execution as soon as current js module starts to execute
54
- // (do not wait in case of top level await)
55
- let resolveExecutingPromise;
56
- const executingPromise = new Promise((resolve) => {
57
- resolveExecutingPromise = resolve;
58
- });
59
- const promise = execution.execute({
60
- onExecuting: () => resolveExecutingPromise(),
61
- });
62
- await Promise.race([promise, executingPromise]);
63
- executing = false;
64
- if (executionQueue.length) {
65
- const nextExecution = executionQueue.shift();
66
- execThenDequeue(nextExecution);
67
- }
68
- };
69
-
70
- const asExecutionId = (src) => {
71
- const url = new URL(src, window.location).href;
72
- if (url.startsWith(window.location.origin)) {
73
- return src;
74
- }
75
- return url;
76
- };
77
-
78
- const createExecutionController = (src, type) => {
79
- const result = {
80
- status: "pending",
81
- duration: null,
82
- coverage: null,
83
- exception: null,
84
- value: null,
85
- };
86
-
87
- let resolve;
88
- const promise = new Promise((_resolve) => {
89
- resolve = _resolve;
90
- });
91
- promises.push(promise);
92
- executionResults[src] = result;
93
-
94
- const start = () => {
95
- result.duration = null;
96
- result.coverage = null;
97
- result.status = "started";
98
- result.exception = null;
99
- if (logs) {
100
- console.group(`[jsenv] ${src} execution started (${type})`);
101
- }
102
- };
103
- const end = () => {
104
- const now = Date.now();
105
- remainingScriptCount--;
106
- result.duration = now - documentExecutionStartTime;
107
- result.coverage = window.__coverage__;
108
- if (logs) {
109
- console.log(`execution ${result.status}`);
110
- console.groupEnd();
111
- }
112
- if (remainingScriptCount === 0) {
113
- documentExecutionEndTime = now;
114
- }
115
- resolve();
116
- };
117
- const complete = () => {
118
- result.status = "completed";
119
- end();
120
- };
121
- const fail = (error, info) => {
122
- result.status = "failed";
123
- const exception = supervisor.createException(error, info);
124
- result.exception = exception;
125
- end();
126
- };
127
-
128
- return { result, start, complete, fail };
129
- };
130
-
131
- const prepareJsClassicRemoteExecution = (src) => {
132
- const urlObject = new URL(src, window.location);
133
- const url = urlObject.href;
134
- const { result, start, complete, fail } = createExecutionController(
135
- src,
136
- "js_classic",
137
- );
138
-
139
- let parentNode;
140
- let currentScript;
141
- let nodeToReplace;
142
- let currentScriptClone;
143
- const init = () => {
144
- currentScript = document.currentScript;
145
- parentNode = currentScript.parentNode;
146
- executions[src].async = currentScript.async;
147
- };
148
- const execute = async ({ isReload } = {}) => {
149
- start();
150
- currentScriptClone = prepareScriptToLoad(currentScript);
151
- if (isReload) {
152
- urlObject.searchParams.set("hmr", Date.now());
153
- nodeToReplace = currentScriptClone;
154
- currentScriptClone.src = urlObject.href;
155
- } else {
156
- nodeToReplace = currentScript;
157
- currentScriptClone.src = url;
158
- }
159
- const scriptLoadPromise = getScriptLoadPromise(currentScriptClone);
160
- parentNode.replaceChild(currentScriptClone, nodeToReplace);
161
- const { detectedBy, failed, error } = await scriptLoadPromise;
162
- if (failed) {
163
- if (detectedBy === "script_error_event") {
164
- // window.error won't be dispatched for this error
165
- reportErrorBackToBrowser(error);
166
- }
167
- fail(error, {
168
- message: `Error while loading script: ${urlObject.href}`,
169
- reportedBy: "script_error_event",
170
- url: urlObject.href,
171
- });
172
- if (detectedBy === "script_error_event") {
173
- supervisor.reportException(result.exception);
174
- }
175
- } else {
176
- complete();
177
- }
178
- return result;
179
- };
180
- executions[src] = { init, execute };
181
- };
182
- const prepareJsClassicInlineExecution = (src) => {
183
- const { start, complete, fail } = createExecutionController(
184
- src,
185
- "js_classic",
186
- );
187
- const end = complete;
188
- const error = (e) => {
189
- reportErrorBackToBrowser(e); // supervision shallowed the error, report back to browser
190
- fail(e);
191
- };
192
- executions[src] = { isInline: true, start, end, error };
193
- };
194
-
195
- const isWebkitOrSafari =
196
- typeof window.webkitConvertPointFromNodeToPage === "function";
197
- // https://twitter.com/damienmaillard/status/1554752482273787906
198
- const prepareJsModuleExecutionWithDynamicImport = (src) => {
199
- const urlObject = new URL(src, window.location);
200
- const { result, start, complete, fail } = createExecutionController(
201
- src,
202
- "js_classic",
203
- );
204
-
205
- let importFn;
206
- let currentScript;
207
- const init = (_importFn) => {
208
- importFn = _importFn;
209
- currentScript = document.querySelector(
210
- `script[type="module"][inlined-from-src="${src}"]`,
211
- );
212
- executions[src].async = currentScript.async;
213
- };
214
- const execute = async ({ isReload } = {}) => {
215
- start();
216
- if (isReload) {
217
- urlObject.searchParams.set("hmr", Date.now());
218
- }
219
- try {
220
- const namespace = await importFn(urlObject.href);
221
- complete(namespace);
222
- return result;
223
- } catch (e) {
224
- fail(e, {
225
- message: `Error while importing module: ${urlObject.href}`,
226
- reportedBy: "dynamic_import",
227
- url: urlObject.href,
228
- });
229
- if (isWebkitOrSafari) {
230
- supervisor.reportException(result.exception);
231
- }
232
- return result;
233
- }
234
- };
235
- executions[src] = { init, execute };
236
- };
237
- const prepareJsModuleExecutionWithScriptThenDynamicImport = (src) => {
238
- const urlObject = new URL(src, window.location);
239
- const { result, start, complete, fail } = createExecutionController(
240
- src,
241
- "js_module",
242
- );
243
-
244
- let importFn;
245
- let currentScript;
246
- let parentNode;
247
- let nodeToReplace;
248
- let currentScriptClone;
249
- const init = (_importFn) => {
250
- importFn = _importFn;
251
- currentScript = document.querySelector(
252
- `script[type="module"][inlined-from-src="${src}"]`,
253
- );
254
- parentNode = currentScript.parentNode;
255
- executions[src].async = currentScript.async;
256
- };
257
- const execute = async ({ isReload, onExecuting = () => {} } = {}) => {
258
- start();
259
- currentScriptClone = prepareScriptToLoad(currentScript);
260
- if (isReload) {
261
- urlObject.searchParams.set("hmr", Date.now());
262
- nodeToReplace = currentScriptClone;
263
- currentScriptClone.src = urlObject.href;
264
- } else {
265
- nodeToReplace = currentScript;
266
- currentScriptClone.src = urlObject.href;
267
- }
268
- const scriptLoadResultPromise =
269
- getScriptLoadPromise(currentScriptClone);
270
- parentNode.replaceChild(currentScriptClone, nodeToReplace);
271
- const { detectedBy, failed, error } = await scriptLoadResultPromise;
272
-
273
- if (failed) {
274
- // if (detectedBy === "script_error_event") {
275
- // reportErrorBackToBrowser(error)
276
- // }
277
- fail(error, {
278
- message: `Error while loading module: ${urlObject.href}`,
279
- reportedBy: "script_error_event",
280
- url: urlObject.href,
281
- });
282
- if (detectedBy === "script_error_event") {
283
- supervisor.reportException(result.exception);
284
- }
285
- return result;
286
- }
287
-
288
- onExecuting();
289
- result.status = "executing";
290
- if (logs) {
291
- console.log(`load ended`);
292
- }
293
- try {
294
- const namespace = await importFn(urlObject.href);
295
- complete(namespace);
296
- return result;
297
- } catch (e) {
298
- fail(e, {
299
- message: `Error while importing module: ${urlObject.href}`,
300
- reportedBy: "dynamic_import",
301
- url: urlObject.href,
302
- });
303
- return result;
304
- }
305
- };
306
- executions[src] = { init, execute };
307
- };
308
- const prepareJsModuleRemoteExecution = isWebkitOrSafari
309
- ? prepareJsModuleExecutionWithDynamicImport
310
- : prepareJsModuleExecutionWithScriptThenDynamicImport;
311
- const prepareJsModuleInlineExecution = (src) => {
312
- const { start, complete, fail } = createExecutionController(
313
- src,
314
- "js_module",
315
- );
316
- const end = complete;
317
- const error = (e) => {
318
- // supervision shallowed the error, report back to browser
319
- reportErrorBackToBrowser(e);
320
- fail(e);
321
- };
322
- executions[src] = { isInline: true, start, end, error };
323
- };
324
-
325
- supervisor.setupReportException({
326
- logs,
327
- serverIsJsenvDevServer,
328
- rootDirectoryUrl,
329
- errorOverlay,
330
- errorBaseUrl,
331
- openInEditor,
332
- });
333
-
334
- scriptInfos.forEach((scriptInfo) => {
335
- const { type, src, isInline } = scriptInfo;
336
- if (type === "js_module") {
337
- if (isInline) {
338
- prepareJsModuleInlineExecution(src);
339
- } else {
340
- prepareJsModuleRemoteExecution(src);
341
- }
342
- } else if (isInline) {
343
- prepareJsClassicInlineExecution(src);
344
- } else {
345
- prepareJsClassicRemoteExecution(src);
346
- }
347
- });
348
-
349
- // js classic
350
- supervisor.jsClassicStart = (inlineSrc) => {
351
- executions[inlineSrc].start();
352
- };
353
- supervisor.jsClassicEnd = (inlineSrc) => {
354
- executions[inlineSrc].end();
355
- };
356
- supervisor.jsClassicError = (inlineSrc, e) => {
357
- executions[inlineSrc].error(e);
358
- };
359
- supervisor.superviseScript = (src) => {
360
- const execution = executions[asExecutionId(src)];
361
- execution.init();
362
- return addToExecutionQueue(execution);
363
- };
364
- // js module
365
- supervisor.jsModuleStart = (inlineSrc) => {
366
- executions[inlineSrc].start();
367
- };
368
- supervisor.jsModuleEnd = (inlineSrc) => {
369
- executions[inlineSrc].end();
370
- };
371
- supervisor.jsModuleError = (inlineSrc, e) => {
372
- executions[inlineSrc].error(e);
373
- };
374
- supervisor.superviseScriptTypeModule = (src, importFn) => {
375
- const execution = executions[asExecutionId(src)];
376
- execution.init(importFn);
377
- return addToExecutionQueue(execution);
378
- };
379
-
380
- supervisor.reloadSupervisedScript = (src) => {
381
- const execution = executions[src];
382
- if (!execution) {
383
- throw new Error(`no execution for ${src}`);
384
- }
385
- if (execution.isInline) {
386
- throw new Error(`cannot reload inline script ${src}`);
387
- }
388
- return execution.execute({ isReload: true });
389
- };
390
-
391
- supervisor.getDocumentExecutionResult = async () => {
392
- await Promise.all(promises);
393
- return {
394
- startTime: documentExecutionStartTime,
395
- endTime: documentExecutionEndTime,
396
- status: "completed",
397
- executionResults,
398
- };
399
- };
400
- };
401
- const reportErrorBackToBrowser = (error) => {
402
- if (typeof window.reportError === "function") {
403
- window.reportError(error);
404
- } else {
405
- console.error(error);
406
- }
407
- };
408
-
409
- supervisor.setupReportException = ({
410
- logs,
411
- rootDirectoryUrl,
412
- serverIsJsenvDevServer,
413
- errorNotification,
414
- errorOverlay,
415
- errorBaseUrl,
416
- openInEditor,
417
- }) => {
418
- const DYNAMIC_IMPORT_FETCH_ERROR = "dynamic_import_fetch_error";
419
- const DYNAMIC_IMPORT_EXPORT_MISSING = "dynamic_import_export_missing";
420
- const DYNAMIC_IMPORT_SYNTAX_ERROR = "dynamic_import_syntax_error";
421
-
422
- const createException = (
423
- reason, // can be error, string, object
424
- { message, reportedBy, url, line, column } = {},
425
- ) => {
426
- const exception = {
427
- reason,
428
- isError: false, // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
429
- reportedBy,
430
- code: null,
431
- message: null,
432
- stack: null,
433
- stackFormatIsV8: null,
434
- stackSourcemapped: null,
435
- originalStack: null,
436
- meta: null,
437
- site: {
438
- isInline: null,
439
- url: null,
440
- line: null,
441
- column: null,
442
- originalUrl: null,
443
- },
444
- };
445
-
446
- const writeBasicProperties = () => {
447
- if (reason === undefined) {
448
- exception.message = "undefined";
449
- return;
450
- }
451
- if (reason === null) {
452
- exception.message = "null";
453
- return;
454
- }
455
- if (typeof reason === "string") {
456
- exception.message = reason;
457
- return;
458
- }
459
- if (reason instanceof Error) {
460
- const error = reason;
461
- let message = error.message;
462
- exception.isError = true;
463
- if (Error.captureStackTrace) {
464
- // stackTrace formatted by V8
465
- exception.message = message;
466
- exception.stack = getErrorStackWithoutErrorMessage(error);
467
- exception.stackFormatIsV8 = true;
468
- exception.stackSourcemapped = true;
469
- } else {
470
- exception.message = message;
471
- exception.stack = error.stack ? ` ${error.stack}` : null;
472
- exception.stackFormatIsV8 = false;
473
- exception.stackSourcemapped = false;
474
- }
475
- if (error.reportedBy) {
476
- exception.reportedBy = error.reportedBy;
477
- }
478
- if (error.url) {
479
- Object.assign(exception.site, resolveUrlSite({ url: error.url }));
480
- }
481
- export_missing: {
482
- // chrome
483
- if (message.includes("does not provide an export named")) {
484
- exception.code = DYNAMIC_IMPORT_EXPORT_MISSING;
485
- return;
486
- }
487
- // firefox
488
- if (
489
- message.startsWith("import not found:") ||
490
- message.startsWith("ambiguous indirect export:")
491
- ) {
492
- exception.code = DYNAMIC_IMPORT_EXPORT_MISSING;
493
- return;
494
- }
495
- // safari
496
- if (message.startsWith("import binding name")) {
497
- exception.code = DYNAMIC_IMPORT_EXPORT_MISSING;
498
- return;
499
- }
500
- if (message.includes("Importing a module script failed")) {
501
- exception.code = DYNAMIC_IMPORT_FETCH_ERROR;
502
- return;
503
- }
504
- }
505
- js_syntax_error: {
506
- if (error.name === "SyntaxError" && typeof line === "number") {
507
- exception.code = DYNAMIC_IMPORT_SYNTAX_ERROR;
508
- return;
509
- }
510
- }
511
- return;
512
- }
513
- if (typeof reason === "object") {
514
- // happens when reason is an Event for instance
515
- exception.code = reason.code;
516
- exception.message = reason.message || message;
517
- exception.stack = reason.stack;
518
- if (reason.reportedBy) {
519
- exception.reportedBy = reason.reportedBy;
520
- }
521
- if (reason.url) {
522
- Object.assign(exception.site, resolveUrlSite({ url: reason.url }));
523
- }
524
- return;
525
- }
526
- exception.message = JSON.stringify(reason);
527
- };
528
- writeBasicProperties();
529
-
530
- // first create a version of the stack with file://
531
- // (and use it to locate exception url+line+column)
532
- if (exception.stack) {
533
- exception.originalStack = exception.stack;
534
- exception.stack = replaceUrls(
535
- exception.originalStack,
536
- (serverUrlSite) => {
537
- const fileUrlSite = resolveUrlSite(serverUrlSite);
538
- if (exception.site.url === null) {
539
- Object.assign(exception.site, fileUrlSite);
540
- }
541
- return stringifyUrlSite(fileUrlSite);
542
- },
543
- );
544
- }
545
- // then if it fails, use url+line+column passed
546
- if (exception.site.url === null && url) {
547
- if (typeof line === "string") {
548
- line = parseInt(line);
549
- }
550
- if (typeof column === "string") {
551
- column = parseInt(column);
552
- }
553
- const fileUrlSite = resolveUrlSite({ url, line, column });
554
- if (
555
- fileUrlSite.isInline &&
556
- exception.code === DYNAMIC_IMPORT_SYNTAX_ERROR
557
- ) {
558
- // syntax error on inline script need line-1 for some reason
559
- fileUrlSite.line = fileUrlSite.line - 1;
560
- }
561
- Object.assign(exception.site, fileUrlSite);
562
- }
563
- exception.text = stringifyMessageAndStack(exception);
564
- return exception;
565
- };
566
-
567
- const stringifyMessageAndStack = ({ message, stack }) => {
568
- if (message && stack) {
569
- return `${message}\n${stack}`;
570
- }
571
- if (stack) {
572
- return stack;
573
- }
574
- return message;
575
- };
576
-
577
- const stringifyUrlSite = ({ url, line, column }) => {
578
- if (typeof line === "number" && typeof column === "number") {
579
- return `${url}:${line}:${column}`;
580
- }
581
- if (typeof line === "number") {
582
- return `${url}:${line}`;
583
- }
584
- return url;
585
- };
586
-
587
- const resolveUrlSite = ({ url, line, column }) => {
588
- const inlineUrlMatch = url.match(
589
- /@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/,
590
- );
591
- if (inlineUrlMatch) {
592
- const htmlUrl = url.slice(0, inlineUrlMatch.index);
593
- const tagLineStart = parseInt(inlineUrlMatch[1]);
594
- const tagColumnStart = parseInt(inlineUrlMatch[2]);
595
- const tagLineEnd = parseInt(inlineUrlMatch[3]);
596
- const tagColumnEnd = parseInt(inlineUrlMatch[4]);
597
- const extension = inlineUrlMatch[5];
598
- url = htmlUrl;
599
- line = tagLineStart + (typeof line === "number" ? line : 0);
600
- line = line - 1; // sauf pour les erreur de syntaxe
601
- column = tagColumnStart + (typeof column === "number" ? column : 0);
602
- const fileUrl = resolveFileUrl(url);
603
- return {
604
- isInline: true,
605
- serverUrl: url,
606
- originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
607
- url: fileUrl,
608
- line,
609
- column,
610
- };
611
- }
612
- return {
613
- isInline: false,
614
- serverUrl: url,
615
- url: resolveFileUrl(url),
616
- line,
617
- column,
618
- };
619
- };
620
-
621
- const getErrorStackWithoutErrorMessage = (error) => {
622
- let stack = error.stack;
623
- if (!stack) return "";
624
- const messageInStack = `${error.name}: ${error.message}`;
625
- if (stack.startsWith(messageInStack)) {
626
- stack = stack.slice(messageInStack.length);
627
- }
628
- const nextLineIndex = stack.indexOf("\n");
629
- if (nextLineIndex > -1) {
630
- stack = stack.slice(nextLineIndex + 1);
631
- }
632
- return stack;
633
- };
634
-
635
- const resolveFileUrl = (url) => {
636
- let urlObject = new URL(url, window.origin);
637
- if (urlObject.origin === window.origin) {
638
- urlObject = new URL(
639
- `${urlObject.pathname.slice(1)}${urlObject.search}`,
640
- rootDirectoryUrl,
641
- );
642
- }
643
- if (urlObject.href.startsWith("file:")) {
644
- const atFsIndex = urlObject.pathname.indexOf("/@fs/");
645
- if (atFsIndex > -1) {
646
- const afterAtFs = urlObject.pathname.slice(
647
- atFsIndex + "/@fs/".length,
648
- );
649
- return new URL(afterAtFs, "file:///").href;
650
- }
651
- }
652
- return urlObject.href;
653
- };
654
-
655
- // `Error: yo
656
- // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
657
- // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
658
- // at postOrderExec (http://127.0.0.1:3000/src/__test__/file-throw.js:448:16)
659
- // at http://127.0.0.1:3000/src/__test__/file-throw.js:399:18`.replace(/(?:https?|ftp|file):\/\/(.*+)$/gm, (...args) => {
660
- // debugger
661
- // })
662
- const replaceUrls = (source, replace) => {
663
- return source.replace(/(?:https?|ftp|file):\/\/\S+/gm, (match) => {
664
- let replacement = "";
665
- const lastChar = match[match.length - 1];
666
-
667
- // hotfix because our url regex sucks a bit
668
- const endsWithSeparationChar = lastChar === ")" || lastChar === ":";
669
- if (endsWithSeparationChar) {
670
- match = match.slice(0, -1);
671
- }
672
-
673
- const lineAndColumnPattern = /:([0-9]+):([0-9]+)$/;
674
- const lineAndColumMatch = match.match(lineAndColumnPattern);
675
- if (lineAndColumMatch) {
676
- const lineAndColumnString = lineAndColumMatch[0];
677
- const lineString = lineAndColumMatch[1];
678
- const columnString = lineAndColumMatch[2];
679
- replacement = replace({
680
- url: match.slice(0, -lineAndColumnString.length),
681
- line: lineString ? parseInt(lineString) : null,
682
- column: columnString ? parseInt(columnString) : null,
683
- });
684
- } else {
685
- const linePattern = /:([0-9]+)$/;
686
- const lineMatch = match.match(linePattern);
687
- if (lineMatch) {
688
- const lineString = lineMatch[0];
689
- replacement = replace({
690
- url: match.slice(0, -lineString.length),
691
- line: lineString ? parseInt(lineString) : null,
692
- });
693
- } else {
694
- replacement = replace({
695
- url: match,
696
- });
697
- }
698
- }
699
- if (endsWithSeparationChar) {
700
- return `${replacement}${lastChar}`;
701
- }
702
- return replacement;
703
- });
704
- };
705
-
706
- let formatError;
707
- error_formatter: {
708
- formatError = (exceptionInfo) => {
709
- const errorParts = {
710
- theme: "dark",
711
- title: "An error occured",
712
- text: "",
713
- tip: "",
714
- errorDetailsPromise: null,
715
- };
716
- const tips = [];
717
- tips.push("Click outside to close.");
718
- errorParts.tip = tips.join(`\n <br />\n `);
719
-
720
- const generateClickableText = (text) => {
721
- const textWithHtmlLinks = makeLinksClickable(text, {
722
- createLink: ({ url, line, column }) => {
723
- const urlSite = resolveUrlSite({ url, line, column });
724
- if (errorBaseUrl) {
725
- if (urlSite.url.startsWith(rootDirectoryUrl)) {
726
- urlSite.url = `${errorBaseUrl}${urlSite.url.slice(
727
- rootDirectoryUrl.length,
728
- )}`;
729
- } else {
730
- urlSite.url = "file:///mocked_for_snapshots";
731
- }
732
- }
733
- const urlWithLineAndColumn = stringifyUrlSite(urlSite);
734
- return {
735
- href:
736
- urlSite.url.startsWith("file:") && openInEditor
737
- ? `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
738
- urlWithLineAndColumn,
739
- )}')`
740
- : urlSite.url,
741
- text: urlWithLineAndColumn,
742
- };
743
- },
744
- });
745
- return textWithHtmlLinks;
746
- };
747
-
748
- errorParts.text = stringifyMessageAndStack({
749
- message: exceptionInfo.message
750
- ? generateClickableText(exceptionInfo.message)
751
- : "",
752
- stack: exceptionInfo.stack
753
- ? generateClickableText(exceptionInfo.stack)
754
- : "",
755
- });
756
- if (exceptionInfo.site.url) {
757
- errorParts.errorDetailsPromise = (async () => {
758
- if (!serverIsJsenvDevServer) {
759
- return null;
760
- }
761
- try {
762
- if (
763
- exceptionInfo.code === DYNAMIC_IMPORT_FETCH_ERROR ||
764
- exceptionInfo.reportedBy === "script_error_event"
765
- ) {
766
- const response = await window.fetch(
767
- `/__get_error_cause__/${encodeURIComponent(
768
- exceptionInfo.site.isInline
769
- ? exceptionInfo.site.originalUrl
770
- : exceptionInfo.site.url,
771
- )}`,
772
- );
773
- if (response.status !== 200) {
774
- return null;
775
- }
776
- const causeInfo = await response.json();
777
- if (!causeInfo) {
778
- return null;
779
- }
780
- const causeText =
781
- causeInfo.code === "NOT_FOUND"
782
- ? stringifyMessageAndStack({
783
- message: generateClickableText(causeInfo.reason),
784
- stack: generateClickableText(causeInfo.codeFrame),
785
- })
786
- : stringifyMessageAndStack({
787
- message: generateClickableText(causeInfo.stack),
788
- stack: generateClickableText(causeInfo.codeFrame),
789
- });
790
- return {
791
- cause: causeText,
792
- };
793
- }
794
- if (exceptionInfo.site.line !== undefined) {
795
- const urlToFetch = new URL(
796
- `/__get_code_frame__/${encodeURIComponent(
797
- stringifyUrlSite(exceptionInfo.site),
798
- )}`,
799
- window.origin,
800
- );
801
- if (!exceptionInfo.stackSourcemapped) {
802
- urlToFetch.searchParams.set("remap", "");
803
- }
804
- const response = await window.fetch(urlToFetch);
805
- if (response.status !== 200) {
806
- return null;
807
- }
808
- const codeFrame = await response.text();
809
- return {
810
- codeFrame: generateClickableText(codeFrame),
811
- };
812
- }
813
- } catch (e) {
814
- // happens if server is closed for instance
815
- return null;
816
- }
817
- return null;
818
- })();
819
- }
820
- return errorParts;
821
- };
822
-
823
- const makeLinksClickable = (
824
- string,
825
- { createLink = ({ url }) => url },
826
- ) => {
827
- // normalize line breaks
828
- string = string.replace(/\n/g, "\n");
829
- string = escapeHtml(string);
830
- // render links
831
- string = replaceUrls(string, ({ url, line, column }) => {
832
- const { href, text } = createLink({ url, line, column });
833
- return link({ href, text });
834
- });
835
- return string;
836
- };
837
-
838
- const escapeHtml = (string) => {
839
- return string
840
- .replace(/&/g, "&amp;")
841
- .replace(/</g, "&lt;")
842
- .replace(/>/g, "&gt;")
843
- .replace(/"/g, "&quot;")
844
- .replace(/'/g, "&#039;");
845
- };
846
-
847
- const link = ({ href, text = href }) => `<a href="${href}">${text}</a>`;
848
- }
849
-
850
- let displayErrorNotification;
851
- error_notification: {
852
- const { Notification } = window;
853
- displayErrorNotification =
854
- typeof Notification === "function"
855
- ? ({ title, text, icon }) => {
856
- if (Notification.permission === "granted") {
857
- const notification = new Notification(title, {
858
- lang: "en",
859
- body: text,
860
- icon,
861
- });
862
- notification.onclick = () => {
863
- window.focus();
864
- };
865
- }
866
- }
867
- : () => {};
868
- }
869
-
870
- const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
871
- let displayJsenvErrorOverlay;
872
- error_overlay: {
873
- displayJsenvErrorOverlay = (params) => {
874
- if (logs) {
875
- console.log("display jsenv error overlay", params);
876
- }
877
- let jsenvErrorOverlay = new JsenvErrorOverlay(params);
878
- document
879
- .querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME)
880
- .forEach((node) => {
881
- node.parentNode.removeChild(node);
882
- });
883
- document.body.appendChild(jsenvErrorOverlay);
884
- const removeErrorOverlay = () => {
885
- if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
886
- document.body.removeChild(jsenvErrorOverlay);
887
- jsenvErrorOverlay = null;
888
- }
889
- };
890
- return removeErrorOverlay;
891
- };
892
-
893
- class JsenvErrorOverlay extends HTMLElement {
894
- constructor({ theme, title, text, tip, errorDetailsPromise }) {
895
- super();
896
- this.root = this.attachShadow({ mode: "open" });
897
- this.root.innerHTML = `
898
- <style>
899
- ${overlayCSS}
900
- </style>
901
- <div class="backdrop"></div>
902
- <div class="overlay" data-theme=${theme}>
903
- <h1 class="title">
904
- ${title}
905
- </h1>
906
- <pre class="text">${text}</pre>
907
- <div class="tip">
908
- ${tip}
909
- </div>
910
- </div>`;
911
- this.root.querySelector(".backdrop").onclick = () => {
912
- if (!this.parentNode) {
913
- // not in document anymore
914
- return;
915
- }
916
- this.root.querySelector(".backdrop").onclick = null;
917
- this.parentNode.removeChild(this);
918
- };
919
- if (errorDetailsPromise) {
920
- errorDetailsPromise.then((errorDetails) => {
921
- if (!errorDetails || !this.parentNode) {
922
- return;
923
- }
924
- const { codeFrame, cause } = errorDetails;
925
- if (codeFrame) {
926
- this.root.querySelector(
927
- ".text",
928
- ).innerHTML += `\n\n${codeFrame}`;
929
- }
930
- if (cause) {
931
- const causeIndented = prefixRemainingLines(cause, " ");
932
- this.root.querySelector(
933
- ".text",
934
- ).innerHTML += `\n [cause]: ${causeIndented}`;
935
- }
936
- });
937
- }
938
- }
939
- }
940
-
941
- const prefixRemainingLines = (text, prefix) => {
942
- const lines = text.split(/\r?\n/);
943
- const firstLine = lines.shift();
944
- let result = firstLine;
945
- let i = 0;
946
- while (i < lines.length) {
947
- const line = lines[i];
948
- i++;
949
- result += line.length ? `\n${prefix}${line}` : `\n`;
950
- }
951
- return result;
952
- };
953
-
954
- if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
955
- customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
956
- }
957
-
958
- const overlayCSS = `
959
- :host {
960
- position: fixed;
961
- z-index: 99999;
962
- top: 0;
963
- left: 0;
964
- width: 100%;
965
- height: 100%;
966
- overflow-y: scroll;
967
- margin: 0;
968
- background: rgba(0, 0, 0, 0.66);
969
- }
970
-
971
- .backdrop {
972
- position: absolute;
973
- left: 0;
974
- right: 0;
975
- top: 0;
976
- bottom: 0;
977
- }
978
-
979
- .overlay {
980
- position: relative;
981
- background: rgba(0, 0, 0, 0.95);
982
- width: 800px;
983
- margin: 30px auto;
984
- padding: 25px 40px;
985
- padding-top: 0;
986
- overflow: hidden; /* for h1 margins */
987
- border-radius: 4px 8px;
988
- box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
989
- box-sizing: border-box;
990
- font-family: monospace;
991
- direction: ltr;
992
- }
993
-
994
- h1 {
995
- color: red;
996
- text-align: center;
997
- }
998
-
999
- pre {
1000
- overflow: auto;
1001
- max-width: 100%;
1002
- /* padding is nice + prevents scrollbar from hiding the text behind it */
1003
- /* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
1004
- padding: 20px;
1005
- }
1006
-
1007
- .tip {
1008
- border-top: 1px solid #999;
1009
- padding-top: 12px;
1010
- }
1011
-
1012
- [data-theme="dark"] {
1013
- color: #999;
1014
- }
1015
- [data-theme="dark"] pre {
1016
- background: #111;
1017
- border: 1px solid #333;
1018
- color: #eee;
1019
- }
1020
-
1021
- [data-theme="light"] {
1022
- color: #EEEEEE;
1023
- }
1024
- [data-theme="light"] pre {
1025
- background: #1E1E1E;
1026
- border: 1px solid white;
1027
- color: #EEEEEE;
1028
- }
1029
-
1030
- pre a {
1031
- color: inherit;
1032
- }`;
1033
- }
1034
-
1035
- supervisor.createException = createException;
1036
- supervisor.reportException = (exception) => {
1037
- const { theme, title, text, tip, errorDetailsPromise } =
1038
- formatError(exception);
1039
-
1040
- if (errorOverlay) {
1041
- const removeErrorOverlay = displayJsenvErrorOverlay({
1042
- theme,
1043
- title,
1044
- text,
1045
- tip,
1046
- errorDetailsPromise,
1047
- });
1048
- if (window.__reloader__) {
1049
- const onchange = window.__reloader__.status.onchange;
1050
- window.__reloader__.status.onchange = () => {
1051
- onchange();
1052
- if (window.__reloader__.status.value === "reloading") {
1053
- removeErrorOverlay();
1054
- }
1055
- };
1056
- }
1057
- }
1058
- if (errorNotification) {
1059
- displayErrorNotification({
1060
- title,
1061
- text,
1062
- });
1063
- }
1064
- return exception;
1065
- };
1066
- window.addEventListener("error", (errorEvent) => {
1067
- if (!errorEvent.isTrusted) {
1068
- // ignore custom error event (not sent by browser)
1069
- if (logs) {
1070
- console.log("ignore non trusted error event", errorEvent);
1071
- }
1072
- return;
1073
- }
1074
- if (logs) {
1075
- console.log('window "error" event received', errorEvent);
1076
- }
1077
- const { error, message, filename, lineno, colno } = errorEvent;
1078
- const exception = supervisor.createException(error || message, {
1079
- // when error is reported within a worker error is null
1080
- // but there is a message property on errorEvent
1081
- reportedBy: "window_error_event",
1082
- url: filename,
1083
- line: lineno,
1084
- column: colno,
1085
- });
1086
- supervisor.reportException(exception);
1087
- });
1088
- window.addEventListener("unhandledrejection", (event) => {
1089
- if (event.defaultPrevented) {
1090
- return;
1091
- }
1092
- const exception = supervisor.createException(event.reason, {
1093
- reportedBy: "window_unhandledrejection_event",
1094
- });
1095
- supervisor.reportException(exception);
1096
- });
1097
- };
1098
-
1099
- const prepareScriptToLoad = (script) => {
1100
- // do not use script.cloneNode()
1101
- // bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
1102
- const scriptClone = document.createElement("script");
1103
- // browsers set async by default when creating script(s)
1104
- // we want an exact copy to preserves how code is executed
1105
- scriptClone.async = false;
1106
- Array.from(script.attributes).forEach((attribute) => {
1107
- scriptClone.setAttribute(attribute.nodeName, attribute.nodeValue);
1108
- });
1109
- scriptClone.removeAttribute("jsenv-cooked-by");
1110
- scriptClone.removeAttribute("jsenv-inlined-by");
1111
- scriptClone.removeAttribute("jsenv-injected-by");
1112
- scriptClone.removeAttribute("inlined-from-src");
1113
- scriptClone.removeAttribute("original-position");
1114
- scriptClone.removeAttribute("original-src-position");
1115
-
1116
- return scriptClone;
1117
- };
1118
-
1119
- const getScriptLoadPromise = async (script) => {
1120
- return new Promise((resolve) => {
1121
- const windowErrorEventCallback = (errorEvent) => {
1122
- if (errorEvent.filename === script.src) {
1123
- removeWindowErrorEventCallback();
1124
- resolve({
1125
- detectedBy: "window_error_event",
1126
- failed: true,
1127
- error: errorEvent,
1128
- });
1129
- }
1130
- };
1131
- const removeWindowErrorEventCallback = () => {
1132
- window.removeEventListener("error", windowErrorEventCallback);
1133
- };
1134
- window.addEventListener("error", windowErrorEventCallback);
1135
- script.addEventListener("error", (errorEvent) => {
1136
- removeWindowErrorEventCallback();
1137
- resolve({
1138
- detectedBy: "script_error_event",
1139
- failed: true,
1140
- error: errorEvent,
1141
- });
1142
- });
1143
- script.addEventListener("load", () => {
1144
- removeWindowErrorEventCallback();
1145
- resolve({
1146
- detectedBy: "script_load_event",
1147
- });
1148
- });
1149
- });
1150
- };
1151
-
1152
- return supervisor;
1153
- })();