codetraxis 1.0.0 → 1.0.1
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.
|
@@ -82,10 +82,11 @@ async function detectProjectKind(targetDir) {
|
|
|
82
82
|
return "expo";
|
|
83
83
|
if (deps["react-native"] && !deps["expo"])
|
|
84
84
|
return "react-native";
|
|
85
|
-
|
|
86
|
-
return "vite-react";
|
|
85
|
+
// react-scripts takes priority over vite (project may have both)
|
|
87
86
|
if (deps["react-scripts"])
|
|
88
87
|
return "cra-react";
|
|
88
|
+
if (deps["vite"] || deps["@vitejs/plugin-react"] || deps["@vitejs/plugin-react-swc"])
|
|
89
|
+
return "vite-react";
|
|
89
90
|
if (deps["webpack"] || deps["@webpack-cli/generators"])
|
|
90
91
|
return "webpack-react";
|
|
91
92
|
if (await fileExists(node_path_1.default.join(targetDir, "index.html")) && !deps["react"])
|
|
@@ -148,6 +149,7 @@ async function scanDirForPattern(dir, patterns, maxDepth = 3, _depth = 0) {
|
|
|
148
149
|
// Step 3 — per-framework adapters
|
|
149
150
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
150
151
|
async function resolveVitePlan(targetDir) {
|
|
152
|
+
// 1. Try to find <script type="module" src="..."> in index.html
|
|
151
153
|
for (const htmlPath of ["index.html", "public/index.html"]) {
|
|
152
154
|
const htmlFile = node_path_1.default.join(targetDir, htmlPath);
|
|
153
155
|
if (!(await fileExists(htmlFile)))
|
|
@@ -162,15 +164,24 @@ async function resolveVitePlan(targetDir) {
|
|
|
162
164
|
reason: `Vite: index.html <script type="module" src="${match[1]}">` };
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
|
-
// index.html exists but no module script → HTML patch
|
|
166
|
-
return { strategy: "patch-html", htmlFile, scriptSrc: "/codetraxisAgent.js",
|
|
167
|
-
confidence: 0.6, reason: "Vite: index.html found, no module entry detected" };
|
|
168
167
|
}
|
|
169
|
-
//
|
|
168
|
+
// 2. Semantic scan — look for createRoot / ReactDOM.render in JS/JSX/TS/TSX
|
|
170
169
|
const found = await scanDirForPattern(targetDir, WEB_BOOTSTRAP_PATTERNS);
|
|
171
170
|
if (found)
|
|
172
171
|
return { strategy: "inject-import", targetFile: found.file, confidence: found.confidence,
|
|
173
|
-
reason: `Vite
|
|
172
|
+
reason: `Vite semantic: ${found.reason}` };
|
|
173
|
+
// 3. Well-known candidate filenames
|
|
174
|
+
const found2 = await resolveCandidateFallback(targetDir);
|
|
175
|
+
if (found2.strategy === "inject-import")
|
|
176
|
+
return { ...found2, reason: `Vite candidate: ${found2.reason}` };
|
|
177
|
+
// 4. Last resort — patch index.html if it exists
|
|
178
|
+
for (const htmlPath of ["index.html", "public/index.html"]) {
|
|
179
|
+
const htmlFile = node_path_1.default.join(targetDir, htmlPath);
|
|
180
|
+
if (await fileExists(htmlFile)) {
|
|
181
|
+
return { strategy: "patch-html", htmlFile, scriptSrc: "/codetraxisAgent.js",
|
|
182
|
+
confidence: 0.5, reason: "Vite: index.html found, no JS entry detected" };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
174
185
|
return { strategy: "manual", reason: "Vite project: no entry file found" };
|
|
175
186
|
}
|
|
176
187
|
async function resolveNextjsPlan(targetDir) {
|
|
@@ -414,9 +425,25 @@ async function installAgent(targetDir) {
|
|
|
414
425
|
return { success: false, alreadyInstalled: false, projectKind: kind, strategy: "manual", error: plan.reason };
|
|
415
426
|
}
|
|
416
427
|
if (plan.strategy === "patch-html") {
|
|
428
|
+
const publicDir = node_path_1.default.dirname(plan.htmlFile);
|
|
429
|
+
const bundleFile = node_path_1.default.join(publicDir, "codetraxisAgent.js");
|
|
430
|
+
const port = process.env.PORT ?? "3333";
|
|
431
|
+
// Check if already installed
|
|
432
|
+
const htmlSource = await promises_1.default.readFile(plan.htmlFile, "utf-8");
|
|
433
|
+
const alreadyInstalled = htmlSource.includes("codetraxisAgent.js");
|
|
434
|
+
// Always overwrite the bundle so it stays up-to-date
|
|
435
|
+
await promises_1.default.writeFile(bundleFile, buildHtmlAgentBundle(port), "utf-8");
|
|
436
|
+
// Patch index.html only if not already patched
|
|
437
|
+
if (!alreadyInstalled) {
|
|
438
|
+
await promises_1.default.writeFile(plan.htmlFile, patchHtmlFile(htmlSource, plan.scriptSrc), "utf-8");
|
|
439
|
+
}
|
|
417
440
|
return {
|
|
418
|
-
success:
|
|
419
|
-
|
|
441
|
+
success: true,
|
|
442
|
+
alreadyInstalled,
|
|
443
|
+
entryFile: plan.htmlFile,
|
|
444
|
+
agentFile: bundleFile,
|
|
445
|
+
projectKind: kind,
|
|
446
|
+
strategy: "patch-html",
|
|
420
447
|
};
|
|
421
448
|
}
|
|
422
449
|
// strategy === "inject-import"
|
|
@@ -428,7 +455,12 @@ async function installAgent(targetDir) {
|
|
|
428
455
|
// expo-router: entry is in app/ — put agent at project root instead
|
|
429
456
|
? targetDir
|
|
430
457
|
: node_path_1.default.dirname(targetFile);
|
|
431
|
-
|
|
458
|
+
// Use .js extension for plain JS projects (webpack / CRA without TS)
|
|
459
|
+
// so webpack can resolve ./codetraxisAgent without a TypeScript loader.
|
|
460
|
+
const entryExt = node_path_1.default.extname(targetFile); // .js | .jsx | .ts | .tsx
|
|
461
|
+
const useJs = entryExt === ".js" || entryExt === ".jsx";
|
|
462
|
+
const agentIndexExt = useJs ? "js" : "ts";
|
|
463
|
+
const agentFile = node_path_1.default.join(agentDir, "codetraxisAgent", `index.${agentIndexExt}`);
|
|
432
464
|
// Import path from the entry file to the agent folder's index
|
|
433
465
|
let agentImportPath = AGENT_IMPORT_MARKER; // default: "./codetraxisAgent"
|
|
434
466
|
if (agentDir !== node_path_1.default.dirname(targetFile)) {
|
|
@@ -439,7 +471,7 @@ async function installAgent(targetDir) {
|
|
|
439
471
|
const entrySource = await promises_1.default.readFile(targetFile, "utf-8");
|
|
440
472
|
const alreadyInstalled = entrySource.includes("codetraxisAgent");
|
|
441
473
|
// Always overwrite the agent folder so the latest version is always present.
|
|
442
|
-
await writeAgentFiles(agentDir, process.env.PORT ?? "3333", kind);
|
|
474
|
+
await writeAgentFiles(agentDir, process.env.PORT ?? "3333", kind, useJs);
|
|
443
475
|
// Only inject the import if it's not there yet.
|
|
444
476
|
if (!alreadyInstalled) {
|
|
445
477
|
await promises_1.default.writeFile(targetFile, injectImportAst(entrySource, agentImportPath), "utf-8");
|
|
@@ -455,11 +487,36 @@ async function installAgent(targetDir) {
|
|
|
455
487
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
456
488
|
async function findEntryFile(targetDir) {
|
|
457
489
|
const { plan } = await buildInstallPlan(targetDir);
|
|
458
|
-
|
|
490
|
+
if (plan.strategy === "inject-import")
|
|
491
|
+
return plan.targetFile;
|
|
492
|
+
if (plan.strategy === "patch-html")
|
|
493
|
+
return plan.htmlFile;
|
|
494
|
+
return null;
|
|
459
495
|
}
|
|
460
496
|
async function removeAgent(targetDir) {
|
|
461
497
|
try {
|
|
462
|
-
const
|
|
498
|
+
const { plan } = await buildInstallPlan(targetDir);
|
|
499
|
+
// ── patch-html case ───────────────────────────────────────────────────────
|
|
500
|
+
if (plan.strategy === "patch-html") {
|
|
501
|
+
const publicDir = node_path_1.default.dirname(plan.htmlFile);
|
|
502
|
+
const bundleFile = node_path_1.default.join(publicDir, "codetraxisAgent.js");
|
|
503
|
+
// Remove <script> tag from index.html
|
|
504
|
+
const htmlSource = await promises_1.default.readFile(plan.htmlFile, "utf-8");
|
|
505
|
+
if (htmlSource.includes("codetraxisAgent.js")) {
|
|
506
|
+
const cleaned = htmlSource
|
|
507
|
+
.split("\n")
|
|
508
|
+
.filter(line => !line.includes("codetraxisAgent.js"))
|
|
509
|
+
.join("\n");
|
|
510
|
+
await promises_1.default.writeFile(plan.htmlFile, cleaned, "utf-8");
|
|
511
|
+
}
|
|
512
|
+
// Remove the bundle file
|
|
513
|
+
if (await fileExists(bundleFile)) {
|
|
514
|
+
await promises_1.default.unlink(bundleFile);
|
|
515
|
+
}
|
|
516
|
+
return { success: true, entryFile: plan.htmlFile, agentFile: bundleFile };
|
|
517
|
+
}
|
|
518
|
+
// ── inject-import case ────────────────────────────────────────────────────
|
|
519
|
+
const entryFile = plan.strategy === "inject-import" ? plan.targetFile : null;
|
|
463
520
|
if (!entryFile) {
|
|
464
521
|
return { success: false, error: "Entry file not found" };
|
|
465
522
|
}
|
|
@@ -507,6 +564,217 @@ async function removeAgent(targetDir) {
|
|
|
507
564
|
}
|
|
508
565
|
}
|
|
509
566
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
567
|
+
// HTML patching helpers (for CRA / plain-web projects with public/index.html)
|
|
568
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
569
|
+
/** Insert <script src="..."> just before </body> (or </head> as fallback). */
|
|
570
|
+
function patchHtmlFile(html, scriptSrc) {
|
|
571
|
+
const tag = `<script src="${scriptSrc}"></script>`;
|
|
572
|
+
if (html.includes(tag))
|
|
573
|
+
return html; // idempotent
|
|
574
|
+
if (/<\/body>/i.test(html)) {
|
|
575
|
+
return html.replace(/<\/body>/i, ` ${tag}\n</body>`);
|
|
576
|
+
}
|
|
577
|
+
if (/<\/head>/i.test(html)) {
|
|
578
|
+
return html.replace(/<\/head>/i, ` ${tag}\n</head>`);
|
|
579
|
+
}
|
|
580
|
+
// No recognisable tags — just append
|
|
581
|
+
return html + `\n${tag}\n`;
|
|
582
|
+
}
|
|
583
|
+
/** Build a self-contained IIFE bundle (no import/export) for injection via <script>. */
|
|
584
|
+
function buildHtmlAgentBundle(serverPort) {
|
|
585
|
+
// We inline shared + interceptors as one IIFE so it works in any plain HTML page
|
|
586
|
+
// without a bundler or module system.
|
|
587
|
+
return `/**
|
|
588
|
+
* codetraxis debug agent — standalone bundle (auto-generated, do not edit).
|
|
589
|
+
* Injected into public/index.html via <script src="/codetraxisAgent.js">.
|
|
590
|
+
*/
|
|
591
|
+
(function () {
|
|
592
|
+
'use strict';
|
|
593
|
+
|
|
594
|
+
// ─── bridge ────────────────────────────────────────────────────────────────
|
|
595
|
+
var PORT = '${serverPort}';
|
|
596
|
+
var WS_URL = 'ws://localhost:' + PORT + '/agent';
|
|
597
|
+
var MAX_LEN = 20000;
|
|
598
|
+
var _ws = null;
|
|
599
|
+
var _queue = [];
|
|
600
|
+
var _reconnectTimer = null;
|
|
601
|
+
|
|
602
|
+
function connect() {
|
|
603
|
+
try {
|
|
604
|
+
_ws = new WebSocket(WS_URL);
|
|
605
|
+
_ws.onopen = function () {
|
|
606
|
+
var q = _queue.splice(0);
|
|
607
|
+
for (var i = 0; i < q.length; i++) _ws.send(q[i]);
|
|
608
|
+
};
|
|
609
|
+
_ws.onclose = function () {
|
|
610
|
+
_ws = null;
|
|
611
|
+
if (!_reconnectTimer) {
|
|
612
|
+
_reconnectTimer = setTimeout(function () { _reconnectTimer = null; connect(); }, 3000);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
_ws.onerror = function () { /* suppress */ };
|
|
616
|
+
} catch (e) { /* WebSocket not available */ }
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
connect();
|
|
620
|
+
|
|
621
|
+
var _uid = 0;
|
|
622
|
+
function uid() { return 'h' + (++_uid) + '_' + Date.now(); }
|
|
623
|
+
|
|
624
|
+
function truncate(s) {
|
|
625
|
+
if (typeof s !== 'string') return s;
|
|
626
|
+
return s.length > MAX_LEN ? s.slice(0, MAX_LEN) + '…[truncated]' : s;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function safeSerialize(v) {
|
|
630
|
+
if (v === undefined || v === null) return undefined;
|
|
631
|
+
if (typeof v === 'string') return truncate(v);
|
|
632
|
+
try { return truncate(JSON.stringify(v)); } catch { return '[unserializable]'; }
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function send(msg) {
|
|
636
|
+
try {
|
|
637
|
+
var s = JSON.stringify(msg);
|
|
638
|
+
if (_ws && _ws.readyState === 1 /* OPEN */) {
|
|
639
|
+
_ws.send(s);
|
|
640
|
+
} else {
|
|
641
|
+
_queue.push(s);
|
|
642
|
+
}
|
|
643
|
+
} catch { /* ignore */ }
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
var bridge = { uid: uid, truncate: truncate, safeSerialize: safeSerialize, send: send };
|
|
647
|
+
|
|
648
|
+
// ─── console interceptor ───────────────────────────────────────────────────
|
|
649
|
+
var LEVELS = ['log', 'warn', 'error', 'info', 'debug'];
|
|
650
|
+
LEVELS.forEach(function (level) {
|
|
651
|
+
var orig = console[level].bind(console);
|
|
652
|
+
console[level] = function () {
|
|
653
|
+
orig.apply(console, arguments);
|
|
654
|
+
try {
|
|
655
|
+
var args = Array.prototype.slice.call(arguments);
|
|
656
|
+
bridge.send({
|
|
657
|
+
id: bridge.uid(), type: 'console', level: level, timestamp: Date.now(),
|
|
658
|
+
args: args.map(function (a) { return bridge.safeSerialize(a); }),
|
|
659
|
+
});
|
|
660
|
+
} catch { /* ignore */ }
|
|
661
|
+
};
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// ─── fetch interceptor ─────────────────────────────────────────────────────
|
|
665
|
+
if (typeof fetch !== 'undefined') {
|
|
666
|
+
var _origFetch = fetch;
|
|
667
|
+
fetch = function (input, init) {
|
|
668
|
+
var id = bridge.uid();
|
|
669
|
+
var start = Date.now();
|
|
670
|
+
var method = (init && init.method ? init.method : 'GET').toUpperCase();
|
|
671
|
+
var url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : (input.url || String(input));
|
|
672
|
+
var reqBody = init && init.body != null ? bridge.truncate(String(init.body)) : undefined;
|
|
673
|
+
var reqHeaders;
|
|
674
|
+
try {
|
|
675
|
+
if (init && init.headers) {
|
|
676
|
+
reqHeaders = {};
|
|
677
|
+
var h = new Headers(init.headers);
|
|
678
|
+
h.forEach(function (v, k) { reqHeaders[k] = v; });
|
|
679
|
+
}
|
|
680
|
+
} catch { /* ignore */ }
|
|
681
|
+
|
|
682
|
+
bridge.send({ id: id, type: 'network', transport: 'fetch', method: method, url: url,
|
|
683
|
+
requestBody: reqBody, requestHeaders: reqHeaders, state: 'pending', timestamp: start });
|
|
684
|
+
|
|
685
|
+
return _origFetch.call(this, input, init).then(function (response) {
|
|
686
|
+
var cloned = response.clone();
|
|
687
|
+
var resHeaders = {};
|
|
688
|
+
try { response.headers.forEach(function (v, k) { resHeaders[k] = v; }); } catch { /* ignore */ }
|
|
689
|
+
cloned.text().then(function (body) {
|
|
690
|
+
bridge.send({ id: id, type: 'network', transport: 'fetch', method: method, url: url,
|
|
691
|
+
status: response.status, requestBody: reqBody, requestHeaders: reqHeaders,
|
|
692
|
+
responseBody: bridge.truncate(body), responseHeaders: resHeaders,
|
|
693
|
+
state: response.ok ? 'success' : 'error',
|
|
694
|
+
duration: Date.now() - start, timestamp: start });
|
|
695
|
+
}).catch(function () {
|
|
696
|
+
bridge.send({ id: id, type: 'network', transport: 'fetch', method: method, url: url,
|
|
697
|
+
status: response.status, requestBody: reqBody, requestHeaders: reqHeaders,
|
|
698
|
+
responseHeaders: resHeaders,
|
|
699
|
+
state: response.ok ? 'success' : 'error',
|
|
700
|
+
duration: Date.now() - start, timestamp: start });
|
|
701
|
+
});
|
|
702
|
+
return response;
|
|
703
|
+
}).catch(function (err) {
|
|
704
|
+
bridge.send({ id: id, type: 'network', transport: 'fetch', method: method, url: url,
|
|
705
|
+
requestBody: reqBody, requestHeaders: reqHeaders,
|
|
706
|
+
state: 'error', duration: Date.now() - start, timestamp: start });
|
|
707
|
+
throw err;
|
|
708
|
+
});
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// ─── XHR interceptor ──────────────────────────────────────────────────────
|
|
713
|
+
try {
|
|
714
|
+
var proto = XMLHttpRequest.prototype;
|
|
715
|
+
var _origOpen = proto.open;
|
|
716
|
+
proto.open = function (method, url) {
|
|
717
|
+
this.__tv_method = method.toUpperCase();
|
|
718
|
+
this.__tv_url = String(url);
|
|
719
|
+
return _origOpen.apply(this, arguments);
|
|
720
|
+
};
|
|
721
|
+
var _origSetHeader = proto.setRequestHeader;
|
|
722
|
+
proto.setRequestHeader = function (name, value) {
|
|
723
|
+
if (!this.__tv_reqHeaders) this.__tv_reqHeaders = {};
|
|
724
|
+
this.__tv_reqHeaders[name] = value;
|
|
725
|
+
return _origSetHeader.apply(this, arguments);
|
|
726
|
+
};
|
|
727
|
+
var _origSend = proto.send;
|
|
728
|
+
proto.send = function (body) {
|
|
729
|
+
var self = this;
|
|
730
|
+
var id = bridge.uid();
|
|
731
|
+
var method = this.__tv_method || 'GET';
|
|
732
|
+
var url = this.__tv_url || '';
|
|
733
|
+
var reqHeaders = this.__tv_reqHeaders;
|
|
734
|
+
var start = Date.now();
|
|
735
|
+
var reqBody;
|
|
736
|
+
if (body != null) {
|
|
737
|
+
try {
|
|
738
|
+
reqBody = typeof body === 'string' ? bridge.truncate(body)
|
|
739
|
+
: body instanceof URLSearchParams ? bridge.truncate(body.toString())
|
|
740
|
+
: '[binary]';
|
|
741
|
+
} catch { /* ignore */ }
|
|
742
|
+
}
|
|
743
|
+
bridge.send({ id: id, type: 'network', transport: 'xhr', method: method, url: url,
|
|
744
|
+
requestBody: reqBody, requestHeaders: reqHeaders, state: 'pending', timestamp: start });
|
|
745
|
+
|
|
746
|
+
this.addEventListener('load', function () {
|
|
747
|
+
var resBody, resHeaders;
|
|
748
|
+
try { resBody = bridge.truncate(self.responseText); } catch { /* binary */ }
|
|
749
|
+
try {
|
|
750
|
+
var raw = self.getAllResponseHeaders();
|
|
751
|
+
if (raw) {
|
|
752
|
+
resHeaders = {};
|
|
753
|
+
raw.trim().split(/\r?\n/).forEach(function (line) {
|
|
754
|
+
var idx = line.indexOf(': ');
|
|
755
|
+
if (idx > 0) resHeaders[line.slice(0, idx).toLowerCase()] = line.slice(idx + 2);
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
} catch { /* ignore */ }
|
|
759
|
+
bridge.send({ id: id, type: 'network', transport: 'xhr', method: method, url: url,
|
|
760
|
+
status: self.status, requestBody: reqBody, requestHeaders: reqHeaders,
|
|
761
|
+
responseBody: resBody, responseHeaders: resHeaders,
|
|
762
|
+
state: self.status >= 200 && self.status < 400 ? 'success' : 'error',
|
|
763
|
+
duration: Date.now() - start, timestamp: start });
|
|
764
|
+
});
|
|
765
|
+
this.addEventListener('error', function () {
|
|
766
|
+
bridge.send({ id: id, type: 'network', transport: 'xhr', method: method, url: url,
|
|
767
|
+
requestBody: reqBody, requestHeaders: reqHeaders,
|
|
768
|
+
state: 'error', duration: Date.now() - start, timestamp: start });
|
|
769
|
+
});
|
|
770
|
+
return _origSend.apply(this, arguments);
|
|
771
|
+
};
|
|
772
|
+
} catch (e) { /* XHR not available */ }
|
|
773
|
+
|
|
774
|
+
})();
|
|
775
|
+
`;
|
|
776
|
+
}
|
|
777
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
510
778
|
// Agent source — modular architecture
|
|
511
779
|
//
|
|
512
780
|
// Generates a codetraxisAgent/ folder with:
|
|
@@ -516,18 +784,131 @@ async function removeAgent(targetDir) {
|
|
|
516
784
|
// interceptors/xhrInterceptor.ts — for React Native & raw XHR users
|
|
517
785
|
// index.ts — wires everything together
|
|
518
786
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
519
|
-
async function writeAgentFiles(agentParentDir, serverPort, kind) {
|
|
787
|
+
async function writeAgentFiles(agentParentDir, serverPort, kind, useJs = false) {
|
|
520
788
|
const agentDir = node_path_1.default.join(agentParentDir, "codetraxisAgent");
|
|
521
789
|
const interceptorsDir = node_path_1.default.join(agentDir, "interceptors");
|
|
790
|
+
const ext = useJs ? "js" : "ts";
|
|
791
|
+
// Always wipe the old agent folder first so stale .ts/.js files don't coexist
|
|
792
|
+
await promises_1.default.rm(agentDir, { recursive: true, force: true });
|
|
522
793
|
await promises_1.default.mkdir(interceptorsDir, { recursive: true });
|
|
523
|
-
await promises_1.default.writeFile(node_path_1.default.join(agentDir,
|
|
524
|
-
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir,
|
|
525
|
-
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir,
|
|
526
|
-
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir,
|
|
527
|
-
await promises_1.default.writeFile(node_path_1.default.join(agentDir,
|
|
794
|
+
await promises_1.default.writeFile(node_path_1.default.join(agentDir, `shared.${ext}`), buildSharedSource(serverPort, useJs), "utf-8");
|
|
795
|
+
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir, `consoleInterceptor.${ext}`), buildConsoleInterceptorSource(useJs), "utf-8");
|
|
796
|
+
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir, `fetchInterceptor.${ext}`), buildFetchInterceptorSource(useJs), "utf-8");
|
|
797
|
+
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir, `xhrInterceptor.${ext}`), buildXhrInterceptorSource(useJs), "utf-8");
|
|
798
|
+
await promises_1.default.writeFile(node_path_1.default.join(agentDir, `index.${ext}`), buildAgentIndexSource(kind, serverPort, useJs), "utf-8");
|
|
799
|
+
}
|
|
800
|
+
// ─── Strip TypeScript annotations for plain JS projects ──────────────────────
|
|
801
|
+
// Removes: import type ..., : Type, <Type>, as Type, interface ..., export type ...
|
|
802
|
+
// Keeps the runtime logic intact so webpack/babel can process it without ts-loader.
|
|
803
|
+
function stripTypescript(src) {
|
|
804
|
+
return src
|
|
805
|
+
// remove `import type { ... } from "..."` lines
|
|
806
|
+
.replace(/^import type\s+\{[^}]*\}\s+from\s+['"][^'"]+['"];?\s*$/gm, "")
|
|
807
|
+
// remove `export type ...` and `export interface ...` blocks (single or multi-line)
|
|
808
|
+
.replace(/^export\s+(?:type|interface)\s+\w+[^{]*\{[^}]*\};?\s*$/gm, "")
|
|
809
|
+
.replace(/^export\s+(?:type|interface)\s+[^=\n]+=[^;\n]+;?\s*$/gm, "")
|
|
810
|
+
// remove `interface Foo { ... }` blocks
|
|
811
|
+
.replace(/^interface\s+\w+[^{]*\{[\s\S]*?\}\s*$/gm, "")
|
|
812
|
+
// remove inline type annotations: `: string`, `: boolean`, `: number`, `: void`, `: unknown`, `: any`, `: never`
|
|
813
|
+
.replace(/:\s*(string|number|boolean|void|unknown|any|never|null|undefined)\b/g, "")
|
|
814
|
+
// remove `: SomeInterface` / `: Record<...>` / `: Array<...>` etc. (complex types)
|
|
815
|
+
.replace(/:\s*[A-Z][A-Za-z0-9_]*(?:<[^>]*>)?/g, "")
|
|
816
|
+
// remove `as SomeType` casts
|
|
817
|
+
.replace(/\bas\s+[A-Za-z][A-Za-z0-9_<>, |&[\]]*(?=\s*[),;\]}])/g, "")
|
|
818
|
+
// remove `<Type>` generic parameters in function calls / declarations (simple)
|
|
819
|
+
.replace(/<[A-Z][A-Za-z0-9_, ]*>/g, "")
|
|
820
|
+
// clean up resulting double spaces / empty lines
|
|
821
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
822
|
+
.trimEnd() + "\n";
|
|
823
|
+
}
|
|
824
|
+
// ─── shared.ts / shared.js ───────────────────────────────────────────────────
|
|
825
|
+
function buildSharedSource(serverPort, useJs = false) {
|
|
826
|
+
if (useJs) {
|
|
827
|
+
return `/**
|
|
828
|
+
* codetraxis agent — shared bridge (auto-generated, do not edit).
|
|
829
|
+
*/
|
|
830
|
+
|
|
831
|
+
export function createTreeViewerBridge(serverPort) {
|
|
832
|
+
const g = globalThis;
|
|
833
|
+
|
|
834
|
+
const isWeb =
|
|
835
|
+
typeof g.document !== "undefined" &&
|
|
836
|
+
typeof g.addEventListener === "function";
|
|
837
|
+
|
|
838
|
+
let ws = null;
|
|
839
|
+
let wsReady = false;
|
|
840
|
+
const queue = [];
|
|
841
|
+
|
|
842
|
+
const getHost = () => {
|
|
843
|
+
if (typeof g.__TREE_VIEWER_HOST__ === "string" && g.__TREE_VIEWER_HOST__) {
|
|
844
|
+
return g.__TREE_VIEWER_HOST__;
|
|
845
|
+
}
|
|
846
|
+
if (isWeb && g.location?.hostname) return g.location.hostname;
|
|
847
|
+
return "localhost";
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const connect = () => {
|
|
851
|
+
try {
|
|
852
|
+
const WS = typeof WebSocket !== "undefined" ? WebSocket : g.WebSocket;
|
|
853
|
+
if (!WS) return;
|
|
854
|
+
|
|
855
|
+
ws = new WS(\`ws://\${getHost()}:${serverPort}/agent\`);
|
|
856
|
+
ws.onopen = () => {
|
|
857
|
+
wsReady = true;
|
|
858
|
+
while (queue.length > 0) {
|
|
859
|
+
const item = queue.shift();
|
|
860
|
+
if (item && ws) ws.send(item);
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
ws.onclose = () => { wsReady = false; setTimeout(connect, 3000); };
|
|
864
|
+
ws.onerror = () => { wsReady = false; };
|
|
865
|
+
} catch { /* unavailable */ }
|
|
866
|
+
};
|
|
867
|
+
connect();
|
|
868
|
+
|
|
869
|
+
const truncate = (value, max = 500000) =>
|
|
870
|
+
value.length > max ? \`\${value.slice(0, max)}…[truncated]\` : value;
|
|
871
|
+
|
|
872
|
+
const safeSerialize = (value, depth = 0) => {
|
|
873
|
+
if (depth > 4) return "[depth limit]";
|
|
874
|
+
if (value == null) return value;
|
|
875
|
+
if (typeof value === "function") return \`[Function: \${value.name || "anonymous"}]\`;
|
|
876
|
+
if (typeof value === "symbol") return value.toString();
|
|
877
|
+
if (typeof value !== "object") return value;
|
|
878
|
+
if (value instanceof Error) return { __error: true, name: value.name, message: value.message, stack: value.stack };
|
|
879
|
+
if (Array.isArray(value)) return value.slice(0, 1000).map(i => safeSerialize(i, depth + 1));
|
|
880
|
+
|
|
881
|
+
const seen = new WeakSet();
|
|
882
|
+
const walk = (obj, d) => {
|
|
883
|
+
if (seen.has(obj)) return "[circular]";
|
|
884
|
+
seen.add(obj);
|
|
885
|
+
const r = {};
|
|
886
|
+
let n = 0;
|
|
887
|
+
for (const k in obj) {
|
|
888
|
+
if (n++ > 500) { r["..."] = "[truncated]"; break; }
|
|
889
|
+
try { r[k] = safeSerialize(obj[k], d + 1); }
|
|
890
|
+
catch { r[k] = "[unserializable]"; }
|
|
891
|
+
}
|
|
892
|
+
return r;
|
|
893
|
+
};
|
|
894
|
+
return walk(value, depth);
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
const uid = () =>
|
|
898
|
+
\`\${Math.random().toString(36).slice(2)}\${Date.now().toString(36)}\`;
|
|
899
|
+
|
|
900
|
+
const send = (event) => {
|
|
901
|
+
try {
|
|
902
|
+
const payload = JSON.stringify(event);
|
|
903
|
+
if (wsReady && ws) { ws.send(payload); }
|
|
904
|
+
else { queue.push(payload); if (queue.length > 200) queue.shift(); }
|
|
905
|
+
} catch { /* ignore */ }
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
return { send, uid, safeSerialize, truncate, isWeb };
|
|
528
909
|
}
|
|
529
|
-
|
|
530
|
-
|
|
910
|
+
`;
|
|
911
|
+
}
|
|
531
912
|
return `/**
|
|
532
913
|
* codetraxis agent — shared bridge (auto-generated, do not edit).
|
|
533
914
|
*/
|
|
@@ -626,8 +1007,42 @@ export function createTreeViewerBridge(serverPort: string): TreeViewerBridge {
|
|
|
626
1007
|
}
|
|
627
1008
|
`;
|
|
628
1009
|
}
|
|
629
|
-
// ─── consoleInterceptor.ts
|
|
630
|
-
function buildConsoleInterceptorSource() {
|
|
1010
|
+
// ─── consoleInterceptor.ts / .js ─────────────────────────────────────────────
|
|
1011
|
+
function buildConsoleInterceptorSource(useJs = false) {
|
|
1012
|
+
if (useJs) {
|
|
1013
|
+
return `/**
|
|
1014
|
+
* codetraxis agent — console interceptor (auto-generated, do not edit).
|
|
1015
|
+
*/
|
|
1016
|
+
|
|
1017
|
+
const INSTALLED_KEY = "__tv_console_installed__";
|
|
1018
|
+
|
|
1019
|
+
export function setupConsoleInterceptor(bridge) {
|
|
1020
|
+
const g = globalThis;
|
|
1021
|
+
if (g[INSTALLED_KEY]) return;
|
|
1022
|
+
g[INSTALLED_KEY] = true;
|
|
1023
|
+
|
|
1024
|
+
const original = {
|
|
1025
|
+
log: console.log.bind(console),
|
|
1026
|
+
info: console.info.bind(console),
|
|
1027
|
+
warn: console.warn.bind(console),
|
|
1028
|
+
error: console.error.bind(console),
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
["log", "info", "warn", "error"].forEach(level => {
|
|
1032
|
+
console[level] = (...args) => {
|
|
1033
|
+
original[level](...args);
|
|
1034
|
+
bridge.send({
|
|
1035
|
+
id: bridge.uid(),
|
|
1036
|
+
type: "console",
|
|
1037
|
+
level,
|
|
1038
|
+
args: args.map(a => bridge.safeSerialize(a)),
|
|
1039
|
+
timestamp: Date.now(),
|
|
1040
|
+
});
|
|
1041
|
+
};
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
`;
|
|
1045
|
+
}
|
|
631
1046
|
return `/**
|
|
632
1047
|
* codetraxis agent — console interceptor (auto-generated, do not edit).
|
|
633
1048
|
*/
|
|
@@ -663,8 +1078,100 @@ export function setupConsoleInterceptor(bridge: TreeViewerBridge): void {
|
|
|
663
1078
|
}
|
|
664
1079
|
`;
|
|
665
1080
|
}
|
|
666
|
-
// ─── fetchInterceptor.ts
|
|
667
|
-
function buildFetchInterceptorSource() {
|
|
1081
|
+
// ─── fetchInterceptor.ts / .js ───────────────────────────────────────────────
|
|
1082
|
+
function buildFetchInterceptorSource(useJs = false) {
|
|
1083
|
+
if (useJs) {
|
|
1084
|
+
return `/**
|
|
1085
|
+
* codetraxis agent — fetch interceptor (auto-generated, do not edit).
|
|
1086
|
+
*/
|
|
1087
|
+
|
|
1088
|
+
const INSTALLED_KEY = "__tv_fetch_installed__";
|
|
1089
|
+
|
|
1090
|
+
function normalizeHeaders(headers) {
|
|
1091
|
+
if (!headers) return undefined;
|
|
1092
|
+
const result = {};
|
|
1093
|
+
try {
|
|
1094
|
+
if (headers instanceof Headers) {
|
|
1095
|
+
headers.forEach((v, k) => { result[k] = v; });
|
|
1096
|
+
} else if (Array.isArray(headers)) {
|
|
1097
|
+
headers.forEach(([k, v]) => { result[k] = v; });
|
|
1098
|
+
} else {
|
|
1099
|
+
Object.assign(result, headers);
|
|
1100
|
+
}
|
|
1101
|
+
return result;
|
|
1102
|
+
} catch { return undefined; }
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
export function setupFetchInterceptor(bridge) {
|
|
1106
|
+
const g = globalThis;
|
|
1107
|
+
if (g[INSTALLED_KEY]) return;
|
|
1108
|
+
g[INSTALLED_KEY] = true;
|
|
1109
|
+
|
|
1110
|
+
const originalFetch =
|
|
1111
|
+
typeof globalThis.fetch === "function"
|
|
1112
|
+
? globalThis.fetch.bind(globalThis)
|
|
1113
|
+
: null;
|
|
1114
|
+
if (!originalFetch) return;
|
|
1115
|
+
|
|
1116
|
+
globalThis.fetch = async function tvFetch(input, init) {
|
|
1117
|
+
const url =
|
|
1118
|
+
typeof input === "string" ? input
|
|
1119
|
+
: input instanceof URL ? input.href
|
|
1120
|
+
: input.url;
|
|
1121
|
+
|
|
1122
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
1123
|
+
const id = bridge.uid();
|
|
1124
|
+
const start = Date.now();
|
|
1125
|
+
|
|
1126
|
+
const requestHeaders = normalizeHeaders(
|
|
1127
|
+
init?.headers ?? (input instanceof Request ? input.headers : undefined),
|
|
1128
|
+
);
|
|
1129
|
+
|
|
1130
|
+
let requestBody;
|
|
1131
|
+
const rawBody = init?.body;
|
|
1132
|
+
if (rawBody != null) {
|
|
1133
|
+
try {
|
|
1134
|
+
requestBody =
|
|
1135
|
+
typeof rawBody === "string" ? bridge.truncate(rawBody)
|
|
1136
|
+
: rawBody instanceof URLSearchParams ? bridge.truncate(rawBody.toString())
|
|
1137
|
+
: "[binary]";
|
|
1138
|
+
} catch { requestBody = "[unserializable-body]"; }
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
bridge.send({ id, type: "network", transport: "fetch", method, url,
|
|
1142
|
+
requestHeaders, requestBody, state: "pending", timestamp: start });
|
|
1143
|
+
|
|
1144
|
+
try {
|
|
1145
|
+
const response = await originalFetch(input, init);
|
|
1146
|
+
|
|
1147
|
+
let responseHeaders;
|
|
1148
|
+
try {
|
|
1149
|
+
responseHeaders = {};
|
|
1150
|
+
response.headers.forEach((v, k) => { responseHeaders[k] = v; });
|
|
1151
|
+
} catch { /* ignore */ }
|
|
1152
|
+
|
|
1153
|
+
let responseBody;
|
|
1154
|
+
try { responseBody = bridge.truncate(await response.clone().text()); }
|
|
1155
|
+
catch { responseBody = "[binary]"; }
|
|
1156
|
+
|
|
1157
|
+
bridge.send({ id, type: "network", transport: "fetch", method, url,
|
|
1158
|
+
status: response.status, requestHeaders, requestBody,
|
|
1159
|
+
responseHeaders, responseBody,
|
|
1160
|
+
state: response.ok ? "success" : "error",
|
|
1161
|
+
duration: Date.now() - start, timestamp: start });
|
|
1162
|
+
|
|
1163
|
+
return response;
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
bridge.send({ id, type: "network", transport: "fetch", method, url,
|
|
1166
|
+
requestHeaders, requestBody, state: "error",
|
|
1167
|
+
duration: Date.now() - start, timestamp: start,
|
|
1168
|
+
error: bridge.safeSerialize(error) });
|
|
1169
|
+
throw error;
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
`;
|
|
1174
|
+
}
|
|
668
1175
|
return `/**
|
|
669
1176
|
* codetraxis agent — fetch interceptor (auto-generated, do not edit).
|
|
670
1177
|
*/
|
|
@@ -760,8 +1267,95 @@ export function setupFetchInterceptor(bridge: TreeViewerBridge): void {
|
|
|
760
1267
|
}
|
|
761
1268
|
`;
|
|
762
1269
|
}
|
|
763
|
-
// ─── xhrInterceptor.ts
|
|
764
|
-
function buildXhrInterceptorSource() {
|
|
1270
|
+
// ─── xhrInterceptor.ts / .js ─────────────────────────────────────────────────
|
|
1271
|
+
function buildXhrInterceptorSource(useJs = false) {
|
|
1272
|
+
if (useJs) {
|
|
1273
|
+
return `/**
|
|
1274
|
+
* codetraxis agent — XHR interceptor (auto-generated, do not edit).
|
|
1275
|
+
*/
|
|
1276
|
+
|
|
1277
|
+
const INSTALLED_KEY = "__tv_xhr_installed__";
|
|
1278
|
+
|
|
1279
|
+
export function setupXhrInterceptor(bridge) {
|
|
1280
|
+
if (typeof XMLHttpRequest === "undefined") return;
|
|
1281
|
+
const g = globalThis;
|
|
1282
|
+
if (g[INSTALLED_KEY]) return;
|
|
1283
|
+
g[INSTALLED_KEY] = true;
|
|
1284
|
+
|
|
1285
|
+
try {
|
|
1286
|
+
const proto = XMLHttpRequest.prototype;
|
|
1287
|
+
|
|
1288
|
+
const _origOpen = proto.open;
|
|
1289
|
+
proto.open = function(method, url, ...rest) {
|
|
1290
|
+
this.__tv_method = method.toUpperCase();
|
|
1291
|
+
this.__tv_url = String(url);
|
|
1292
|
+
this.__tv_reqHeaders = undefined;
|
|
1293
|
+
return _origOpen.apply(this, [method, url, ...rest]);
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
const _origSetHeader = proto.setRequestHeader;
|
|
1297
|
+
proto.setRequestHeader = function(name, value) {
|
|
1298
|
+
if (!this.__tv_reqHeaders) this.__tv_reqHeaders = {};
|
|
1299
|
+
this.__tv_reqHeaders[name] = value;
|
|
1300
|
+
return _origSetHeader.apply(this, [name, value]);
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
const _origSend = proto.send;
|
|
1304
|
+
proto.send = function(body) {
|
|
1305
|
+
const id = bridge.uid();
|
|
1306
|
+
const method = this.__tv_method ?? "GET";
|
|
1307
|
+
const url = this.__tv_url ?? "";
|
|
1308
|
+
const reqHeaders = this.__tv_reqHeaders;
|
|
1309
|
+
const start = Date.now();
|
|
1310
|
+
|
|
1311
|
+
let requestBody;
|
|
1312
|
+
if (body != null) {
|
|
1313
|
+
try {
|
|
1314
|
+
requestBody =
|
|
1315
|
+
typeof body === "string" ? bridge.truncate(body)
|
|
1316
|
+
: body instanceof URLSearchParams ? bridge.truncate(body.toString())
|
|
1317
|
+
: "[binary]";
|
|
1318
|
+
} catch { /* ignore */ }
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
bridge.send({ id, type: "network", transport: "xhr", method, url,
|
|
1322
|
+
requestBody, requestHeaders: reqHeaders, state: "pending", timestamp: start });
|
|
1323
|
+
|
|
1324
|
+
this.addEventListener("load", () => {
|
|
1325
|
+
let responseBody;
|
|
1326
|
+
try { responseBody = bridge.truncate(this.responseText); } catch { /* binary */ }
|
|
1327
|
+
|
|
1328
|
+
let responseHeaders;
|
|
1329
|
+
try {
|
|
1330
|
+
const raw = this.getAllResponseHeaders();
|
|
1331
|
+
if (raw) {
|
|
1332
|
+
responseHeaders = {};
|
|
1333
|
+
raw.trim().split(/\\r?\\n/).forEach(line => {
|
|
1334
|
+
const idx = line.indexOf(": ");
|
|
1335
|
+
if (idx > 0) responseHeaders[line.slice(0, idx).toLowerCase()] = line.slice(idx + 2);
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
} catch { /* ignore */ }
|
|
1339
|
+
|
|
1340
|
+
bridge.send({ id, type: "network", transport: "xhr", method, url,
|
|
1341
|
+
status: this.status, requestBody, requestHeaders: reqHeaders,
|
|
1342
|
+
responseBody, responseHeaders,
|
|
1343
|
+
state: this.status >= 200 && this.status < 400 ? "success" : "error",
|
|
1344
|
+
duration: Date.now() - start, timestamp: start });
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
this.addEventListener("error", () => {
|
|
1348
|
+
bridge.send({ id, type: "network", transport: "xhr", method, url,
|
|
1349
|
+
requestBody, requestHeaders: reqHeaders,
|
|
1350
|
+
state: "error", duration: Date.now() - start, timestamp: start });
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
return _origSend.apply(this, [body]);
|
|
1354
|
+
};
|
|
1355
|
+
} catch { /* not available */ }
|
|
1356
|
+
}
|
|
1357
|
+
`;
|
|
1358
|
+
}
|
|
765
1359
|
return `/**
|
|
766
1360
|
* codetraxis agent — XHR interceptor (auto-generated, do not edit).
|
|
767
1361
|
* Patches XMLHttpRequest.prototype so existing references (e.g. axios
|
|
@@ -853,8 +1447,171 @@ export function setupXhrInterceptor(bridge: TreeViewerBridge): void {
|
|
|
853
1447
|
`;
|
|
854
1448
|
}
|
|
855
1449
|
// ─── index.ts ─────────────────────────────────────────────────────────────────
|
|
856
|
-
function buildAgentIndexSource(kind) {
|
|
1450
|
+
function buildAgentIndexSource(kind, serverPort, useJs = false) {
|
|
857
1451
|
const isRn = kind === "expo" || kind === "react-native";
|
|
1452
|
+
if (useJs) {
|
|
1453
|
+
return `/**
|
|
1454
|
+
* codetraxis debug agent — entry point (auto-generated, do not edit).
|
|
1455
|
+
*/
|
|
1456
|
+
import { createTreeViewerBridge } from "./shared";
|
|
1457
|
+
import { setupConsoleInterceptor } from "./interceptors/consoleInterceptor";
|
|
1458
|
+
import { setupFetchInterceptor } from "./interceptors/fetchInterceptor";
|
|
1459
|
+
import { setupXhrInterceptor } from "./interceptors/xhrInterceptor";
|
|
1460
|
+
|
|
1461
|
+
export const treeViewerBridge = createTreeViewerBridge("__PORT__");
|
|
1462
|
+
|
|
1463
|
+
setupConsoleInterceptor(treeViewerBridge);
|
|
1464
|
+
setupFetchInterceptor(treeViewerBridge);
|
|
1465
|
+
${isRn ? "setupXhrInterceptor(treeViewerBridge); // React Native uses XHR under the hood" : "setupXhrInterceptor(treeViewerBridge);"}
|
|
1466
|
+
|
|
1467
|
+
// ─── Auto-attach default axios instance ──────────────────────────────────────
|
|
1468
|
+
try {
|
|
1469
|
+
const axiosModule = require("axios");
|
|
1470
|
+
const axiosInstance = axiosModule?.default ?? axiosModule;
|
|
1471
|
+
if (axiosInstance?.interceptors) {
|
|
1472
|
+
attachAxios(axiosInstance);
|
|
1473
|
+
}
|
|
1474
|
+
} catch { /* axios not installed — skip */ }
|
|
1475
|
+
|
|
1476
|
+
// ─── attachAxios — for axios.create() instances ───────────────────────────────
|
|
1477
|
+
// Usage: import { attachAxios } from "./codetraxisAgent";
|
|
1478
|
+
// attachAxios(myAxiosInstance);
|
|
1479
|
+
export function attachAxios(instance) {
|
|
1480
|
+
if (!instance?.interceptors) return;
|
|
1481
|
+
|
|
1482
|
+
const INSTALLED_KEY = "__tv_axios_installed__";
|
|
1483
|
+
if (instance[INSTALLED_KEY]) return;
|
|
1484
|
+
instance[INSTALLED_KEY] = true;
|
|
1485
|
+
|
|
1486
|
+
const REQ_ID_KEY = "__tv_req_id__";
|
|
1487
|
+
const REQ_START_KEY = "__tv_req_start__";
|
|
1488
|
+
|
|
1489
|
+
const joinUrl = (base, url) => {
|
|
1490
|
+
if (!base) return url || "";
|
|
1491
|
+
if (!url) return base;
|
|
1492
|
+
try { return new URL(url, base).toString(); } catch { return \`\${base}\${url}\`; }
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
const normalizeHeaders = (h) => {
|
|
1496
|
+
if (!h) return undefined;
|
|
1497
|
+
try {
|
|
1498
|
+
if (typeof h.toJSON === "function") return h.toJSON();
|
|
1499
|
+
return { ...h };
|
|
1500
|
+
} catch { return undefined; }
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1503
|
+
instance.interceptors.request.use(
|
|
1504
|
+
(config) => {
|
|
1505
|
+
const id = treeViewerBridge.uid();
|
|
1506
|
+
const start = Date.now();
|
|
1507
|
+
config[REQ_ID_KEY] = id;
|
|
1508
|
+
config[REQ_START_KEY] = start;
|
|
1509
|
+
treeViewerBridge.send({
|
|
1510
|
+
id, type: "network", transport: "axios",
|
|
1511
|
+
method: (config.method || "get").toUpperCase(),
|
|
1512
|
+
url: joinUrl(config.baseURL, config.url),
|
|
1513
|
+
requestHeaders: normalizeHeaders(config.headers),
|
|
1514
|
+
requestBody: treeViewerBridge.safeSerialize(config.data),
|
|
1515
|
+
state: "pending", timestamp: start,
|
|
1516
|
+
});
|
|
1517
|
+
return config;
|
|
1518
|
+
},
|
|
1519
|
+
(error) => {
|
|
1520
|
+
treeViewerBridge.send({
|
|
1521
|
+
id: treeViewerBridge.uid(), type: "network", transport: "axios",
|
|
1522
|
+
state: "error", timestamp: Date.now(),
|
|
1523
|
+
error: treeViewerBridge.safeSerialize(error),
|
|
1524
|
+
});
|
|
1525
|
+
return Promise.reject(error);
|
|
1526
|
+
},
|
|
1527
|
+
);
|
|
1528
|
+
|
|
1529
|
+
instance.interceptors.response.use(
|
|
1530
|
+
(response) => {
|
|
1531
|
+
const id = response.config[REQ_ID_KEY] || treeViewerBridge.uid();
|
|
1532
|
+
const start = response.config[REQ_START_KEY] || Date.now();
|
|
1533
|
+
treeViewerBridge.send({
|
|
1534
|
+
id, type: "network", transport: "axios",
|
|
1535
|
+
method: (response.config.method || "get").toUpperCase(),
|
|
1536
|
+
url: joinUrl(response.config.baseURL, response.config.url),
|
|
1537
|
+
status: response.status,
|
|
1538
|
+
requestHeaders: normalizeHeaders(response.config.headers),
|
|
1539
|
+
requestBody: treeViewerBridge.safeSerialize(response.config.data),
|
|
1540
|
+
responseHeaders: normalizeHeaders(response.headers),
|
|
1541
|
+
responseBody: treeViewerBridge.safeSerialize(response.data),
|
|
1542
|
+
state: "success", duration: Date.now() - start, timestamp: start,
|
|
1543
|
+
});
|
|
1544
|
+
return response;
|
|
1545
|
+
},
|
|
1546
|
+
(error) => {
|
|
1547
|
+
const cfg = error?.config || {};
|
|
1548
|
+
const id = cfg[REQ_ID_KEY] || treeViewerBridge.uid();
|
|
1549
|
+
const start = cfg[REQ_START_KEY] || Date.now();
|
|
1550
|
+
treeViewerBridge.send({
|
|
1551
|
+
id, type: "network", transport: "axios",
|
|
1552
|
+
method: (cfg.method || "get").toUpperCase(),
|
|
1553
|
+
url: joinUrl(cfg.baseURL, cfg.url),
|
|
1554
|
+
status: error?.response?.status,
|
|
1555
|
+
requestHeaders: normalizeHeaders(cfg.headers),
|
|
1556
|
+
requestBody: treeViewerBridge.safeSerialize(cfg.data),
|
|
1557
|
+
responseHeaders: normalizeHeaders(error?.response?.headers),
|
|
1558
|
+
responseBody: treeViewerBridge.safeSerialize(error?.response?.data),
|
|
1559
|
+
state: "error", duration: Date.now() - start, timestamp: start,
|
|
1560
|
+
error: treeViewerBridge.safeSerialize({ message: error?.message, code: error?.code, name: error?.name }),
|
|
1561
|
+
});
|
|
1562
|
+
return Promise.reject(error);
|
|
1563
|
+
},
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// ─── attachSocketIO — for socket.io-client instances ─────────────────────────
|
|
1568
|
+
// Usage: import { attachSocketIO } from "./codetraxisAgent";
|
|
1569
|
+
// attachSocketIO(socket);
|
|
1570
|
+
export function attachSocketIO(socket) {
|
|
1571
|
+
if (!socket) return;
|
|
1572
|
+
|
|
1573
|
+
const INSTALLED_KEY = "__tv_sio_installed__";
|
|
1574
|
+
if (socket[INSTALLED_KEY]) return;
|
|
1575
|
+
socket[INSTALLED_KEY] = true;
|
|
1576
|
+
|
|
1577
|
+
const url = socket.io?.uri ?? socket.nsp ?? "socket.io";
|
|
1578
|
+
|
|
1579
|
+
socket.onAny((event, ...args) => {
|
|
1580
|
+
treeViewerBridge.send({
|
|
1581
|
+
id: treeViewerBridge.uid(),
|
|
1582
|
+
type: "network",
|
|
1583
|
+
transport: "websocket",
|
|
1584
|
+
url,
|
|
1585
|
+
method: "MESSAGE",
|
|
1586
|
+
responseBody: treeViewerBridge.truncate(
|
|
1587
|
+
JSON.stringify({ event, data: args.length === 1 ? args[0] : args }),
|
|
1588
|
+
),
|
|
1589
|
+
state: "success",
|
|
1590
|
+
timestamp: Date.now(),
|
|
1591
|
+
});
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
const origEmit = socket.emit.bind(socket);
|
|
1595
|
+
socket.emit = (event, ...args) => {
|
|
1596
|
+
if (!["ping", "pong"].includes(event)) {
|
|
1597
|
+
treeViewerBridge.send({
|
|
1598
|
+
id: treeViewerBridge.uid(),
|
|
1599
|
+
type: "network",
|
|
1600
|
+
transport: "websocket",
|
|
1601
|
+
url,
|
|
1602
|
+
method: "SEND",
|
|
1603
|
+
requestBody: treeViewerBridge.truncate(
|
|
1604
|
+
JSON.stringify({ event, data: args[0] }),
|
|
1605
|
+
),
|
|
1606
|
+
state: "success",
|
|
1607
|
+
timestamp: Date.now(),
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
return origEmit(event, ...args);
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
`.replace("__PORT__", serverPort);
|
|
1614
|
+
}
|
|
858
1615
|
return `/**
|
|
859
1616
|
* codetraxis debug agent — entry point (auto-generated, do not edit).
|
|
860
1617
|
*/
|
|
@@ -1023,5 +1780,5 @@ export function attachSocketIO(socket: any): void {
|
|
|
1023
1780
|
}
|
|
1024
1781
|
|
|
1025
1782
|
export {};
|
|
1026
|
-
`.replace("__PORT__",
|
|
1783
|
+
`.replace("__PORT__", serverPort);
|
|
1027
1784
|
}
|