codetraxis 1.0.1 → 1.0.2
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 +30 -183
- package/client/dist/assets/index-DxucfvnK.js +193 -0
- package/client/dist/index.html +1 -1
- package/codetraxisAgent/index.ts +171 -0
- package/codetraxisAgent/interceptors/consoleInterceptor.ts +33 -0
- package/codetraxisAgent/interceptors/fetchInterceptor.ts +93 -0
- package/codetraxisAgent/interceptors/xhrInterceptor.ts +88 -0
- package/codetraxisAgent/shared.ts +97 -0
- package/package.json +3 -2
- package/server/dist/utils/agent/agentInstaller.js +87 -1186
- package/client/dist/assets/index-DLOUIosw.js +0 -193
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codetraxis agent — console interceptor.
|
|
3
|
+
*/
|
|
4
|
+
import type { TreeViewerBridge } from "../shared";
|
|
5
|
+
|
|
6
|
+
const INSTALLED_KEY = "__tv_console_installed__";
|
|
7
|
+
|
|
8
|
+
export function setupConsoleInterceptor(bridge: TreeViewerBridge): void {
|
|
9
|
+
const g = globalThis as Record<string, any>;
|
|
10
|
+
if (g[INSTALLED_KEY]) return;
|
|
11
|
+
g[INSTALLED_KEY] = true;
|
|
12
|
+
|
|
13
|
+
const original = {
|
|
14
|
+
log: console.log.bind(console),
|
|
15
|
+
info: console.info.bind(console),
|
|
16
|
+
warn: console.warn.bind(console),
|
|
17
|
+
error: console.error.bind(console),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
(["log", "info", "warn", "error"] as const).forEach(level => {
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
console[level] = (...args: unknown[]) => {
|
|
23
|
+
original[level](...args);
|
|
24
|
+
bridge.send({
|
|
25
|
+
id: bridge.uid(),
|
|
26
|
+
type: "console",
|
|
27
|
+
level,
|
|
28
|
+
args: args.map(a => bridge.safeSerialize(a)),
|
|
29
|
+
timestamp: Date.now(),
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codetraxis agent — fetch interceptor.
|
|
3
|
+
*/
|
|
4
|
+
import type { TreeViewerBridge } from "../shared";
|
|
5
|
+
|
|
6
|
+
const INSTALLED_KEY = "__tv_fetch_installed__";
|
|
7
|
+
|
|
8
|
+
function normalizeHeaders(headers: HeadersInit | undefined): Record<string, string> | undefined {
|
|
9
|
+
if (!headers) return undefined;
|
|
10
|
+
const result: Record<string, string> = {};
|
|
11
|
+
try {
|
|
12
|
+
if (headers instanceof Headers) {
|
|
13
|
+
headers.forEach((v, k) => { result[k] = v; });
|
|
14
|
+
} else if (Array.isArray(headers)) {
|
|
15
|
+
(headers as [string, string][]).forEach(([k, v]) => { result[k] = v; });
|
|
16
|
+
} else {
|
|
17
|
+
Object.assign(result, headers as Record<string, string>);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
} catch { return undefined; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function setupFetchInterceptor(bridge: TreeViewerBridge): void {
|
|
24
|
+
const g = globalThis as Record<string, any>;
|
|
25
|
+
if (g[INSTALLED_KEY]) return;
|
|
26
|
+
g[INSTALLED_KEY] = true;
|
|
27
|
+
|
|
28
|
+
const originalFetch =
|
|
29
|
+
typeof globalThis.fetch === "function"
|
|
30
|
+
? globalThis.fetch.bind(globalThis)
|
|
31
|
+
: null;
|
|
32
|
+
if (!originalFetch) return;
|
|
33
|
+
|
|
34
|
+
globalThis.fetch = async function tvFetch(
|
|
35
|
+
input: RequestInfo | URL,
|
|
36
|
+
init?: RequestInit,
|
|
37
|
+
): Promise<Response> {
|
|
38
|
+
const url =
|
|
39
|
+
typeof input === "string" ? input
|
|
40
|
+
: input instanceof URL ? input.href
|
|
41
|
+
: (input as Request).url;
|
|
42
|
+
|
|
43
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
44
|
+
const id = bridge.uid();
|
|
45
|
+
const start = Date.now();
|
|
46
|
+
|
|
47
|
+
const requestHeaders = normalizeHeaders(
|
|
48
|
+
init?.headers ?? (input instanceof Request ? input.headers : undefined),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
let requestBody: string | undefined;
|
|
52
|
+
const rawBody = init?.body;
|
|
53
|
+
if (rawBody != null) {
|
|
54
|
+
try {
|
|
55
|
+
requestBody =
|
|
56
|
+
typeof rawBody === "string" ? bridge.truncate(rawBody)
|
|
57
|
+
: rawBody instanceof URLSearchParams ? bridge.truncate(rawBody.toString())
|
|
58
|
+
: "[binary]";
|
|
59
|
+
} catch { requestBody = "[unserializable-body]"; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
bridge.send({ id, type: "network", transport: "fetch", method, url,
|
|
63
|
+
requestHeaders, requestBody, state: "pending", timestamp: start });
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const response = await originalFetch(input, init);
|
|
67
|
+
|
|
68
|
+
let responseHeaders: Record<string, string> | undefined;
|
|
69
|
+
try {
|
|
70
|
+
responseHeaders = {};
|
|
71
|
+
response.headers.forEach((v, k) => { responseHeaders![k] = v; });
|
|
72
|
+
} catch { /* ignore */ }
|
|
73
|
+
|
|
74
|
+
let responseBody: string | undefined;
|
|
75
|
+
try { responseBody = bridge.truncate(await response.clone().text()); }
|
|
76
|
+
catch { responseBody = "[binary]"; }
|
|
77
|
+
|
|
78
|
+
bridge.send({ id, type: "network", transport: "fetch", method, url,
|
|
79
|
+
status: response.status, requestHeaders, requestBody,
|
|
80
|
+
responseHeaders, responseBody,
|
|
81
|
+
state: response.ok ? "success" : "error",
|
|
82
|
+
duration: Date.now() - start, timestamp: start });
|
|
83
|
+
|
|
84
|
+
return response;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
bridge.send({ id, type: "network", transport: "fetch", method, url,
|
|
87
|
+
requestHeaders, requestBody, state: "error",
|
|
88
|
+
duration: Date.now() - start, timestamp: start,
|
|
89
|
+
error: bridge.safeSerialize(error) });
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codetraxis agent — XHR interceptor.
|
|
3
|
+
* Patches XMLHttpRequest.prototype so existing references (e.g. axios
|
|
4
|
+
* internals captured at import time) are also intercepted.
|
|
5
|
+
* Works in browser and React Native (which ships its own XHR polyfill).
|
|
6
|
+
*/
|
|
7
|
+
import type { TreeViewerBridge } from "../shared";
|
|
8
|
+
|
|
9
|
+
const INSTALLED_KEY = "__tv_xhr_installed__";
|
|
10
|
+
|
|
11
|
+
export function setupXhrInterceptor(bridge: TreeViewerBridge): void {
|
|
12
|
+
if (typeof XMLHttpRequest === "undefined") return;
|
|
13
|
+
const g = globalThis as Record<string, any>;
|
|
14
|
+
if (g[INSTALLED_KEY]) return;
|
|
15
|
+
g[INSTALLED_KEY] = true;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const proto = XMLHttpRequest.prototype;
|
|
19
|
+
|
|
20
|
+
const _origOpen = proto.open;
|
|
21
|
+
proto.open = function(this: XMLHttpRequest, method: string, url: string, ...rest: unknown[]) {
|
|
22
|
+
(this as any).__tv_method = method.toUpperCase();
|
|
23
|
+
(this as any).__tv_url = String(url);
|
|
24
|
+
(this as any).__tv_reqHeaders = undefined;
|
|
25
|
+
return (_origOpen as Function).apply(this, [method, url, ...rest]);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const _origSetHeader = proto.setRequestHeader;
|
|
29
|
+
proto.setRequestHeader = function(this: XMLHttpRequest, name: string, value: string) {
|
|
30
|
+
if (!(this as any).__tv_reqHeaders) (this as any).__tv_reqHeaders = {} as Record<string, string>;
|
|
31
|
+
(this as any).__tv_reqHeaders[name] = value;
|
|
32
|
+
return _origSetHeader.apply(this, [name, value]);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const _origSend = proto.send;
|
|
36
|
+
proto.send = function(this: XMLHttpRequest, body?: Document | XMLHttpRequestBodyInit | null) {
|
|
37
|
+
const id = bridge.uid();
|
|
38
|
+
const method = (this as any).__tv_method ?? "GET";
|
|
39
|
+
const url = (this as any).__tv_url ?? "";
|
|
40
|
+
const reqHeaders = (this as any).__tv_reqHeaders as Record<string, string> | undefined;
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
|
|
43
|
+
let requestBody: string | undefined;
|
|
44
|
+
if (body != null) {
|
|
45
|
+
try {
|
|
46
|
+
requestBody =
|
|
47
|
+
typeof body === "string" ? bridge.truncate(body)
|
|
48
|
+
: body instanceof URLSearchParams ? bridge.truncate(body.toString())
|
|
49
|
+
: "[binary]";
|
|
50
|
+
} catch { /* ignore */ }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
bridge.send({ id, type: "network", transport: "xhr", method, url,
|
|
54
|
+
requestBody, requestHeaders: reqHeaders, state: "pending", timestamp: start });
|
|
55
|
+
|
|
56
|
+
this.addEventListener("load", () => {
|
|
57
|
+
let responseBody: string | undefined;
|
|
58
|
+
try { responseBody = bridge.truncate(this.responseText); } catch { /* binary */ }
|
|
59
|
+
|
|
60
|
+
let responseHeaders: Record<string, string> | undefined;
|
|
61
|
+
try {
|
|
62
|
+
const raw = this.getAllResponseHeaders();
|
|
63
|
+
if (raw) {
|
|
64
|
+
responseHeaders = {};
|
|
65
|
+
raw.trim().split(/\r?\n/).forEach(line => {
|
|
66
|
+
const idx = line.indexOf(": ");
|
|
67
|
+
if (idx > 0) responseHeaders![line.slice(0, idx).toLowerCase()] = line.slice(idx + 2);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
} catch { /* ignore */ }
|
|
71
|
+
|
|
72
|
+
bridge.send({ id, type: "network", transport: "xhr", method, url,
|
|
73
|
+
status: this.status, requestBody, requestHeaders: reqHeaders,
|
|
74
|
+
responseBody, responseHeaders,
|
|
75
|
+
state: this.status >= 200 && this.status < 400 ? "success" : "error",
|
|
76
|
+
duration: Date.now() - start, timestamp: start });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
this.addEventListener("error", () => {
|
|
80
|
+
bridge.send({ id, type: "network", transport: "xhr", method, url,
|
|
81
|
+
requestBody, requestHeaders: reqHeaders,
|
|
82
|
+
state: "error", duration: Date.now() - start, timestamp: start });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return _origSend.apply(this, [body]);
|
|
86
|
+
};
|
|
87
|
+
} catch { /* not available */ }
|
|
88
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codetraxis agent — shared bridge.
|
|
3
|
+
* __PORT__ is replaced at install time with the actual server port.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type TreeViewerEvent = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
export interface TreeViewerBridge {
|
|
9
|
+
send: (event: TreeViewerEvent) => void;
|
|
10
|
+
uid: () => string;
|
|
11
|
+
safeSerialize: (value: unknown, depth?: number) => unknown;
|
|
12
|
+
truncate: (value: string, max?: number) => string;
|
|
13
|
+
isWeb: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createTreeViewerBridge(serverPort: string): TreeViewerBridge {
|
|
17
|
+
const g = globalThis as Record<string, any>;
|
|
18
|
+
|
|
19
|
+
const isWeb: boolean =
|
|
20
|
+
typeof g.document !== "undefined" &&
|
|
21
|
+
typeof g.addEventListener === "function";
|
|
22
|
+
|
|
23
|
+
let ws: WebSocket | null = null;
|
|
24
|
+
let wsReady = false;
|
|
25
|
+
const queue: string[] = [];
|
|
26
|
+
|
|
27
|
+
const getHost = (): string => {
|
|
28
|
+
if (typeof g.__TREE_VIEWER_HOST__ === "string" && g.__TREE_VIEWER_HOST__) {
|
|
29
|
+
return g.__TREE_VIEWER_HOST__ as string;
|
|
30
|
+
}
|
|
31
|
+
if (isWeb && g.location?.hostname) return g.location.hostname as string;
|
|
32
|
+
return "localhost";
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const connect = () => {
|
|
36
|
+
try {
|
|
37
|
+
const WS =
|
|
38
|
+
typeof WebSocket !== "undefined"
|
|
39
|
+
? WebSocket
|
|
40
|
+
: (g.WebSocket as typeof WebSocket | undefined);
|
|
41
|
+
if (!WS) return;
|
|
42
|
+
|
|
43
|
+
ws = new WS(`ws://${getHost()}:${serverPort}/agent`);
|
|
44
|
+
ws.onopen = () => {
|
|
45
|
+
wsReady = true;
|
|
46
|
+
while (queue.length > 0) {
|
|
47
|
+
const item = queue.shift();
|
|
48
|
+
if (item && ws) ws.send(item);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
ws.onclose = () => { wsReady = false; setTimeout(connect, 3000); };
|
|
52
|
+
ws.onerror = () => { wsReady = false; };
|
|
53
|
+
} catch { /* unavailable */ }
|
|
54
|
+
};
|
|
55
|
+
connect();
|
|
56
|
+
|
|
57
|
+
const truncate = (value: string, max = 500000): string =>
|
|
58
|
+
value.length > max ? `${value.slice(0, max)}…[truncated]` : value;
|
|
59
|
+
|
|
60
|
+
const safeSerialize = (value: unknown, depth = 0): unknown => {
|
|
61
|
+
if (depth > 4) return "[depth limit]";
|
|
62
|
+
if (value == null) return value;
|
|
63
|
+
if (typeof value === "function") return `[Function: ${(value as Function).name || "anonymous"}]`;
|
|
64
|
+
if (typeof value === "symbol") return value.toString();
|
|
65
|
+
if (typeof value !== "object") return value;
|
|
66
|
+
if (value instanceof Error) return { __error: true, name: value.name, message: value.message, stack: value.stack };
|
|
67
|
+
if (Array.isArray(value)) return value.slice(0, 1000).map(i => safeSerialize(i, depth + 1));
|
|
68
|
+
|
|
69
|
+
const seen = new WeakSet<object>();
|
|
70
|
+
const walk = (obj: object, d: number): Record<string, unknown> | string => {
|
|
71
|
+
if (seen.has(obj)) return "[circular]";
|
|
72
|
+
seen.add(obj);
|
|
73
|
+
const r: Record<string, unknown> = {};
|
|
74
|
+
let n = 0;
|
|
75
|
+
for (const k in obj as Record<string, unknown>) {
|
|
76
|
+
if (n++ > 500) { r["..."] = "[truncated]"; break; }
|
|
77
|
+
try { r[k] = safeSerialize((obj as Record<string, unknown>)[k], d + 1); }
|
|
78
|
+
catch { r[k] = "[unserializable]"; }
|
|
79
|
+
}
|
|
80
|
+
return r;
|
|
81
|
+
};
|
|
82
|
+
return walk(value as object, depth);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const uid = () =>
|
|
86
|
+
`${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
87
|
+
|
|
88
|
+
const send = (event: TreeViewerEvent) => {
|
|
89
|
+
try {
|
|
90
|
+
const payload = JSON.stringify(event);
|
|
91
|
+
if (wsReady && ws) { ws.send(payload); }
|
|
92
|
+
else { queue.push(payload); if (queue.length > 200) queue.shift(); }
|
|
93
|
+
} catch { /* ignore */ }
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return { send, uid, safeSerialize, truncate, isWeb };
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codetraxis",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "React & React Native debug tool with network inspector, console timeline and live file explorer",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react debug tool",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"bin",
|
|
32
32
|
"server/dist",
|
|
33
|
-
"client/dist"
|
|
33
|
+
"client/dist",
|
|
34
|
+
"codetraxisAgent"
|
|
34
35
|
],
|
|
35
36
|
"scripts": {
|
|
36
37
|
"dev": "concurrently \"npm run dev --prefix client\" \"npm run dev --prefix server\"",
|