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
|
@@ -432,7 +432,7 @@ async function installAgent(targetDir) {
|
|
|
432
432
|
const htmlSource = await promises_1.default.readFile(plan.htmlFile, "utf-8");
|
|
433
433
|
const alreadyInstalled = htmlSource.includes("codetraxisAgent.js");
|
|
434
434
|
// Always overwrite the bundle so it stays up-to-date
|
|
435
|
-
await promises_1.default.writeFile(bundleFile, buildHtmlAgentBundle(port), "utf-8");
|
|
435
|
+
await promises_1.default.writeFile(bundleFile, await buildHtmlAgentBundle(port), "utf-8");
|
|
436
436
|
// Patch index.html only if not already patched
|
|
437
437
|
if (!alreadyInstalled) {
|
|
438
438
|
await promises_1.default.writeFile(plan.htmlFile, patchHtmlFile(htmlSource, plan.scriptSrc), "utf-8");
|
|
@@ -564,239 +564,14 @@ async function removeAgent(targetDir) {
|
|
|
564
564
|
}
|
|
565
565
|
}
|
|
566
566
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
567
|
-
//
|
|
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
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
778
|
-
// Agent source — modular architecture
|
|
567
|
+
// Agent source location
|
|
779
568
|
//
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
// interceptors/xhrInterceptor.ts — for React Native & raw XHR users
|
|
785
|
-
// index.ts — wires everything together
|
|
569
|
+
// In production: __dirname = server/dist/utils/agent/
|
|
570
|
+
// pkg root = ../../../../ → codetraxisAgent/ at root
|
|
571
|
+
// In dev (ts-node): __dirname = server/src/utils/agent/
|
|
572
|
+
// pkg root = ../../../../ → codetraxisAgent/ at root
|
|
786
573
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
787
|
-
|
|
788
|
-
const agentDir = node_path_1.default.join(agentParentDir, "codetraxisAgent");
|
|
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 });
|
|
793
|
-
await promises_1.default.mkdir(interceptorsDir, { recursive: true });
|
|
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
|
-
}
|
|
574
|
+
const AGENT_SRC_DIR = node_path_1.default.resolve(__dirname, "../../../../codetraxisAgent");
|
|
800
575
|
// ─── Strip TypeScript annotations for plain JS projects ──────────────────────
|
|
801
576
|
// Removes: import type ..., : Type, <Type>, as Type, interface ..., export type ...
|
|
802
577
|
// Keeps the runtime logic intact so webpack/babel can process it without ts-loader.
|
|
@@ -809,976 +584,102 @@ function stripTypescript(src) {
|
|
|
809
584
|
.replace(/^export\s+(?:type|interface)\s+[^=\n]+=[^;\n]+;?\s*$/gm, "")
|
|
810
585
|
// remove `interface Foo { ... }` blocks
|
|
811
586
|
.replace(/^interface\s+\w+[^{]*\{[\s\S]*?\}\s*$/gm, "")
|
|
812
|
-
// remove inline type annotations: `: string`, `: boolean`,
|
|
587
|
+
// remove inline type annotations: `: string`, `: boolean`, etc.
|
|
813
588
|
.replace(/:\s*(string|number|boolean|void|unknown|any|never|null|undefined)\b/g, "")
|
|
814
|
-
// remove `: SomeInterface` / `: Record<...>`
|
|
589
|
+
// remove `: SomeInterface` / `: Record<...>` etc.
|
|
815
590
|
.replace(/:\s*[A-Z][A-Za-z0-9_]*(?:<[^>]*>)?/g, "")
|
|
816
591
|
// remove `as SomeType` casts
|
|
817
592
|
.replace(/\bas\s+[A-Za-z][A-Za-z0-9_<>, |&[\]]*(?=\s*[),;\]}])/g, "")
|
|
818
|
-
// remove `<Type>` generic parameters
|
|
593
|
+
// remove `<Type>` generic parameters
|
|
819
594
|
.replace(/<[A-Z][A-Za-z0-9_, ]*>/g, "")
|
|
820
|
-
// clean up resulting
|
|
595
|
+
// clean up resulting blank lines
|
|
821
596
|
.replace(/\n{3,}/g, "\n\n")
|
|
822
597
|
.trimEnd() + "\n";
|
|
823
598
|
}
|
|
824
|
-
// ───
|
|
825
|
-
function
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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 };
|
|
909
|
-
}
|
|
910
|
-
`;
|
|
911
|
-
}
|
|
912
|
-
return `/**
|
|
913
|
-
* codetraxis agent — shared bridge (auto-generated, do not edit).
|
|
914
|
-
*/
|
|
915
|
-
|
|
916
|
-
export type TreeViewerEvent = Record<string, unknown>;
|
|
917
|
-
|
|
918
|
-
export interface TreeViewerBridge {
|
|
919
|
-
send: (event: TreeViewerEvent) => void;
|
|
920
|
-
uid: () => string;
|
|
921
|
-
safeSerialize: (value: unknown, depth?: number) => unknown;
|
|
922
|
-
truncate: (value: string, max?: number) => string;
|
|
923
|
-
isWeb: boolean;
|
|
599
|
+
// ─── Read agent source file and apply substitutions ──────────────────────────
|
|
600
|
+
async function readAgentFile(relativePath, port, useJs) {
|
|
601
|
+
const fullPath = node_path_1.default.join(AGENT_SRC_DIR, relativePath);
|
|
602
|
+
let src = await promises_1.default.readFile(fullPath, "utf-8");
|
|
603
|
+
src = src.split("__PORT__").join(port);
|
|
604
|
+
if (useJs)
|
|
605
|
+
src = stripTypescript(src);
|
|
606
|
+
return src;
|
|
924
607
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
while (queue.length > 0) {
|
|
957
|
-
const item = queue.shift();
|
|
958
|
-
if (item && ws) ws.send(item);
|
|
959
|
-
}
|
|
960
|
-
};
|
|
961
|
-
ws.onclose = () => { wsReady = false; setTimeout(connect, 3000); };
|
|
962
|
-
ws.onerror = () => { wsReady = false; };
|
|
963
|
-
} catch { /* unavailable */ }
|
|
964
|
-
};
|
|
965
|
-
connect();
|
|
966
|
-
|
|
967
|
-
const truncate = (value: string, max = 500000): string =>
|
|
968
|
-
value.length > max ? \`\${value.slice(0, max)}…[truncated]\` : value;
|
|
969
|
-
|
|
970
|
-
const safeSerialize = (value: unknown, depth = 0): unknown => {
|
|
971
|
-
if (depth > 4) return "[depth limit]";
|
|
972
|
-
if (value == null) return value;
|
|
973
|
-
if (typeof value === "function") return \`[Function: \${(value as Function).name || "anonymous"}]\`;
|
|
974
|
-
if (typeof value === "symbol") return value.toString();
|
|
975
|
-
if (typeof value !== "object") return value;
|
|
976
|
-
if (value instanceof Error) return { __error: true, name: value.name, message: value.message, stack: value.stack };
|
|
977
|
-
if (Array.isArray(value)) return value.slice(0, 1000).map(i => safeSerialize(i, depth + 1));
|
|
978
|
-
|
|
979
|
-
const seen = new WeakSet<object>();
|
|
980
|
-
const walk = (obj: object, d: number): Record<string, unknown> | string => {
|
|
981
|
-
if (seen.has(obj)) return "[circular]";
|
|
982
|
-
seen.add(obj);
|
|
983
|
-
const r: Record<string, unknown> = {};
|
|
984
|
-
let n = 0;
|
|
985
|
-
for (const k in obj as Record<string, unknown>) {
|
|
986
|
-
if (n++ > 500) { r["..."] = "[truncated]"; break; }
|
|
987
|
-
try { r[k] = safeSerialize((obj as Record<string, unknown>)[k], d + 1); }
|
|
988
|
-
catch { r[k] = "[unserializable]"; }
|
|
989
|
-
}
|
|
990
|
-
return r;
|
|
991
|
-
};
|
|
992
|
-
return walk(value as object, depth);
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
const uid = () =>
|
|
996
|
-
\`\${Math.random().toString(36).slice(2)}\${Date.now().toString(36)}\`;
|
|
997
|
-
|
|
998
|
-
const send = (event: TreeViewerEvent) => {
|
|
999
|
-
try {
|
|
1000
|
-
const payload = JSON.stringify(event);
|
|
1001
|
-
if (wsReady && ws) { ws.send(payload); }
|
|
1002
|
-
else { queue.push(payload); if (queue.length > 200) queue.shift(); }
|
|
1003
|
-
} catch { /* ignore */ }
|
|
1004
|
-
};
|
|
1005
|
-
|
|
1006
|
-
return { send, uid, safeSerialize, truncate, isWeb };
|
|
1007
|
-
}
|
|
1008
|
-
`;
|
|
1009
|
-
}
|
|
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
|
-
}
|
|
1046
|
-
return `/**
|
|
1047
|
-
* codetraxis agent — console interceptor (auto-generated, do not edit).
|
|
1048
|
-
*/
|
|
1049
|
-
import type { TreeViewerBridge } from "../shared";
|
|
1050
|
-
|
|
1051
|
-
const INSTALLED_KEY = "__tv_console_installed__";
|
|
1052
|
-
|
|
1053
|
-
export function setupConsoleInterceptor(bridge: TreeViewerBridge): void {
|
|
1054
|
-
const g = globalThis as Record<string, any>;
|
|
1055
|
-
if (g[INSTALLED_KEY]) return;
|
|
1056
|
-
g[INSTALLED_KEY] = true;
|
|
1057
|
-
|
|
1058
|
-
const original = {
|
|
1059
|
-
log: console.log.bind(console),
|
|
1060
|
-
info: console.info.bind(console),
|
|
1061
|
-
warn: console.warn.bind(console),
|
|
1062
|
-
error: console.error.bind(console),
|
|
1063
|
-
};
|
|
1064
|
-
|
|
1065
|
-
(["log", "info", "warn", "error"] as const).forEach(level => {
|
|
1066
|
-
// @ts-ignore
|
|
1067
|
-
console[level] = (...args: unknown[]) => {
|
|
1068
|
-
original[level](...args);
|
|
1069
|
-
bridge.send({
|
|
1070
|
-
id: bridge.uid(),
|
|
1071
|
-
type: "console",
|
|
1072
|
-
level,
|
|
1073
|
-
args: args.map(a => bridge.safeSerialize(a)),
|
|
1074
|
-
timestamp: Date.now(),
|
|
1075
|
-
});
|
|
1076
|
-
};
|
|
1077
|
-
});
|
|
1078
|
-
}
|
|
1079
|
-
`;
|
|
1080
|
-
}
|
|
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
|
-
}
|
|
1175
|
-
return `/**
|
|
1176
|
-
* codetraxis agent — fetch interceptor (auto-generated, do not edit).
|
|
1177
|
-
*/
|
|
1178
|
-
import type { TreeViewerBridge } from "../shared";
|
|
1179
|
-
|
|
1180
|
-
const INSTALLED_KEY = "__tv_fetch_installed__";
|
|
1181
|
-
|
|
1182
|
-
function normalizeHeaders(headers: HeadersInit | undefined): Record<string, string> | undefined {
|
|
1183
|
-
if (!headers) return undefined;
|
|
1184
|
-
const result: Record<string, string> = {};
|
|
1185
|
-
try {
|
|
1186
|
-
if (headers instanceof Headers) {
|
|
1187
|
-
headers.forEach((v, k) => { result[k] = v; });
|
|
1188
|
-
} else if (Array.isArray(headers)) {
|
|
1189
|
-
(headers as [string, string][]).forEach(([k, v]) => { result[k] = v; });
|
|
1190
|
-
} else {
|
|
1191
|
-
Object.assign(result, headers as Record<string, string>);
|
|
1192
|
-
}
|
|
1193
|
-
return result;
|
|
1194
|
-
} catch { return undefined; }
|
|
608
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
609
|
+
// Agent source — modular architecture
|
|
610
|
+
//
|
|
611
|
+
// Reads from codetraxisAgent/ at package root and writes to target project:
|
|
612
|
+
// shared.ts/js
|
|
613
|
+
// interceptors/consoleInterceptor.ts/js
|
|
614
|
+
// interceptors/fetchInterceptor.ts/js
|
|
615
|
+
// interceptors/xhrInterceptor.ts/js
|
|
616
|
+
// index.ts/js (with __PORT__ and __XHR_LINE__ substituted)
|
|
617
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
618
|
+
async function writeAgentFiles(agentParentDir, serverPort, kind, useJs = false) {
|
|
619
|
+
const agentDir = node_path_1.default.join(agentParentDir, "codetraxisAgent");
|
|
620
|
+
const interceptorsDir = node_path_1.default.join(agentDir, "interceptors");
|
|
621
|
+
const ext = useJs ? "js" : "ts";
|
|
622
|
+
const isRn = kind === "expo" || kind === "react-native";
|
|
623
|
+
// Always wipe the old agent folder first so stale .ts/.js files don't coexist
|
|
624
|
+
await promises_1.default.rm(agentDir, { recursive: true, force: true });
|
|
625
|
+
await promises_1.default.mkdir(interceptorsDir, { recursive: true });
|
|
626
|
+
// shared
|
|
627
|
+
await promises_1.default.writeFile(node_path_1.default.join(agentDir, `shared.${ext}`), await readAgentFile("shared.ts", serverPort, useJs), "utf-8");
|
|
628
|
+
// interceptors
|
|
629
|
+
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir, `consoleInterceptor.${ext}`), await readAgentFile("interceptors/consoleInterceptor.ts", serverPort, useJs), "utf-8");
|
|
630
|
+
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir, `fetchInterceptor.${ext}`), await readAgentFile("interceptors/fetchInterceptor.ts", serverPort, useJs), "utf-8");
|
|
631
|
+
await promises_1.default.writeFile(node_path_1.default.join(interceptorsDir, `xhrInterceptor.${ext}`), await readAgentFile("interceptors/xhrInterceptor.ts", serverPort, useJs), "utf-8");
|
|
632
|
+
// index — substitute __XHR_LINE__ based on project kind
|
|
633
|
+
const xhrLine = isRn
|
|
634
|
+
? "setupXhrInterceptor(treeViewerBridge); // React Native uses XHR under the hood"
|
|
635
|
+
: "setupXhrInterceptor(treeViewerBridge);";
|
|
636
|
+
let indexSrc = await readAgentFile("index.ts", serverPort, useJs);
|
|
637
|
+
indexSrc = indexSrc.split("__XHR_LINE__").join(xhrLine);
|
|
638
|
+
await promises_1.default.writeFile(node_path_1.default.join(agentDir, `index.${ext}`), indexSrc, "utf-8");
|
|
1195
639
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
typeof globalThis.fetch === "function"
|
|
1204
|
-
? globalThis.fetch.bind(globalThis)
|
|
1205
|
-
: null;
|
|
1206
|
-
if (!originalFetch) return;
|
|
1207
|
-
|
|
1208
|
-
globalThis.fetch = async function tvFetch(
|
|
1209
|
-
input: RequestInfo | URL,
|
|
1210
|
-
init?: RequestInit,
|
|
1211
|
-
): Promise<Response> {
|
|
1212
|
-
const url =
|
|
1213
|
-
typeof input === "string" ? input
|
|
1214
|
-
: input instanceof URL ? input.href
|
|
1215
|
-
: (input as Request).url;
|
|
1216
|
-
|
|
1217
|
-
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
1218
|
-
const id = bridge.uid();
|
|
1219
|
-
const start = Date.now();
|
|
1220
|
-
|
|
1221
|
-
const requestHeaders = normalizeHeaders(
|
|
1222
|
-
init?.headers ?? (input instanceof Request ? input.headers : undefined),
|
|
1223
|
-
);
|
|
1224
|
-
|
|
1225
|
-
let requestBody: string | undefined;
|
|
1226
|
-
const rawBody = init?.body;
|
|
1227
|
-
if (rawBody != null) {
|
|
1228
|
-
try {
|
|
1229
|
-
requestBody =
|
|
1230
|
-
typeof rawBody === "string" ? bridge.truncate(rawBody)
|
|
1231
|
-
: rawBody instanceof URLSearchParams ? bridge.truncate(rawBody.toString())
|
|
1232
|
-
: "[binary]";
|
|
1233
|
-
} catch { requestBody = "[unserializable-body]"; }
|
|
640
|
+
/** Insert <script src="..."> just before </body> (or </head> as fallback). */
|
|
641
|
+
function patchHtmlFile(html, scriptSrc) {
|
|
642
|
+
const tag = `<script src="${scriptSrc}"></script>`;
|
|
643
|
+
if (html.includes(tag))
|
|
644
|
+
return html; // idempotent
|
|
645
|
+
if (/<\/body>/i.test(html)) {
|
|
646
|
+
return html.replace(/<\/body>/i, ` ${tag}\n</body>`);
|
|
1234
647
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
requestHeaders, requestBody, state: "pending", timestamp: start });
|
|
1238
|
-
|
|
1239
|
-
try {
|
|
1240
|
-
const response = await originalFetch(input, init);
|
|
1241
|
-
|
|
1242
|
-
let responseHeaders: Record<string, string> | undefined;
|
|
1243
|
-
try {
|
|
1244
|
-
responseHeaders = {};
|
|
1245
|
-
response.headers.forEach((v, k) => { responseHeaders![k] = v; });
|
|
1246
|
-
} catch { /* ignore */ }
|
|
1247
|
-
|
|
1248
|
-
let responseBody: string | undefined;
|
|
1249
|
-
try { responseBody = bridge.truncate(await response.clone().text()); }
|
|
1250
|
-
catch { responseBody = "[binary]"; }
|
|
1251
|
-
|
|
1252
|
-
bridge.send({ id, type: "network", transport: "fetch", method, url,
|
|
1253
|
-
status: response.status, requestHeaders, requestBody,
|
|
1254
|
-
responseHeaders, responseBody,
|
|
1255
|
-
state: response.ok ? "success" : "error",
|
|
1256
|
-
duration: Date.now() - start, timestamp: start });
|
|
1257
|
-
|
|
1258
|
-
return response;
|
|
1259
|
-
} catch (error) {
|
|
1260
|
-
bridge.send({ id, type: "network", transport: "fetch", method, url,
|
|
1261
|
-
requestHeaders, requestBody, state: "error",
|
|
1262
|
-
duration: Date.now() - start, timestamp: start,
|
|
1263
|
-
error: bridge.safeSerialize(error) });
|
|
1264
|
-
throw error;
|
|
648
|
+
if (/<\/head>/i.test(html)) {
|
|
649
|
+
return html.replace(/<\/head>/i, ` ${tag}\n</head>`);
|
|
1265
650
|
}
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
`;
|
|
1269
|
-
}
|
|
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 */ }
|
|
651
|
+
// No recognisable tags — just append
|
|
652
|
+
return html + `\n${tag}\n`;
|
|
1356
653
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
654
|
+
/** Build a self-contained IIFE bundle for plain-HTML projects by reading agent sources from disk. */
|
|
655
|
+
async function buildHtmlAgentBundle(serverPort) {
|
|
656
|
+
const read = (rel) => readAgentFile(rel, serverPort, true /* JS */);
|
|
657
|
+
const shared = await read("shared.ts");
|
|
658
|
+
const console_ = await read("interceptors/consoleInterceptor.ts");
|
|
659
|
+
const fetch_ = await read("interceptors/fetchInterceptor.ts");
|
|
660
|
+
const xhr_ = await read("interceptors/xhrInterceptor.ts");
|
|
661
|
+
// Convert ESM exports to local variable assignments for IIFE context
|
|
662
|
+
const strip = (src) => src
|
|
663
|
+
.replace(/^export\s+function\s+/gm, "function ")
|
|
664
|
+
.replace(/^export\s+(const|let|var)\s+/gm, "$1 ")
|
|
665
|
+
.replace(/^import[^\n]+\n/gm, "")
|
|
666
|
+
.replace(/^export\s+\{\s*\};\s*$/gm, "");
|
|
1359
667
|
return `/**
|
|
1360
|
-
* codetraxis agent —
|
|
1361
|
-
*
|
|
1362
|
-
* internals captured at import time) are also intercepted.
|
|
1363
|
-
* Works in browser and React Native (which ships its own XHR polyfill).
|
|
668
|
+
* codetraxis debug agent — standalone bundle (auto-generated, do not edit).
|
|
669
|
+
* Injected into public/index.html via <script src="/codetraxisAgent.js">.
|
|
1364
670
|
*/
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const INSTALLED_KEY = "__tv_xhr_installed__";
|
|
1368
|
-
|
|
1369
|
-
export function setupXhrInterceptor(bridge: TreeViewerBridge): void {
|
|
1370
|
-
if (typeof XMLHttpRequest === "undefined") return;
|
|
1371
|
-
const g = globalThis as Record<string, any>;
|
|
1372
|
-
if (g[INSTALLED_KEY]) return;
|
|
1373
|
-
g[INSTALLED_KEY] = true;
|
|
1374
|
-
|
|
1375
|
-
try {
|
|
1376
|
-
const proto = XMLHttpRequest.prototype;
|
|
1377
|
-
|
|
1378
|
-
const _origOpen = proto.open;
|
|
1379
|
-
proto.open = function(this: XMLHttpRequest, method: string, url: string, ...rest: unknown[]) {
|
|
1380
|
-
(this as any).__tv_method = method.toUpperCase();
|
|
1381
|
-
(this as any).__tv_url = String(url);
|
|
1382
|
-
(this as any).__tv_reqHeaders = undefined;
|
|
1383
|
-
return (_origOpen as Function).apply(this, [method, url, ...rest]);
|
|
1384
|
-
};
|
|
1385
|
-
|
|
1386
|
-
const _origSetHeader = proto.setRequestHeader;
|
|
1387
|
-
proto.setRequestHeader = function(this: XMLHttpRequest, name: string, value: string) {
|
|
1388
|
-
if (!(this as any).__tv_reqHeaders) (this as any).__tv_reqHeaders = {} as Record<string, string>;
|
|
1389
|
-
(this as any).__tv_reqHeaders[name] = value;
|
|
1390
|
-
return _origSetHeader.apply(this, [name, value]);
|
|
1391
|
-
};
|
|
1392
|
-
|
|
1393
|
-
const _origSend = proto.send;
|
|
1394
|
-
proto.send = function(this: XMLHttpRequest, body?: Document | XMLHttpRequestBodyInit | null) {
|
|
1395
|
-
const id = bridge.uid();
|
|
1396
|
-
const method = (this as any).__tv_method ?? "GET";
|
|
1397
|
-
const url = (this as any).__tv_url ?? "";
|
|
1398
|
-
const reqHeaders = (this as any).__tv_reqHeaders as Record<string, string> | undefined;
|
|
1399
|
-
const start = Date.now();
|
|
1400
|
-
|
|
1401
|
-
let requestBody: string | undefined;
|
|
1402
|
-
if (body != null) {
|
|
1403
|
-
try {
|
|
1404
|
-
requestBody =
|
|
1405
|
-
typeof body === "string" ? bridge.truncate(body)
|
|
1406
|
-
: body instanceof URLSearchParams ? bridge.truncate(body.toString())
|
|
1407
|
-
: "[binary]";
|
|
1408
|
-
} catch { /* ignore */ }
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
bridge.send({ id, type: "network", transport: "xhr", method, url,
|
|
1412
|
-
requestBody, requestHeaders: reqHeaders, state: "pending", timestamp: start });
|
|
1413
|
-
|
|
1414
|
-
this.addEventListener("load", () => {
|
|
1415
|
-
let responseBody: string | undefined;
|
|
1416
|
-
try { responseBody = bridge.truncate(this.responseText); } catch { /* binary */ }
|
|
1417
|
-
|
|
1418
|
-
let responseHeaders: Record<string, string> | undefined;
|
|
1419
|
-
try {
|
|
1420
|
-
const raw = this.getAllResponseHeaders();
|
|
1421
|
-
if (raw) {
|
|
1422
|
-
responseHeaders = {};
|
|
1423
|
-
raw.trim().split(/\\r?\\n/).forEach(line => {
|
|
1424
|
-
const idx = line.indexOf(": ");
|
|
1425
|
-
if (idx > 0) responseHeaders![line.slice(0, idx).toLowerCase()] = line.slice(idx + 2);
|
|
1426
|
-
});
|
|
1427
|
-
}
|
|
1428
|
-
} catch { /* ignore */ }
|
|
1429
|
-
|
|
1430
|
-
bridge.send({ id, type: "network", transport: "xhr", method, url,
|
|
1431
|
-
status: this.status, requestBody, requestHeaders: reqHeaders,
|
|
1432
|
-
responseBody, responseHeaders,
|
|
1433
|
-
state: this.status >= 200 && this.status < 400 ? "success" : "error",
|
|
1434
|
-
duration: Date.now() - start, timestamp: start });
|
|
1435
|
-
});
|
|
671
|
+
(function () {
|
|
672
|
+
'use strict';
|
|
1436
673
|
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
});
|
|
674
|
+
${strip(shared)}
|
|
675
|
+
${strip(console_)}
|
|
676
|
+
${strip(fetch_)}
|
|
677
|
+
${strip(xhr_)}
|
|
1442
678
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
679
|
+
const _bridge = createTreeViewerBridge("${serverPort}");
|
|
680
|
+
setupConsoleInterceptor(_bridge);
|
|
681
|
+
setupFetchInterceptor(_bridge);
|
|
682
|
+
setupXhrInterceptor(_bridge);
|
|
683
|
+
})();
|
|
1447
684
|
`;
|
|
1448
685
|
}
|
|
1449
|
-
// ─── index.ts ─────────────────────────────────────────────────────────────────
|
|
1450
|
-
function buildAgentIndexSource(kind, serverPort, useJs = false) {
|
|
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
|
-
}
|
|
1615
|
-
return `/**
|
|
1616
|
-
* codetraxis debug agent — entry point (auto-generated, do not edit).
|
|
1617
|
-
*/
|
|
1618
|
-
import { createTreeViewerBridge } from "./shared";
|
|
1619
|
-
import { setupConsoleInterceptor } from "./interceptors/consoleInterceptor";
|
|
1620
|
-
import { setupFetchInterceptor } from "./interceptors/fetchInterceptor";
|
|
1621
|
-
import { setupXhrInterceptor } from "./interceptors/xhrInterceptor";
|
|
1622
|
-
|
|
1623
|
-
export const treeViewerBridge = createTreeViewerBridge("__PORT__");
|
|
1624
|
-
|
|
1625
|
-
setupConsoleInterceptor(treeViewerBridge);
|
|
1626
|
-
setupFetchInterceptor(treeViewerBridge);
|
|
1627
|
-
${isRn ? "setupXhrInterceptor(treeViewerBridge); // React Native uses XHR under the hood" : "setupXhrInterceptor(treeViewerBridge);"}
|
|
1628
|
-
|
|
1629
|
-
// ─── Auto-attach default axios instance ──────────────────────────────────────
|
|
1630
|
-
// If the project uses axios, we attach interceptors to the default instance.
|
|
1631
|
-
// For axios.create() instances, call attachAxios(instance) manually.
|
|
1632
|
-
try {
|
|
1633
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1634
|
-
const axiosModule = require("axios");
|
|
1635
|
-
const axiosInstance = axiosModule?.default ?? axiosModule;
|
|
1636
|
-
if (axiosInstance?.interceptors) {
|
|
1637
|
-
attachAxios(axiosInstance);
|
|
1638
|
-
}
|
|
1639
|
-
} catch { /* axios not installed — skip */ }
|
|
1640
|
-
|
|
1641
|
-
// ─── attachAxios — for axios.create() instances ───────────────────────────────
|
|
1642
|
-
// Usage: import { attachAxios } from "./codetraxisAgent";
|
|
1643
|
-
// attachAxios(myAxiosInstance);
|
|
1644
|
-
export function attachAxios(instance: any): void {
|
|
1645
|
-
if (!instance?.interceptors) return;
|
|
1646
|
-
|
|
1647
|
-
const INSTALLED_KEY = "__tv_axios_installed__";
|
|
1648
|
-
if (instance[INSTALLED_KEY]) return;
|
|
1649
|
-
instance[INSTALLED_KEY] = true;
|
|
1650
|
-
|
|
1651
|
-
const REQ_ID_KEY = "__tv_req_id__";
|
|
1652
|
-
const REQ_START_KEY = "__tv_req_start__";
|
|
1653
|
-
|
|
1654
|
-
const joinUrl = (base?: string, url?: string) => {
|
|
1655
|
-
if (!base) return url || "";
|
|
1656
|
-
if (!url) return base;
|
|
1657
|
-
try { return new URL(url, base).toString(); } catch { return \`\${base}\${url}\`; }
|
|
1658
|
-
};
|
|
1659
|
-
|
|
1660
|
-
const normalizeHeaders = (h: unknown): Record<string, string> | undefined => {
|
|
1661
|
-
if (!h) return undefined;
|
|
1662
|
-
try {
|
|
1663
|
-
if (typeof (h as any).toJSON === "function") return (h as any).toJSON() as Record<string, string>;
|
|
1664
|
-
return { ...(h as Record<string, string>) };
|
|
1665
|
-
} catch { return undefined; }
|
|
1666
|
-
};
|
|
1667
|
-
|
|
1668
|
-
instance.interceptors.request.use(
|
|
1669
|
-
(config: any) => {
|
|
1670
|
-
const id = treeViewerBridge.uid();
|
|
1671
|
-
const start = Date.now();
|
|
1672
|
-
config[REQ_ID_KEY] = id;
|
|
1673
|
-
config[REQ_START_KEY] = start;
|
|
1674
|
-
treeViewerBridge.send({
|
|
1675
|
-
id, type: "network", transport: "axios",
|
|
1676
|
-
method: (config.method || "get").toUpperCase(),
|
|
1677
|
-
url: joinUrl(config.baseURL, config.url),
|
|
1678
|
-
requestHeaders: normalizeHeaders(config.headers),
|
|
1679
|
-
requestBody: treeViewerBridge.safeSerialize(config.data),
|
|
1680
|
-
state: "pending", timestamp: start,
|
|
1681
|
-
});
|
|
1682
|
-
return config;
|
|
1683
|
-
},
|
|
1684
|
-
(error: any) => {
|
|
1685
|
-
treeViewerBridge.send({
|
|
1686
|
-
id: treeViewerBridge.uid(), type: "network", transport: "axios",
|
|
1687
|
-
state: "error", timestamp: Date.now(),
|
|
1688
|
-
error: treeViewerBridge.safeSerialize(error),
|
|
1689
|
-
});
|
|
1690
|
-
return Promise.reject(error);
|
|
1691
|
-
},
|
|
1692
|
-
);
|
|
1693
|
-
|
|
1694
|
-
instance.interceptors.response.use(
|
|
1695
|
-
(response: any) => {
|
|
1696
|
-
const id = response.config[REQ_ID_KEY] || treeViewerBridge.uid();
|
|
1697
|
-
const start = response.config[REQ_START_KEY] || Date.now();
|
|
1698
|
-
treeViewerBridge.send({
|
|
1699
|
-
id, type: "network", transport: "axios",
|
|
1700
|
-
method: (response.config.method || "get").toUpperCase(),
|
|
1701
|
-
url: joinUrl(response.config.baseURL, response.config.url),
|
|
1702
|
-
status: response.status,
|
|
1703
|
-
requestHeaders: normalizeHeaders(response.config.headers),
|
|
1704
|
-
requestBody: treeViewerBridge.safeSerialize(response.config.data),
|
|
1705
|
-
responseHeaders: normalizeHeaders(response.headers),
|
|
1706
|
-
responseBody: treeViewerBridge.safeSerialize(response.data),
|
|
1707
|
-
state: "success", duration: Date.now() - start, timestamp: start,
|
|
1708
|
-
});
|
|
1709
|
-
return response;
|
|
1710
|
-
},
|
|
1711
|
-
(error: any) => {
|
|
1712
|
-
const cfg = error?.config || {};
|
|
1713
|
-
const id = cfg[REQ_ID_KEY] || treeViewerBridge.uid();
|
|
1714
|
-
const start = cfg[REQ_START_KEY] || Date.now();
|
|
1715
|
-
treeViewerBridge.send({
|
|
1716
|
-
id, type: "network", transport: "axios",
|
|
1717
|
-
method: (cfg.method || "get").toUpperCase(),
|
|
1718
|
-
url: joinUrl(cfg.baseURL, cfg.url),
|
|
1719
|
-
status: error?.response?.status,
|
|
1720
|
-
requestHeaders: normalizeHeaders(cfg.headers),
|
|
1721
|
-
requestBody: treeViewerBridge.safeSerialize(cfg.data),
|
|
1722
|
-
responseHeaders: normalizeHeaders(error?.response?.headers),
|
|
1723
|
-
responseBody: treeViewerBridge.safeSerialize(error?.response?.data),
|
|
1724
|
-
state: "error", duration: Date.now() - start, timestamp: start,
|
|
1725
|
-
error: treeViewerBridge.safeSerialize({ message: error?.message, code: error?.code, name: error?.name }),
|
|
1726
|
-
});
|
|
1727
|
-
return Promise.reject(error);
|
|
1728
|
-
},
|
|
1729
|
-
);
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
// ─── attachSocketIO — for socket.io-client instances ─────────────────────────
|
|
1733
|
-
// React Native uses socket.io which doesn't go through globalThis.WebSocket.
|
|
1734
|
-
// Usage: import { attachSocketIO } from "./codetraxisAgent";
|
|
1735
|
-
// attachSocketIO(socket); // call after getChatSocket() or io()
|
|
1736
|
-
export function attachSocketIO(socket: any): void {
|
|
1737
|
-
if (!socket) return;
|
|
1738
|
-
|
|
1739
|
-
const INSTALLED_KEY = "__tv_sio_installed__";
|
|
1740
|
-
if (socket[INSTALLED_KEY]) return;
|
|
1741
|
-
socket[INSTALLED_KEY] = true;
|
|
1742
|
-
|
|
1743
|
-
const url: string = socket.io?.uri ?? socket.nsp ?? "socket.io";
|
|
1744
|
-
|
|
1745
|
-
// ── Incoming events ──────────────────────────────────────────────────────
|
|
1746
|
-
socket.onAny((event: string, ...args: unknown[]) => {
|
|
1747
|
-
treeViewerBridge.send({
|
|
1748
|
-
id: treeViewerBridge.uid(),
|
|
1749
|
-
type: "network",
|
|
1750
|
-
transport: "websocket",
|
|
1751
|
-
url,
|
|
1752
|
-
method: "MESSAGE",
|
|
1753
|
-
responseBody: treeViewerBridge.truncate(
|
|
1754
|
-
JSON.stringify({ event, data: args.length === 1 ? args[0] : args }),
|
|
1755
|
-
),
|
|
1756
|
-
state: "success",
|
|
1757
|
-
timestamp: Date.now(),
|
|
1758
|
-
});
|
|
1759
|
-
});
|
|
1760
|
-
|
|
1761
|
-
// ── Outgoing events ──────────────────────────────────────────────────────
|
|
1762
|
-
const origEmit = socket.emit.bind(socket);
|
|
1763
|
-
socket.emit = (event: string, ...args: unknown[]) => {
|
|
1764
|
-
if (!["ping", "pong"].includes(event)) {
|
|
1765
|
-
treeViewerBridge.send({
|
|
1766
|
-
id: treeViewerBridge.uid(),
|
|
1767
|
-
type: "network",
|
|
1768
|
-
transport: "websocket",
|
|
1769
|
-
url,
|
|
1770
|
-
method: "SEND",
|
|
1771
|
-
requestBody: treeViewerBridge.truncate(
|
|
1772
|
-
JSON.stringify({ event, data: args[0] }),
|
|
1773
|
-
),
|
|
1774
|
-
state: "success",
|
|
1775
|
-
timestamp: Date.now(),
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1778
|
-
return origEmit(event, ...args);
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
export {};
|
|
1783
|
-
`.replace("__PORT__", serverPort);
|
|
1784
|
-
}
|