@graphty/remote-logger 1.2.2 → 1.3.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/README.md +53 -2
- package/dist/bundle/browser-entry.d.ts +34 -0
- package/dist/bundle/browser-entry.d.ts.map +1 -0
- package/dist/bundle/browser-entry.js +129 -0
- package/dist/bundle/browser-entry.js.map +1 -0
- package/dist/client/RemoteLogClient.d.ts +2 -1
- package/dist/client/RemoteLogClient.d.ts.map +1 -1
- package/dist/client/RemoteLogClient.js +20 -10
- package/dist/client/RemoteLogClient.js.map +1 -1
- package/dist/client/types.d.ts +6 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +20 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/mcp-server.d.ts +5 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +54 -8
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +6 -6
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +1 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/logs-clear.d.ts +1 -2
- package/dist/mcp/tools/logs-clear.d.ts.map +1 -1
- package/dist/mcp/tools/logs-get-all.d.ts +1 -2
- package/dist/mcp/tools/logs-get-all.d.ts.map +1 -1
- package/dist/mcp/tools/logs-get-errors.d.ts +1 -2
- package/dist/mcp/tools/logs-get-errors.d.ts.map +1 -1
- package/dist/mcp/tools/logs-get-file-path.d.ts +8 -2
- package/dist/mcp/tools/logs-get-file-path.d.ts.map +1 -1
- package/dist/mcp/tools/logs-get-file-path.js +1 -1
- package/dist/mcp/tools/logs-get-file-path.js.map +1 -1
- package/dist/mcp/tools/logs-receive.d.ts +9 -10
- package/dist/mcp/tools/logs-receive.d.ts.map +1 -1
- package/dist/mcp/tools/logs-search.d.ts +1 -2
- package/dist/mcp/tools/logs-search.d.ts.map +1 -1
- package/dist/mcp/tools/logs-status.d.ts.map +1 -1
- package/dist/mcp/tools/logs-status.js +4 -1
- package/dist/mcp/tools/logs-status.js.map +1 -1
- package/dist/remote-logger.browser.js +166 -0
- package/dist/remote-logger.browser.js.map +1 -0
- package/dist/server/dual-server.d.ts +7 -4
- package/dist/server/dual-server.d.ts.map +1 -1
- package/dist/server/dual-server.js +124 -50
- package/dist/server/dual-server.js.map +1 -1
- package/dist/server/log-server.d.ts +12 -2
- package/dist/server/log-server.d.ts.map +1 -1
- package/dist/server/log-server.js +109 -63
- package/dist/server/log-server.js.map +1 -1
- package/dist/server/log-storage.d.ts +4 -0
- package/dist/server/log-storage.d.ts.map +1 -1
- package/dist/server/log-storage.js.map +1 -1
- package/dist/server/proxy.d.ts +39 -0
- package/dist/server/proxy.d.ts.map +1 -0
- package/dist/server/proxy.js +290 -0
- package/dist/server/proxy.js.map +1 -0
- package/dist/server/self-signed-cert.d.ts +9 -0
- package/dist/server/self-signed-cert.d.ts.map +1 -1
- package/dist/server/self-signed-cert.js +9 -0
- package/dist/server/self-signed-cert.js.map +1 -1
- package/dist/ui/ConsoleCaptureUI.d.ts.map +1 -1
- package/dist/ui/ConsoleCaptureUI.js +6 -1
- package/dist/ui/ConsoleCaptureUI.js.map +1 -1
- package/package.json +7 -2
- package/src/bundle/browser-entry.ts +157 -0
- package/src/client/RemoteLogClient.ts +22 -11
- package/src/client/types.ts +6 -0
- package/src/mcp/index.ts +42 -0
- package/src/mcp/mcp-server.ts +54 -8
- package/src/mcp/tools/index.ts +7 -0
- package/src/mcp/tools/logs-clear.ts +1 -1
- package/src/mcp/tools/logs-get-all.ts +1 -1
- package/src/mcp/tools/logs-get-errors.ts +1 -1
- package/src/mcp/tools/logs-get-file-path.ts +2 -2
- package/src/mcp/tools/logs-receive.ts +1 -1
- package/src/mcp/tools/logs-search.ts +1 -1
- package/src/mcp/tools/logs-status.ts +4 -1
- package/src/server/dual-server.ts +165 -57
- package/src/server/log-server.ts +126 -68
- package/src/server/log-storage.ts +4 -0
- package/src/server/proxy.ts +357 -0
- package/src/server/self-signed-cert.ts +9 -0
- package/src/ui/ConsoleCaptureUI.ts +6 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reverse proxy with automatic remote-logger injection.
|
|
3
|
+
*
|
|
4
|
+
* Proxies requests to a target URL and injects the remote-logger script
|
|
5
|
+
* into HTML responses. Non-HTML responses pass through unmodified.
|
|
6
|
+
* @module server/proxy
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type * as http from "http";
|
|
10
|
+
import httpProxy from "http-proxy";
|
|
11
|
+
import * as https from "https";
|
|
12
|
+
import { defaultTreeAdapter, type DefaultTreeAdapterMap,html as parse5Html, parse, serialize } from "parse5";
|
|
13
|
+
|
|
14
|
+
const HEADERS_TO_REMOVE = [
|
|
15
|
+
"content-security-policy",
|
|
16
|
+
"content-security-policy-report-only",
|
|
17
|
+
"x-frame-options",
|
|
18
|
+
"strict-transport-security",
|
|
19
|
+
"content-length",
|
|
20
|
+
"content-encoding",
|
|
21
|
+
"transfer-encoding",
|
|
22
|
+
"link",
|
|
23
|
+
"etag",
|
|
24
|
+
"cache-control",
|
|
25
|
+
"age",
|
|
26
|
+
"server",
|
|
27
|
+
"x-sveltekit-page",
|
|
28
|
+
"x-vercel-cache",
|
|
29
|
+
"x-vercel-id",
|
|
30
|
+
"x-powered-by",
|
|
31
|
+
"via",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extract the origin (protocol + host) from a full URL.
|
|
36
|
+
* @param targetUrl - The full target URL
|
|
37
|
+
* @returns The origin portion of the URL
|
|
38
|
+
*/
|
|
39
|
+
function extractOrigin(targetUrl: string): string {
|
|
40
|
+
const parsed = new URL(targetUrl);
|
|
41
|
+
return parsed.origin;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extract the path portion from a full URL.
|
|
46
|
+
* @param targetUrl - The full target URL
|
|
47
|
+
* @returns The path including query string
|
|
48
|
+
*/
|
|
49
|
+
function extractPath(targetUrl: string): string {
|
|
50
|
+
const parsed = new URL(targetUrl);
|
|
51
|
+
return parsed.pathname + parsed.search;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Rewrite a Location header to route back through the proxy.
|
|
57
|
+
* @param location - The original Location header value
|
|
58
|
+
* @param proxyBaseUrl - The proxy base URL (e.g., "/proxy/")
|
|
59
|
+
* @returns The rewritten Location header
|
|
60
|
+
*/
|
|
61
|
+
function rewriteLocationHeader(location: string, proxyBaseUrl: string): string {
|
|
62
|
+
if (location.startsWith("http://") || location.startsWith("https://")) {
|
|
63
|
+
return proxyBaseUrl + location;
|
|
64
|
+
}
|
|
65
|
+
return location;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Rewrite Set-Cookie attributes for proxied responses.
|
|
70
|
+
* Removes Domain (wrong domain) and Secure (proxy may serve over HTTP).
|
|
71
|
+
* @param cookie - The original Set-Cookie header value
|
|
72
|
+
* @returns The rewritten cookie
|
|
73
|
+
*/
|
|
74
|
+
function rewriteSetCookie(cookie: string): string {
|
|
75
|
+
let rewritten = cookie.replace(/;\s*domain=[^;]*/gi, "");
|
|
76
|
+
rewritten = rewritten.replace(/;\s*secure/gi, "");
|
|
77
|
+
return rewritten;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type Element = DefaultTreeAdapterMap["element"];
|
|
81
|
+
type Document = DefaultTreeAdapterMap["document"];
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Find the first element with a given tag name in the AST.
|
|
85
|
+
* @param node - The AST node to search from
|
|
86
|
+
* @param tagName - The tag name to find
|
|
87
|
+
* @returns The element or null
|
|
88
|
+
*/
|
|
89
|
+
function findElement(node: Document | Element, tagName: string): Element | null {
|
|
90
|
+
if (defaultTreeAdapter.isElementNode(node) && node.tagName === tagName) {
|
|
91
|
+
return node;
|
|
92
|
+
}
|
|
93
|
+
const children = node.childNodes ?? [];
|
|
94
|
+
for (const child of children) {
|
|
95
|
+
if (defaultTreeAdapter.isElementNode(child)) {
|
|
96
|
+
const found = findElement(child, tagName);
|
|
97
|
+
if (found) {
|
|
98
|
+
return found;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Strip integrity attributes from all script and link elements in the AST.
|
|
107
|
+
* @param node - The AST node to search from
|
|
108
|
+
*/
|
|
109
|
+
function stripIntegrityAttributes(node: Document | Element): void {
|
|
110
|
+
if (defaultTreeAdapter.isElementNode(node)) {
|
|
111
|
+
if (node.tagName === "script" || node.tagName === "link") {
|
|
112
|
+
node.attrs = node.attrs.filter(a => a.name !== "integrity");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const children = node.childNodes ?? [];
|
|
116
|
+
for (const child of children) {
|
|
117
|
+
if (defaultTreeAdapter.isElementNode(child)) {
|
|
118
|
+
stripIntegrityAttributes(child);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const XHTML_NS = parse5Html.NS.HTML;
|
|
124
|
+
|
|
125
|
+
const CHROME_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Inject the remote-logger script and base tag into HTML using parse5.
|
|
129
|
+
* Inserts base at start of head, script at end of head, and strips
|
|
130
|
+
* integrity attributes from script/link elements.
|
|
131
|
+
* @param html - The original HTML string
|
|
132
|
+
* @param scriptUrl - URL to the remote-logger script
|
|
133
|
+
* @param baseHref - Base href for rewriting absolute paths
|
|
134
|
+
* @returns The modified HTML
|
|
135
|
+
*/
|
|
136
|
+
export function injectScript(html: string, scriptUrl: string, baseHref: string): string {
|
|
137
|
+
const doc = parse(html);
|
|
138
|
+
|
|
139
|
+
const head = findElement(doc, "head");
|
|
140
|
+
if (head) {
|
|
141
|
+
const baseEl = defaultTreeAdapter.createElement("base", XHTML_NS, [{ name: "href", value: baseHref }]);
|
|
142
|
+
const scriptEl = defaultTreeAdapter.createElement("script", XHTML_NS, [{ name: "src", value: scriptUrl }]);
|
|
143
|
+
|
|
144
|
+
// Insert base at start of head (before any relative URLs)
|
|
145
|
+
if (head.childNodes.length > 0) {
|
|
146
|
+
defaultTreeAdapter.insertBefore(head, baseEl, head.childNodes[0]);
|
|
147
|
+
} else {
|
|
148
|
+
defaultTreeAdapter.appendChild(head, baseEl);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Append script at end of head
|
|
152
|
+
defaultTreeAdapter.appendChild(head, scriptEl);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
stripIntegrityAttributes(doc);
|
|
156
|
+
|
|
157
|
+
return serialize(doc);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if a content-type header indicates HTML.
|
|
162
|
+
* @param contentType - The Content-Type header value
|
|
163
|
+
* @returns True if the content is HTML
|
|
164
|
+
*/
|
|
165
|
+
function isHtmlContentType(contentType: string | undefined): boolean {
|
|
166
|
+
if (!contentType) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
return contentType.includes("text/html");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validate that a URL is a valid proxy target.
|
|
174
|
+
* @param targetUrl - The URL to validate
|
|
175
|
+
* @returns True if the URL is valid for proxying
|
|
176
|
+
*/
|
|
177
|
+
export function isValidProxyTarget(targetUrl: string): boolean {
|
|
178
|
+
try {
|
|
179
|
+
const parsed = new URL(targetUrl);
|
|
180
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Result of creating a proxy instance. */
|
|
187
|
+
export interface ProxyInstance {
|
|
188
|
+
/** Handle an incoming proxy request. */
|
|
189
|
+
handleRequest: (
|
|
190
|
+
req: http.IncomingMessage,
|
|
191
|
+
res: http.ServerResponse,
|
|
192
|
+
targetUrl: string,
|
|
193
|
+
requestProtocol: string,
|
|
194
|
+
requestHost: string,
|
|
195
|
+
) => void;
|
|
196
|
+
/** Close the proxy and free resources. */
|
|
197
|
+
close: () => void;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create a proxy instance that injects the remote-logger into HTML responses.
|
|
202
|
+
* @param proxyBasePath - Base path for the proxy (e.g., "/proxy/")
|
|
203
|
+
* @param quiet - Suppress terminal output
|
|
204
|
+
* @returns A proxy instance with handleRequest and close methods
|
|
205
|
+
*/
|
|
206
|
+
export function createProxy(proxyBasePath: string, quiet: boolean): ProxyInstance {
|
|
207
|
+
// Custom TLS agent to avoid JA3 fingerprint-based blocking by CDNs.
|
|
208
|
+
// Fastly, Akamai, etc. blocklist Node.js's default TLS fingerprint.
|
|
209
|
+
// Using a custom sigalgs list changes the fingerprint without restricting
|
|
210
|
+
// TLS version, so sites that only support TLS 1.2 still work.
|
|
211
|
+
const httpsAgent = new https.Agent({
|
|
212
|
+
rejectUnauthorized: false,
|
|
213
|
+
sigalgs: "ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256:rsa_pkcs1_sha256:ecdsa_secp384r1_sha384:rsa_pss_rsae_sha384:rsa_pkcs1_sha384:rsa_pss_rsae_sha512:rsa_pkcs1_sha512",
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const proxy = httpProxy.createProxyServer({
|
|
217
|
+
selfHandleResponse: true,
|
|
218
|
+
changeOrigin: true,
|
|
219
|
+
followRedirects: false,
|
|
220
|
+
secure: false,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
proxy.on("proxyRes", (proxyRes, req, res) => {
|
|
224
|
+
const chunks: Buffer[] = [];
|
|
225
|
+
|
|
226
|
+
proxyRes.on("data", (chunk: Buffer) => {
|
|
227
|
+
chunks.push(chunk);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
proxyRes.on("end", () => {
|
|
231
|
+
const body = Buffer.concat(chunks);
|
|
232
|
+
|
|
233
|
+
// Copy headers with modifications
|
|
234
|
+
const headers: Record<string, string | string[]> = {};
|
|
235
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
236
|
+
if (value === undefined) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (HEADERS_TO_REMOVE.includes(key.toLowerCase())) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (key.toLowerCase() === "location" && typeof value === "string") {
|
|
244
|
+
headers[key] = rewriteLocationHeader(value, proxyBasePath);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (key.toLowerCase() === "set-cookie" && Array.isArray(value)) {
|
|
249
|
+
headers[key] = value.map(rewriteSetCookie);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
headers[key] = value;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const contentType = proxyRes.headers["content-type"];
|
|
257
|
+
const statusCode = proxyRes.statusCode ?? 200;
|
|
258
|
+
|
|
259
|
+
if (isHtmlContentType(contentType)) {
|
|
260
|
+
const html = body.toString("utf-8");
|
|
261
|
+
const reqExtra = req as http.IncomingMessage & { _proxyTargetUrl?: string; _proxyScriptUrl?: string };
|
|
262
|
+
const originalUrl = reqExtra._proxyTargetUrl;
|
|
263
|
+
const reqScriptUrl = reqExtra._proxyScriptUrl ?? "/remote-logger.js";
|
|
264
|
+
const baseHref = originalUrl ? `${proxyBasePath}${extractOrigin(originalUrl)}/` : proxyBasePath;
|
|
265
|
+
const modified = injectScript(html, reqScriptUrl, baseHref);
|
|
266
|
+
|
|
267
|
+
headers["content-type"] = "text/html; charset=utf-8";
|
|
268
|
+
headers["cache-control"] = "no-cache, no-store";
|
|
269
|
+
|
|
270
|
+
res.writeHead(statusCode, headers);
|
|
271
|
+
res.end(modified);
|
|
272
|
+
} else {
|
|
273
|
+
if (body.length > 0) {
|
|
274
|
+
headers["content-length"] = String(body.length);
|
|
275
|
+
}
|
|
276
|
+
res.writeHead(statusCode, headers);
|
|
277
|
+
res.end(body);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
proxy.on("error", (err, _req, res) => {
|
|
283
|
+
const isTlsError = err.message.includes("SSL") ||
|
|
284
|
+
err.message.includes("TLS") ||
|
|
285
|
+
err.message.includes("EPROTO");
|
|
286
|
+
|
|
287
|
+
const message = isTlsError
|
|
288
|
+
? `TLS connection failed: ${err.message}`
|
|
289
|
+
: err.message;
|
|
290
|
+
|
|
291
|
+
if (!quiet) {
|
|
292
|
+
console.error(`[proxy] Error: ${message}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const serverRes = res as http.ServerResponse;
|
|
296
|
+
if (!serverRes.headersSent) {
|
|
297
|
+
serverRes.writeHead(502, { "Content-Type": "application/json" });
|
|
298
|
+
}
|
|
299
|
+
serverRes.end(JSON.stringify({ error: "Bad Gateway", message }));
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const handleRequest = (
|
|
303
|
+
req: http.IncomingMessage,
|
|
304
|
+
res: http.ServerResponse,
|
|
305
|
+
targetUrl: string,
|
|
306
|
+
requestProtocol: string,
|
|
307
|
+
requestHost: string,
|
|
308
|
+
): void => {
|
|
309
|
+
if (!isValidProxyTarget(targetUrl)) {
|
|
310
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
311
|
+
res.end(JSON.stringify({ error: "Invalid target URL. Must start with http:// or https://" }));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const target = extractOrigin(targetUrl);
|
|
316
|
+
const targetPath = extractPath(targetUrl);
|
|
317
|
+
|
|
318
|
+
// Stash per-request data for the proxyRes handler
|
|
319
|
+
const reqExtra = req as http.IncomingMessage & { _proxyTargetUrl?: string; _proxyScriptUrl?: string };
|
|
320
|
+
reqExtra._proxyTargetUrl = targetUrl;
|
|
321
|
+
reqExtra._proxyScriptUrl = `${requestProtocol}://${requestHost}/remote-logger.js`;
|
|
322
|
+
|
|
323
|
+
// Override the request path to the target's path
|
|
324
|
+
req.url = targetPath;
|
|
325
|
+
|
|
326
|
+
// Request uncompressed content so we can modify HTML
|
|
327
|
+
req.headers["accept-encoding"] = "identity";
|
|
328
|
+
|
|
329
|
+
// Strip headers that reveal this is a cross-origin/proxy/headless request.
|
|
330
|
+
// CDNs like Fastly use these for bot detection.
|
|
331
|
+
delete req.headers["sec-fetch-site"];
|
|
332
|
+
delete req.headers["sec-fetch-mode"];
|
|
333
|
+
delete req.headers["sec-fetch-dest"];
|
|
334
|
+
delete req.headers["sec-fetch-user"];
|
|
335
|
+
delete req.headers["sec-ch-ua"];
|
|
336
|
+
delete req.headers["sec-ch-ua-mobile"];
|
|
337
|
+
delete req.headers["sec-ch-ua-platform"];
|
|
338
|
+
delete req.headers.origin;
|
|
339
|
+
delete req.headers.referer;
|
|
340
|
+
|
|
341
|
+
// Use a standard Chrome UA to avoid bot detection
|
|
342
|
+
req.headers["user-agent"] = CHROME_UA;
|
|
343
|
+
|
|
344
|
+
const proxyOptions: httpProxy.ServerOptions = { target };
|
|
345
|
+
if (targetUrl.startsWith("https://")) {
|
|
346
|
+
proxyOptions.agent = httpsAgent;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
proxy.web(req, res, proxyOptions);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const close = (): void => {
|
|
353
|
+
proxy.close();
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
return { handleRequest, close };
|
|
357
|
+
}
|
|
@@ -14,8 +14,17 @@ export interface GeneratedCert {
|
|
|
14
14
|
/**
|
|
15
15
|
* Generate a self-signed certificate for HTTPS.
|
|
16
16
|
* The certificate is valid for localhost and common local development hostnames.
|
|
17
|
+
*
|
|
18
|
+
* NOTE: This function is currently unused because modern browsers reject
|
|
19
|
+
* self-signed certificates by default. It is retained for potential use cases:
|
|
20
|
+
* - Node.js clients that can disable certificate verification
|
|
21
|
+
* - Testing environments
|
|
22
|
+
* - Development setups where users manually trust the certificate
|
|
23
|
+
*
|
|
24
|
+
* For browser use, provide valid certificates via --cert and --key flags.
|
|
17
25
|
* @param hostname - Optional hostname to include in the certificate (default: localhost)
|
|
18
26
|
* @returns Object containing PEM-encoded certificate and private key
|
|
27
|
+
* @internal
|
|
19
28
|
*/
|
|
20
29
|
export function generateSelfSignedCert(hostname = "localhost"): GeneratedCert {
|
|
21
30
|
const attrs = [
|
|
@@ -313,6 +313,8 @@ export class ConsoleCaptureUI {
|
|
|
313
313
|
*/
|
|
314
314
|
private setupGlobalMethods(): void {
|
|
315
315
|
if (typeof window !== "undefined") {
|
|
316
|
+
// Create a function that returns current logs to avoid stale reference
|
|
317
|
+
const getCurrentLogs = (): CapturedLogEntry[] => [...this.logs];
|
|
316
318
|
window.__console__ = {
|
|
317
319
|
copy: () => this.copyLogs(),
|
|
318
320
|
download: () => {
|
|
@@ -322,7 +324,10 @@ export class ConsoleCaptureUI {
|
|
|
322
324
|
this.clearLogs();
|
|
323
325
|
},
|
|
324
326
|
get: () => this.getLogs(),
|
|
325
|
-
|
|
327
|
+
// Use getter to always return fresh copy of current logs
|
|
328
|
+
get logs() {
|
|
329
|
+
return getCurrentLogs();
|
|
330
|
+
},
|
|
326
331
|
};
|
|
327
332
|
}
|
|
328
333
|
}
|