2g 0.3.1 → 0.4.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/CHANGELOG.md +12 -0
- package/dist/2g-api.js +5 -120
- package/dist/2g-api.js.map +1 -1
- package/dist/2g-api.mjs +1 -120
- package/dist/2g-api.mjs.map +1 -1
- package/dist/2g.js +42 -42
- package/dist/2g.js.map +1 -1
- package/dist/2g.mjs +115 -115
- package/dist/2g.mjs.map +1 -1
- package/dist/chunks/api-chunk.js +430 -0
- package/dist/chunks/api-chunk.js.map +1 -0
- package/dist/chunks/api-chunk.mjs +430 -0
- package/dist/chunks/api-chunk.mjs.map +1 -0
- package/dist/chunks/cli-chunk.js +160 -46
- package/dist/chunks/cli-chunk.js.map +1 -1
- package/dist/chunks/cli-chunk.mjs +214 -100
- package/dist/chunks/cli-chunk.mjs.map +1 -1
- package/dist/chunks/sessionSockets-chunk.js +4 -0
- package/dist/chunks/sessionSockets-chunk.js.map +1 -1
- package/dist/chunks/sessionSockets-chunk.mjs +9 -5
- package/dist/chunks/sessionSockets-chunk.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/tap-chunk.js +0 -287
- package/dist/chunks/tap-chunk.js.map +0 -1
- package/dist/chunks/tap-chunk.mjs +0 -287
- package/dist/chunks/tap-chunk.mjs.map +0 -1
package/dist/chunks/cli-chunk.js
CHANGED
|
@@ -8,13 +8,15 @@ var s = require("./pretty-chunk.js");
|
|
|
8
8
|
|
|
9
9
|
var o = require("node:fs");
|
|
10
10
|
|
|
11
|
-
var r = require("./
|
|
11
|
+
var r = require("./api-chunk.js");
|
|
12
12
|
|
|
13
13
|
var i = require("node:readline");
|
|
14
14
|
|
|
15
15
|
var a = require("node:crypto");
|
|
16
16
|
|
|
17
|
-
var l = "
|
|
17
|
+
var l = require("node:child_process");
|
|
18
|
+
|
|
19
|
+
var c = "undefined" != typeof document ? document.currentScript : null;
|
|
18
20
|
|
|
19
21
|
function runCleanCli(e = []) {
|
|
20
22
|
const o = function parseCleanOptions(e) {
|
|
@@ -48,7 +50,7 @@ function runCleanCli(e = []) {
|
|
|
48
50
|
})}\n` : `Removed ${r} ${o.all ? "exited" : "stale"} sessions.\n`);
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
const
|
|
53
|
+
const p = {
|
|
52
54
|
since: {
|
|
53
55
|
type: "string"
|
|
54
56
|
},
|
|
@@ -67,6 +69,13 @@ const c = {
|
|
|
67
69
|
}
|
|
68
70
|
};
|
|
69
71
|
|
|
72
|
+
async function warnOnRotationLoss(e, t) {
|
|
73
|
+
if (!await r.detectRotationLoss(e)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.warn(`⚠ Earlier events were rotated out of the retained window.\n For a complete trace, start \`${t}\` before the workload.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
70
79
|
async function* readJsonlStream(e) {
|
|
71
80
|
const t = i.createInterface({
|
|
72
81
|
input: e
|
|
@@ -84,6 +93,21 @@ async function* readJsonlStream(e) {
|
|
|
84
93
|
}
|
|
85
94
|
}
|
|
86
95
|
|
|
96
|
+
async function* filterEvents(e, t) {
|
|
97
|
+
const s = n.compileEventFilter(t.filter);
|
|
98
|
+
const o = n.parseSince(t.since);
|
|
99
|
+
for await (const r of e) {
|
|
100
|
+
if (n.matchesTapOptions(r, {
|
|
101
|
+
filter: t.filter,
|
|
102
|
+
since: o,
|
|
103
|
+
spans: t.spans,
|
|
104
|
+
debug: t.debug
|
|
105
|
+
}, s)) {
|
|
106
|
+
yield r;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
87
111
|
function parseSharedTapOptions(e) {
|
|
88
112
|
return {
|
|
89
113
|
since: e.since,
|
|
@@ -108,7 +132,15 @@ function parseHelp(e) {
|
|
|
108
132
|
}).values.help;
|
|
109
133
|
}
|
|
110
134
|
|
|
111
|
-
const
|
|
135
|
+
const u = [ ":started", ":done", ":failed" ];
|
|
136
|
+
|
|
137
|
+
async function convertToChromeTrace(e, t = {}) {
|
|
138
|
+
const n = new TraceConverter(t);
|
|
139
|
+
for await (const t of e) {
|
|
140
|
+
n.add(t);
|
|
141
|
+
}
|
|
142
|
+
return n.toTraceFile();
|
|
143
|
+
}
|
|
112
144
|
|
|
113
145
|
class TraceConverter {
|
|
114
146
|
#e=[];
|
|
@@ -297,7 +329,7 @@ function assignLanes(e) {
|
|
|
297
329
|
}
|
|
298
330
|
|
|
299
331
|
function stripSuffix$1(e) {
|
|
300
|
-
for (const t of
|
|
332
|
+
for (const t of u) {
|
|
301
333
|
if (e.endsWith(t)) {
|
|
302
334
|
return e.slice(0, -t.length);
|
|
303
335
|
}
|
|
@@ -305,7 +337,15 @@ function stripSuffix$1(e) {
|
|
|
305
337
|
return e;
|
|
306
338
|
}
|
|
307
339
|
|
|
308
|
-
const
|
|
340
|
+
const d = [ ":started", ":done", ":failed" ];
|
|
341
|
+
|
|
342
|
+
async function convertToOpenTelemetry(e, t = {}) {
|
|
343
|
+
const n = new OpenTelemetryConverter(t);
|
|
344
|
+
for await (const t of e) {
|
|
345
|
+
n.add(t);
|
|
346
|
+
}
|
|
347
|
+
return n.toFile();
|
|
348
|
+
}
|
|
309
349
|
|
|
310
350
|
class OpenTelemetryConverter {
|
|
311
351
|
#i=a.randomBytes(16).toString("hex");
|
|
@@ -491,7 +531,7 @@ function splitEventName(e) {
|
|
|
491
531
|
}
|
|
492
532
|
|
|
493
533
|
function stripSuffix(e) {
|
|
494
|
-
for (const t of
|
|
534
|
+
for (const t of d) {
|
|
495
535
|
if (e.endsWith(t)) {
|
|
496
536
|
return e.slice(0, -t.length);
|
|
497
537
|
}
|
|
@@ -502,7 +542,7 @@ function stripSuffix(e) {
|
|
|
502
542
|
async function runExportCli(e) {
|
|
503
543
|
if (parseHelp(e)) {
|
|
504
544
|
!function printExportHelp() {
|
|
505
|
-
process.stdout.write([ "Usage: 2g export [selector]", "", "Exports session events to Chrome Trace or OTLP JSON.", "", "Options:", " selector Substring of PID, CWD, or command; a running", " session wins ties", " --input <file> Read JSONL events from a file", " -o, --output <file> Write output to a file", " --format <format> chrome-trace or opentelemetry", " --json Print JSON output", " --since <time> Duration ago (5m, 90s), Unix time, or ISO date", " --filter <pattern> Event name prefix (whole segments), e.g. category,", " category:kind; * wildcards; comma-separated", " --spans Keep only span events (log.span() pairs with a _d", " duration); omit to include point events too. A", ' plain "ms" payload field is not a span', " --debug Include debug events (only recorded when the", " command ran with LOG_DEBUG set)", " --tail Follow live events after replaying history", " --timeout <duration>
|
|
545
|
+
process.stdout.write([ "Usage: 2g export [selector]", "", "Exports session events to Chrome Trace or OTLP JSON.", "(Without --tail it reads only the retained window. Rotated out logs may be lost)", "", "--tail attaches to an already-running session and follows live from that point.", " Run it after the session is up and leave it attached while the workload runs.", " It will automatically stop on 30s idle.", "To trace a full command instead, use `2g record`.", "", "Options:", " selector Substring of PID, CWD, or command; a running", " session wins ties", " --input <file> Read JSONL events from a file", " -o, --output <file> Write output to a file", " --format <format> chrome-trace or opentelemetry", " --json Print JSON output", " --since <time> Duration ago (5m, 90s), Unix time, or ISO date", " --filter <pattern> Event name prefix (whole segments), e.g. category,", " category:kind; * wildcards; comma-separated", " --spans Keep only span events (log.span() pairs with a _d", " duration); omit to include point events too. A", ' plain "ms" payload field is not a span', " --debug Include debug events (only recorded when the", " command ran with LOG_DEBUG set)", " --tail Follow live events after replaying history; stops", " automatically on 30s idle", " --timeout <duration> Also stop tailing after this absolute duration", "" ].join("\n"));
|
|
506
546
|
}();
|
|
507
547
|
return;
|
|
508
548
|
}
|
|
@@ -511,7 +551,7 @@ async function runExportCli(e) {
|
|
|
511
551
|
args: e,
|
|
512
552
|
allowPositionals: !0,
|
|
513
553
|
options: {
|
|
514
|
-
...
|
|
554
|
+
...p,
|
|
515
555
|
input: {
|
|
516
556
|
type: "string"
|
|
517
557
|
},
|
|
@@ -546,7 +586,7 @@ async function runExportCli(e) {
|
|
|
546
586
|
return t;
|
|
547
587
|
}(s.values.timeout);
|
|
548
588
|
const i = s.values.format ?? "chrome-trace";
|
|
549
|
-
if (!function isExportFormat(e) {
|
|
589
|
+
if (!function isExportFormat$1(e) {
|
|
550
590
|
return "chrome-trace" === e || "opentelemetry" === e;
|
|
551
591
|
}(i)) {
|
|
552
592
|
throw new Error(`Unsupported format: ${i}`);
|
|
@@ -577,13 +617,14 @@ async function runExportCli(e) {
|
|
|
577
617
|
}
|
|
578
618
|
let a;
|
|
579
619
|
let l;
|
|
580
|
-
let
|
|
620
|
+
let c;
|
|
581
621
|
if (i.selector) {
|
|
582
622
|
const e = await r.resolveSession(i.selector);
|
|
583
623
|
l = function formatSessionProcessName(e) {
|
|
584
624
|
return e.version ? `${e.command} (v${e.version}, PID ${e.pid})` : `${e.command} (PID ${e.pid})`;
|
|
585
625
|
}(e);
|
|
586
|
-
|
|
626
|
+
c = e.pid;
|
|
627
|
+
await warnOnRotationLoss(e.sessionDir, "2g export --tail");
|
|
587
628
|
a = r.tap(e.sessionDir, {
|
|
588
629
|
follow: i.follow,
|
|
589
630
|
debug: i.debug,
|
|
@@ -601,37 +642,12 @@ async function runExportCli(e) {
|
|
|
601
642
|
yield* readJsonlStream(process.stdin);
|
|
602
643
|
}();
|
|
603
644
|
}
|
|
604
|
-
const u =
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
for await (const r of e) {
|
|
608
|
-
if (n.matchesTapOptions(r, {
|
|
609
|
-
filter: t.filter,
|
|
610
|
-
since: o,
|
|
611
|
-
spans: t.spans,
|
|
612
|
-
debug: t.debug
|
|
613
|
-
}, s)) {
|
|
614
|
-
yield r;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}(a, i);
|
|
618
|
-
const d = "opentelemetry" === i.format ? await async function convertToOpenTelemetry(e, t = {}) {
|
|
619
|
-
const n = new OpenTelemetryConverter(t);
|
|
620
|
-
for await (const t of e) {
|
|
621
|
-
n.add(t);
|
|
622
|
-
}
|
|
623
|
-
return n.toFile();
|
|
624
|
-
}(u, {
|
|
625
|
-
pid: p,
|
|
645
|
+
const u = filterEvents(a, i);
|
|
646
|
+
const d = "opentelemetry" === i.format ? await convertToOpenTelemetry(u, {
|
|
647
|
+
pid: c,
|
|
626
648
|
processName: l
|
|
627
|
-
}) : await
|
|
628
|
-
|
|
629
|
-
for await (const t of e) {
|
|
630
|
-
n.add(t);
|
|
631
|
-
}
|
|
632
|
-
return n.toTraceFile();
|
|
633
|
-
}(u, {
|
|
634
|
-
pid: p,
|
|
649
|
+
}) : await convertToChromeTrace(u, {
|
|
650
|
+
pid: c,
|
|
635
651
|
processName: l
|
|
636
652
|
});
|
|
637
653
|
const f = JSON.stringify(d, null, 2);
|
|
@@ -646,6 +662,99 @@ function escapeTsv(e) {
|
|
|
646
662
|
return String(e).replace(/\\/g, "\\\\").replace(/\t/g, "\\t").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
647
663
|
}
|
|
648
664
|
|
|
665
|
+
async function runRecordCli(e) {
|
|
666
|
+
if (parseHelp(e)) {
|
|
667
|
+
!function printRecordHelp() {
|
|
668
|
+
process.stdout.write([ "Usage: 2g record [options] -- <command> [args...]", "", "Runs <command>, traces the events it emits, and writes the trace on exit.", "", "This is the reliable way to capture a one-off run (a build, a script): 2g", "owns the process, so the trace is complete end-to-end with no window to", "attach within and nothing to rotate out. To trace an already-running server", "instead, use `2g export <selector> --tail`.", "", "Options:", " -o, --output <file> Write the trace to a file (default: stdout)", " --format <format> chrome-trace or opentelemetry", " --json Print JSON output", " --filter <pattern> Event name prefix (whole segments), e.g. category,", " category:kind; * wildcards; comma-separated", " --spans Keep only span events (log.span() pairs with a _d", " duration); omit to include point events too", " --debug Enable and export debug events; no LOG_DEBUG needed", "", "Example:", " 2g record -o trace.json -- expo export # run & trace a build end-to-end", "" ].join("\n"));
|
|
669
|
+
}();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const n = e.indexOf("--");
|
|
673
|
+
const i = -1 === n ? e : e.slice(0, n);
|
|
674
|
+
const a = -1 === n ? [] : e.slice(n + 1);
|
|
675
|
+
const c = function parseRecordOptions(e) {
|
|
676
|
+
const n = t.parseArgs({
|
|
677
|
+
args: e,
|
|
678
|
+
allowPositionals: !0,
|
|
679
|
+
options: {
|
|
680
|
+
filter: {
|
|
681
|
+
type: "string",
|
|
682
|
+
multiple: !0
|
|
683
|
+
},
|
|
684
|
+
spans: {
|
|
685
|
+
type: "boolean"
|
|
686
|
+
},
|
|
687
|
+
debug: {
|
|
688
|
+
type: "boolean"
|
|
689
|
+
},
|
|
690
|
+
output: {
|
|
691
|
+
type: "string",
|
|
692
|
+
short: "o"
|
|
693
|
+
},
|
|
694
|
+
format: {
|
|
695
|
+
type: "string"
|
|
696
|
+
},
|
|
697
|
+
json: {
|
|
698
|
+
type: "boolean"
|
|
699
|
+
},
|
|
700
|
+
help: {
|
|
701
|
+
type: "boolean",
|
|
702
|
+
short: "h"
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
const s = n.values.format ?? "chrome-trace";
|
|
707
|
+
if (!function isExportFormat(e) {
|
|
708
|
+
return "chrome-trace" === e || "opentelemetry" === e;
|
|
709
|
+
}(s)) {
|
|
710
|
+
throw new Error(`Unsupported format: ${s}`);
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
output: n.values.output,
|
|
714
|
+
format: s,
|
|
715
|
+
filter: n.values.filter?.flatMap(e => e.split(",")).map(e => e.trim()).filter(Boolean),
|
|
716
|
+
spans: !0 === n.values.spans,
|
|
717
|
+
debug: !0 === n.values.debug,
|
|
718
|
+
json: n.values.json ?? !1
|
|
719
|
+
};
|
|
720
|
+
}(i);
|
|
721
|
+
if (!a.length) {
|
|
722
|
+
throw new Error("record needs a command to run: 2g record [options] -- <command> [args...]");
|
|
723
|
+
}
|
|
724
|
+
if (c.json) {
|
|
725
|
+
s.redirectConsoleToStderr();
|
|
726
|
+
}
|
|
727
|
+
const p = r.captureEvents({
|
|
728
|
+
debug: c.debug,
|
|
729
|
+
filter: c.filter
|
|
730
|
+
});
|
|
731
|
+
const u = l.spawn(a[0], a.slice(1), p.spawnOptions({
|
|
732
|
+
env: process.env,
|
|
733
|
+
stdio: [ "inherit", c.output ? "inherit" : 2, "inherit" ]
|
|
734
|
+
}));
|
|
735
|
+
p.attach(u);
|
|
736
|
+
const d = new Promise((e, t) => {
|
|
737
|
+
u.on("exit", t => e(t ?? 0));
|
|
738
|
+
u.on("error", e => t(new Error(`Failed to run ${a[0]}: ${e.message}`)));
|
|
739
|
+
});
|
|
740
|
+
const f = {
|
|
741
|
+
pid: u.pid,
|
|
742
|
+
processName: a.join(" ")
|
|
743
|
+
};
|
|
744
|
+
const m = filterEvents(p, c);
|
|
745
|
+
const h = "opentelemetry" === c.format ? await convertToOpenTelemetry(m, f) : await convertToChromeTrace(m, f);
|
|
746
|
+
const g = await d;
|
|
747
|
+
const v = JSON.stringify(h, null, 2);
|
|
748
|
+
if (c.output) {
|
|
749
|
+
o.writeFileSync(c.output, `${v}\n`);
|
|
750
|
+
} else {
|
|
751
|
+
process.stdout.write(`${v}\n`);
|
|
752
|
+
}
|
|
753
|
+
if (g) {
|
|
754
|
+
process.exitCode = g;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
649
758
|
async function runTapCli(e) {
|
|
650
759
|
if (parseHelp(e)) {
|
|
651
760
|
!function printTapHelp() {
|
|
@@ -658,7 +767,7 @@ async function runTapCli(e) {
|
|
|
658
767
|
args: e,
|
|
659
768
|
allowPositionals: !0,
|
|
660
769
|
options: {
|
|
661
|
-
...
|
|
770
|
+
...p,
|
|
662
771
|
format: {
|
|
663
772
|
type: "string"
|
|
664
773
|
},
|
|
@@ -690,6 +799,7 @@ async function runTapCli(e) {
|
|
|
690
799
|
s.redirectConsoleToStderr();
|
|
691
800
|
}
|
|
692
801
|
const o = await r.resolveSession(n.selector);
|
|
802
|
+
await warnOnRotationLoss(o.sessionDir, "2g tap --tail");
|
|
693
803
|
const i = s.shouldColorizeStream(process.stdout);
|
|
694
804
|
for await (const e of r.tap(o.sessionDir, n)) {
|
|
695
805
|
if (n.pretty) {
|
|
@@ -705,9 +815,9 @@ async function runTapCli(e) {
|
|
|
705
815
|
|
|
706
816
|
async function main(e = process.argv.slice(2)) {
|
|
707
817
|
const [n, ...o] = e;
|
|
708
|
-
if (!n || "--help" === n || "-h" === n) {
|
|
818
|
+
if (!n || "--help" === n || "-h" === n || "help" === n) {
|
|
709
819
|
!function printHelp() {
|
|
710
|
-
process.stdout.write([ "Usage: 2g <command>", "", "Reads structured JSONL event logs that instrumented CLIs record during runs", "into the system temp dir, with no setup: run a command, then tap it here.", "", "Commands:", " ps [selector] List recorded sessions", " --active, -a (running only) --json", "", " tap [selector] Replay session events as JSONL, oldest first", " --since <time> --filter <pattern> --spans --debug --format pretty", " --tail (follow live events after replay; runs until killed)", "", " export [selector] Export
|
|
820
|
+
process.stdout.write([ "Usage: 2g <command>", "", "Reads structured JSONL event logs that instrumented CLIs record during runs", "into the system temp dir, with no setup: run a command, then tap it here.", "", "Commands:", " ps [selector] List recorded sessions", " --active, -a (running only) --json", "", " tap [selector] Replay session events as JSONL, oldest first", " --since <time> --filter <pattern> --spans --debug --format pretty", " --tail (follow live events after replay; runs until killed)", "", " record -- <command> Run a command, trace it end-to-end, write on exit", " -o, --output <file> --format chrome-trace|opentelemetry --json", " --filter <pattern> --spans --debug", "", " export [selector] Export a past or live (--tail) session", " --input <jsonl-file> -o, --output <file> --json", " --format chrome-trace|opentelemetry", " --since <time> --filter <pattern> --spans --debug", " --tail --timeout <duration> (absolute; tail also stops after 30s idle)", "", " typegen List every event name/payload a project can emit", " -p, --project <tsconfig> -o, --output <file> --format dts|json --json", "", " clean Remove stale sessions", " --all, -a (remove all exited sessions) --json", "", "Typical use:", " 2g ps -a # what is running now?", ' 2g tap "expo start" --tail # follow live events', ' 2g tap "expo start" --since 5m --format pretty # recent history, readable', ' 2g tap "expo start" --filter metro:bundling # narrow to one area', "", "Trace a run to a flame chart:", " 2g record -o trace.json -- expo export # run & trace a build", ' 2g export "expo start" --tail -o trace.json # attach to a running server', ' 2g export "expo start" -o trace.json # after the fact (partial)', "", "Selectors match sessions by substring of PID, CWD, or command. If several", "match, and exactly one of them is running, it is chosen; otherwise 2g errors", "and lists the candidates.", "", "--filter takes comma-separated event-name prefixes matched on whole", 'segments: "category" and "category:kind" both match "category:kind:test";', "* matches anything. Discover names by tapping unfiltered or via typegen.", "", "--since accepts a duration ago (5m, 90s, 1h30m), Unix time, or ISO date.", '--spans keeps only span events (log.span() pairs with a "_d" duration in', "ms); without it, point events are included alongside spans.", "", 'Events are single JSON lines: {"_e":"category:kind","_t":<epoch ms>,...}.', '"_d" is a span duration, "_w" a worker id; debug events need --debug.', "", "Set LOG_EVENTS=<file|fd> on an instrumented command to redirect its events.", "Debug events are only recorded when the command runs with LOG_DEBUG set.", "", "Pass LOG_DEBUG=<filter> when debugging; it also mirrors matching events to", "stderr. Reading them back needs --debug.", "", "Run 2g <command> --help for per-command help.", "" ].join("\n"));
|
|
711
821
|
}();
|
|
712
822
|
return 0;
|
|
713
823
|
}
|
|
@@ -777,6 +887,10 @@ async function main(e = process.argv.slice(2)) {
|
|
|
777
887
|
runCleanCli(o);
|
|
778
888
|
return 0;
|
|
779
889
|
|
|
890
|
+
case "record":
|
|
891
|
+
await runRecordCli(o);
|
|
892
|
+
return 0;
|
|
893
|
+
|
|
780
894
|
case "export":
|
|
781
895
|
await runExportCli(o);
|
|
782
896
|
return 0;
|
|
@@ -788,7 +902,7 @@ async function main(e = process.argv.slice(2)) {
|
|
|
788
902
|
|
|
789
903
|
if (function isCliEntrypoint() {
|
|
790
904
|
try {
|
|
791
|
-
return process.argv[1] === e.fileURLToPath("undefined" == typeof document ? require("url").pathToFileURL(__filename).href :
|
|
905
|
+
return process.argv[1] === e.fileURLToPath("undefined" == typeof document ? require("url").pathToFileURL(__filename).href : c && "SCRIPT" === c.tagName.toUpperCase() && c.src || new URL("dist/chunks/cli-chunk.js", document.baseURI).href);
|
|
792
906
|
} catch {
|
|
793
907
|
return !1;
|
|
794
908
|
}
|