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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RenderReport,
|
|
2
|
-
import { R as RankedFix, B as Budget, a as BudgetResult } from './budget-
|
|
1
|
+
import { R as RenderReport, b as ReportLike, N as Notifier } from './types-DwNN5cWe.cjs';
|
|
2
|
+
import { R as RankedFix, B as Budget, a as BudgetResult } from './budget-CA1MS7lB.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Session summaries and before/after comparison, the same shape the extension records and exports,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RenderReport,
|
|
2
|
-
import { R as RankedFix, B as Budget, a as BudgetResult } from './budget-
|
|
1
|
+
import { R as RenderReport, b as ReportLike, N as Notifier } from './types-DwNN5cWe.js';
|
|
2
|
+
import { R as RankedFix, B as Budget, a as BudgetResult } from './budget-ok1VDJ4t.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Session summaries and before/after comparison, the same shape the extension records and exports,
|
package/dist/playwright.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as RenderReport } from './types-
|
|
2
|
-
import { S as SerializableOptions } from './devtools-
|
|
3
|
-
import { B as Budget, a as BudgetResult } from './budget-
|
|
4
|
-
export { R as RankedFix, c as checkBudget, f as formatFixes, r as rankFixes } from './budget-
|
|
1
|
+
import { R as RenderReport } from './types-DwNN5cWe.cjs';
|
|
2
|
+
import { S as SerializableOptions } from './devtools-UwQx2VYS.cjs';
|
|
3
|
+
import { B as Budget, a as BudgetResult } from './budget-CA1MS7lB.cjs';
|
|
4
|
+
export { R as RankedFix, c as checkBudget, f as formatFixes, r as rankFixes } from './budget-CA1MS7lB.cjs';
|
|
5
5
|
|
|
6
6
|
/** The subset of Playwright's `Page` used here (keeps `@playwright/test` an optional peer). */
|
|
7
7
|
interface PageLike {
|
package/dist/playwright.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as RenderReport } from './types-
|
|
2
|
-
import { S as SerializableOptions } from './devtools-
|
|
3
|
-
import { B as Budget, a as BudgetResult } from './budget-
|
|
4
|
-
export { R as RankedFix, c as checkBudget, f as formatFixes, r as rankFixes } from './budget-
|
|
1
|
+
import { R as RenderReport } from './types-DwNN5cWe.js';
|
|
2
|
+
import { S as SerializableOptions } from './devtools-Cl5n_52v.js';
|
|
3
|
+
import { B as Budget, a as BudgetResult } from './budget-ok1VDJ4t.js';
|
|
4
|
+
export { R as RankedFix, c as checkBudget, f as formatFixes, r as rankFixes } from './budget-ok1VDJ4t.js';
|
|
5
5
|
|
|
6
6
|
/** The subset of Playwright's `Page` used here (keeps `@playwright/test` an optional peer). */
|
|
7
7
|
interface PageLike {
|
package/dist/relay.cjs
CHANGED
|
@@ -8,6 +8,8 @@ var url = require('url');
|
|
|
8
8
|
// node_modules/tsup/assets/cjs_shims.js
|
|
9
9
|
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;
|
|
10
10
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
11
|
+
var MARKER = "__rerenderLens";
|
|
12
|
+
var PROTOCOL = 2;
|
|
11
13
|
var MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8" };
|
|
12
14
|
function findPanelDir() {
|
|
13
15
|
const here = path.dirname(url.fileURLToPath(importMetaUrl));
|
|
@@ -42,26 +44,55 @@ function createRelayServer(options = {}) {
|
|
|
42
44
|
const host = options.host ?? "127.0.0.1";
|
|
43
45
|
const panelDir = options.panelDir === void 0 ? findPanelDir() : options.panelDir;
|
|
44
46
|
const keepAliveMs = options.keepAliveMs ?? 15e3;
|
|
45
|
-
const
|
|
47
|
+
const token = options.token || null;
|
|
48
|
+
const apps = /* @__PURE__ */ new Map();
|
|
46
49
|
const panels = /* @__PURE__ */ new Set();
|
|
47
50
|
let url = "";
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
let nextAppId = 1;
|
|
52
|
+
const connectUrl = () => token ? `${url}?token=${encodeURIComponent(token)}` : url;
|
|
53
|
+
const line = (message) => `data: ${JSON.stringify(message)}
|
|
50
54
|
|
|
51
55
|
`;
|
|
52
|
-
|
|
56
|
+
const sendTo = (res, message) => {
|
|
57
|
+
try {
|
|
58
|
+
res.write(line(message));
|
|
59
|
+
} catch {
|
|
60
|
+
apps.delete(res);
|
|
61
|
+
panels.delete(res);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const send = (targets, message) => {
|
|
65
|
+
const text = line(message);
|
|
66
|
+
for (const res of [...targets]) {
|
|
53
67
|
try {
|
|
54
|
-
res.write(
|
|
68
|
+
res.write(text);
|
|
55
69
|
} catch {
|
|
56
|
-
|
|
70
|
+
apps.delete(res);
|
|
71
|
+
panels.delete(res);
|
|
57
72
|
}
|
|
58
73
|
}
|
|
59
74
|
};
|
|
60
|
-
const
|
|
75
|
+
const appList = () => [...apps.values()];
|
|
76
|
+
const roster = () => ({ [MARKER]: true, version: PROTOCOL, type: "relay", payload: { apps: apps.size, list: appList() } });
|
|
77
|
+
const streamOf = (id) => {
|
|
78
|
+
for (const [res, app] of apps) if (app.id === id) return res;
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
81
|
+
const route = (message, from) => {
|
|
61
82
|
if (!message || typeof message !== "object") return;
|
|
62
83
|
const m = message;
|
|
63
|
-
if (m.__rerenderLensCmd === true)
|
|
64
|
-
|
|
84
|
+
if (m.__rerenderLensCmd === true) {
|
|
85
|
+
const target = typeof m.app === "string" ? streamOf(m.app) : null;
|
|
86
|
+
if (target) sendTo(target, m);
|
|
87
|
+
else send(apps.keys(), m);
|
|
88
|
+
} else if (m.__rerenderLens === true || m.__rerenderLensReply === true) {
|
|
89
|
+
send(panels, from ? { ...m, app: from.id } : m);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const authorized = (req, u) => {
|
|
93
|
+
if (!token) return true;
|
|
94
|
+
const given = u.searchParams.get("token") || (typeof req.headers["x-rerender-lens-token"] === "string" ? req.headers["x-rerender-lens-token"] : "");
|
|
95
|
+
return given === token;
|
|
65
96
|
};
|
|
66
97
|
const server = http.createServer(async (req, res) => {
|
|
67
98
|
cors(res);
|
|
@@ -73,18 +104,26 @@ function createRelayServer(options = {}) {
|
|
|
73
104
|
}
|
|
74
105
|
if (u.pathname === "/" && req.method === "GET") {
|
|
75
106
|
res.statusCode = 302;
|
|
76
|
-
res.setHeader("Location", `/panel.html?relay=${encodeURIComponent(
|
|
107
|
+
res.setHeader("Location", `/panel.html?relay=${encodeURIComponent(connectUrl())}`);
|
|
77
108
|
res.end();
|
|
78
109
|
return;
|
|
79
110
|
}
|
|
111
|
+
if (/^\/(events|message|status)$/.test(u.pathname) && !authorized(req, u)) {
|
|
112
|
+
res.statusCode = 401;
|
|
113
|
+
res.setHeader("Content-Type", "text/plain");
|
|
114
|
+
res.end("rerender-lens relay: this relay needs the token from the URL it printed (?token=...)");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
80
117
|
if (u.pathname === "/events" && req.method === "GET") {
|
|
81
|
-
const
|
|
118
|
+
const isApp = u.searchParams.get("role") === "app";
|
|
82
119
|
res.statusCode = 200;
|
|
83
120
|
res.setHeader("Content-Type", "text/event-stream");
|
|
84
121
|
res.setHeader("Cache-Control", "no-store");
|
|
85
122
|
res.setHeader("Connection", "keep-alive");
|
|
86
123
|
res.write(": connected\n\n");
|
|
87
|
-
|
|
124
|
+
const app = isApp ? { id: `a${nextAppId++}`, label: (u.searchParams.get("label") || req.headers.origin || "").slice(0, 120) || `app ${nextAppId - 1}` } : null;
|
|
125
|
+
if (app) apps.set(res, app);
|
|
126
|
+
else panels.add(res);
|
|
88
127
|
const ping = setInterval(() => {
|
|
89
128
|
try {
|
|
90
129
|
res.write(": ping\n\n");
|
|
@@ -93,14 +132,15 @@ function createRelayServer(options = {}) {
|
|
|
93
132
|
}, keepAliveMs);
|
|
94
133
|
req.on("close", () => {
|
|
95
134
|
clearInterval(ping);
|
|
96
|
-
|
|
135
|
+
apps.delete(res);
|
|
136
|
+
panels.delete(res);
|
|
97
137
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
send(panels,
|
|
101
|
-
req.on("close", () => send(panels,
|
|
138
|
+
if (app) {
|
|
139
|
+
sendTo(res, { __rerenderLensRelay: true, version: PROTOCOL, app: app.id });
|
|
140
|
+
send(panels, roster());
|
|
141
|
+
req.on("close", () => send(panels, roster()));
|
|
102
142
|
} else {
|
|
103
|
-
|
|
143
|
+
sendTo(res, roster());
|
|
104
144
|
}
|
|
105
145
|
return;
|
|
106
146
|
}
|
|
@@ -112,7 +152,9 @@ function createRelayServer(options = {}) {
|
|
|
112
152
|
if (u.pathname === "/message" && req.method === "POST") {
|
|
113
153
|
try {
|
|
114
154
|
const parsed = JSON.parse(await readBody(req) || "null");
|
|
115
|
-
|
|
155
|
+
const sender = u.searchParams.get("app");
|
|
156
|
+
const from = sender ? appList().find((a) => a.id === sender) ?? void 0 : void 0;
|
|
157
|
+
for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m, from);
|
|
116
158
|
res.statusCode = 204;
|
|
117
159
|
res.end();
|
|
118
160
|
} catch (e) {
|
|
@@ -125,7 +167,7 @@ function createRelayServer(options = {}) {
|
|
|
125
167
|
if (u.pathname === "/status" && req.method === "GET") {
|
|
126
168
|
res.statusCode = 200;
|
|
127
169
|
res.setHeader("Content-Type", "application/json");
|
|
128
|
-
res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size }));
|
|
170
|
+
res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size, list: appList() }));
|
|
129
171
|
return;
|
|
130
172
|
}
|
|
131
173
|
const file = u.pathname.replace(/^\//, "");
|
|
@@ -155,10 +197,12 @@ function createRelayServer(options = {}) {
|
|
|
155
197
|
resolve({
|
|
156
198
|
server,
|
|
157
199
|
url,
|
|
200
|
+
connectUrl: connectUrl(),
|
|
158
201
|
port,
|
|
159
202
|
counts: () => ({ apps: apps.size, panels: panels.size }),
|
|
203
|
+
appList,
|
|
160
204
|
close: () => new Promise((done) => {
|
|
161
|
-
for (const res of [...apps, ...panels]) res.end();
|
|
205
|
+
for (const res of [...apps.keys(), ...panels]) res.end();
|
|
162
206
|
apps.clear();
|
|
163
207
|
panels.clear();
|
|
164
208
|
server.close(() => done());
|
package/dist/relay.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/tsup/assets/cjs_shims.js","../src/relay.ts"],"names":["dirname","fileURLToPath","join","existsSync","createServer","readFileSync"],"mappings":"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","file":"relay.cjs","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * A tiny local relay (`npx rerender-lens panel`): serves the panel and forwards messages between\n * app pages and panel pages over Server-Sent Events + POST, so any app (Next.js, Webpack, a\n * production-like build) gets the panel without the extension and without a same-origin channel.\n *\n * GET / redirect to /panel.html?relay=<this server>\n * GET /panel.(html|js|css) the panel page\n * GET /events?role=app SSE: commands from panels\n * GET /events?role=panel SSE: hello / report / clear from apps, replies to commands\n * POST /message one message or an array; `{ __rerenderLens }` and replies go to\n * panels, `{ __rerenderLensCmd }` goes to apps\n *\n * Dependency-free (node:http). CORS is open: the relay is a local dev tool, bind it to 127.0.0.1.\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport interface RelayOptions {\n port?: number;\n host?: string;\n /** Directory with panel.html/js/css; found automatically in the package. */\n panelDir?: string | null;\n /** Milliseconds between SSE keep-alive comments. Default 15000. */\n keepAliveMs?: number;\n}\n\nexport interface RelayServer {\n server: Server;\n /** `http://127.0.0.1:4141` once listening. */\n url: string;\n port: number;\n close(): Promise<void>;\n /** Connected app and panel streams. */\n counts(): { apps: number; panels: number };\n}\n\nconst MIME: Record<string, string> = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8' };\n\nexport function findPanelDir(): string | null {\n const here = dirname(fileURLToPath(import.meta.url));\n for (const dir of [join(here, '..', 'panel'), join(here, '..', 'extension'), join(here, '..', '..', 'extension')]) {\n if (existsSync(join(dir, 'panel.html')) && existsSync(join(dir, 'panel.js'))) return dir;\n }\n return null;\n}\n\nconst cors = (res: ServerResponse): void => {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n};\n\nfunction readBody(req: IncomingMessage, limit = 8 * 1024 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > limit) {\n reject(new Error('body too large'));\n req.destroy();\n return;\n }\n chunks.push(c);\n });\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n req.on('error', reject);\n });\n}\n\nexport function createRelayServer(options: RelayOptions = {}): Promise<RelayServer> {\n const host = options.host ?? '127.0.0.1';\n const panelDir = options.panelDir === undefined ? findPanelDir() : options.panelDir;\n const keepAliveMs = options.keepAliveMs ?? 15_000;\n const apps = new Set<ServerResponse>();\n const panels = new Set<ServerResponse>();\n let url = '';\n\n const send = (targets: Set<ServerResponse>, message: unknown): void => {\n const line = `data: ${JSON.stringify(message)}\\n\\n`;\n for (const res of targets) {\n try {\n res.write(line);\n } catch {\n targets.delete(res);\n }\n }\n };\n\n const route = (message: unknown): void => {\n if (!message || typeof message !== 'object') return;\n const m = message as Record<string, unknown>;\n if (m.__rerenderLensCmd === true) send(apps, m);\n else if (m.__rerenderLens === true || m.__rerenderLensReply === true) send(panels, m);\n };\n\n const server = createServer(async (req, res) => {\n cors(res);\n const u = new URL(req.url || '/', 'http://relay');\n if (req.method === 'OPTIONS') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/' && req.method === 'GET') {\n res.statusCode = 302;\n res.setHeader('Location', `/panel.html?relay=${encodeURIComponent(url)}`);\n res.end();\n return;\n }\n if (u.pathname === '/events' && req.method === 'GET') {\n const role = u.searchParams.get('role') === 'app' ? apps : panels;\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.setHeader('Connection', 'keep-alive');\n res.write(': connected\\n\\n');\n role.add(res);\n const ping = setInterval(() => {\n try {\n res.write(': ping\\n\\n');\n } catch {\n /* closed */\n }\n }, keepAliveMs);\n req.on('close', () => {\n clearInterval(ping);\n role.delete(res);\n });\n // Panels learn the app count as soon as their stream is open (that first message is also how a\n // panel knows it is safe to send commands: replies only reach panels that are already connected),\n // and again whenever an app comes or goes, so they show \"disconnected\" instead of waiting.\n const count = (): unknown => ({ __rerenderLens: true, version: 2, type: 'relay', payload: { apps: apps.size } });\n if (role === apps) {\n send(panels, count());\n req.on('close', () => send(panels, count()));\n } else {\n send(new Set([res]), count());\n }\n return;\n }\n if (u.pathname === '/favicon.ico') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/message' && req.method === 'POST') {\n try {\n const parsed = JSON.parse((await readBody(req)) || 'null') as unknown;\n for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m);\n res.statusCode = 204;\n res.end();\n } catch (e) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'text/plain');\n res.end(String((e as Error).message || e));\n }\n return;\n }\n if (u.pathname === '/status' && req.method === 'GET') {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size }));\n return;\n }\n const file = u.pathname.replace(/^\\//, '');\n if (/^panel\\.(html|js|css)$/.test(file) && req.method === 'GET') {\n if (!panelDir) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'text/plain');\n res.end('rerender-lens: panel files not found (is the package built?)');\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', MIME[file.slice(file.lastIndexOf('.'))] || 'application/octet-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.end(readFileSync(join(panelDir, file)));\n return;\n }\n res.statusCode = 404;\n res.setHeader('Content-Type', 'text/plain');\n res.end('not found');\n });\n\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(options.port ?? 4141, host, () => {\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : options.port ?? 4141;\n url = `http://${host === '0.0.0.0' || host === '::' ? 'localhost' : host}:${port}`;\n resolve({\n server,\n url,\n port,\n counts: () => ({ apps: apps.size, panels: panels.size }),\n close: () =>\n new Promise<void>((done) => {\n for (const res of [...apps, ...panels]) res.end();\n apps.clear();\n panels.clear();\n server.close(() => done());\n }),\n });\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../node_modules/tsup/assets/cjs_shims.js","../src/relay.ts"],"names":["dirname","fileURLToPath","join","existsSync","createServer","readFileSync"],"mappings":"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","file":"relay.cjs","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * A tiny local relay (`npx rerender-lens panel`): serves the panel and forwards messages between\n * app pages and panel pages over Server-Sent Events + POST, so any app (Next.js, Webpack, a\n * production-like build) gets the panel without the extension and without a same-origin channel.\n *\n * GET / redirect to /panel.html?relay=<this server>\n * GET /panel.(html|js|css) the panel page\n * GET /events?role=app SSE: commands from panels (first message: this app's id)\n * GET /events?role=panel SSE: hello / report / clear from apps, replies to commands\n * POST /message one message or an array; `{ __rerenderLens }` and replies go to\n * panels, `{ __rerenderLensCmd }` goes to apps\n *\n * Several apps can share one relay. Each app connection gets an id; the relay tells the app its id,\n * stamps `app` on everything that app sends, and sends panels the roster whenever it changes. A\n * command carrying `app` goes to that one app, a command without it goes to all of them, so a panel\n * or a library that knows nothing about ids behaves exactly as before.\n *\n * Dependency-free (node:http). Bound to 127.0.0.1 by default, where CORS is open and no token is\n * needed. On any other host `rerender-lens panel` generates a token: `/events`, `/message` and\n * `/status` then require it (`?token=` or the `x-rerender-lens-token` header) and the URL it prints\n * carries it, because reports contain the app's prop, state and context values.\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport interface RelayOptions {\n port?: number;\n host?: string;\n /** Directory with panel.html/js/css; found automatically in the package. */\n panelDir?: string | null;\n /** Milliseconds between SSE keep-alive comments. Default 15000. */\n keepAliveMs?: number;\n /**\n * Require this token on `/events`, `/message` and `/status`. `rerender-lens panel` generates one\n * whenever it binds to anything but loopback; the URL it prints carries it as `?token=`.\n */\n token?: string;\n}\n\n/** One connected app page. `label` is what the panel's app chooser shows. */\nexport interface RelayApp {\n id: string;\n label: string;\n}\n\nexport interface RelayServer {\n server: Server;\n /** `http://127.0.0.1:4141` once listening. */\n url: string;\n /** The URL to hand to a panel or an app: `url`, plus `?token=` when one is required. */\n connectUrl: string;\n port: number;\n close(): Promise<void>;\n /** Connected app and panel streams. */\n counts(): { apps: number; panels: number };\n /** The connected apps, in connection order. */\n appList(): RelayApp[];\n}\n\n/** Same marker and protocol the library and the panel use (`src/devtools.ts`); kept literal so the relay stays dependency-free. */\nconst MARKER = '__rerenderLens';\nconst PROTOCOL = 2;\n\nconst MIME: Record<string, string> = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8' };\n\nexport function findPanelDir(): string | null {\n const here = dirname(fileURLToPath(import.meta.url));\n for (const dir of [join(here, '..', 'panel'), join(here, '..', 'extension'), join(here, '..', '..', 'extension')]) {\n if (existsSync(join(dir, 'panel.html')) && existsSync(join(dir, 'panel.js'))) return dir;\n }\n return null;\n}\n\nconst cors = (res: ServerResponse): void => {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n};\n\nfunction readBody(req: IncomingMessage, limit = 8 * 1024 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > limit) {\n reject(new Error('body too large'));\n req.destroy();\n return;\n }\n chunks.push(c);\n });\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n req.on('error', reject);\n });\n}\n\nexport function createRelayServer(options: RelayOptions = {}): Promise<RelayServer> {\n const host = options.host ?? '127.0.0.1';\n const panelDir = options.panelDir === undefined ? findPanelDir() : options.panelDir;\n const keepAliveMs = options.keepAliveMs ?? 15_000;\n const token = options.token || null;\n const apps = new Map<ServerResponse, RelayApp>();\n const panels = new Set<ServerResponse>();\n let url = '';\n let nextAppId = 1;\n const connectUrl = (): string => (token ? `${url}?token=${encodeURIComponent(token)}` : url);\n\n const line = (message: unknown): string => `data: ${JSON.stringify(message)}\\n\\n`;\n const sendTo = (res: ServerResponse, message: unknown): void => {\n try {\n res.write(line(message));\n } catch {\n apps.delete(res);\n panels.delete(res);\n }\n };\n const send = (targets: Iterable<ServerResponse>, message: unknown): void => {\n const text = line(message);\n for (const res of [...targets]) {\n try {\n res.write(text);\n } catch {\n apps.delete(res);\n panels.delete(res);\n }\n }\n };\n\n const appList = (): RelayApp[] => [...apps.values()];\n const roster = (): unknown => ({ [MARKER]: true, version: PROTOCOL, type: 'relay', payload: { apps: apps.size, list: appList() } });\n const streamOf = (id: string): ServerResponse | null => {\n for (const [res, app] of apps) if (app.id === id) return res;\n return null;\n };\n\n const route = (message: unknown, from?: RelayApp): void => {\n if (!message || typeof message !== 'object') return;\n const m = message as Record<string, unknown>;\n if (m.__rerenderLensCmd === true) {\n // A command names its app when the panel is watching one of several; otherwise every app answers.\n const target = typeof m.app === 'string' ? streamOf(m.app) : null;\n if (target) sendTo(target, m);\n else send(apps.keys(), m);\n } else if (m.__rerenderLens === true || m.__rerenderLensReply === true) {\n send(panels, from ? { ...m, app: from.id } : m);\n }\n };\n\n /** With a token set, everything that carries data needs it; the static panel files do not. */\n const authorized = (req: IncomingMessage, u: URL): boolean => {\n if (!token) return true;\n const given = u.searchParams.get('token') || (typeof req.headers['x-rerender-lens-token'] === 'string' ? req.headers['x-rerender-lens-token'] : '');\n return given === token;\n };\n\n const server = createServer(async (req, res) => {\n cors(res);\n const u = new URL(req.url || '/', 'http://relay');\n if (req.method === 'OPTIONS') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/' && req.method === 'GET') {\n res.statusCode = 302;\n res.setHeader('Location', `/panel.html?relay=${encodeURIComponent(connectUrl())}`);\n res.end();\n return;\n }\n if (/^\\/(events|message|status)$/.test(u.pathname) && !authorized(req, u)) {\n res.statusCode = 401;\n res.setHeader('Content-Type', 'text/plain');\n res.end('rerender-lens relay: this relay needs the token from the URL it printed (?token=...)');\n return;\n }\n if (u.pathname === '/events' && req.method === 'GET') {\n const isApp = u.searchParams.get('role') === 'app';\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.setHeader('Connection', 'keep-alive');\n res.write(': connected\\n\\n');\n const app: RelayApp | null = isApp ? { id: `a${nextAppId++}`, label: (u.searchParams.get('label') || req.headers.origin || '').slice(0, 120) || `app ${nextAppId - 1}` } : null;\n if (app) apps.set(res, app);\n else panels.add(res);\n const ping = setInterval(() => {\n try {\n res.write(': ping\\n\\n');\n } catch {\n /* closed */\n }\n }, keepAliveMs);\n req.on('close', () => {\n clearInterval(ping);\n apps.delete(res);\n panels.delete(res);\n });\n // Panels learn the roster as soon as their stream is open (that first message is also how a\n // panel knows it is safe to send commands: replies only reach panels that are already connected),\n // and again whenever an app comes or goes, so they show \"disconnected\" instead of waiting.\n if (app) {\n sendTo(res, { __rerenderLensRelay: true, version: PROTOCOL, app: app.id }); // the app stamps this on what it sends\n send(panels, roster());\n req.on('close', () => send(panels, roster()));\n } else {\n sendTo(res, roster());\n }\n return;\n }\n if (u.pathname === '/favicon.ico') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/message' && req.method === 'POST') {\n try {\n const parsed = JSON.parse((await readBody(req)) || 'null') as unknown;\n const sender = u.searchParams.get('app');\n const from = sender ? (appList().find((a) => a.id === sender) ?? undefined) : undefined;\n for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m, from);\n res.statusCode = 204;\n res.end();\n } catch (e) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'text/plain');\n res.end(String((e as Error).message || e));\n }\n return;\n }\n if (u.pathname === '/status' && req.method === 'GET') {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size, list: appList() }));\n return;\n }\n const file = u.pathname.replace(/^\\//, '');\n if (/^panel\\.(html|js|css)$/.test(file) && req.method === 'GET') {\n if (!panelDir) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'text/plain');\n res.end('rerender-lens: panel files not found (is the package built?)');\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', MIME[file.slice(file.lastIndexOf('.'))] || 'application/octet-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.end(readFileSync(join(panelDir, file)));\n return;\n }\n res.statusCode = 404;\n res.setHeader('Content-Type', 'text/plain');\n res.end('not found');\n });\n\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(options.port ?? 4141, host, () => {\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : options.port ?? 4141;\n url = `http://${host === '0.0.0.0' || host === '::' ? 'localhost' : host}:${port}`;\n resolve({\n server,\n url,\n connectUrl: connectUrl(),\n port,\n counts: () => ({ apps: apps.size, panels: panels.size }),\n appList,\n close: () =>\n new Promise<void>((done) => {\n for (const res of [...apps.keys(), ...panels]) res.end();\n apps.clear();\n panels.clear();\n server.close(() => done());\n }),\n });\n });\n });\n}\n"]}
|
package/dist/relay.d.cts
CHANGED
|
@@ -7,12 +7,20 @@ import { Server } from 'node:http';
|
|
|
7
7
|
*
|
|
8
8
|
* GET / redirect to /panel.html?relay=<this server>
|
|
9
9
|
* GET /panel.(html|js|css) the panel page
|
|
10
|
-
* GET /events?role=app SSE: commands from panels
|
|
10
|
+
* GET /events?role=app SSE: commands from panels (first message: this app's id)
|
|
11
11
|
* GET /events?role=panel SSE: hello / report / clear from apps, replies to commands
|
|
12
12
|
* POST /message one message or an array; `{ __rerenderLens }` and replies go to
|
|
13
13
|
* panels, `{ __rerenderLensCmd }` goes to apps
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* Several apps can share one relay. Each app connection gets an id; the relay tells the app its id,
|
|
16
|
+
* stamps `app` on everything that app sends, and sends panels the roster whenever it changes. A
|
|
17
|
+
* command carrying `app` goes to that one app, a command without it goes to all of them, so a panel
|
|
18
|
+
* or a library that knows nothing about ids behaves exactly as before.
|
|
19
|
+
*
|
|
20
|
+
* Dependency-free (node:http). Bound to 127.0.0.1 by default, where CORS is open and no token is
|
|
21
|
+
* needed. On any other host `rerender-lens panel` generates a token: `/events`, `/message` and
|
|
22
|
+
* `/status` then require it (`?token=` or the `x-rerender-lens-token` header) and the URL it prints
|
|
23
|
+
* carries it, because reports contain the app's prop, state and context values.
|
|
16
24
|
*/
|
|
17
25
|
|
|
18
26
|
interface RelayOptions {
|
|
@@ -22,11 +30,23 @@ interface RelayOptions {
|
|
|
22
30
|
panelDir?: string | null;
|
|
23
31
|
/** Milliseconds between SSE keep-alive comments. Default 15000. */
|
|
24
32
|
keepAliveMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Require this token on `/events`, `/message` and `/status`. `rerender-lens panel` generates one
|
|
35
|
+
* whenever it binds to anything but loopback; the URL it prints carries it as `?token=`.
|
|
36
|
+
*/
|
|
37
|
+
token?: string;
|
|
38
|
+
}
|
|
39
|
+
/** One connected app page. `label` is what the panel's app chooser shows. */
|
|
40
|
+
interface RelayApp {
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
25
43
|
}
|
|
26
44
|
interface RelayServer {
|
|
27
45
|
server: Server;
|
|
28
46
|
/** `http://127.0.0.1:4141` once listening. */
|
|
29
47
|
url: string;
|
|
48
|
+
/** The URL to hand to a panel or an app: `url`, plus `?token=` when one is required. */
|
|
49
|
+
connectUrl: string;
|
|
30
50
|
port: number;
|
|
31
51
|
close(): Promise<void>;
|
|
32
52
|
/** Connected app and panel streams. */
|
|
@@ -34,8 +54,10 @@ interface RelayServer {
|
|
|
34
54
|
apps: number;
|
|
35
55
|
panels: number;
|
|
36
56
|
};
|
|
57
|
+
/** The connected apps, in connection order. */
|
|
58
|
+
appList(): RelayApp[];
|
|
37
59
|
}
|
|
38
60
|
declare function findPanelDir(): string | null;
|
|
39
61
|
declare function createRelayServer(options?: RelayOptions): Promise<RelayServer>;
|
|
40
62
|
|
|
41
|
-
export { type RelayOptions, type RelayServer, createRelayServer, findPanelDir };
|
|
63
|
+
export { type RelayApp, type RelayOptions, type RelayServer, createRelayServer, findPanelDir };
|
package/dist/relay.d.ts
CHANGED
|
@@ -7,12 +7,20 @@ import { Server } from 'node:http';
|
|
|
7
7
|
*
|
|
8
8
|
* GET / redirect to /panel.html?relay=<this server>
|
|
9
9
|
* GET /panel.(html|js|css) the panel page
|
|
10
|
-
* GET /events?role=app SSE: commands from panels
|
|
10
|
+
* GET /events?role=app SSE: commands from panels (first message: this app's id)
|
|
11
11
|
* GET /events?role=panel SSE: hello / report / clear from apps, replies to commands
|
|
12
12
|
* POST /message one message or an array; `{ __rerenderLens }` and replies go to
|
|
13
13
|
* panels, `{ __rerenderLensCmd }` goes to apps
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* Several apps can share one relay. Each app connection gets an id; the relay tells the app its id,
|
|
16
|
+
* stamps `app` on everything that app sends, and sends panels the roster whenever it changes. A
|
|
17
|
+
* command carrying `app` goes to that one app, a command without it goes to all of them, so a panel
|
|
18
|
+
* or a library that knows nothing about ids behaves exactly as before.
|
|
19
|
+
*
|
|
20
|
+
* Dependency-free (node:http). Bound to 127.0.0.1 by default, where CORS is open and no token is
|
|
21
|
+
* needed. On any other host `rerender-lens panel` generates a token: `/events`, `/message` and
|
|
22
|
+
* `/status` then require it (`?token=` or the `x-rerender-lens-token` header) and the URL it prints
|
|
23
|
+
* carries it, because reports contain the app's prop, state and context values.
|
|
16
24
|
*/
|
|
17
25
|
|
|
18
26
|
interface RelayOptions {
|
|
@@ -22,11 +30,23 @@ interface RelayOptions {
|
|
|
22
30
|
panelDir?: string | null;
|
|
23
31
|
/** Milliseconds between SSE keep-alive comments. Default 15000. */
|
|
24
32
|
keepAliveMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Require this token on `/events`, `/message` and `/status`. `rerender-lens panel` generates one
|
|
35
|
+
* whenever it binds to anything but loopback; the URL it prints carries it as `?token=`.
|
|
36
|
+
*/
|
|
37
|
+
token?: string;
|
|
38
|
+
}
|
|
39
|
+
/** One connected app page. `label` is what the panel's app chooser shows. */
|
|
40
|
+
interface RelayApp {
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
25
43
|
}
|
|
26
44
|
interface RelayServer {
|
|
27
45
|
server: Server;
|
|
28
46
|
/** `http://127.0.0.1:4141` once listening. */
|
|
29
47
|
url: string;
|
|
48
|
+
/** The URL to hand to a panel or an app: `url`, plus `?token=` when one is required. */
|
|
49
|
+
connectUrl: string;
|
|
30
50
|
port: number;
|
|
31
51
|
close(): Promise<void>;
|
|
32
52
|
/** Connected app and panel streams. */
|
|
@@ -34,8 +54,10 @@ interface RelayServer {
|
|
|
34
54
|
apps: number;
|
|
35
55
|
panels: number;
|
|
36
56
|
};
|
|
57
|
+
/** The connected apps, in connection order. */
|
|
58
|
+
appList(): RelayApp[];
|
|
37
59
|
}
|
|
38
60
|
declare function findPanelDir(): string | null;
|
|
39
61
|
declare function createRelayServer(options?: RelayOptions): Promise<RelayServer>;
|
|
40
62
|
|
|
41
|
-
export { type RelayOptions, type RelayServer, createRelayServer, findPanelDir };
|
|
63
|
+
export { type RelayApp, type RelayOptions, type RelayServer, createRelayServer, findPanelDir };
|
package/dist/relay.js
CHANGED
|
@@ -4,6 +4,8 @@ import { dirname, join } from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
|
|
6
6
|
// src/relay.ts
|
|
7
|
+
var MARKER = "__rerenderLens";
|
|
8
|
+
var PROTOCOL = 2;
|
|
7
9
|
var MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8" };
|
|
8
10
|
function findPanelDir() {
|
|
9
11
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -38,26 +40,55 @@ function createRelayServer(options = {}) {
|
|
|
38
40
|
const host = options.host ?? "127.0.0.1";
|
|
39
41
|
const panelDir = options.panelDir === void 0 ? findPanelDir() : options.panelDir;
|
|
40
42
|
const keepAliveMs = options.keepAliveMs ?? 15e3;
|
|
41
|
-
const
|
|
43
|
+
const token = options.token || null;
|
|
44
|
+
const apps = /* @__PURE__ */ new Map();
|
|
42
45
|
const panels = /* @__PURE__ */ new Set();
|
|
43
46
|
let url = "";
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
let nextAppId = 1;
|
|
48
|
+
const connectUrl = () => token ? `${url}?token=${encodeURIComponent(token)}` : url;
|
|
49
|
+
const line = (message) => `data: ${JSON.stringify(message)}
|
|
46
50
|
|
|
47
51
|
`;
|
|
48
|
-
|
|
52
|
+
const sendTo = (res, message) => {
|
|
53
|
+
try {
|
|
54
|
+
res.write(line(message));
|
|
55
|
+
} catch {
|
|
56
|
+
apps.delete(res);
|
|
57
|
+
panels.delete(res);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const send = (targets, message) => {
|
|
61
|
+
const text = line(message);
|
|
62
|
+
for (const res of [...targets]) {
|
|
49
63
|
try {
|
|
50
|
-
res.write(
|
|
64
|
+
res.write(text);
|
|
51
65
|
} catch {
|
|
52
|
-
|
|
66
|
+
apps.delete(res);
|
|
67
|
+
panels.delete(res);
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
70
|
};
|
|
56
|
-
const
|
|
71
|
+
const appList = () => [...apps.values()];
|
|
72
|
+
const roster = () => ({ [MARKER]: true, version: PROTOCOL, type: "relay", payload: { apps: apps.size, list: appList() } });
|
|
73
|
+
const streamOf = (id) => {
|
|
74
|
+
for (const [res, app] of apps) if (app.id === id) return res;
|
|
75
|
+
return null;
|
|
76
|
+
};
|
|
77
|
+
const route = (message, from) => {
|
|
57
78
|
if (!message || typeof message !== "object") return;
|
|
58
79
|
const m = message;
|
|
59
|
-
if (m.__rerenderLensCmd === true)
|
|
60
|
-
|
|
80
|
+
if (m.__rerenderLensCmd === true) {
|
|
81
|
+
const target = typeof m.app === "string" ? streamOf(m.app) : null;
|
|
82
|
+
if (target) sendTo(target, m);
|
|
83
|
+
else send(apps.keys(), m);
|
|
84
|
+
} else if (m.__rerenderLens === true || m.__rerenderLensReply === true) {
|
|
85
|
+
send(panels, from ? { ...m, app: from.id } : m);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const authorized = (req, u) => {
|
|
89
|
+
if (!token) return true;
|
|
90
|
+
const given = u.searchParams.get("token") || (typeof req.headers["x-rerender-lens-token"] === "string" ? req.headers["x-rerender-lens-token"] : "");
|
|
91
|
+
return given === token;
|
|
61
92
|
};
|
|
62
93
|
const server = createServer(async (req, res) => {
|
|
63
94
|
cors(res);
|
|
@@ -69,18 +100,26 @@ function createRelayServer(options = {}) {
|
|
|
69
100
|
}
|
|
70
101
|
if (u.pathname === "/" && req.method === "GET") {
|
|
71
102
|
res.statusCode = 302;
|
|
72
|
-
res.setHeader("Location", `/panel.html?relay=${encodeURIComponent(
|
|
103
|
+
res.setHeader("Location", `/panel.html?relay=${encodeURIComponent(connectUrl())}`);
|
|
73
104
|
res.end();
|
|
74
105
|
return;
|
|
75
106
|
}
|
|
107
|
+
if (/^\/(events|message|status)$/.test(u.pathname) && !authorized(req, u)) {
|
|
108
|
+
res.statusCode = 401;
|
|
109
|
+
res.setHeader("Content-Type", "text/plain");
|
|
110
|
+
res.end("rerender-lens relay: this relay needs the token from the URL it printed (?token=...)");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
76
113
|
if (u.pathname === "/events" && req.method === "GET") {
|
|
77
|
-
const
|
|
114
|
+
const isApp = u.searchParams.get("role") === "app";
|
|
78
115
|
res.statusCode = 200;
|
|
79
116
|
res.setHeader("Content-Type", "text/event-stream");
|
|
80
117
|
res.setHeader("Cache-Control", "no-store");
|
|
81
118
|
res.setHeader("Connection", "keep-alive");
|
|
82
119
|
res.write(": connected\n\n");
|
|
83
|
-
|
|
120
|
+
const app = isApp ? { id: `a${nextAppId++}`, label: (u.searchParams.get("label") || req.headers.origin || "").slice(0, 120) || `app ${nextAppId - 1}` } : null;
|
|
121
|
+
if (app) apps.set(res, app);
|
|
122
|
+
else panels.add(res);
|
|
84
123
|
const ping = setInterval(() => {
|
|
85
124
|
try {
|
|
86
125
|
res.write(": ping\n\n");
|
|
@@ -89,14 +128,15 @@ function createRelayServer(options = {}) {
|
|
|
89
128
|
}, keepAliveMs);
|
|
90
129
|
req.on("close", () => {
|
|
91
130
|
clearInterval(ping);
|
|
92
|
-
|
|
131
|
+
apps.delete(res);
|
|
132
|
+
panels.delete(res);
|
|
93
133
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
send(panels,
|
|
97
|
-
req.on("close", () => send(panels,
|
|
134
|
+
if (app) {
|
|
135
|
+
sendTo(res, { __rerenderLensRelay: true, version: PROTOCOL, app: app.id });
|
|
136
|
+
send(panels, roster());
|
|
137
|
+
req.on("close", () => send(panels, roster()));
|
|
98
138
|
} else {
|
|
99
|
-
|
|
139
|
+
sendTo(res, roster());
|
|
100
140
|
}
|
|
101
141
|
return;
|
|
102
142
|
}
|
|
@@ -108,7 +148,9 @@ function createRelayServer(options = {}) {
|
|
|
108
148
|
if (u.pathname === "/message" && req.method === "POST") {
|
|
109
149
|
try {
|
|
110
150
|
const parsed = JSON.parse(await readBody(req) || "null");
|
|
111
|
-
|
|
151
|
+
const sender = u.searchParams.get("app");
|
|
152
|
+
const from = sender ? appList().find((a) => a.id === sender) ?? void 0 : void 0;
|
|
153
|
+
for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m, from);
|
|
112
154
|
res.statusCode = 204;
|
|
113
155
|
res.end();
|
|
114
156
|
} catch (e) {
|
|
@@ -121,7 +163,7 @@ function createRelayServer(options = {}) {
|
|
|
121
163
|
if (u.pathname === "/status" && req.method === "GET") {
|
|
122
164
|
res.statusCode = 200;
|
|
123
165
|
res.setHeader("Content-Type", "application/json");
|
|
124
|
-
res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size }));
|
|
166
|
+
res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size, list: appList() }));
|
|
125
167
|
return;
|
|
126
168
|
}
|
|
127
169
|
const file = u.pathname.replace(/^\//, "");
|
|
@@ -151,10 +193,12 @@ function createRelayServer(options = {}) {
|
|
|
151
193
|
resolve({
|
|
152
194
|
server,
|
|
153
195
|
url,
|
|
196
|
+
connectUrl: connectUrl(),
|
|
154
197
|
port,
|
|
155
198
|
counts: () => ({ apps: apps.size, panels: panels.size }),
|
|
199
|
+
appList,
|
|
156
200
|
close: () => new Promise((done) => {
|
|
157
|
-
for (const res of [...apps, ...panels]) res.end();
|
|
201
|
+
for (const res of [...apps.keys(), ...panels]) res.end();
|
|
158
202
|
apps.clear();
|
|
159
203
|
panels.clear();
|
|
160
204
|
server.close(() => done());
|