appdoctor-rn 0.1.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/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/index.cjs +609 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +187 -0
- package/dist/index.d.ts +187 -0
- package/dist/index.js +595 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { createContext, useRef, useEffect, useContext } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/core/types.ts
|
|
5
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
|
|
6
|
+
var DEFAULT_MAX_QUEUE_SIZE = 200;
|
|
7
|
+
|
|
8
|
+
// src/core/sampling.ts
|
|
9
|
+
function shouldSample(rate) {
|
|
10
|
+
if (rate >= 1) return true;
|
|
11
|
+
if (rate <= 0) return false;
|
|
12
|
+
return Math.random() < rate;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/core/client.ts
|
|
16
|
+
function now() {
|
|
17
|
+
return Date.now();
|
|
18
|
+
}
|
|
19
|
+
function newSessionId() {
|
|
20
|
+
return `${now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
21
|
+
}
|
|
22
|
+
var AppDoctorClient = class {
|
|
23
|
+
constructor(config = {}) {
|
|
24
|
+
this.queue = [];
|
|
25
|
+
this.fetchPatched = false;
|
|
26
|
+
this.sessionId = newSessionId();
|
|
27
|
+
this.config = {
|
|
28
|
+
enabled: config.enabled ?? true,
|
|
29
|
+
noop: config.noop ?? false,
|
|
30
|
+
sampleRate: config.sampleRate ?? 1,
|
|
31
|
+
flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
32
|
+
maxQueueSize: config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
|
|
33
|
+
instrumentFetch: config.instrumentFetch ?? true,
|
|
34
|
+
tags: config.tags,
|
|
35
|
+
context: config.context,
|
|
36
|
+
redactNetworkEvent: config.redactNetworkEvent
|
|
37
|
+
};
|
|
38
|
+
this.transports = [...config.transports ?? []];
|
|
39
|
+
if (this.config.enabled) {
|
|
40
|
+
this.startFlushTimer();
|
|
41
|
+
if (this.config.instrumentFetch && typeof globalThis.fetch === "function") {
|
|
42
|
+
this.patchFetch();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
updateConfig(partial) {
|
|
47
|
+
if (partial.enabled !== void 0) {
|
|
48
|
+
this.config.enabled = partial.enabled;
|
|
49
|
+
}
|
|
50
|
+
if (partial.noop !== void 0) {
|
|
51
|
+
this.config.noop = partial.noop;
|
|
52
|
+
}
|
|
53
|
+
if (partial.sampleRate !== void 0) {
|
|
54
|
+
this.config.sampleRate = partial.sampleRate;
|
|
55
|
+
}
|
|
56
|
+
if (partial.flushIntervalMs !== void 0) {
|
|
57
|
+
this.config.flushIntervalMs = partial.flushIntervalMs;
|
|
58
|
+
this.restartFlushTimer();
|
|
59
|
+
}
|
|
60
|
+
if (partial.maxQueueSize !== void 0) {
|
|
61
|
+
this.config.maxQueueSize = partial.maxQueueSize;
|
|
62
|
+
}
|
|
63
|
+
if (partial.tags !== void 0) {
|
|
64
|
+
this.config.tags = partial.tags;
|
|
65
|
+
}
|
|
66
|
+
if (partial.context !== void 0) {
|
|
67
|
+
this.config.context = partial.context;
|
|
68
|
+
}
|
|
69
|
+
if (partial.instrumentFetch !== void 0) {
|
|
70
|
+
const next = partial.instrumentFetch;
|
|
71
|
+
this.config.instrumentFetch = next;
|
|
72
|
+
if (next && !this.fetchPatched) this.patchFetch();
|
|
73
|
+
if (!next && this.fetchPatched) this.restoreFetch();
|
|
74
|
+
}
|
|
75
|
+
if (partial.transports !== void 0) {
|
|
76
|
+
this.transports.length = 0;
|
|
77
|
+
this.transports.push(...partial.transports);
|
|
78
|
+
}
|
|
79
|
+
if (partial.redactNetworkEvent !== void 0) {
|
|
80
|
+
this.config.redactNetworkEvent = partial.redactNetworkEvent;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
addTransport(transport) {
|
|
84
|
+
this.transports.push(transport);
|
|
85
|
+
}
|
|
86
|
+
emit(event) {
|
|
87
|
+
if (!this.config.enabled) return;
|
|
88
|
+
if (this.config.noop) return;
|
|
89
|
+
if (!shouldSample(this.config.sampleRate)) return;
|
|
90
|
+
const { tags: eventTags, ...payload } = event;
|
|
91
|
+
const tags = this.config.tags || eventTags ? { ...this.config.tags, ...eventTags } : void 0;
|
|
92
|
+
const full = {
|
|
93
|
+
...payload,
|
|
94
|
+
timestamp: now(),
|
|
95
|
+
sessionId: this.sessionId,
|
|
96
|
+
tags,
|
|
97
|
+
context: this.config.context
|
|
98
|
+
};
|
|
99
|
+
this.enqueue(full);
|
|
100
|
+
}
|
|
101
|
+
enqueue(event) {
|
|
102
|
+
if (this.queue.length >= this.config.maxQueueSize) {
|
|
103
|
+
this.queue.shift();
|
|
104
|
+
}
|
|
105
|
+
this.queue.push(event);
|
|
106
|
+
}
|
|
107
|
+
captureError(message, context, err) {
|
|
108
|
+
const stack = err instanceof Error && typeof err.stack === "string" ? err.stack : void 0;
|
|
109
|
+
const sdkErr = {
|
|
110
|
+
name: "sdk_error",
|
|
111
|
+
message,
|
|
112
|
+
stack,
|
|
113
|
+
errorContext: context
|
|
114
|
+
};
|
|
115
|
+
this.emit(sdkErr);
|
|
116
|
+
}
|
|
117
|
+
async flush() {
|
|
118
|
+
if (this.queue.length === 0) return;
|
|
119
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
120
|
+
await Promise.all(
|
|
121
|
+
this.transports.map(async (t) => {
|
|
122
|
+
try {
|
|
123
|
+
await t.send(batch);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
this.safeInternalError("transport.send failed", e);
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
async shutdown() {
|
|
131
|
+
if (this.flushTimer) {
|
|
132
|
+
clearInterval(this.flushTimer);
|
|
133
|
+
this.flushTimer = void 0;
|
|
134
|
+
}
|
|
135
|
+
this.restoreFetch();
|
|
136
|
+
await this.flush();
|
|
137
|
+
await Promise.all(
|
|
138
|
+
this.transports.map(async (t) => {
|
|
139
|
+
if (!t.shutdown) return;
|
|
140
|
+
try {
|
|
141
|
+
await t.shutdown();
|
|
142
|
+
} catch (e) {
|
|
143
|
+
this.safeInternalError("transport.shutdown failed", e);
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
startFlushTimer() {
|
|
149
|
+
this.flushTimer = setInterval(() => {
|
|
150
|
+
void this.flush();
|
|
151
|
+
}, this.config.flushIntervalMs);
|
|
152
|
+
if (typeof this.flushTimer === "object" && this.flushTimer && "unref" in this.flushTimer && typeof this.flushTimer.unref === "function") {
|
|
153
|
+
this.flushTimer.unref();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
restartFlushTimer() {
|
|
157
|
+
if (this.flushTimer) clearInterval(this.flushTimer);
|
|
158
|
+
this.startFlushTimer();
|
|
159
|
+
}
|
|
160
|
+
safeInternalError(msg, err) {
|
|
161
|
+
const dev = typeof __DEV__ !== "undefined" ? __DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
162
|
+
if (dev) console.warn(`[AppDoctor] ${msg}`, err);
|
|
163
|
+
}
|
|
164
|
+
patchFetch() {
|
|
165
|
+
if (this.fetchPatched) return;
|
|
166
|
+
const g = globalThis;
|
|
167
|
+
this.originalFetch = g.fetch.bind(globalThis);
|
|
168
|
+
const originalFetch = this.originalFetch;
|
|
169
|
+
g.fetch = async (input, init) => {
|
|
170
|
+
const start = now();
|
|
171
|
+
let method = "GET";
|
|
172
|
+
let url = "";
|
|
173
|
+
try {
|
|
174
|
+
if (typeof input === "string") {
|
|
175
|
+
url = input;
|
|
176
|
+
} else if (input instanceof URL) {
|
|
177
|
+
url = input.href;
|
|
178
|
+
} else if (typeof Request !== "undefined" && input instanceof Request) {
|
|
179
|
+
url = input.url;
|
|
180
|
+
method = input.method;
|
|
181
|
+
}
|
|
182
|
+
if (init?.method) method = init.method;
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
const urlForEvent = url || (typeof input === "string" ? input : input instanceof URL ? input.href : typeof Request !== "undefined" && input instanceof Request ? input.url : "[request]");
|
|
186
|
+
try {
|
|
187
|
+
const res = await originalFetch(input, init);
|
|
188
|
+
const durationMs = now() - start;
|
|
189
|
+
const event = this.applyNetworkRedaction({
|
|
190
|
+
method,
|
|
191
|
+
url: urlForEvent,
|
|
192
|
+
status: res.status,
|
|
193
|
+
durationMs,
|
|
194
|
+
success: true
|
|
195
|
+
});
|
|
196
|
+
this.emit({
|
|
197
|
+
name: "api_request",
|
|
198
|
+
...event
|
|
199
|
+
});
|
|
200
|
+
return res;
|
|
201
|
+
} catch (e) {
|
|
202
|
+
const durationMs = now() - start;
|
|
203
|
+
const event = this.applyNetworkRedaction({
|
|
204
|
+
method,
|
|
205
|
+
url: urlForEvent,
|
|
206
|
+
durationMs,
|
|
207
|
+
success: false,
|
|
208
|
+
errorMessage: e instanceof Error ? e.message : String(e)
|
|
209
|
+
});
|
|
210
|
+
this.emit({
|
|
211
|
+
name: "api_request",
|
|
212
|
+
...event
|
|
213
|
+
});
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
this.fetchPatched = true;
|
|
218
|
+
}
|
|
219
|
+
restoreFetch() {
|
|
220
|
+
if (!this.fetchPatched || !this.originalFetch) return;
|
|
221
|
+
globalThis.fetch = this.originalFetch;
|
|
222
|
+
this.fetchPatched = false;
|
|
223
|
+
this.originalFetch = void 0;
|
|
224
|
+
}
|
|
225
|
+
applyNetworkRedaction(event) {
|
|
226
|
+
if (!this.config.redactNetworkEvent) return event;
|
|
227
|
+
const redacted = this.config.redactNetworkEvent(event);
|
|
228
|
+
return { ...event, ...redacted };
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// src/transports/console-transport.ts
|
|
233
|
+
function createConsoleTransport(options = {}) {
|
|
234
|
+
const label = options.label ?? "AppDoctor";
|
|
235
|
+
const slowScreenThresholdMs = options.slowScreenThresholdMs ?? 1e3;
|
|
236
|
+
const slowApiThresholdMs = options.slowApiThresholdMs ?? 800;
|
|
237
|
+
function toHint(event) {
|
|
238
|
+
if (event.name === "screen_load" && event.phase === "ready" && event.durationMs >= slowScreenThresholdMs) {
|
|
239
|
+
return `Slow screen "${event.screen}" (${event.durationMs}ms). Check expensive effects and repeated renders.`;
|
|
240
|
+
}
|
|
241
|
+
if (event.name === "api_request" && event.durationMs >= slowApiThresholdMs) {
|
|
242
|
+
return `Slow API "${event.method} ${event.url}" (${event.durationMs}ms). Check server latency, payload size, and retry loops.`;
|
|
243
|
+
}
|
|
244
|
+
return void 0;
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
send(events) {
|
|
248
|
+
if (events.length === 0) return;
|
|
249
|
+
for (const event of events) {
|
|
250
|
+
const hint = toHint(event);
|
|
251
|
+
if (hint) {
|
|
252
|
+
console.warn(`[${label}] ${hint}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
console.log(`[${label}]`, events);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/transports/http-transport.ts
|
|
261
|
+
async function sleep(ms) {
|
|
262
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
263
|
+
}
|
|
264
|
+
function createHttpTransport(options) {
|
|
265
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
266
|
+
const initialBackoffMs = options.initialBackoffMs ?? 500;
|
|
267
|
+
return {
|
|
268
|
+
async send(events) {
|
|
269
|
+
if (events.length === 0) return;
|
|
270
|
+
const body = {
|
|
271
|
+
events: [...events],
|
|
272
|
+
...options.getExtraBody?.() ?? {}
|
|
273
|
+
};
|
|
274
|
+
let attempt = 0;
|
|
275
|
+
let backoff = initialBackoffMs;
|
|
276
|
+
for (; ; ) {
|
|
277
|
+
try {
|
|
278
|
+
const res = await fetch(options.url, {
|
|
279
|
+
method: "POST",
|
|
280
|
+
headers: {
|
|
281
|
+
"Content-Type": "application/json",
|
|
282
|
+
...options.headers
|
|
283
|
+
},
|
|
284
|
+
body: JSON.stringify(body)
|
|
285
|
+
});
|
|
286
|
+
if (res.ok) return;
|
|
287
|
+
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
292
|
+
attempt += 1;
|
|
293
|
+
if (attempt > maxRetries) return;
|
|
294
|
+
await sleep(backoff);
|
|
295
|
+
backoff *= 2;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/navigation/route-name.ts
|
|
302
|
+
function getActiveRouteName(state) {
|
|
303
|
+
if (!state || typeof state !== "object") return void 0;
|
|
304
|
+
const s = state;
|
|
305
|
+
if (!Array.isArray(s.routes) || typeof s.index !== "number") return void 0;
|
|
306
|
+
const route = s.routes[s.index];
|
|
307
|
+
if (!route || typeof route !== "object") return void 0;
|
|
308
|
+
if (route.state !== void 0) return getActiveRouteName(route.state);
|
|
309
|
+
return typeof route.name === "string" ? route.name : void 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/navigation/create-navigation-listener.ts
|
|
313
|
+
function createNavigationStateListener(client) {
|
|
314
|
+
const timing = {};
|
|
315
|
+
return {
|
|
316
|
+
listener(state) {
|
|
317
|
+
const screen = getActiveRouteName(state);
|
|
318
|
+
if (!screen || screen === timing.lastScreen) return;
|
|
319
|
+
timing.lastScreen = screen;
|
|
320
|
+
const startedAt = Date.now();
|
|
321
|
+
client.emit({
|
|
322
|
+
name: "screen_load",
|
|
323
|
+
screen,
|
|
324
|
+
phase: "start",
|
|
325
|
+
durationMs: 0
|
|
326
|
+
});
|
|
327
|
+
queueMicrotask(() => {
|
|
328
|
+
if (timing.lastScreen !== screen) return;
|
|
329
|
+
client.emit({
|
|
330
|
+
name: "screen_load",
|
|
331
|
+
screen,
|
|
332
|
+
phase: "ready",
|
|
333
|
+
durationMs: Date.now() - startedAt
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
reset() {
|
|
338
|
+
timing.lastScreen = void 0;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/network/axios-instrumentation.ts
|
|
344
|
+
function getConfig(errorOrResponse) {
|
|
345
|
+
if (!errorOrResponse || typeof errorOrResponse !== "object") return void 0;
|
|
346
|
+
if ("config" in errorOrResponse && errorOrResponse.config && typeof errorOrResponse.config === "object") {
|
|
347
|
+
return errorOrResponse.config;
|
|
348
|
+
}
|
|
349
|
+
return void 0;
|
|
350
|
+
}
|
|
351
|
+
function getResponseMeta(response) {
|
|
352
|
+
if (!response || typeof response !== "object") return {};
|
|
353
|
+
const r = response;
|
|
354
|
+
const status = "status" in r && typeof r.status === "number" ? r.status : void 0;
|
|
355
|
+
const rawConfig = "config" in r ? r.config : void 0;
|
|
356
|
+
let method;
|
|
357
|
+
let url;
|
|
358
|
+
if (rawConfig !== null && typeof rawConfig === "object") {
|
|
359
|
+
if ("method" in rawConfig && typeof rawConfig.method === "string") {
|
|
360
|
+
method = rawConfig.method;
|
|
361
|
+
}
|
|
362
|
+
if ("url" in rawConfig && typeof rawConfig.url === "string") {
|
|
363
|
+
url = rawConfig.url;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
status,
|
|
368
|
+
method,
|
|
369
|
+
url
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function instrumentAxios(axios, client, options = {}) {
|
|
373
|
+
function applyRedaction(event) {
|
|
374
|
+
const redacted = options.redactNetworkEvent?.(event);
|
|
375
|
+
return redacted ? { ...event, ...redacted } : event;
|
|
376
|
+
}
|
|
377
|
+
const starts = /* @__PURE__ */ new WeakMap();
|
|
378
|
+
const reqId = axios.interceptors.request.use((config) => {
|
|
379
|
+
if (config !== null && typeof config === "object") {
|
|
380
|
+
starts.set(config, Date.now());
|
|
381
|
+
}
|
|
382
|
+
return config;
|
|
383
|
+
});
|
|
384
|
+
const resId = axios.interceptors.response.use(
|
|
385
|
+
(response) => {
|
|
386
|
+
const cfg = getConfig(response);
|
|
387
|
+
if (cfg !== null && typeof cfg === "object") {
|
|
388
|
+
const start = starts.get(cfg);
|
|
389
|
+
if (start !== void 0) {
|
|
390
|
+
const { status, method, url } = getResponseMeta(response);
|
|
391
|
+
const event = applyRedaction({
|
|
392
|
+
method: (method ?? "GET").toUpperCase(),
|
|
393
|
+
url: url ?? "",
|
|
394
|
+
status,
|
|
395
|
+
durationMs: Date.now() - start,
|
|
396
|
+
success: true
|
|
397
|
+
});
|
|
398
|
+
client.emit({
|
|
399
|
+
name: "api_request",
|
|
400
|
+
...event
|
|
401
|
+
});
|
|
402
|
+
starts.delete(cfg);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return response;
|
|
406
|
+
},
|
|
407
|
+
(error) => {
|
|
408
|
+
const cfg = getConfig(error);
|
|
409
|
+
if (cfg !== null && typeof cfg === "object") {
|
|
410
|
+
const start = starts.get(cfg);
|
|
411
|
+
if (start !== void 0) {
|
|
412
|
+
let method = "GET";
|
|
413
|
+
let url = "";
|
|
414
|
+
let status;
|
|
415
|
+
let errorMessage = String(error);
|
|
416
|
+
if (error !== null && typeof error === "object") {
|
|
417
|
+
if ("message" in error && typeof error.message === "string") {
|
|
418
|
+
errorMessage = error.message;
|
|
419
|
+
}
|
|
420
|
+
const resp = "response" in error && error.response !== null && typeof error.response === "object" ? error.response : void 0;
|
|
421
|
+
if (resp && "status" in resp && typeof resp.status === "number") {
|
|
422
|
+
status = resp.status;
|
|
423
|
+
}
|
|
424
|
+
const errCfg = "config" in error && error.config !== null && typeof error.config === "object" ? error.config : void 0;
|
|
425
|
+
if (errCfg) {
|
|
426
|
+
if ("method" in errCfg && typeof errCfg.method === "string") {
|
|
427
|
+
method = errCfg.method.toUpperCase();
|
|
428
|
+
}
|
|
429
|
+
if ("url" in errCfg && typeof errCfg.url === "string") {
|
|
430
|
+
url = errCfg.url;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const event = applyRedaction({
|
|
435
|
+
method,
|
|
436
|
+
url,
|
|
437
|
+
status,
|
|
438
|
+
durationMs: Date.now() - start,
|
|
439
|
+
success: false,
|
|
440
|
+
errorMessage
|
|
441
|
+
});
|
|
442
|
+
client.emit({
|
|
443
|
+
name: "api_request",
|
|
444
|
+
...event
|
|
445
|
+
});
|
|
446
|
+
starts.delete(cfg);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const rejectReason = error instanceof Error ? error : new Error(String(error));
|
|
450
|
+
return Promise.reject(rejectReason);
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
return () => {
|
|
454
|
+
axios.interceptors.request.eject(reqId);
|
|
455
|
+
axios.interceptors.response.eject(resId);
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/utils/track-api.ts
|
|
460
|
+
async function trackApi(client, label, fn) {
|
|
461
|
+
const start = Date.now();
|
|
462
|
+
try {
|
|
463
|
+
const result = await fn();
|
|
464
|
+
client.emit({
|
|
465
|
+
name: "api_request",
|
|
466
|
+
method: "TRACK",
|
|
467
|
+
url: label,
|
|
468
|
+
status: 200,
|
|
469
|
+
durationMs: Date.now() - start,
|
|
470
|
+
success: true
|
|
471
|
+
});
|
|
472
|
+
return result;
|
|
473
|
+
} catch (e) {
|
|
474
|
+
client.emit({
|
|
475
|
+
name: "api_request",
|
|
476
|
+
method: "TRACK",
|
|
477
|
+
url: label,
|
|
478
|
+
durationMs: Date.now() - start,
|
|
479
|
+
success: false,
|
|
480
|
+
errorMessage: e instanceof Error ? e.message : String(e)
|
|
481
|
+
});
|
|
482
|
+
throw e;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
var AppDoctorContext = createContext(null);
|
|
486
|
+
function AppDoctorProvider({
|
|
487
|
+
children,
|
|
488
|
+
...config
|
|
489
|
+
}) {
|
|
490
|
+
const clientRef = useRef(null);
|
|
491
|
+
if (!clientRef.current) {
|
|
492
|
+
clientRef.current = new AppDoctorClient(config);
|
|
493
|
+
}
|
|
494
|
+
const {
|
|
495
|
+
enabled,
|
|
496
|
+
noop,
|
|
497
|
+
sampleRate,
|
|
498
|
+
flushIntervalMs,
|
|
499
|
+
maxQueueSize,
|
|
500
|
+
instrumentFetch,
|
|
501
|
+
tags,
|
|
502
|
+
context,
|
|
503
|
+
transports,
|
|
504
|
+
redactNetworkEvent
|
|
505
|
+
} = config;
|
|
506
|
+
useEffect(() => {
|
|
507
|
+
clientRef.current?.updateConfig({
|
|
508
|
+
enabled,
|
|
509
|
+
noop,
|
|
510
|
+
sampleRate,
|
|
511
|
+
flushIntervalMs,
|
|
512
|
+
maxQueueSize,
|
|
513
|
+
instrumentFetch,
|
|
514
|
+
tags,
|
|
515
|
+
context,
|
|
516
|
+
transports,
|
|
517
|
+
redactNetworkEvent
|
|
518
|
+
});
|
|
519
|
+
}, [
|
|
520
|
+
enabled,
|
|
521
|
+
noop,
|
|
522
|
+
sampleRate,
|
|
523
|
+
flushIntervalMs,
|
|
524
|
+
maxQueueSize,
|
|
525
|
+
instrumentFetch,
|
|
526
|
+
tags,
|
|
527
|
+
context,
|
|
528
|
+
transports,
|
|
529
|
+
redactNetworkEvent
|
|
530
|
+
]);
|
|
531
|
+
useEffect(() => {
|
|
532
|
+
return () => {
|
|
533
|
+
void clientRef.current?.shutdown();
|
|
534
|
+
clientRef.current = null;
|
|
535
|
+
};
|
|
536
|
+
}, []);
|
|
537
|
+
return /* @__PURE__ */ jsx(AppDoctorContext.Provider, { value: clientRef.current, children });
|
|
538
|
+
}
|
|
539
|
+
function useAppDoctor() {
|
|
540
|
+
const client = useContext(AppDoctorContext);
|
|
541
|
+
if (!client) {
|
|
542
|
+
throw new Error("useAppDoctor must be used within AppDoctorProvider");
|
|
543
|
+
}
|
|
544
|
+
return client;
|
|
545
|
+
}
|
|
546
|
+
function useTrackScreen(screen, options = {}) {
|
|
547
|
+
const fromContext = useContext(AppDoctorContext);
|
|
548
|
+
const client = options.client ?? fromContext;
|
|
549
|
+
if (!client) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
"useTrackScreen requires AppDoctorProvider or options.client"
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
useEffect(() => {
|
|
555
|
+
const startedAt = Date.now();
|
|
556
|
+
client.emit({
|
|
557
|
+
name: "screen_load",
|
|
558
|
+
screen,
|
|
559
|
+
phase: "start",
|
|
560
|
+
durationMs: 0
|
|
561
|
+
});
|
|
562
|
+
queueMicrotask(() => {
|
|
563
|
+
client.emit({
|
|
564
|
+
name: "screen_load",
|
|
565
|
+
screen,
|
|
566
|
+
phase: "ready",
|
|
567
|
+
durationMs: Date.now() - startedAt
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
}, [client, screen]);
|
|
571
|
+
}
|
|
572
|
+
function useTrackRender(componentName, options = {}) {
|
|
573
|
+
const fromContext = useContext(AppDoctorContext);
|
|
574
|
+
const client = options.client ?? fromContext;
|
|
575
|
+
if (!client) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
"useTrackRender requires AppDoctorProvider or options.client"
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
const every = options.every ?? 10;
|
|
581
|
+
const count = useRef(0);
|
|
582
|
+
count.current += 1;
|
|
583
|
+
useEffect(() => {
|
|
584
|
+
if (count.current % every !== 0) return;
|
|
585
|
+
client.emit({
|
|
586
|
+
name: "render_event",
|
|
587
|
+
component: componentName,
|
|
588
|
+
renderCount: count.current
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export { AppDoctorClient, AppDoctorContext, AppDoctorProvider, createConsoleTransport, createHttpTransport, createNavigationStateListener, getActiveRouteName, instrumentAxios, shouldSample, trackApi, useAppDoctor, useTrackRender, useTrackScreen };
|
|
594
|
+
//# sourceMappingURL=index.js.map
|
|
595
|
+
//# sourceMappingURL=index.js.map
|