rerender-lens 0.5.0 → 0.7.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 +50 -1
- package/README.md +23 -4
- package/dist/{budget-CRswn-nC.d.cts → budget-CA1MS7lB.d.cts} +1 -1
- package/dist/{budget-BnJlYwrV.d.ts → budget-ok1VDJ4t.d.ts} +1 -1
- package/dist/cli.cjs +78 -26
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +78 -26
- package/dist/cli.js.map +1 -1
- package/dist/{devtools-BYWat7nQ.d.ts → devtools-Cl5n_52v.d.ts} +11 -2
- package/dist/{devtools-DJi_6AfJ.d.cts → devtools-UwQx2VYS.d.cts} +11 -2
- package/dist/index.cjs +99 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -7
- package/dist/index.d.ts +13 -7
- package/dist/index.js +99 -10
- package/dist/index.js.map +1 -1
- package/dist/jest-setup.cjs +20 -3
- package/dist/jest-setup.cjs.map +1 -1
- package/dist/jest-setup.d.cts +4 -4
- package/dist/jest-setup.d.ts +4 -4
- package/dist/jest-setup.js +20 -3
- package/dist/jest-setup.js.map +1 -1
- package/dist/jest.cjs +20 -3
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +4 -4
- package/dist/jest.d.ts +4 -4
- package/dist/jest.js +20 -3
- package/dist/jest.js.map +1 -1
- package/dist/{notifiers-DwKlvv4H.d.cts → notifiers-B5poAEvE.d.cts} +2 -2
- package/dist/{notifiers-FNzKHs0a.d.ts → notifiers-CqQW0Eum.d.ts} +2 -2
- package/dist/playwright.d.cts +4 -4
- package/dist/playwright.d.ts +4 -4
- package/dist/relay.cjs +65 -21
- package/dist/relay.cjs.map +1 -1
- package/dist/relay.d.cts +25 -3
- package/dist/relay.d.ts +25 -3
- package/dist/relay.js +65 -21
- package/dist/relay.js.map +1 -1
- package/dist/rerender-lens.iife.js +105 -12
- package/dist/{runner-report-CW8X5RwK.d.ts → runner-report-Bvqoe89M.d.ts} +2 -2
- package/dist/{runner-report-B8Sc3FZO.d.cts → runner-report-CvpDg2zt.d.cts} +2 -2
- package/dist/setup.cjs +98 -9
- package/dist/setup.cjs.map +1 -1
- package/dist/setup.js +98 -9
- package/dist/setup.js.map +1 -1
- package/dist/{types-DW5-N2XH.d.cts → types-DwNN5cWe.d.cts} +28 -1
- package/dist/{types-DW5-N2XH.d.ts → types-DwNN5cWe.d.ts} +28 -1
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vitest-setup.cjs +20 -3
- package/dist/vitest-setup.cjs.map +1 -1
- package/dist/vitest-setup.d.cts +4 -4
- package/dist/vitest-setup.d.ts +4 -4
- package/dist/vitest-setup.js +20 -3
- package/dist/vitest-setup.js.map +1 -1
- package/dist/vitest.cjs +20 -3
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +5 -5
- package/dist/vitest.d.ts +5 -5
- package/dist/vitest.js +20 -3
- package/dist/vitest.js.map +1 -1
- package/package.json +3 -2
- package/panel/panel.css +48 -0
- package/panel/panel.js +348 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
Highlights: verdicts pinned for modern React (Compiler output, `use()`, transitions, Suspense),
|
|
6
|
+
a Jest integration, React 17 and 18 in CI, several apps on one relay, a commit timeline in the
|
|
7
|
+
panel, a docs site that answers "how do I start", a relay that authenticates off loopback, and a
|
|
8
|
+
panel that tells you why a component is not tracked. Details, newest first:
|
|
9
|
+
|
|
10
|
+
- **The relay authenticates when it is not on loopback.** `npx rerender-lens panel --host 0.0.0.0`
|
|
11
|
+
now generates a token, prints it inside the URL and refuses `/events`, `/message` and `/status`
|
|
12
|
+
without it, because reports carry your app's prop, state and context values. Pass the printed URL
|
|
13
|
+
to the app and the panel and both pick the token up; `--token` sets your own, `--no-token` opts
|
|
14
|
+
out with a warning. Loopback is unchanged and needs no secret.
|
|
15
|
+
- A nightly job records what the library costs per commit on a large tree (`npm run e2e:perf`,
|
|
16
|
+
3,000 and 10,000 memoized rows) and fails when a commit blows its budget. On a laptop the worst
|
|
17
|
+
commit is about 32 ms at both sizes: the cost is flat in tree size because the per-commit cap
|
|
18
|
+
stops the walk. The extension README now documents that the package ships no remote code and no
|
|
19
|
+
`eval` beyond `chrome.devtools.inspectedWindow.eval`, under MV3's default content security
|
|
20
|
+
policy.
|
|
21
|
+
- **"Why is this component not tracked?"** New `explainTracking(type, options)` returns the same
|
|
22
|
+
decision as `shouldTrack` plus the rule that made it and what to change, and the bridge carries
|
|
23
|
+
it: `info().tracking` (mode, include/exclude, and how many distinct components rendered since
|
|
24
|
+
`init` versus how many were tracked), `inspect(node).tracking`, and a new `explain(node | instanceId)`
|
|
25
|
+
command that also works over the BroadcastChannel and the relay. The panel's empty state now says
|
|
26
|
+
"tracking every React.memo and PureComponent; 42 components rendered, 3 of them tracked" and
|
|
27
|
+
offers *Track every component* and *Track components matching…*; the Elements sidebar shows the
|
|
28
|
+
verdict, the reason and a *Track this component* button for the selected element. Additive:
|
|
29
|
+
the protocol stays at 2.
|
|
30
|
+
- **The docs site is a product page.** A landing page that answers "how do I start with my
|
|
31
|
+
bundler" (Vite, Next.js, Webpack, any app through the relay, the extension, tests) with the
|
|
32
|
+
snippets lifted straight out of the README so they cannot drift, the README as the guide, the
|
|
33
|
+
extension guide, a generated API reference for every entry point, and a hosted copy of the panel
|
|
34
|
+
running its demo data so you can click around before installing anything. A published release
|
|
35
|
+
recaptures the screenshots before building, so they never show an old panel.
|
|
36
|
+
- **Several apps on one relay.** `npx rerender-lens panel` now gives each connected app an id and a
|
|
37
|
+
label (its address), tells the app its id so what it posts is stamped, and sends panels the
|
|
38
|
+
roster. The panel shows a picker when more than one app is connected, watches one at a time and
|
|
39
|
+
addresses its commands to that app, so a host app and a microfrontend (or two pages, or two
|
|
40
|
+
machines) no longer interleave into one stream. A panel or a library that knows nothing about
|
|
41
|
+
ids behaves exactly as before, and the protocol stays at 2.
|
|
42
|
+
- The side panel and the DevTools panel are now covered by a test that changes a setting in one and
|
|
43
|
+
reads it back in the other, in both directions.
|
|
44
|
+
- **Panel: a commit timeline strip above the tree.** One bar per React commit, oldest left, height
|
|
45
|
+
on a sqrt scale of the commit's render count and filled from the bottom with its avoidable share.
|
|
46
|
+
Click a bar to open that commit in the Commits view; drag across bars to brush a time window, and
|
|
47
|
+
the tree, Offenders, Fixes, Commits and the live stream all narrow to it (a chip in the strip
|
|
48
|
+
header says how wide the window is and how many reports it holds; `Esc` or the chip clears it).
|
|
49
|
+
Bars are focusable, labelled ("commit 12, 4 renders, 3 avoidable, 8 seconds ago") and navigable
|
|
50
|
+
with the arrow keys. The strip collapses from its chevron (persisted per origin), draws at most
|
|
51
|
+
the 200 most recent commits, and repaints on the same `LEFT_RENDER_INTERVAL` throttle as the
|
|
52
|
+
other heavy views once the buffer passes 200 reports.
|
|
4
53
|
|
|
5
54
|
- **Jest.** `rerender-lens/jest` and `rerender-lens/jest/setup`: the same deal as the Vitest
|
|
6
55
|
integration — one `setupFilesAfterEnv` line collects every avoidable re-render, and the reporter
|
package/README.md
CHANGED
|
@@ -30,9 +30,11 @@ Adds a **Re-renders** tab: the component tree with avoidable counts, why each co
|
|
|
30
30
|
the props and hooks that changed, and the fix as a snippet. Other views rank components by wasted
|
|
31
31
|
renders (Offenders), group renders by React commit with their root cause (Commits), rank every fix
|
|
32
32
|
by how many re-renders it removes (Fixes), and compare a recording before and after a fix
|
|
33
|
-
(Sessions).
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
(Sessions). A timeline strip above the tree draws one bar per commit sized by its renders and
|
|
34
|
+
coloured by its avoidable share: click a bar for that commit, drag across bars to brush a time
|
|
35
|
+
window that every view narrows to. Also: an Elements-panel sidebar, a badge with the tab's
|
|
36
|
+
avoidable count, hover to highlight in the page, "open source" links, and the same panel next to
|
|
37
|
+
the page (side panel) or in its own window.
|
|
36
38
|
|
|
37
39
|
**Install** (until the Web Store listing is live): download `rerender-lens-chrome-<version>.zip`
|
|
38
40
|
from the [latest release](https://github.com/NexaLeaf/rerender-lens/releases), unzip it, open
|
|
@@ -53,6 +55,16 @@ Injection tracks every `memo` / `PureComponent` and leaves the hook/context/stat
|
|
|
53
55
|
(*Include state* in Settings turns them on; they are the costliest part on large apps). Settings
|
|
54
56
|
are saved per origin. Details, shortcuts and packaging: [`extension/README.md`](extension/README.md).
|
|
55
57
|
|
|
58
|
+
### Nothing shows up?
|
|
59
|
+
|
|
60
|
+
Most often the default is doing exactly what it says: injection tracks only `React.memo` components
|
|
61
|
+
and `PureComponent` classes, and an app of plain function components has none. The panel's empty
|
|
62
|
+
state says so in words — *"tracking every React.memo and PureComponent; 42 components rendered, 3 of
|
|
63
|
+
them tracked"* — with two buttons: **Track every component** and **Track components matching…**.
|
|
64
|
+
For one component, select its element in **Elements**: the *rerender-lens* sidebar gives the verdict,
|
|
65
|
+
the reason, and a *Track this component* button. Nothing avoidable in a tracked component is the
|
|
66
|
+
other, good, case: it simply has not re-rendered.
|
|
67
|
+
|
|
56
68
|
## Vite
|
|
57
69
|
|
|
58
70
|
```ts
|
|
@@ -80,10 +92,17 @@ production builds. To see the panel without the extension:
|
|
|
80
92
|
npx rerender-lens panel # http://127.0.0.1:4141/ (--port, --host 0.0.0.0 for another machine)
|
|
81
93
|
```
|
|
82
94
|
|
|
95
|
+
On loopback it needs no secret. Given any other `--host` it generates a token, prints it in the
|
|
96
|
+
URL, and refuses connections without it, because reports carry your app's prop, state and context
|
|
97
|
+
values. Pass the whole printed URL (token and all) to the app and the panel; `--token` sets your
|
|
98
|
+
own and `--no-token` opts out.
|
|
99
|
+
|
|
83
100
|
and point the app at it with `RERENDER_LENS_RELAY=http://127.0.0.1:4141`
|
|
84
101
|
(`NEXT_PUBLIC_RERENDER_LENS_RELAY` for Next.js), `createDevtoolsNotifier({ relay })`, or
|
|
85
102
|
`window.__RERENDER_LENS_RELAY__` set before the app loads. Settings, highlight and replay travel
|
|
86
|
-
back to the app
|
|
103
|
+
back to the app, and the panel re-attaches when an app reloads. Point **several apps** at one relay
|
|
104
|
+
(a host app and a microfrontend, two pages, two machines) and the panel shows a picker listing each
|
|
105
|
+
by address; it watches one at a time and commands go only to that one.
|
|
87
106
|
|
|
88
107
|
## Manual setup
|
|
89
108
|
|
package/dist/cli.cjs
CHANGED
|
@@ -5,6 +5,7 @@ var fs = require('fs');
|
|
|
5
5
|
var http = require('http');
|
|
6
6
|
var path = require('path');
|
|
7
7
|
var url = require('url');
|
|
8
|
+
var crypto = require('crypto');
|
|
8
9
|
|
|
9
10
|
// node_modules/tsup/assets/cjs_shims.js
|
|
10
11
|
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
@@ -408,6 +409,8 @@ function checkBudget(reports, budget) {
|
|
|
408
409
|
function toBudget(reports) {
|
|
409
410
|
return { "*": 0, ...avoidableCounts(reports) };
|
|
410
411
|
}
|
|
412
|
+
var MARKER = "__rerenderLens";
|
|
413
|
+
var PROTOCOL = 2;
|
|
411
414
|
var MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8" };
|
|
412
415
|
function findPanelDir() {
|
|
413
416
|
const here = path.dirname(url.fileURLToPath(importMetaUrl));
|
|
@@ -442,26 +445,55 @@ function createRelayServer(options = {}) {
|
|
|
442
445
|
const host = options.host ?? "127.0.0.1";
|
|
443
446
|
const panelDir = options.panelDir === void 0 ? findPanelDir() : options.panelDir;
|
|
444
447
|
const keepAliveMs = options.keepAliveMs ?? 15e3;
|
|
445
|
-
const
|
|
448
|
+
const token = options.token || null;
|
|
449
|
+
const apps = /* @__PURE__ */ new Map();
|
|
446
450
|
const panels = /* @__PURE__ */ new Set();
|
|
447
451
|
let url = "";
|
|
448
|
-
|
|
449
|
-
|
|
452
|
+
let nextAppId = 1;
|
|
453
|
+
const connectUrl = () => token ? `${url}?token=${encodeURIComponent(token)}` : url;
|
|
454
|
+
const line = (message) => `data: ${JSON.stringify(message)}
|
|
450
455
|
|
|
451
456
|
`;
|
|
452
|
-
|
|
457
|
+
const sendTo = (res, message) => {
|
|
458
|
+
try {
|
|
459
|
+
res.write(line(message));
|
|
460
|
+
} catch {
|
|
461
|
+
apps.delete(res);
|
|
462
|
+
panels.delete(res);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
const send = (targets, message) => {
|
|
466
|
+
const text = line(message);
|
|
467
|
+
for (const res of [...targets]) {
|
|
453
468
|
try {
|
|
454
|
-
res.write(
|
|
469
|
+
res.write(text);
|
|
455
470
|
} catch {
|
|
456
|
-
|
|
471
|
+
apps.delete(res);
|
|
472
|
+
panels.delete(res);
|
|
457
473
|
}
|
|
458
474
|
}
|
|
459
475
|
};
|
|
460
|
-
const
|
|
476
|
+
const appList = () => [...apps.values()];
|
|
477
|
+
const roster = () => ({ [MARKER]: true, version: PROTOCOL, type: "relay", payload: { apps: apps.size, list: appList() } });
|
|
478
|
+
const streamOf = (id) => {
|
|
479
|
+
for (const [res, app] of apps) if (app.id === id) return res;
|
|
480
|
+
return null;
|
|
481
|
+
};
|
|
482
|
+
const route = (message, from) => {
|
|
461
483
|
if (!message || typeof message !== "object") return;
|
|
462
484
|
const m = message;
|
|
463
|
-
if (m.__rerenderLensCmd === true)
|
|
464
|
-
|
|
485
|
+
if (m.__rerenderLensCmd === true) {
|
|
486
|
+
const target = typeof m.app === "string" ? streamOf(m.app) : null;
|
|
487
|
+
if (target) sendTo(target, m);
|
|
488
|
+
else send(apps.keys(), m);
|
|
489
|
+
} else if (m.__rerenderLens === true || m.__rerenderLensReply === true) {
|
|
490
|
+
send(panels, from ? { ...m, app: from.id } : m);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
const authorized = (req, u) => {
|
|
494
|
+
if (!token) return true;
|
|
495
|
+
const given = u.searchParams.get("token") || (typeof req.headers["x-rerender-lens-token"] === "string" ? req.headers["x-rerender-lens-token"] : "");
|
|
496
|
+
return given === token;
|
|
465
497
|
};
|
|
466
498
|
const server = http.createServer(async (req, res) => {
|
|
467
499
|
cors(res);
|
|
@@ -473,18 +505,26 @@ function createRelayServer(options = {}) {
|
|
|
473
505
|
}
|
|
474
506
|
if (u.pathname === "/" && req.method === "GET") {
|
|
475
507
|
res.statusCode = 302;
|
|
476
|
-
res.setHeader("Location", `/panel.html?relay=${encodeURIComponent(
|
|
508
|
+
res.setHeader("Location", `/panel.html?relay=${encodeURIComponent(connectUrl())}`);
|
|
477
509
|
res.end();
|
|
478
510
|
return;
|
|
479
511
|
}
|
|
512
|
+
if (/^\/(events|message|status)$/.test(u.pathname) && !authorized(req, u)) {
|
|
513
|
+
res.statusCode = 401;
|
|
514
|
+
res.setHeader("Content-Type", "text/plain");
|
|
515
|
+
res.end("rerender-lens relay: this relay needs the token from the URL it printed (?token=...)");
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
480
518
|
if (u.pathname === "/events" && req.method === "GET") {
|
|
481
|
-
const
|
|
519
|
+
const isApp = u.searchParams.get("role") === "app";
|
|
482
520
|
res.statusCode = 200;
|
|
483
521
|
res.setHeader("Content-Type", "text/event-stream");
|
|
484
522
|
res.setHeader("Cache-Control", "no-store");
|
|
485
523
|
res.setHeader("Connection", "keep-alive");
|
|
486
524
|
res.write(": connected\n\n");
|
|
487
|
-
|
|
525
|
+
const app = isApp ? { id: `a${nextAppId++}`, label: (u.searchParams.get("label") || req.headers.origin || "").slice(0, 120) || `app ${nextAppId - 1}` } : null;
|
|
526
|
+
if (app) apps.set(res, app);
|
|
527
|
+
else panels.add(res);
|
|
488
528
|
const ping = setInterval(() => {
|
|
489
529
|
try {
|
|
490
530
|
res.write(": ping\n\n");
|
|
@@ -493,14 +533,15 @@ function createRelayServer(options = {}) {
|
|
|
493
533
|
}, keepAliveMs);
|
|
494
534
|
req.on("close", () => {
|
|
495
535
|
clearInterval(ping);
|
|
496
|
-
|
|
536
|
+
apps.delete(res);
|
|
537
|
+
panels.delete(res);
|
|
497
538
|
});
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
send(panels,
|
|
501
|
-
req.on("close", () => send(panels,
|
|
539
|
+
if (app) {
|
|
540
|
+
sendTo(res, { __rerenderLensRelay: true, version: PROTOCOL, app: app.id });
|
|
541
|
+
send(panels, roster());
|
|
542
|
+
req.on("close", () => send(panels, roster()));
|
|
502
543
|
} else {
|
|
503
|
-
|
|
544
|
+
sendTo(res, roster());
|
|
504
545
|
}
|
|
505
546
|
return;
|
|
506
547
|
}
|
|
@@ -512,7 +553,9 @@ function createRelayServer(options = {}) {
|
|
|
512
553
|
if (u.pathname === "/message" && req.method === "POST") {
|
|
513
554
|
try {
|
|
514
555
|
const parsed = JSON.parse(await readBody(req) || "null");
|
|
515
|
-
|
|
556
|
+
const sender = u.searchParams.get("app");
|
|
557
|
+
const from = sender ? appList().find((a) => a.id === sender) ?? void 0 : void 0;
|
|
558
|
+
for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m, from);
|
|
516
559
|
res.statusCode = 204;
|
|
517
560
|
res.end();
|
|
518
561
|
} catch (e) {
|
|
@@ -525,7 +568,7 @@ function createRelayServer(options = {}) {
|
|
|
525
568
|
if (u.pathname === "/status" && req.method === "GET") {
|
|
526
569
|
res.statusCode = 200;
|
|
527
570
|
res.setHeader("Content-Type", "application/json");
|
|
528
|
-
res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size }));
|
|
571
|
+
res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size, list: appList() }));
|
|
529
572
|
return;
|
|
530
573
|
}
|
|
531
574
|
const file = u.pathname.replace(/^\//, "");
|
|
@@ -555,10 +598,12 @@ function createRelayServer(options = {}) {
|
|
|
555
598
|
resolve({
|
|
556
599
|
server,
|
|
557
600
|
url,
|
|
601
|
+
connectUrl: connectUrl(),
|
|
558
602
|
port,
|
|
559
603
|
counts: () => ({ apps: apps.size, panels: panels.size }),
|
|
604
|
+
appList,
|
|
560
605
|
close: () => new Promise((done) => {
|
|
561
|
-
for (const res of [...apps, ...panels]) res.end();
|
|
606
|
+
for (const res of [...apps.keys(), ...panels]) res.end();
|
|
562
607
|
apps.clear();
|
|
563
608
|
panels.clear();
|
|
564
609
|
server.close(() => done());
|
|
@@ -567,8 +612,6 @@ function createRelayServer(options = {}) {
|
|
|
567
612
|
});
|
|
568
613
|
});
|
|
569
614
|
}
|
|
570
|
-
|
|
571
|
-
// src/cli.ts
|
|
572
615
|
var read = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
|
|
573
616
|
function summaryOf(file) {
|
|
574
617
|
const parsed = parseExport(read(file));
|
|
@@ -576,14 +619,20 @@ function summaryOf(file) {
|
|
|
576
619
|
if (parsed.sessions.length && !parsed.reports.length) return parsed.sessions[parsed.sessions.length - 1];
|
|
577
620
|
return summarizeReports(parsed.reports, { name: file.replace(/^.*[\\/]/, "").replace(/\.json$/, "") });
|
|
578
621
|
}
|
|
622
|
+
var isLoopback = (host) => host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
579
623
|
async function panelCommand(args) {
|
|
580
624
|
const flag = (name) => {
|
|
581
625
|
const i = args.indexOf(name);
|
|
582
626
|
return i >= 0 ? args[i + 1] ?? "" : null;
|
|
583
627
|
};
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
628
|
+
const host = flag("--host") || "127.0.0.1";
|
|
629
|
+
const given = flag("--token");
|
|
630
|
+
const token = args.includes("--no-token") ? null : given || (isLoopback(host) ? null : randomToken());
|
|
631
|
+
const relay = await createRelayServer({ port: Number(flag("--port") || 4141), host, token: token ?? void 0 });
|
|
632
|
+
console.log(`rerender-lens panel: ${relay.connectUrl}/`);
|
|
633
|
+
console.log(`in the app: createDevtoolsNotifier({ relay: '${relay.connectUrl}' }) or window.__RERENDER_LENS_RELAY__ = '${relay.connectUrl}'`);
|
|
634
|
+
if (token) console.log("The token in that URL is required: reports carry your app's prop, state and context values.");
|
|
635
|
+
else if (!isLoopback(host)) console.log(`WARNING: ${host} is reachable by other machines and --no-token was given: anyone who can reach this port can read every report.`);
|
|
587
636
|
console.log("Ctrl+C to stop.");
|
|
588
637
|
return new Promise((resolve) => {
|
|
589
638
|
const stop = () => void relay.close().then(() => resolve(0));
|
|
@@ -592,6 +641,9 @@ async function panelCommand(args) {
|
|
|
592
641
|
relay.server.once("close", () => resolve(0));
|
|
593
642
|
});
|
|
594
643
|
}
|
|
644
|
+
function randomToken() {
|
|
645
|
+
return crypto.randomBytes(16).toString("base64url");
|
|
646
|
+
}
|
|
595
647
|
function main(argv) {
|
|
596
648
|
const [cmd, ...args] = argv;
|
|
597
649
|
if (cmd === "panel") return panelCommand(args).catch((e) => (console.error(e.message), 1));
|