react-native-inapp-inspector 1.0.14 → 1.0.15
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 +203 -44
- package/dist/commonjs/customHooks/analyticsLogger.d.ts +1 -0
- package/dist/commonjs/customHooks/analyticsLogger.js +57 -17
- package/dist/commonjs/customHooks/networkLogger.d.ts +1 -1
- package/dist/commonjs/customHooks/networkLogger.js +54 -46
- package/dist/commonjs/index.js +1499 -792
- package/dist/esm/customHooks/analyticsLogger.d.ts +1 -0
- package/dist/esm/customHooks/analyticsLogger.js +55 -16
- package/dist/esm/customHooks/networkLogger.d.ts +1 -1
- package/dist/esm/customHooks/networkLogger.js +56 -48
- package/dist/esm/index.js +1500 -793
- package/example/App.tsx +1 -3
- package/package.json +3 -2
|
@@ -19,3 +19,4 @@ export declare const logAnalyticsEvent: (name: string, params?: Record<string, a
|
|
|
19
19
|
* setupAnalyticsLogger(analytics());
|
|
20
20
|
*/
|
|
21
21
|
export declare const setupAnalyticsLogger: (analyticsInstance: any) => void;
|
|
22
|
+
export declare const autoSetupAnalyticsLogger: () => boolean;
|
|
@@ -30,7 +30,7 @@ let currentUserId;
|
|
|
30
30
|
// ─── Core helpers ─────────────────────────────────────────────────────────────
|
|
31
31
|
const notify = () => {
|
|
32
32
|
const snapshot = [...events];
|
|
33
|
-
listeners.forEach(
|
|
33
|
+
listeners.forEach(cb => cb(snapshot));
|
|
34
34
|
};
|
|
35
35
|
const addEvent = (event) => {
|
|
36
36
|
events.unshift(event);
|
|
@@ -42,7 +42,7 @@ export const subscribeAnalyticsEvents = (callback) => {
|
|
|
42
42
|
listeners.push(callback);
|
|
43
43
|
callback([...events]);
|
|
44
44
|
return () => {
|
|
45
|
-
listeners = listeners.filter(
|
|
45
|
+
listeners = listeners.filter(l => l !== callback);
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
48
|
export const clearAnalyticsEvents = () => {
|
|
@@ -62,10 +62,10 @@ export const logAnalyticsEvent = (name, params = {}, userProperties = {}) => {
|
|
|
62
62
|
params,
|
|
63
63
|
userProperties: { ...currentUserProperties, ...userProperties },
|
|
64
64
|
timestamp: Date.now(),
|
|
65
|
-
source:
|
|
66
|
-
userId: currentUserId ??
|
|
67
|
-
screenName:
|
|
68
|
-
screenClass:
|
|
65
|
+
source: 'manual',
|
|
66
|
+
userId: currentUserId ?? '',
|
|
67
|
+
screenName: '',
|
|
68
|
+
screenClass: '',
|
|
69
69
|
});
|
|
70
70
|
};
|
|
71
71
|
// ─── Firebase Analytics instance patcher ─────────────────────────────────────
|
|
@@ -82,7 +82,7 @@ export const logAnalyticsEvent = (name, params = {}, userProperties = {}) => {
|
|
|
82
82
|
*/
|
|
83
83
|
export const setupAnalyticsLogger = (analyticsInstance) => {
|
|
84
84
|
if (!analyticsInstance) {
|
|
85
|
-
console.warn(
|
|
85
|
+
console.warn('[AnalyticsLogger] No analytics instance provided — skipping setup.');
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
// Guard against double-patching the same instance
|
|
@@ -98,10 +98,10 @@ export const setupAnalyticsLogger = (analyticsInstance) => {
|
|
|
98
98
|
params: params ?? {},
|
|
99
99
|
userProperties: { ...currentUserProperties },
|
|
100
100
|
timestamp: Date.now(),
|
|
101
|
-
source:
|
|
102
|
-
userId: currentUserId ??
|
|
103
|
-
screenName:
|
|
104
|
-
screenClass:
|
|
101
|
+
source: 'firebase',
|
|
102
|
+
userId: currentUserId ?? '',
|
|
103
|
+
screenName: '',
|
|
104
|
+
screenClass: '',
|
|
105
105
|
});
|
|
106
106
|
return originalLogEvent(name, params);
|
|
107
107
|
};
|
|
@@ -110,14 +110,14 @@ export const setupAnalyticsLogger = (analyticsInstance) => {
|
|
|
110
110
|
analyticsInstance.logScreenView = async (params) => {
|
|
111
111
|
addEvent({
|
|
112
112
|
id: counter++,
|
|
113
|
-
name:
|
|
113
|
+
name: 'screen_view',
|
|
114
114
|
params: params ?? {},
|
|
115
115
|
userProperties: { ...currentUserProperties },
|
|
116
116
|
timestamp: Date.now(),
|
|
117
|
-
source:
|
|
118
|
-
screenName: params?.screen_name ??
|
|
119
|
-
screenClass: params?.screen_class ??
|
|
120
|
-
userId: currentUserId ??
|
|
117
|
+
source: 'firebase',
|
|
118
|
+
screenName: params?.screen_name ?? '',
|
|
119
|
+
screenClass: params?.screen_class ?? '',
|
|
120
|
+
userId: currentUserId ?? '',
|
|
121
121
|
});
|
|
122
122
|
return originalLogScreenView(params);
|
|
123
123
|
};
|
|
@@ -150,3 +150,42 @@ export const setupAnalyticsLogger = (analyticsInstance) => {
|
|
|
150
150
|
return originalSetUserId(id);
|
|
151
151
|
};
|
|
152
152
|
};
|
|
153
|
+
export const autoSetupAnalyticsLogger = () => {
|
|
154
|
+
if (globalThis.__INSPECTOR_ANALYTICS_AUTOSETUP__)
|
|
155
|
+
return true;
|
|
156
|
+
let mod;
|
|
157
|
+
try {
|
|
158
|
+
mod = require('@react-native-firebase/analytics'); // optional dep
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return false; // GA not installed — silent no-op
|
|
162
|
+
}
|
|
163
|
+
const accessor = mod?.default ?? mod;
|
|
164
|
+
if (typeof accessor !== 'function')
|
|
165
|
+
return false;
|
|
166
|
+
try {
|
|
167
|
+
setupAnalyticsLogger(accessor());
|
|
168
|
+
}
|
|
169
|
+
catch { } // patch default-app instance
|
|
170
|
+
if (!accessor.__INSPECTOR_WRAPPED__) {
|
|
171
|
+
// patch future/named instances
|
|
172
|
+
const wrapped = function (...args) {
|
|
173
|
+
const instance = accessor.apply(this, args);
|
|
174
|
+
try {
|
|
175
|
+
setupAnalyticsLogger(instance);
|
|
176
|
+
}
|
|
177
|
+
catch { }
|
|
178
|
+
return instance;
|
|
179
|
+
};
|
|
180
|
+
Object.setPrototypeOf(wrapped, accessor);
|
|
181
|
+
Object.assign(wrapped, accessor);
|
|
182
|
+
wrapped.__INSPECTOR_WRAPPED__ = true;
|
|
183
|
+
try {
|
|
184
|
+
if (mod.default !== undefined)
|
|
185
|
+
mod.default = wrapped;
|
|
186
|
+
}
|
|
187
|
+
catch { }
|
|
188
|
+
}
|
|
189
|
+
globalThis.__INSPECTOR_ANALYTICS_AUTOSETUP__ = true;
|
|
190
|
+
return true;
|
|
191
|
+
};
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import axios from
|
|
1
|
+
import "./webViewLogger";
|
|
2
|
+
import axios from "axios";
|
|
3
3
|
let logs = [];
|
|
4
4
|
let listeners = [];
|
|
5
5
|
let counter = 0;
|
|
6
|
-
const ALLOWED_METHODS = [
|
|
6
|
+
const ALLOWED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
7
|
+
const IGNORED_URL_PATTERNS = [/\/symbolicate(?:[/?#]|$)/];
|
|
8
|
+
function shouldIgnoreUrl(url) {
|
|
9
|
+
if (!url)
|
|
10
|
+
return false;
|
|
11
|
+
return IGNORED_URL_PATTERNS.some((re) => re.test(url));
|
|
12
|
+
}
|
|
7
13
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
8
14
|
function normaliseHeaders(raw) {
|
|
9
15
|
if (!raw)
|
|
10
16
|
return undefined;
|
|
11
|
-
if (typeof raw.forEach ===
|
|
17
|
+
if (typeof raw.forEach === "function") {
|
|
12
18
|
const result = {};
|
|
13
19
|
raw.forEach((value, key) => {
|
|
14
20
|
result[key] = value;
|
|
@@ -40,11 +46,11 @@ function parseRequestData(data) {
|
|
|
40
46
|
data._parts.forEach((part) => {
|
|
41
47
|
const key = part[0];
|
|
42
48
|
const value = part[1];
|
|
43
|
-
if (value && typeof value ===
|
|
49
|
+
if (value && typeof value === "object" && value.uri) {
|
|
44
50
|
parsedFormData[key] = {
|
|
45
51
|
_isFile: true,
|
|
46
|
-
name: value.name ||
|
|
47
|
-
type: value.type ||
|
|
52
|
+
name: value.name || "unknown",
|
|
53
|
+
type: value.type || "unknown",
|
|
48
54
|
uri: value.uri,
|
|
49
55
|
};
|
|
50
56
|
}
|
|
@@ -61,29 +67,29 @@ function getCallerFromStack() {
|
|
|
61
67
|
try {
|
|
62
68
|
const stack = new Error().stack;
|
|
63
69
|
if (!stack)
|
|
64
|
-
return
|
|
65
|
-
const lines = stack.split(
|
|
70
|
+
return "Unknown";
|
|
71
|
+
const lines = stack.split("\n");
|
|
66
72
|
for (let i = 0; i < lines.length; i++) {
|
|
67
73
|
const line = lines[i];
|
|
68
74
|
// Skip internal react-native network modules and the logger itself
|
|
69
|
-
if (line.includes(
|
|
70
|
-
line.includes(
|
|
71
|
-
line.includes(
|
|
72
|
-
line.includes(
|
|
75
|
+
if (line.includes("networkLogger") ||
|
|
76
|
+
line.includes("node_modules") ||
|
|
77
|
+
line.includes("Error") ||
|
|
78
|
+
line.includes("regeneratorRuntime")) {
|
|
73
79
|
continue;
|
|
74
80
|
}
|
|
75
|
-
return line.trim().replace(/^at /,
|
|
81
|
+
return line.trim().replace(/^at /, "");
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
catch (e) { }
|
|
79
|
-
return
|
|
85
|
+
return "Unknown";
|
|
80
86
|
}
|
|
81
87
|
// ─── Subscribe ────────────────────────────────────────────────────────────────
|
|
82
88
|
export const subscribeNetworkLogs = (callback) => {
|
|
83
89
|
listeners.push(callback);
|
|
84
90
|
callback([...logs]);
|
|
85
91
|
return () => {
|
|
86
|
-
listeners = listeners.filter(l => l !== callback);
|
|
92
|
+
listeners = listeners.filter((l) => l !== callback);
|
|
87
93
|
};
|
|
88
94
|
};
|
|
89
95
|
export const clearNetworkLogs = () => {
|
|
@@ -94,13 +100,15 @@ export const getNetworkLogs = () => [...logs];
|
|
|
94
100
|
// ─── Internal ─────────────────────────────────────────────────────────────────
|
|
95
101
|
const notify = () => {
|
|
96
102
|
const snapshot = [...logs];
|
|
97
|
-
listeners.forEach(cb => cb(snapshot));
|
|
103
|
+
listeners.forEach((cb) => cb(snapshot));
|
|
98
104
|
};
|
|
99
105
|
const addOrUpdateLog = (log) => {
|
|
100
106
|
const method = log.method?.toUpperCase();
|
|
101
107
|
if (!ALLOWED_METHODS.includes(method))
|
|
102
108
|
return;
|
|
103
|
-
|
|
109
|
+
if (shouldIgnoreUrl(log.url))
|
|
110
|
+
return;
|
|
111
|
+
const index = logs.findIndex((l) => l.id === log.id);
|
|
104
112
|
if (index >= 0) {
|
|
105
113
|
logs[index] = { ...logs[index], ...log };
|
|
106
114
|
}
|
|
@@ -118,12 +126,14 @@ export const setupNetworkLogger = () => {
|
|
|
118
126
|
const originalFetch = globalThis.fetch;
|
|
119
127
|
if (originalFetch) {
|
|
120
128
|
globalThis.fetch = async (url, options = {}) => {
|
|
121
|
-
const method = (options?.method ||
|
|
129
|
+
const method = (options?.method || "GET").toUpperCase();
|
|
122
130
|
if (!ALLOWED_METHODS.includes(method))
|
|
123
131
|
return originalFetch(url, options);
|
|
124
132
|
const id = counter++;
|
|
125
133
|
const start = Date.now();
|
|
126
|
-
const finalUrl = typeof url ===
|
|
134
|
+
const finalUrl = typeof url === "string" ? url : url?.url;
|
|
135
|
+
if (shouldIgnoreUrl(finalUrl))
|
|
136
|
+
return originalFetch(url, options);
|
|
127
137
|
const requestHeaders = normaliseHeaders(options?.headers);
|
|
128
138
|
const caller = getCallerFromStack(); // ✅ Capture call line
|
|
129
139
|
addOrUpdateLog({
|
|
@@ -132,7 +142,7 @@ export const setupNetworkLogger = () => {
|
|
|
132
142
|
method,
|
|
133
143
|
startTime: start,
|
|
134
144
|
caller,
|
|
135
|
-
request: method ===
|
|
145
|
+
request: method === "GET" ? undefined : parseRequestData(options?.body),
|
|
136
146
|
requestHeaders,
|
|
137
147
|
});
|
|
138
148
|
try {
|
|
@@ -140,11 +150,11 @@ export const setupNetworkLogger = () => {
|
|
|
140
150
|
const duration = Date.now() - start;
|
|
141
151
|
const responseHeaders = normaliseHeaders(response.headers);
|
|
142
152
|
let data = null;
|
|
143
|
-
const contentType = responseHeaders?.[
|
|
144
|
-
responseHeaders?.[
|
|
145
|
-
|
|
146
|
-
if (contentType.includes(
|
|
147
|
-
data =
|
|
153
|
+
const contentType = responseHeaders?.["content-type"] ||
|
|
154
|
+
responseHeaders?.["Content-Type"] ||
|
|
155
|
+
"";
|
|
156
|
+
if (contentType.includes("image/")) {
|
|
157
|
+
data = "[Image Data]";
|
|
148
158
|
}
|
|
149
159
|
else {
|
|
150
160
|
try {
|
|
@@ -187,52 +197,50 @@ export const setupNetworkLogger = () => {
|
|
|
187
197
|
}
|
|
188
198
|
};
|
|
189
199
|
}
|
|
190
|
-
//
|
|
200
|
+
// Hook Axios — patches both the default instance and any future axios.create() instances
|
|
191
201
|
try {
|
|
192
202
|
if (axios) {
|
|
193
203
|
addAxiosInterceptors(axios);
|
|
194
|
-
console.log('✅ Axios interceptors added to imported axios');
|
|
195
204
|
const originalCreate = axios.create;
|
|
196
|
-
if (typeof originalCreate ===
|
|
205
|
+
if (typeof originalCreate === "function") {
|
|
197
206
|
axios.create = function (...args) {
|
|
198
207
|
const instance = originalCreate.apply(this, args);
|
|
199
208
|
addAxiosInterceptors(instance);
|
|
200
|
-
console.log('✅ Axios interceptors added to custom axios instance (imported)');
|
|
201
209
|
return instance;
|
|
202
210
|
};
|
|
203
211
|
}
|
|
204
212
|
}
|
|
205
|
-
else {
|
|
206
|
-
console.warn('⚠️ Axios module not found for interceptor setup');
|
|
207
|
-
}
|
|
208
213
|
}
|
|
209
|
-
catch (
|
|
210
|
-
|
|
214
|
+
catch (_e) {
|
|
215
|
+
// Axios not available — fetch-only mode
|
|
211
216
|
}
|
|
212
217
|
globalThis.__NETWORK_LOGGER_INITIALIZED__ = true;
|
|
213
218
|
};
|
|
214
219
|
// ─── Axios interceptor helper ─────────────────────────────────────────────────
|
|
215
220
|
export const addAxiosInterceptors = (axiosInstance) => {
|
|
216
221
|
axiosInstance.interceptors.request.use(async (config) => {
|
|
217
|
-
const method = (config.method ||
|
|
222
|
+
const method = (config.method || "GET").toUpperCase();
|
|
218
223
|
if (!ALLOWED_METHODS.includes(method))
|
|
219
224
|
return config;
|
|
220
225
|
const id = counter++;
|
|
221
226
|
const start = Date.now();
|
|
222
227
|
const caller = getCallerFromStack(); // ✅ Capture call line
|
|
228
|
+
let url = config.url ?? "";
|
|
229
|
+
if (!url.startsWith("http"))
|
|
230
|
+
url = `${config.baseURL ?? ""}${url}`;
|
|
231
|
+
// ✅ Leave config untagged so the response interceptor skips it too.
|
|
232
|
+
if (shouldIgnoreUrl(url))
|
|
233
|
+
return config;
|
|
223
234
|
config.__logId = id;
|
|
224
235
|
config.__logStart = start;
|
|
225
236
|
config.__logCaller = caller;
|
|
226
|
-
let url = config.url ?? '';
|
|
227
|
-
if (!url.startsWith('http'))
|
|
228
|
-
url = `${config.baseURL ?? ''}${url}`;
|
|
229
237
|
addOrUpdateLog({
|
|
230
238
|
id,
|
|
231
239
|
url,
|
|
232
240
|
method,
|
|
233
241
|
startTime: start,
|
|
234
242
|
caller,
|
|
235
|
-
request: method ===
|
|
243
|
+
request: method === "GET" ? undefined : parseRequestData(config.data),
|
|
236
244
|
requestHeaders: normaliseHeaders(config.headers),
|
|
237
245
|
});
|
|
238
246
|
return config;
|
|
@@ -242,12 +250,12 @@ export const addAxiosInterceptors = (axiosInstance) => {
|
|
|
242
250
|
const id = config.__logId;
|
|
243
251
|
const start = config.__logStart;
|
|
244
252
|
const caller = config.__logCaller;
|
|
245
|
-
const method = (config.method ||
|
|
253
|
+
const method = (config.method || "GET").toUpperCase();
|
|
246
254
|
if (id == null || !ALLOWED_METHODS.includes(method))
|
|
247
255
|
return response;
|
|
248
|
-
let url = config.url ??
|
|
249
|
-
if (!url.startsWith(
|
|
250
|
-
url = `${config.baseURL ??
|
|
256
|
+
let url = config.url ?? "";
|
|
257
|
+
if (!url.startsWith("http"))
|
|
258
|
+
url = `${config.baseURL ?? ""}${url}`;
|
|
251
259
|
addOrUpdateLog({
|
|
252
260
|
id,
|
|
253
261
|
url,
|
|
@@ -265,11 +273,11 @@ export const addAxiosInterceptors = (axiosInstance) => {
|
|
|
265
273
|
const id = config.__logId;
|
|
266
274
|
const start = config.__logStart;
|
|
267
275
|
const caller = config.__logCaller;
|
|
268
|
-
const method = (config.method ||
|
|
276
|
+
const method = (config.method || "GET").toUpperCase();
|
|
269
277
|
if (id != null && ALLOWED_METHODS.includes(method)) {
|
|
270
|
-
let url = config.url ??
|
|
271
|
-
if (!url.startsWith(
|
|
272
|
-
url = `${config.baseURL ??
|
|
278
|
+
let url = config.url ?? "";
|
|
279
|
+
if (!url.startsWith("http"))
|
|
280
|
+
url = `${config.baseURL ?? ""}${url}`;
|
|
273
281
|
addOrUpdateLog({
|
|
274
282
|
id,
|
|
275
283
|
url,
|