execbro 2.6.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/build/__tests__/helpers/mock-harness.d.ts +35 -0
- package/build/__tests__/helpers/mock-harness.d.ts.map +1 -0
- package/build/__tests__/helpers/mock-harness.js +83 -0
- package/build/__tests__/helpers/mock-harness.js.map +1 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.js +2 -1
- package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -1
- package/build/__tests__/unit/buildToken.test.js +20 -3
- package/build/__tests__/unit/buildToken.test.js.map +1 -1
- package/build/__tests__/unit/commandInjection.test.d.ts +2 -0
- package/build/__tests__/unit/commandInjection.test.d.ts.map +1 -0
- package/build/__tests__/unit/commandInjection.test.js +55 -0
- package/build/__tests__/unit/commandInjection.test.js.map +1 -0
- package/build/__tests__/unit/errorCategorization.test.js +14 -0
- package/build/__tests__/unit/errorCategorization.test.js.map +1 -1
- package/build/__tests__/unit/fingerprint.test.js +2 -2
- package/build/__tests__/unit/fingerprint.test.js.map +1 -1
- package/build/__tests__/unit/inputTarget.test.js +46 -0
- package/build/__tests__/unit/inputTarget.test.js.map +1 -1
- package/build/__tests__/unit/logSourceAndroid.test.js +5 -5
- package/build/__tests__/unit/logSourceAndroid.test.js.map +1 -1
- package/build/__tests__/unit/logSourceIos.test.js +10 -6
- package/build/__tests__/unit/logSourceIos.test.js.map +1 -1
- package/build/__tests__/unit/logStack.test.d.ts +2 -0
- package/build/__tests__/unit/logStack.test.d.ts.map +1 -0
- package/build/__tests__/unit/logStack.test.js +86 -0
- package/build/__tests__/unit/logStack.test.js.map +1 -0
- package/build/__tests__/unit/logSymbolication.test.d.ts +2 -0
- package/build/__tests__/unit/logSymbolication.test.d.ts.map +1 -0
- package/build/__tests__/unit/logSymbolication.test.js +215 -0
- package/build/__tests__/unit/logSymbolication.test.js.map +1 -0
- package/build/__tests__/unit/mockRulePush.test.d.ts +2 -0
- package/build/__tests__/unit/mockRulePush.test.d.ts.map +1 -0
- package/build/__tests__/unit/mockRulePush.test.js +65 -0
- package/build/__tests__/unit/mockRulePush.test.js.map +1 -0
- package/build/__tests__/unit/mockRules.test.d.ts +2 -0
- package/build/__tests__/unit/mockRules.test.d.ts.map +1 -0
- package/build/__tests__/unit/mockRules.test.js +111 -0
- package/build/__tests__/unit/mockRules.test.js.map +1 -0
- package/build/__tests__/unit/nativeLogs.test.js +8 -2
- package/build/__tests__/unit/nativeLogs.test.js.map +1 -1
- package/build/__tests__/unit/netInfoPatch.test.d.ts +2 -0
- package/build/__tests__/unit/netInfoPatch.test.d.ts.map +1 -0
- package/build/__tests__/unit/netInfoPatch.test.js +46 -0
- package/build/__tests__/unit/netInfoPatch.test.js.map +1 -0
- package/build/__tests__/unit/networkInterceptorXhr.test.d.ts +2 -0
- package/build/__tests__/unit/networkInterceptorXhr.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkInterceptorXhr.test.js +440 -0
- package/build/__tests__/unit/networkInterceptorXhr.test.js.map +1 -0
- package/build/__tests__/unit/networkMock.test.d.ts +2 -0
- package/build/__tests__/unit/networkMock.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkMock.test.js +183 -0
- package/build/__tests__/unit/networkMock.test.js.map +1 -0
- package/build/__tests__/unit/networkMockRender.test.d.ts +2 -0
- package/build/__tests__/unit/networkMockRender.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkMockRender.test.js +131 -0
- package/build/__tests__/unit/networkMockRender.test.js.map +1 -0
- package/build/__tests__/unit/networkMockTamper.test.d.ts +2 -0
- package/build/__tests__/unit/networkMockTamper.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkMockTamper.test.js +162 -0
- package/build/__tests__/unit/networkMockTamper.test.js.map +1 -0
- package/build/__tests__/unit/networkMockTool.test.d.ts +2 -0
- package/build/__tests__/unit/networkMockTool.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkMockTool.test.js +69 -0
- package/build/__tests__/unit/networkMockTool.test.js.map +1 -0
- package/build/__tests__/unit/networkReplay.test.d.ts +2 -0
- package/build/__tests__/unit/networkReplay.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkReplay.test.js +86 -0
- package/build/__tests__/unit/networkReplay.test.js.map +1 -0
- package/build/__tests__/unit/serialQueue.test.d.ts +2 -0
- package/build/__tests__/unit/serialQueue.test.d.ts.map +1 -0
- package/build/__tests__/unit/serialQueue.test.js +77 -0
- package/build/__tests__/unit/serialQueue.test.js.map +1 -0
- package/build/__tests__/unit/symbolicate.test.js +36 -0
- package/build/__tests__/unit/symbolicate.test.js.map +1 -1
- package/build/__tests__/unit/tap.test.js +0 -48
- package/build/__tests__/unit/tap.test.js.map +1 -1
- package/build/__tests__/unit/textEntry.test.js +53 -0
- package/build/__tests__/unit/textEntry.test.js.map +1 -1
- package/build/__tests__/unit/toolArgInjection.test.d.ts +2 -0
- package/build/__tests__/unit/toolArgInjection.test.d.ts.map +1 -0
- package/build/__tests__/unit/toolArgInjection.test.js +86 -0
- package/build/__tests__/unit/toolArgInjection.test.js.map +1 -0
- package/build/core/android.d.ts.map +1 -1
- package/build/core/android.js +54 -111
- package/build/core/android.js.map +1 -1
- package/build/core/androidSystemBars.d.ts.map +1 -1
- package/build/core/androidSystemBars.js +3 -3
- package/build/core/androidSystemBars.js.map +1 -1
- package/build/core/appRequest.d.ts +1 -0
- package/build/core/appRequest.d.ts.map +1 -1
- package/build/core/appRequest.js +5 -2
- package/build/core/appRequest.js.map +1 -1
- package/build/core/buildInfo.d.ts +3 -0
- package/build/core/buildInfo.d.ts.map +1 -0
- package/build/core/buildInfo.js +6 -0
- package/build/core/buildInfo.js.map +1 -0
- package/build/core/componentSource.d.ts.map +1 -1
- package/build/core/componentSource.js +1 -1
- package/build/core/componentSource.js.map +1 -1
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +15 -5
- package/build/core/connection.js.map +1 -1
- package/build/core/exec.d.ts +5 -0
- package/build/core/exec.d.ts.map +1 -1
- package/build/core/exec.js +34 -1
- package/build/core/exec.js.map +1 -1
- package/build/core/fingerprint.d.ts.map +1 -1
- package/build/core/fingerprint.js +3 -3
- package/build/core/fingerprint.js.map +1 -1
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +21 -1
- package/build/core/guides.js.map +1 -1
- package/build/core/inputTarget.d.ts.map +1 -1
- package/build/core/inputTarget.js +42 -2
- package/build/core/inputTarget.js.map +1 -1
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +14 -17
- package/build/core/ios.js.map +1 -1
- package/build/core/jsLogEvents.d.ts.map +1 -1
- package/build/core/jsLogEvents.js +1 -0
- package/build/core/jsLogEvents.js.map +1 -1
- package/build/core/keyboardRaise.d.ts.map +1 -1
- package/build/core/keyboardRaise.js +3 -3
- package/build/core/keyboardRaise.js.map +1 -1
- package/build/core/logEventFormat.d.ts +2 -0
- package/build/core/logEventFormat.d.ts.map +1 -1
- package/build/core/logEventFormat.js +6 -1
- package/build/core/logEventFormat.js.map +1 -1
- package/build/core/logEvents.d.ts +2 -1
- package/build/core/logEvents.d.ts.map +1 -1
- package/build/core/logEvents.js.map +1 -1
- package/build/core/logSourceAndroid.d.ts +1 -1
- package/build/core/logSourceAndroid.d.ts.map +1 -1
- package/build/core/logSourceAndroid.js +9 -9
- package/build/core/logSourceAndroid.js.map +1 -1
- package/build/core/logSourceIos.d.ts +1 -1
- package/build/core/logSourceIos.d.ts.map +1 -1
- package/build/core/logSourceIos.js +9 -9
- package/build/core/logSourceIos.js.map +1 -1
- package/build/core/logStack.d.ts +6 -0
- package/build/core/logStack.d.ts.map +1 -0
- package/build/core/logStack.js +44 -0
- package/build/core/logStack.js.map +1 -0
- package/build/core/logSymbolication.d.ts +11 -0
- package/build/core/logSymbolication.d.ts.map +1 -0
- package/build/core/logSymbolication.js +91 -0
- package/build/core/logSymbolication.js.map +1 -0
- package/build/core/mockRules.d.ts +30 -0
- package/build/core/mockRules.d.ts.map +1 -0
- package/build/core/mockRules.js +200 -0
- package/build/core/mockRules.js.map +1 -0
- package/build/core/nativeInputValue.js +6 -6
- package/build/core/nativeInputValue.js.map +1 -1
- package/build/core/nativeLogs.js +2 -2
- package/build/core/nativeLogs.js.map +1 -1
- package/build/core/netInfoPatch.d.ts +5 -0
- package/build/core/netInfoPatch.d.ts.map +1 -0
- package/build/core/netInfoPatch.js +88 -0
- package/build/core/netInfoPatch.js.map +1 -0
- package/build/core/network.d.ts.map +1 -1
- package/build/core/network.js +9 -0
- package/build/core/network.js.map +1 -1
- package/build/core/networkInterceptor.d.ts +6 -0
- package/build/core/networkInterceptor.d.ts.map +1 -1
- package/build/core/networkInterceptor.js +660 -13
- package/build/core/networkInterceptor.js.map +1 -1
- package/build/core/replay.d.ts +14 -0
- package/build/core/replay.d.ts.map +1 -0
- package/build/core/replay.js +14 -0
- package/build/core/replay.js.map +1 -0
- package/build/core/serialQueue.d.ts +6 -0
- package/build/core/serialQueue.d.ts.map +1 -0
- package/build/core/serialQueue.js +18 -0
- package/build/core/serialQueue.js.map +1 -0
- package/build/core/symbolicate.d.ts +2 -2
- package/build/core/symbolicate.d.ts.map +1 -1
- package/build/core/symbolicate.js +2 -2
- package/build/core/symbolicate.js.map +1 -1
- package/build/core/telemetry.d.ts.map +1 -1
- package/build/core/telemetry.js +5 -3
- package/build/core/telemetry.js.map +1 -1
- package/build/core/textEntry.d.ts +2 -0
- package/build/core/textEntry.d.ts.map +1 -1
- package/build/core/textEntry.js +30 -3
- package/build/core/textEntry.js.map +1 -1
- package/build/core/types.d.ts +11 -0
- package/build/core/types.d.ts.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +55 -9
- package/build/index.js.map +1 -1
- package/build/tools/deviceTools.d.ts.map +1 -1
- package/build/tools/deviceTools.js +4 -1
- package/build/tools/deviceTools.js.map +1 -1
- package/build/tools/interactionTools.d.ts.map +1 -1
- package/build/tools/interactionTools.js +6 -3
- package/build/tools/interactionTools.js.map +1 -1
- package/build/tools/logTools.d.ts.map +1 -1
- package/build/tools/logTools.js +16 -3
- package/build/tools/logTools.js.map +1 -1
- package/build/tools/networkTools.d.ts.map +1 -1
- package/build/tools/networkTools.js +196 -4
- package/build/tools/networkTools.js.map +1 -1
- package/build/tools/requestTools.d.ts.map +1 -1
- package/build/tools/requestTools.js +93 -26
- package/build/tools/requestTools.js.map +1 -1
- package/package.json +3 -3
- package/tools.json +36 -1
- package/build/__tests__/helpers/fake-cdp-server.d.ts +0 -53
- package/build/__tests__/helpers/fake-cdp-server.d.ts.map +0 -1
- package/build/__tests__/helpers/fake-cdp-server.js +0 -116
- package/build/__tests__/helpers/fake-cdp-server.js.map +0 -1
- package/build/__tests__/helpers/tap-test-helpers.d.ts +0 -13
- package/build/__tests__/helpers/tap-test-helpers.d.ts.map +0 -1
- package/build/__tests__/helpers/tap-test-helpers.js +0 -103
- package/build/__tests__/helpers/tap-test-helpers.js.map +0 -1
- package/build/__tests__/integration/auto-reconnect.test.d.ts +0 -2
- package/build/__tests__/integration/auto-reconnect.test.d.ts.map +0 -1
- package/build/__tests__/integration/auto-reconnect.test.js +0 -108
- package/build/__tests__/integration/auto-reconnect.test.js.map +0 -1
- package/build/__tests__/integration/connection-health.test.d.ts +0 -2
- package/build/__tests__/integration/connection-health.test.d.ts.map +0 -1
- package/build/__tests__/integration/connection-health.test.js +0 -155
- package/build/__tests__/integration/connection-health.test.js.map +0 -1
- package/build/__tests__/integration/execute-in-app.test.d.ts +0 -2
- package/build/__tests__/integration/execute-in-app.test.d.ts.map +0 -1
- package/build/__tests__/integration/execute-in-app.test.js +0 -114
- package/build/__tests__/integration/execute-in-app.test.js.map +0 -1
- package/build/__tests__/integration/native-logs.test.d.ts +0 -2
- package/build/__tests__/integration/native-logs.test.d.ts.map +0 -1
- package/build/__tests__/integration/native-logs.test.js +0 -34
- package/build/__tests__/integration/native-logs.test.js.map +0 -1
- package/build/__tests__/integration/per-call-timeout.test.d.ts +0 -2
- package/build/__tests__/integration/per-call-timeout.test.d.ts.map +0 -1
- package/build/__tests__/integration/per-call-timeout.test.js +0 -99
- package/build/__tests__/integration/per-call-timeout.test.js.map +0 -1
- package/build/__tests__/integration/socket-close-midflight.test.d.ts +0 -2
- package/build/__tests__/integration/socket-close-midflight.test.d.ts.map +0 -1
- package/build/__tests__/integration/socket-close-midflight.test.js +0 -96
- package/build/__tests__/integration/socket-close-midflight.test.js.map +0 -1
- package/build/__tests__/integration/stale-target-probe.test.d.ts +0 -2
- package/build/__tests__/integration/stale-target-probe.test.d.ts.map +0 -1
- package/build/__tests__/integration/stale-target-probe.test.js +0 -70
- package/build/__tests__/integration/stale-target-probe.test.js.map +0 -1
- package/build/__tests__/integration/tap-strategies.test.d.ts +0 -2
- package/build/__tests__/integration/tap-strategies.test.d.ts.map +0 -1
- package/build/__tests__/integration/tap-strategies.test.js +0 -296
- package/build/__tests__/integration/tap-strategies.test.js.map +0 -1
- package/build/__tests__/integration/text-input.test.d.ts +0 -2
- package/build/__tests__/integration/text-input.test.d.ts.map +0 -1
- package/build/__tests__/integration/text-input.test.js +0 -142
- package/build/__tests__/integration/text-input.test.js.map +0 -1
- package/build/__tests__/integration/tools.test.d.ts +0 -2
- package/build/__tests__/integration/tools.test.d.ts.map +0 -1
- package/build/__tests__/integration/tools.test.js +0 -221
- package/build/__tests__/integration/tools.test.js.map +0 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { getNextMessageId, getEpoch } from "./state.js";
|
|
2
|
+
import { recordHit } from "./mockRules.js";
|
|
3
|
+
export const REQUEST_BODY_CAP = 8 * 1024;
|
|
4
|
+
export const RESPONSE_BODY_CAP = 32 * 1024;
|
|
2
5
|
export function getInterceptorScript() {
|
|
3
6
|
return `(function() { try {
|
|
4
7
|
if (globalThis.__RN_NET_INJECTED__) return;
|
|
@@ -6,6 +9,8 @@ export function getInterceptorScript() {
|
|
|
6
9
|
|
|
7
10
|
var _counter = 0;
|
|
8
11
|
var _prefix = 'js-' + Math.random().toString(36).substring(2, 6) + '-';
|
|
12
|
+
var _REQ_CAP = ${REQUEST_BODY_CAP};
|
|
13
|
+
var _RES_CAP = ${RESPONSE_BODY_CAP};
|
|
9
14
|
|
|
10
15
|
function _genId() {
|
|
11
16
|
return _prefix + (++_counter);
|
|
@@ -16,17 +21,538 @@ export function getInterceptorScript() {
|
|
|
16
21
|
// flips this flag via Runtime.evaluate once it detects __RN_AI_DEVTOOLS__,
|
|
17
22
|
// so the wrapper stops emitting debug lines and CDP traffic.
|
|
18
23
|
if (globalThis.__RN_NET_DISABLED__) return;
|
|
24
|
+
_reportAlways(evt);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Mock events bypass the SDK suppression gate. That flag exists so two
|
|
28
|
+
// capture layers do not both report the same request; a mock is not
|
|
29
|
+
// captured traffic, and the SDK knows nothing about it. Suppressing it
|
|
30
|
+
// would hide altered traffic from the agent and freeze every server-side
|
|
31
|
+
// hit counter at zero — the one thing a mock must never do.
|
|
32
|
+
function _reportAlways(evt) {
|
|
19
33
|
try {
|
|
20
34
|
console.debug('__RN_NET__:' + JSON.stringify(evt));
|
|
21
35
|
} catch(e) {}
|
|
22
36
|
}
|
|
23
37
|
|
|
38
|
+
// Bodies travel inline in a console.debug line shared with the app's own
|
|
39
|
+
// output, so they are capped here rather than at read time.
|
|
40
|
+
function _cap(s, limit) {
|
|
41
|
+
if (typeof s !== 'string') return undefined;
|
|
42
|
+
if (s.length <= limit) return s;
|
|
43
|
+
return s.slice(0, limit) + '... [truncated ' + (s.length - limit) + ' bytes]';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function _bodyToString(b) {
|
|
47
|
+
if (b === null || b === undefined) return undefined;
|
|
48
|
+
try {
|
|
49
|
+
if (typeof b === 'string') return b;
|
|
50
|
+
if (typeof URLSearchParams !== 'undefined' && b instanceof URLSearchParams) return b.toString();
|
|
51
|
+
if (typeof FormData !== 'undefined' && b instanceof FormData) return '[FormData]';
|
|
52
|
+
if (typeof Blob !== 'undefined' && b instanceof Blob) {
|
|
53
|
+
return '[binary body, ' + (typeof b.size === 'number' ? b.size : 0) + ' bytes]';
|
|
54
|
+
}
|
|
55
|
+
if (typeof ArrayBuffer !== 'undefined' && b instanceof ArrayBuffer) {
|
|
56
|
+
return '[binary body, ' + b.byteLength + ' bytes]';
|
|
57
|
+
}
|
|
58
|
+
if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView && ArrayBuffer.isView(b)) {
|
|
59
|
+
return '[binary body, ' + b.byteLength + ' bytes]';
|
|
60
|
+
}
|
|
61
|
+
if (typeof b === 'object') {
|
|
62
|
+
// Duck-typed fallbacks: instanceof misses values built in another
|
|
63
|
+
// realm, and String(binary) yields a useless '[object ArrayBuffer]'.
|
|
64
|
+
if (typeof b.getParts === 'function') return '[FormData]';
|
|
65
|
+
if (typeof b.byteLength === 'number') return '[binary body, ' + b.byteLength + ' bytes]';
|
|
66
|
+
if (typeof b.size === 'number') return '[binary body, ' + b.size + ' bytes]';
|
|
67
|
+
try { return JSON.stringify(b); } catch(e) {}
|
|
68
|
+
}
|
|
69
|
+
return String(b);
|
|
70
|
+
} catch(e) { return undefined; }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function _parseHeaders(raw) {
|
|
74
|
+
var out = {};
|
|
75
|
+
if (typeof raw !== 'string' || raw.length === 0) return out;
|
|
76
|
+
var lines = raw.split('\\n');
|
|
77
|
+
for (var i = 0; i < lines.length; i++) {
|
|
78
|
+
var idx = lines[i].indexOf(':');
|
|
79
|
+
if (idx <= 0) continue;
|
|
80
|
+
out[lines[i].slice(0, idx).trim().toLowerCase()] = lines[i].slice(idx + 1).trim();
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function _isTextual(ct) {
|
|
86
|
+
if (!ct) return false;
|
|
87
|
+
var v = String(ct).toLowerCase();
|
|
88
|
+
return v.indexOf('json') >= 0 || v.indexOf('text/') === 0 || v.indexOf('xml') >= 0 ||
|
|
89
|
+
v.indexOf('javascript') >= 0 || v.indexOf('x-www-form-urlencoded') >= 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _responseBody(xhr) {
|
|
93
|
+
try {
|
|
94
|
+
var rt = xhr.responseType;
|
|
95
|
+
if (rt === '' || rt === undefined || rt === 'text') return _cap(xhr.responseText, _RES_CAP);
|
|
96
|
+
if (rt === 'json') return _cap(JSON.stringify(xhr.response), _RES_CAP);
|
|
97
|
+
if (rt === 'arraybuffer') {
|
|
98
|
+
var ab = xhr.response;
|
|
99
|
+
return '[binary response, ' + ((ab && ab.byteLength) || 0) + ' bytes]';
|
|
100
|
+
}
|
|
101
|
+
if (rt === 'blob') {
|
|
102
|
+
var b = xhr.response;
|
|
103
|
+
return '[binary response, ' + ((b && b.size) || 0) + ' bytes]';
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
} catch(e) { return undefined; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// RN's fetch polyfill sets responseType='blob', so a JSON response arrives
|
|
110
|
+
// as a Blob and responseText throws. Read it out of band and patch the
|
|
111
|
+
// entry with a follow-up event rather than reporting a binary placeholder.
|
|
112
|
+
function _readBlobText(id, blob) {
|
|
113
|
+
try {
|
|
114
|
+
var reader = new FileReader();
|
|
115
|
+
reader.onload = function() {
|
|
116
|
+
try {
|
|
117
|
+
if (typeof reader.result === 'string') {
|
|
118
|
+
_report({type: 'body', id: id, body: _cap(reader.result, _RES_CAP)});
|
|
119
|
+
}
|
|
120
|
+
} catch(e) {}
|
|
121
|
+
};
|
|
122
|
+
reader.readAsText(blob);
|
|
123
|
+
} catch(e) {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---- mock layer ------------------------------------------------------
|
|
127
|
+
// Rules are pushed from the server (buildMockPushScript). Never reset here:
|
|
128
|
+
// a re-injection into a live context must not drop them.
|
|
129
|
+
if (!globalThis.__RN_NET_MOCKS__) globalThis.__RN_NET_MOCKS__ = [];
|
|
130
|
+
|
|
131
|
+
function _ruleMatches(rule, method, url) {
|
|
132
|
+
try {
|
|
133
|
+
if (rule.method && String(rule.method).toUpperCase() !== method) return false;
|
|
134
|
+
var pat = String(rule.url === undefined || rule.url === null ? '' : rule.url);
|
|
135
|
+
if (pat.length > 1 && pat.charAt(0) === '/' && pat.charAt(pat.length - 1) === '/') {
|
|
136
|
+
// Slash-wrapped means regex. Compiled per call rather than cached:
|
|
137
|
+
// the rule list is short and re-pushed on every context creation.
|
|
138
|
+
// The server rejects catastrophic patterns before they get here.
|
|
139
|
+
return new RegExp(pat.slice(1, -1)).test(url);
|
|
140
|
+
}
|
|
141
|
+
return url.indexOf(pat) !== -1;
|
|
142
|
+
} catch (e) { return false; }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function _matchRule(method, url) {
|
|
146
|
+
var rules = globalThis.__RN_NET_MOCKS__;
|
|
147
|
+
if (!rules || !rules.length) return null;
|
|
148
|
+
for (var i = 0; i < rules.length; i++) {
|
|
149
|
+
var r = rules[i];
|
|
150
|
+
if (!r || r.__spent) continue;
|
|
151
|
+
if (_ruleMatches(r, method, url)) {
|
|
152
|
+
if (typeof r.times === 'number') {
|
|
153
|
+
r.__used = (r.__used || 0) + 1;
|
|
154
|
+
if (r.__used >= r.times) r.__spent = true;
|
|
155
|
+
}
|
|
156
|
+
return r;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// A plain assignment is silently dropped when the prototype exposes a
|
|
163
|
+
// getter with no setter — which is exactly how React Native's
|
|
164
|
+
// XMLHttpRequest defines responseText and response. Verify the write took
|
|
165
|
+
// and fall back to an own data property, which shadows the accessor.
|
|
166
|
+
function _setProp(obj, name, value) {
|
|
167
|
+
try {
|
|
168
|
+
obj[name] = value;
|
|
169
|
+
if (obj[name] === value) return;
|
|
170
|
+
} catch (e) {}
|
|
171
|
+
try {
|
|
172
|
+
Object.defineProperty(obj, name, {
|
|
173
|
+
configurable: true, enumerable: true, writable: true, value: value
|
|
174
|
+
});
|
|
175
|
+
} catch (e) {}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function _headersToText(headers) {
|
|
179
|
+
var out = '';
|
|
180
|
+
if (!headers || typeof headers !== 'object') return out;
|
|
181
|
+
try {
|
|
182
|
+
for (var k in headers) {
|
|
183
|
+
if (Object.prototype.hasOwnProperty.call(headers, k)) {
|
|
184
|
+
out += String(k).toLowerCase() + ': ' + String(headers[k]) + '\\r\\n';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch (e) {}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// getAllResponseHeaders / getResponseHeader are methods, not properties, so
|
|
192
|
+
// _setProp cannot reach them. Shadow them per instance for this response.
|
|
193
|
+
function _installMockHeaders(xhr, headersText) {
|
|
194
|
+
try {
|
|
195
|
+
var text = headersText || '';
|
|
196
|
+
Object.defineProperty(xhr, 'getAllResponseHeaders', {
|
|
197
|
+
configurable: true, enumerable: false, writable: true,
|
|
198
|
+
value: function () { return text; }
|
|
199
|
+
});
|
|
200
|
+
Object.defineProperty(xhr, 'getResponseHeader', {
|
|
201
|
+
configurable: true, enumerable: false, writable: true,
|
|
202
|
+
value: function (name) {
|
|
203
|
+
var v = _parseHeaders(text)[String(name).toLowerCase()];
|
|
204
|
+
return (v === undefined) ? null : v;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
} catch (e) {}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Fires the on<event> property handler and every addEventListener listener,
|
|
211
|
+
// which is what a real XHR does. dispatchEvent is deliberately not used:
|
|
212
|
+
// RN's EventTarget rejects a plain object, and the listener list below is
|
|
213
|
+
// complete because addEventListener is patched on the prototype.
|
|
214
|
+
function _fire(xhr, type) {
|
|
215
|
+
try {
|
|
216
|
+
var evt = { type: type, target: xhr, currentTarget: xhr };
|
|
217
|
+
var prop = xhr['on' + type];
|
|
218
|
+
if (typeof prop === 'function') { try { prop.call(xhr, evt); } catch (e) {} }
|
|
219
|
+
var ls = xhr.__rn_net_listeners__ && xhr.__rn_net_listeners__[type];
|
|
220
|
+
if (ls) {
|
|
221
|
+
var copy = ls.slice();
|
|
222
|
+
for (var i = 0; i < copy.length; i++) {
|
|
223
|
+
try { copy[i].call(xhr, evt); } catch (e) {}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Writes a synthetic response onto the app's own XHR and fires its
|
|
231
|
+
* handlers, so the app reads mocked values through the normal API.
|
|
232
|
+
* Also emits the terminal capture event — without it the request would sit
|
|
233
|
+
* in the buffer as pending forever.
|
|
234
|
+
*/
|
|
235
|
+
function _deliverMock(xhr, rule, s, bodyText, statusOverride, headersText, warning) {
|
|
236
|
+
var go = function () {
|
|
237
|
+
try {
|
|
238
|
+
var duration = Date.now() - s.start;
|
|
239
|
+
s.done = true;
|
|
240
|
+
if (rule.networkError) {
|
|
241
|
+
_setProp(xhr, 'readyState', 4);
|
|
242
|
+
_setProp(xhr, 'status', 0);
|
|
243
|
+
_setProp(xhr, 'statusText', '');
|
|
244
|
+
_reportAlways({ type: 'mock', id: s.id, ruleId: rule.id, warning: warning });
|
|
245
|
+
_report({ type: 'error', id: s.id, error: String(rule.networkError), duration: duration, mocked: true });
|
|
246
|
+
_fire(xhr, 'readystatechange');
|
|
247
|
+
_fire(xhr, 'error');
|
|
248
|
+
_fire(xhr, 'loadend');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
var text = (bodyText === null || bodyText === undefined) ? '' : String(bodyText);
|
|
252
|
+
var status = (typeof statusOverride === 'number') ? statusOverride : 200;
|
|
253
|
+
var text2 = headersText || '';
|
|
254
|
+
_setProp(xhr, 'readyState', 4);
|
|
255
|
+
_setProp(xhr, 'status', status);
|
|
256
|
+
_setProp(xhr, 'statusText', '');
|
|
257
|
+
_setProp(xhr, 'responseText', text);
|
|
258
|
+
_setProp(xhr, 'response', text);
|
|
259
|
+
_installMockHeaders(xhr, text2);
|
|
260
|
+
_reportAlways({ type: 'mock', id: s.id, ruleId: rule.id, warning: warning });
|
|
261
|
+
var evt = {
|
|
262
|
+
type: 'response', id: s.id, status: status, statusText: '',
|
|
263
|
+
duration: duration, responseHeaders: _parseHeaders(text2),
|
|
264
|
+
body: _cap(text, _RES_CAP), mocked: true
|
|
265
|
+
};
|
|
266
|
+
if (evt.responseHeaders['content-type']) evt.mimeType = evt.responseHeaders['content-type'];
|
|
267
|
+
_report(evt);
|
|
268
|
+
_fire(xhr, 'readystatechange');
|
|
269
|
+
_fire(xhr, 'load');
|
|
270
|
+
_fire(xhr, 'loadend');
|
|
271
|
+
} catch (e) {}
|
|
272
|
+
};
|
|
273
|
+
// Always deferred, never synchronous: a real XHR response never arrives
|
|
274
|
+
// inside send(), and code that assumes it does breaks in surprising ways.
|
|
275
|
+
var delay = (typeof rule.delayMs === 'number' && rule.delayMs > 0) ? rule.delayMs : 0;
|
|
276
|
+
setTimeout(go, delay);
|
|
277
|
+
}
|
|
278
|
+
function _setPath(obj, path, value) {
|
|
279
|
+
var parts = String(path).split('.');
|
|
280
|
+
var cur = obj;
|
|
281
|
+
for (var i = 0; i < parts.length - 1; i++) {
|
|
282
|
+
if (cur[parts[i]] === null || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {};
|
|
283
|
+
cur = cur[parts[i]];
|
|
284
|
+
}
|
|
285
|
+
cur[parts[parts.length - 1]] = value;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function _removePath(obj, path) {
|
|
289
|
+
var parts = String(path).split('.');
|
|
290
|
+
var cur = obj;
|
|
291
|
+
for (var i = 0; i < parts.length - 1; i++) {
|
|
292
|
+
if (cur === null || typeof cur !== 'object') return;
|
|
293
|
+
cur = cur[parts[i]];
|
|
294
|
+
}
|
|
295
|
+
if (cur && typeof cur === 'object') delete cur[parts[parts.length - 1]];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Returns { body, warning }. A non-JSON body is passed through untouched
|
|
300
|
+
* with a warning rather than replaced: handing the app a mangled body would
|
|
301
|
+
* turn a tamper into a parse error the agent then has to debug.
|
|
302
|
+
*/
|
|
303
|
+
function _applyTamper(rule, bodyText) {
|
|
304
|
+
if (typeof rule.bodyReplace === 'string') return { body: rule.bodyReplace };
|
|
305
|
+
var wantsJson = rule.set || rule.remove;
|
|
306
|
+
if (!wantsJson) return { body: bodyText };
|
|
307
|
+
var parsed;
|
|
308
|
+
try {
|
|
309
|
+
parsed = JSON.parse(bodyText);
|
|
310
|
+
} catch (e) {
|
|
311
|
+
return { body: bodyText, warning: 'tamper skipped - response was not JSON' };
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
if (rule.remove) {
|
|
315
|
+
for (var i = 0; i < rule.remove.length; i++) _removePath(parsed, rule.remove[i]);
|
|
316
|
+
}
|
|
317
|
+
if (rule.set) {
|
|
318
|
+
for (var k in rule.set) {
|
|
319
|
+
if (Object.prototype.hasOwnProperty.call(rule.set, k)) _setPath(parsed, k, rule.set[k]);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return { body: JSON.stringify(parsed) };
|
|
323
|
+
} catch (e) {
|
|
324
|
+
return { body: bodyText, warning: 'tamper failed: ' + String(e && e.message ? e.message : e) };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// ---- end mock layer --------------------------------------------------
|
|
328
|
+
|
|
329
|
+
function _patchXHR() {
|
|
330
|
+
try {
|
|
331
|
+
if (typeof XMLHttpRequest === 'undefined') return false;
|
|
332
|
+
var proto = XMLHttpRequest.prototype;
|
|
333
|
+
if (!proto || typeof proto.open !== 'function' || typeof proto.send !== 'function') return false;
|
|
334
|
+
if (proto.open.__rn_net_wrapped__) {
|
|
335
|
+
// Already patched (phase 2 after phase 1). The flag still has to be
|
|
336
|
+
// set, or the fetch wrapper would report alongside a live XHR layer.
|
|
337
|
+
globalThis.__RN_NET_XHR_ACTIVE__ = true;
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
var origOpen = proto.open;
|
|
342
|
+
var origSend = proto.send;
|
|
343
|
+
var origSetHeader = proto.setRequestHeader;
|
|
344
|
+
var _states = (typeof WeakMap === 'function') ? new WeakMap() : null;
|
|
345
|
+
|
|
346
|
+
function _getState(x) {
|
|
347
|
+
try { return _states ? _states.get(x) : x.__rn_net_state__; } catch(e) { return undefined; }
|
|
348
|
+
}
|
|
349
|
+
function _setState(x, s) {
|
|
350
|
+
try { if (_states) { _states.set(x, s); } else { x.__rn_net_state__ = s; } } catch(e) {}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
var patchedOpen = function(method, url) {
|
|
354
|
+
try {
|
|
355
|
+
_setState(this, {
|
|
356
|
+
id: _genId(),
|
|
357
|
+
method: String(method === undefined || method === null ? 'GET' : method).toUpperCase(),
|
|
358
|
+
url: (typeof url === 'string') ? url : String(url),
|
|
359
|
+
headers: {},
|
|
360
|
+
start: 0,
|
|
361
|
+
done: false
|
|
362
|
+
});
|
|
363
|
+
} catch(e) {}
|
|
364
|
+
return origOpen.apply(this, arguments);
|
|
365
|
+
};
|
|
366
|
+
patchedOpen.__rn_net_wrapped__ = true;
|
|
367
|
+
proto.open = patchedOpen;
|
|
368
|
+
|
|
369
|
+
if (typeof origSetHeader === 'function') {
|
|
370
|
+
proto.setRequestHeader = function(name, value) {
|
|
371
|
+
try {
|
|
372
|
+
var s = _getState(this);
|
|
373
|
+
if (s) s.headers[String(name)] = String(value);
|
|
374
|
+
} catch(e) {}
|
|
375
|
+
return origSetHeader.apply(this, arguments);
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Recorded so _fire can reach the app's handlers when a mock replaces
|
|
380
|
+
// the response. There is no other way to enumerate them.
|
|
381
|
+
var origAddEventListener = proto.addEventListener;
|
|
382
|
+
if (typeof origAddEventListener === 'function') {
|
|
383
|
+
proto.addEventListener = function(type, fn) {
|
|
384
|
+
try {
|
|
385
|
+
if (!this.__rn_net_listeners__) this.__rn_net_listeners__ = {};
|
|
386
|
+
if (!this.__rn_net_listeners__[type]) this.__rn_net_listeners__[type] = [];
|
|
387
|
+
this.__rn_net_listeners__[type].push(fn);
|
|
388
|
+
} catch(e) {}
|
|
389
|
+
return origAddEventListener.apply(this, arguments);
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Runs the real request on a second XHR so the app's own request never
|
|
395
|
+
* reaches the wire, then hands the app a response we synthesized.
|
|
396
|
+
*
|
|
397
|
+
* This is what removes the listener-ordering hazard. Letting the app's
|
|
398
|
+
* request fly and mutating it in a load listener does not work: apps
|
|
399
|
+
* commonly assign xhr.onload BEFORE send(), and a property handler runs
|
|
400
|
+
* ahead of any listener added afterwards, so the app would read the
|
|
401
|
+
* untampered response.
|
|
402
|
+
*
|
|
403
|
+
* Lives inside _patchXHR because it must call the ORIGINAL open/send —
|
|
404
|
+
* a shadow that went through the patched ones would be reported as a
|
|
405
|
+
* second request and could match a rule itself.
|
|
406
|
+
*/
|
|
407
|
+
var _shadowFetch = function(appXhr, s, rule, body) {
|
|
408
|
+
try {
|
|
409
|
+
var shadow = new XMLHttpRequest();
|
|
410
|
+
origOpen.call(shadow, s.method, s.url);
|
|
411
|
+
if (typeof origSetHeader === 'function') {
|
|
412
|
+
for (var name in s.headers) {
|
|
413
|
+
if (Object.prototype.hasOwnProperty.call(s.headers, name)) {
|
|
414
|
+
try { origSetHeader.call(shadow, name, s.headers[name]); } catch (e) {}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
var done = false;
|
|
419
|
+
var onDone = function() {
|
|
420
|
+
if (done) return;
|
|
421
|
+
done = true;
|
|
422
|
+
var real = '';
|
|
423
|
+
try { real = shadow.responseText || ''; } catch (e) {}
|
|
424
|
+
var out = _applyTamper(rule, real);
|
|
425
|
+
var headersText = '';
|
|
426
|
+
try {
|
|
427
|
+
headersText = (typeof shadow.getAllResponseHeaders === 'function')
|
|
428
|
+
? shadow.getAllResponseHeaders() : '';
|
|
429
|
+
} catch (e) {}
|
|
430
|
+
var status = (typeof rule.status === 'number') ? rule.status : shadow.status;
|
|
431
|
+
_deliverMock(appXhr, rule, s, out.body, status, headersText, out.warning);
|
|
432
|
+
};
|
|
433
|
+
var onFail = function() {
|
|
434
|
+
if (done) return;
|
|
435
|
+
done = true;
|
|
436
|
+
_deliverMock(appXhr, { id: rule.id, networkError: 'Network request failed' },
|
|
437
|
+
s, null, 0, '', 'shadow request failed');
|
|
438
|
+
};
|
|
439
|
+
if (typeof shadow.addEventListener === 'function') {
|
|
440
|
+
shadow.addEventListener('load', onDone);
|
|
441
|
+
shadow.addEventListener('error', onFail);
|
|
442
|
+
shadow.addEventListener('abort', onFail);
|
|
443
|
+
shadow.addEventListener('timeout', onFail);
|
|
444
|
+
}
|
|
445
|
+
origSend.call(shadow, body);
|
|
446
|
+
} catch (e) {
|
|
447
|
+
_deliverMock(appXhr, { id: rule.id, networkError: 'Network request failed' },
|
|
448
|
+
s, null, 0, '', 'shadow request threw');
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
proto.send = function(body) {
|
|
453
|
+
var self = this;
|
|
454
|
+
try {
|
|
455
|
+
var s = _getState(self);
|
|
456
|
+
if (s && !s.done && s.start === 0) {
|
|
457
|
+
s.start = Date.now();
|
|
458
|
+
var _rule = _matchRule(s.method, s.url);
|
|
459
|
+
|
|
460
|
+
_report({
|
|
461
|
+
type: 'request',
|
|
462
|
+
id: s.id,
|
|
463
|
+
method: s.method,
|
|
464
|
+
url: s.url,
|
|
465
|
+
timestamp: s.start,
|
|
466
|
+
headers: s.headers,
|
|
467
|
+
body: _cap(_bodyToString(body), _REQ_CAP),
|
|
468
|
+
// Tells the server this request will never reach the wire, so
|
|
469
|
+
// the CDP Network domain cannot possibly report it and the
|
|
470
|
+
// usual dedupe gate must not drop ours. Without it a mocked
|
|
471
|
+
// request is invisible on every CDP-capture target.
|
|
472
|
+
mocked: _rule ? true : undefined
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
if (_rule) {
|
|
476
|
+
s.mocked = true;
|
|
477
|
+
if (_rule.mode === 'tamper') {
|
|
478
|
+
_shadowFetch(self, s, _rule, body);
|
|
479
|
+
} else {
|
|
480
|
+
_deliverMock(self, _rule, s, _rule.body, _rule.status,
|
|
481
|
+
_headersToText(_rule.headers), undefined);
|
|
482
|
+
}
|
|
483
|
+
// The app's request never reaches the wire: returning here is
|
|
484
|
+
// what makes the mock authoritative rather than a race with
|
|
485
|
+
// the real response.
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
var _finish = function(kind, errText) {
|
|
490
|
+
if (s.done) return;
|
|
491
|
+
s.done = true;
|
|
492
|
+
var duration = Date.now() - s.start;
|
|
493
|
+
if (kind !== 'load') {
|
|
494
|
+
_report({type: 'error', id: s.id, error: errText, duration: duration});
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
var evt = {
|
|
498
|
+
type: 'response',
|
|
499
|
+
id: s.id,
|
|
500
|
+
status: self.status,
|
|
501
|
+
statusText: self.statusText || '',
|
|
502
|
+
duration: duration
|
|
503
|
+
};
|
|
504
|
+
try {
|
|
505
|
+
var rh = (typeof self.getAllResponseHeaders === 'function')
|
|
506
|
+
? _parseHeaders(self.getAllResponseHeaders()) : {};
|
|
507
|
+
evt.responseHeaders = rh;
|
|
508
|
+
if (rh['content-type']) evt.mimeType = rh['content-type'];
|
|
509
|
+
if (rh['content-length']) {
|
|
510
|
+
var cl = Number(rh['content-length']);
|
|
511
|
+
if (!isNaN(cl)) evt.contentLength = cl;
|
|
512
|
+
}
|
|
513
|
+
} catch(e) {}
|
|
514
|
+
try {
|
|
515
|
+
if (self.responseURL && self.responseURL !== s.url) evt.url = self.responseURL;
|
|
516
|
+
} catch(e) {}
|
|
517
|
+
var textualBlob = false;
|
|
518
|
+
try {
|
|
519
|
+
textualBlob = self.responseType === 'blob' && _isTextual(evt.mimeType) &&
|
|
520
|
+
typeof FileReader !== 'undefined' && typeof Blob !== 'undefined' &&
|
|
521
|
+
self.response instanceof Blob;
|
|
522
|
+
} catch(e) {}
|
|
523
|
+
if (!textualBlob) evt.body = _responseBody(self);
|
|
524
|
+
_report(evt);
|
|
525
|
+
if (textualBlob) _readBlobText(s.id, self.response);
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
if (typeof self.addEventListener === 'function') {
|
|
529
|
+
self.addEventListener('load', function() { _finish('load'); });
|
|
530
|
+
self.addEventListener('error', function() { _finish('error', 'Network request failed'); });
|
|
531
|
+
self.addEventListener('abort', function() { _finish('error', 'Request aborted'); });
|
|
532
|
+
self.addEventListener('timeout', function() { _finish('error', 'Request timed out'); });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
} catch(e) {}
|
|
536
|
+
return origSend.apply(self, arguments);
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
globalThis.__RN_NET_XHR_ACTIVE__ = true;
|
|
540
|
+
return true;
|
|
541
|
+
} catch(e) { return false; }
|
|
542
|
+
}
|
|
543
|
+
|
|
24
544
|
function _wrapFetch(origFetch) {
|
|
25
545
|
if (typeof origFetch !== 'function') return origFetch;
|
|
26
546
|
if (origFetch.__rn_net_wrapped__) return origFetch;
|
|
27
547
|
|
|
28
548
|
var wrapped = function(input, init) {
|
|
29
|
-
|
|
549
|
+
// Decided once per call, not per report: if the XHR patch lands
|
|
550
|
+
// mid-flight, a request whose start was reported here must still
|
|
551
|
+
// have its response reported here, or it hangs as 'pending' forever.
|
|
552
|
+
var suppressed = !!globalThis.__RN_NET_XHR_ACTIVE__;
|
|
553
|
+
// Don't burn an id on a suppressed call — it would leave gaps in the
|
|
554
|
+
// sequence the XHR layer is handing out for the same requests.
|
|
555
|
+
var id = suppressed ? '' : _genId();
|
|
30
556
|
var method = (init && init.method) ? init.method : 'GET';
|
|
31
557
|
var url = '';
|
|
32
558
|
if (typeof input === 'string') {
|
|
@@ -38,29 +564,37 @@ export function getInterceptorScript() {
|
|
|
38
564
|
}
|
|
39
565
|
var startTime = Date.now();
|
|
40
566
|
|
|
41
|
-
|
|
567
|
+
if (!suppressed) {
|
|
568
|
+
_report({type: 'request', id: id, method: method, url: url, timestamp: startTime});
|
|
569
|
+
}
|
|
42
570
|
|
|
43
571
|
try {
|
|
44
572
|
return origFetch.apply(globalThis, arguments).then(
|
|
45
573
|
function(response) {
|
|
46
574
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
575
|
+
if (!suppressed) {
|
|
576
|
+
var duration = Date.now() - startTime;
|
|
577
|
+
_report({type: 'response', id: id, status: response.status, statusText: response.statusText || '', duration: duration});
|
|
578
|
+
}
|
|
49
579
|
} catch(e) {}
|
|
50
580
|
return response;
|
|
51
581
|
},
|
|
52
582
|
function(err) {
|
|
53
583
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
584
|
+
if (!suppressed) {
|
|
585
|
+
var duration = Date.now() - startTime;
|
|
586
|
+
_report({type: 'error', id: id, error: (err && err.message) ? err.message : 'Fetch failed', duration: duration});
|
|
587
|
+
}
|
|
56
588
|
} catch(e) {}
|
|
57
589
|
throw err;
|
|
58
590
|
}
|
|
59
591
|
);
|
|
60
592
|
} catch(e) {
|
|
61
593
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
594
|
+
if (!suppressed) {
|
|
595
|
+
var duration = Date.now() - startTime;
|
|
596
|
+
_report({type: 'error', id: id, error: (e && e.message) ? e.message : 'Fetch failed', duration: duration});
|
|
597
|
+
}
|
|
64
598
|
} catch(e2) {}
|
|
65
599
|
throw e;
|
|
66
600
|
}
|
|
@@ -69,7 +603,9 @@ export function getInterceptorScript() {
|
|
|
69
603
|
return wrapped;
|
|
70
604
|
}
|
|
71
605
|
|
|
72
|
-
// Phase 1:
|
|
606
|
+
// Phase 1: patch XHR (axios and RN's own fetch polyfill both ride on it),
|
|
607
|
+
// then try to trap fetch assignment via defineProperty (best-effort)
|
|
608
|
+
_patchXHR();
|
|
73
609
|
try {
|
|
74
610
|
if (typeof globalThis.fetch === 'function') {
|
|
75
611
|
globalThis.fetch = _wrapFetch(globalThis.fetch);
|
|
@@ -83,17 +619,58 @@ export function getInterceptorScript() {
|
|
|
83
619
|
});
|
|
84
620
|
} catch(e) {}
|
|
85
621
|
|
|
86
|
-
// Phase 2: wrap fetch after module init completes
|
|
87
|
-
|
|
622
|
+
// Phase 2: retry XHR and wrap fetch after module init completes.
|
|
623
|
+
//
|
|
624
|
+
// Bounded retry rather than a single tick. On a cold app launch the CDP
|
|
625
|
+
// connect can land before the JS bundle has defined XMLHttpRequest, and a
|
|
626
|
+
// one-shot retry loses that race silently: __RN_NET_XHR_ACTIVE__ stays
|
|
627
|
+
// false, capture and mocking are dead for the whole run, and because
|
|
628
|
+
// __RN_NET_INJECTED__ is already set no later injection can repair it.
|
|
629
|
+
// Observed on a freshly launched Android emulator while iOS, connected
|
|
630
|
+
// earlier, patched fine — the failure is pure timing and leaves no trace.
|
|
631
|
+
var _retries = 0;
|
|
632
|
+
var _retry = function() {
|
|
633
|
+
var patched = false;
|
|
634
|
+
try { patched = _patchXHR(); } catch(e) {}
|
|
88
635
|
try {
|
|
89
636
|
if (typeof globalThis.fetch === 'function' && !globalThis.fetch.__rn_net_wrapped__) {
|
|
90
637
|
globalThis.fetch = _wrapFetch(globalThis.fetch);
|
|
91
638
|
}
|
|
92
639
|
} catch(e) {}
|
|
93
|
-
|
|
640
|
+
if (patched) return;
|
|
641
|
+
_retries++;
|
|
642
|
+
if (_retries >= 40) return; // ~10s, then give up rather than spin forever
|
|
643
|
+
try { setTimeout(_retry, 250); } catch(e) {}
|
|
644
|
+
};
|
|
645
|
+
setTimeout(_retry, 0);
|
|
94
646
|
|
|
95
647
|
} catch(e) {} })();`;
|
|
96
648
|
}
|
|
649
|
+
export function buildMockPushScript(rulesJson) {
|
|
650
|
+
return `(function(){ try {
|
|
651
|
+
var __eb_prev = globalThis.__RN_NET_MOCKS__ || [];
|
|
652
|
+
var __eb_next = ${rulesJson};
|
|
653
|
+
var __eb_byId = {};
|
|
654
|
+
for (var __eb_i = 0; __eb_i < __eb_prev.length; __eb_i++) {
|
|
655
|
+
if (__eb_prev[__eb_i] && __eb_prev[__eb_i].id) __eb_byId[__eb_prev[__eb_i].id] = __eb_prev[__eb_i];
|
|
656
|
+
}
|
|
657
|
+
for (var __eb_j = 0; __eb_j < __eb_next.length; __eb_j++) {
|
|
658
|
+
var __eb_old = __eb_byId[__eb_next[__eb_j].id];
|
|
659
|
+
if (__eb_old) {
|
|
660
|
+
__eb_next[__eb_j].__used = __eb_old.__used;
|
|
661
|
+
__eb_next[__eb_j].__spent = __eb_old.__spent;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
globalThis.__RN_NET_MOCKS__ = __eb_next;
|
|
665
|
+
} catch(e) { try { globalThis.__RN_NET_MOCKS__ = ${rulesJson}; } catch(e2) {} } })();`;
|
|
666
|
+
}
|
|
667
|
+
export function pushMockRules(ws, rulesJson) {
|
|
668
|
+
ws.send(JSON.stringify({
|
|
669
|
+
id: getNextMessageId(),
|
|
670
|
+
method: "Runtime.evaluate",
|
|
671
|
+
params: { expression: buildMockPushScript(rulesJson), silent: true },
|
|
672
|
+
}));
|
|
673
|
+
}
|
|
97
674
|
export function injectNetworkInterceptor(ws) {
|
|
98
675
|
const message = JSON.stringify({
|
|
99
676
|
id: getNextMessageId(),
|
|
@@ -126,6 +703,38 @@ export function isInterceptorEvent(args) {
|
|
|
126
703
|
return null;
|
|
127
704
|
return val.slice(prefix.length);
|
|
128
705
|
}
|
|
706
|
+
export function isMockEvent(jsonStr) {
|
|
707
|
+
try {
|
|
708
|
+
const parsed = JSON.parse(jsonStr);
|
|
709
|
+
return parsed?.type === "mock";
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
export function isMockedTrafficEvent(jsonStr) {
|
|
716
|
+
try {
|
|
717
|
+
const parsed = JSON.parse(jsonStr);
|
|
718
|
+
return parsed?.mocked === true;
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function toStringRecord(value) {
|
|
725
|
+
const out = {};
|
|
726
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
727
|
+
return out;
|
|
728
|
+
for (const [key, val] of Object.entries(value)) {
|
|
729
|
+
if (typeof val === "string") {
|
|
730
|
+
out[key] = val;
|
|
731
|
+
}
|
|
732
|
+
else if (typeof val === "number" || typeof val === "boolean") {
|
|
733
|
+
out[key] = String(val);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return out;
|
|
737
|
+
}
|
|
129
738
|
export function applyInterceptedEvent(jsonStr, networkBuffer, deviceName) {
|
|
130
739
|
let event;
|
|
131
740
|
try {
|
|
@@ -147,7 +756,8 @@ export function applyInterceptedEvent(jsonStr, networkBuffer, deviceName) {
|
|
|
147
756
|
: new Date(),
|
|
148
757
|
method: String(event.method || "GET"),
|
|
149
758
|
url: String(event.url || ""),
|
|
150
|
-
headers:
|
|
759
|
+
headers: toStringRecord(event.headers),
|
|
760
|
+
postData: typeof event.body === "string" ? event.body : undefined,
|
|
151
761
|
completed: false,
|
|
152
762
|
epoch: getEpoch(deviceName),
|
|
153
763
|
};
|
|
@@ -167,8 +777,45 @@ export function applyInterceptedEvent(jsonStr, networkBuffer, deviceName) {
|
|
|
167
777
|
responseTime: Date.now(),
|
|
168
778
|
duration,
|
|
169
779
|
};
|
|
780
|
+
if (event.responseHeaders !== undefined) {
|
|
781
|
+
existing.responseHeaders = toStringRecord(event.responseHeaders);
|
|
782
|
+
}
|
|
783
|
+
if (typeof event.mimeType === "string") {
|
|
784
|
+
existing.mimeType = event.mimeType;
|
|
785
|
+
}
|
|
786
|
+
if (typeof event.contentLength === "number" && Number.isFinite(event.contentLength)) {
|
|
787
|
+
existing.contentLength = event.contentLength;
|
|
788
|
+
}
|
|
789
|
+
if (typeof event.body === "string") {
|
|
790
|
+
existing.responseBody = event.body;
|
|
791
|
+
}
|
|
792
|
+
if (typeof event.url === "string" && event.url.length > 0) {
|
|
793
|
+
existing.url = event.url;
|
|
794
|
+
}
|
|
170
795
|
networkBuffer.set(id, existing);
|
|
171
796
|
}
|
|
797
|
+
else if (type === "mock") {
|
|
798
|
+
if (typeof event.ruleId === "string")
|
|
799
|
+
recordHit(deviceName, event.ruleId);
|
|
800
|
+
const existing = networkBuffer.get(id);
|
|
801
|
+
if (!existing)
|
|
802
|
+
return;
|
|
803
|
+
existing.mocked = true;
|
|
804
|
+
if (typeof event.ruleId === "string")
|
|
805
|
+
existing.mockId = event.ruleId;
|
|
806
|
+
if (typeof event.warning === "string")
|
|
807
|
+
existing.mockWarning = event.warning;
|
|
808
|
+
networkBuffer.set(id, existing);
|
|
809
|
+
}
|
|
810
|
+
else if (type === "body") {
|
|
811
|
+
const existing = networkBuffer.get(id);
|
|
812
|
+
if (!existing)
|
|
813
|
+
return;
|
|
814
|
+
if (typeof event.body === "string") {
|
|
815
|
+
existing.responseBody = event.body;
|
|
816
|
+
networkBuffer.set(id, existing);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
172
819
|
else if (type === "error") {
|
|
173
820
|
const existing = networkBuffer.get(id);
|
|
174
821
|
if (!existing)
|