react-native-debug-toolkit 3.0.0 → 3.1.3
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 +115 -97
- package/README.zh-CN.md +113 -95
- package/lib/commonjs/core/initialize.js +5 -0
- package/lib/commonjs/core/initialize.js.map +1 -1
- package/lib/commonjs/index.js +23 -26
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js +24 -58
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js.map +1 -1
- package/lib/commonjs/utils/DaemonClient.js +721 -0
- package/lib/commonjs/utils/DaemonClient.js.map +1 -0
- package/lib/commonjs/utils/{sessionReport.js → deviceReport.js} +3 -3
- package/lib/commonjs/utils/deviceReport.js.map +1 -0
- package/lib/module/core/initialize.js +6 -0
- package/lib/module/core/initialize.js.map +1 -1
- package/lib/module/index.js +3 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/panel/StreamingSettingsModal.js +21 -55
- package/lib/module/ui/panel/StreamingSettingsModal.js.map +1 -1
- package/lib/module/utils/DaemonClient.js +703 -0
- package/lib/module/utils/DaemonClient.js.map +1 -0
- package/lib/module/utils/{sessionReport.js → deviceReport.js} +2 -2
- package/lib/module/utils/deviceReport.js.map +1 -0
- package/lib/typescript/src/core/initialize.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -10
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts.map +1 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts +141 -0
- package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -0
- package/lib/typescript/src/utils/{sessionReport.d.ts → deviceReport.d.ts} +4 -4
- package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -0
- package/node/daemon/src/cli.js +9 -2
- package/node/daemon/src/console/console.html +1052 -249
- package/node/daemon/src/constants.js +6 -0
- package/node/daemon/src/server.js +205 -123
- package/node/daemon/src/store.js +122 -45
- package/node/mcp/src/daemonClient.js +6 -6
- package/node/mcp/src/index.js +2 -2
- package/node/mcp/src/logs.js +5 -4
- package/node/mcp/src/tools.js +16 -16
- package/package.json +2 -2
- package/src/core/initialize.ts +8 -0
- package/src/index.ts +18 -10
- package/src/ui/panel/StreamingSettingsModal.tsx +25 -63
- package/src/utils/DaemonClient.ts +887 -0
- package/src/utils/{sessionReport.ts → deviceReport.ts} +6 -6
- package/lib/commonjs/utils/autoDetectDaemon.js +0 -141
- package/lib/commonjs/utils/autoDetectDaemon.js.map +0 -1
- package/lib/commonjs/utils/daemonConnection.js +0 -81
- package/lib/commonjs/utils/daemonConnection.js.map +0 -1
- package/lib/commonjs/utils/daemonSettings.js +0 -110
- package/lib/commonjs/utils/daemonSettings.js.map +0 -1
- package/lib/commonjs/utils/reportToDaemon.js +0 -112
- package/lib/commonjs/utils/reportToDaemon.js.map +0 -1
- package/lib/commonjs/utils/sessionReport.js.map +0 -1
- package/lib/commonjs/utils/streamToDaemon.js +0 -334
- package/lib/commonjs/utils/streamToDaemon.js.map +0 -1
- package/lib/module/utils/autoDetectDaemon.js +0 -136
- package/lib/module/utils/autoDetectDaemon.js.map +0 -1
- package/lib/module/utils/daemonConnection.js +0 -77
- package/lib/module/utils/daemonConnection.js.map +0 -1
- package/lib/module/utils/daemonSettings.js +0 -102
- package/lib/module/utils/daemonSettings.js.map +0 -1
- package/lib/module/utils/reportToDaemon.js +0 -105
- package/lib/module/utils/reportToDaemon.js.map +0 -1
- package/lib/module/utils/sessionReport.js.map +0 -1
- package/lib/module/utils/streamToDaemon.js +0 -328
- package/lib/module/utils/streamToDaemon.js.map +0 -1
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts +0 -15
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts.map +0 -1
- package/lib/typescript/src/utils/daemonConnection.d.ts +0 -18
- package/lib/typescript/src/utils/daemonConnection.d.ts.map +0 -1
- package/lib/typescript/src/utils/daemonSettings.d.ts +0 -19
- package/lib/typescript/src/utils/daemonSettings.d.ts.map +0 -1
- package/lib/typescript/src/utils/reportToDaemon.d.ts +0 -34
- package/lib/typescript/src/utils/reportToDaemon.d.ts.map +0 -1
- package/lib/typescript/src/utils/sessionReport.d.ts.map +0 -1
- package/lib/typescript/src/utils/streamToDaemon.d.ts +0 -23
- package/lib/typescript/src/utils/streamToDaemon.d.ts.map +0 -1
- package/src/utils/autoDetectDaemon.ts +0 -175
- package/src/utils/daemonConnection.ts +0 -133
- package/src/utils/daemonSettings.ts +0 -134
- package/src/utils/reportToDaemon.ts +0 -172
- package/src/utils/streamToDaemon.ts +0 -419
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
import { AppState, type AppStateStatus } from 'react-native';
|
|
2
|
-
|
|
3
|
-
import { DebugToolkit } from '../core/DebugToolkit';
|
|
4
|
-
import { _addDaemonEndpointToNetworkBlacklist } from '../features/network';
|
|
5
|
-
import { createDebugSessionReport } from './sessionReport';
|
|
6
|
-
import {
|
|
7
|
-
buildDaemonUrl,
|
|
8
|
-
getDefaultDaemonEndpoint,
|
|
9
|
-
getGlobalFetch,
|
|
10
|
-
} from './reportToDaemon';
|
|
11
|
-
import { safeStringify } from './safeStringify';
|
|
12
|
-
|
|
13
|
-
export interface StreamToDaemonOptions {
|
|
14
|
-
endpoint?: string;
|
|
15
|
-
token?: string;
|
|
16
|
-
debounceMs?: number;
|
|
17
|
-
timeoutMs?: number;
|
|
18
|
-
onStatus?: (status: StreamStatus) => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type StreamStatus =
|
|
22
|
-
| { state: 'connecting' }
|
|
23
|
-
| { state: 'connected'; sessionId: string }
|
|
24
|
-
| { state: 'retrying'; retryInMs: number }
|
|
25
|
-
| { state: 'failed'; reason: 'auth' | 'retry_limit' };
|
|
26
|
-
|
|
27
|
-
const DEFAULT_DEBOUNCE_MS = 200;
|
|
28
|
-
const DEFAULT_TIMEOUT_MS = 3000;
|
|
29
|
-
const RETRY_BASE_MS = 1000;
|
|
30
|
-
const MAX_RETRY_DELAY_MS = 30000;
|
|
31
|
-
const MAX_RETRY_ATTEMPTS = 10;
|
|
32
|
-
const BACKGROUND_RESYNC_THRESHOLD_MS = 5 * 60 * 1000;
|
|
33
|
-
type SendResult = 'ok' | 'retry' | 'auth_failed';
|
|
34
|
-
|
|
35
|
-
type FetchHeaders = Record<string, string>;
|
|
36
|
-
|
|
37
|
-
interface StreamState {
|
|
38
|
-
endpoint: string;
|
|
39
|
-
reportUrl: string;
|
|
40
|
-
ingestUrl: string;
|
|
41
|
-
token: string | undefined;
|
|
42
|
-
debounceMs: number;
|
|
43
|
-
timeoutMs: number;
|
|
44
|
-
sessionId: string | null;
|
|
45
|
-
sending: boolean;
|
|
46
|
-
debounceTimer: ReturnType<typeof setTimeout> | null;
|
|
47
|
-
retryTimer: ReturnType<typeof setTimeout> | null;
|
|
48
|
-
retryAttempt: number;
|
|
49
|
-
dirtyFeatures: Set<string>;
|
|
50
|
-
lastSentIds: Map<string, Set<string | number>>;
|
|
51
|
-
featureUnsubscribes: Array<() => void>;
|
|
52
|
-
appStateUnsubscribe: (() => void) | null;
|
|
53
|
-
backgroundedAt: number | null;
|
|
54
|
-
onStatus: ((status: StreamStatus) => void) | undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let active: StreamState | null = null;
|
|
58
|
-
|
|
59
|
-
type AbortControllerLike = {
|
|
60
|
-
signal: unknown;
|
|
61
|
-
abort: () => void;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
function createAbortController(): AbortControllerLike | undefined {
|
|
65
|
-
const AbortControllerCtor = (globalThis as {
|
|
66
|
-
AbortController?: new () => AbortControllerLike;
|
|
67
|
-
}).AbortController;
|
|
68
|
-
return AbortControllerCtor ? new AbortControllerCtor() : undefined;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function getEntryId(entry: unknown): string | number | null {
|
|
72
|
-
if (!entry || typeof entry !== 'object') {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const id = (entry as Record<string, unknown>).id;
|
|
77
|
-
return typeof id === 'string' || typeof id === 'number' ? id : null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function snapshotToIds(snapshot: unknown[]): Set<string | number> {
|
|
81
|
-
return new Set(
|
|
82
|
-
snapshot
|
|
83
|
-
.map(getEntryId)
|
|
84
|
-
.filter((id): id is string | number => id != null),
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function fetchHeaders(state: StreamState): FetchHeaders {
|
|
89
|
-
const headers: FetchHeaders = { 'Content-Type': 'application/json' };
|
|
90
|
-
if (state.token) headers.Authorization = `Bearer ${state.token}`;
|
|
91
|
-
return headers;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function emitStatus(state: StreamState, status: StreamStatus): void {
|
|
95
|
-
try {
|
|
96
|
-
state.onStatus?.(status);
|
|
97
|
-
} catch {
|
|
98
|
-
// Consumer status callbacks should not affect log delivery.
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function isAuthFailure(status: number): boolean {
|
|
103
|
-
return status === 401 || status === 403;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function failStreaming(state: StreamState, reason: 'auth' | 'retry_limit'): void {
|
|
107
|
-
if (active !== state) return;
|
|
108
|
-
emitStatus(state, { state: 'failed', reason });
|
|
109
|
-
stopStreaming();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function doPost(
|
|
113
|
-
url: string,
|
|
114
|
-
headers: FetchHeaders,
|
|
115
|
-
body: unknown,
|
|
116
|
-
timeoutMs: number,
|
|
117
|
-
): Promise<{ status: number; json?: () => Promise<unknown> } | null> {
|
|
118
|
-
const fetchImpl = getGlobalFetch();
|
|
119
|
-
if (!fetchImpl) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
const controller = createAbortController();
|
|
123
|
-
const timeout = controller && timeoutMs > 0
|
|
124
|
-
? setTimeout(() => controller.abort(), timeoutMs)
|
|
125
|
-
: undefined;
|
|
126
|
-
try {
|
|
127
|
-
return await fetchImpl(url, {
|
|
128
|
-
method: 'POST',
|
|
129
|
-
headers,
|
|
130
|
-
body: safeStringify(body),
|
|
131
|
-
signal: controller?.signal,
|
|
132
|
-
});
|
|
133
|
-
} catch {
|
|
134
|
-
return null;
|
|
135
|
-
} finally {
|
|
136
|
-
if (timeout) {
|
|
137
|
-
clearTimeout(timeout);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function resetRetry(state: StreamState): void {
|
|
143
|
-
state.retryAttempt = 0;
|
|
144
|
-
if (state.retryTimer) {
|
|
145
|
-
clearTimeout(state.retryTimer);
|
|
146
|
-
state.retryTimer = null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function scheduleRetry(state: StreamState): void {
|
|
151
|
-
if (state.retryTimer) return;
|
|
152
|
-
if (state.retryAttempt >= MAX_RETRY_ATTEMPTS) {
|
|
153
|
-
failStreaming(state, 'retry_limit');
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const delay = Math.min(
|
|
157
|
-
RETRY_BASE_MS * (2 ** state.retryAttempt),
|
|
158
|
-
MAX_RETRY_DELAY_MS,
|
|
159
|
-
);
|
|
160
|
-
state.retryAttempt += 1;
|
|
161
|
-
emitStatus(state, { state: 'retrying', retryInMs: delay });
|
|
162
|
-
state.retryTimer = setTimeout(() => {
|
|
163
|
-
state.retryTimer = null;
|
|
164
|
-
if (active !== state) return;
|
|
165
|
-
if (state.sessionId) {
|
|
166
|
-
sendDelta(state);
|
|
167
|
-
} else {
|
|
168
|
-
sendFullReport(state);
|
|
169
|
-
}
|
|
170
|
-
}, delay);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function sendFullReport(state: StreamState): Promise<void> {
|
|
174
|
-
if (state.sending) return;
|
|
175
|
-
state.sending = true;
|
|
176
|
-
let result: SendResult = 'ok';
|
|
177
|
-
try {
|
|
178
|
-
result = await doSendFullReport(state);
|
|
179
|
-
if (result === 'ok') resetRetry(state);
|
|
180
|
-
} finally {
|
|
181
|
-
state.sending = false;
|
|
182
|
-
if (active !== state) return;
|
|
183
|
-
if (result === 'auth_failed') {
|
|
184
|
-
failStreaming(state, 'auth');
|
|
185
|
-
} else if (result === 'retry') {
|
|
186
|
-
scheduleRetry(state);
|
|
187
|
-
} else if (state.dirtyFeatures.size > 0 && !state.debounceTimer) {
|
|
188
|
-
scheduleDelta(state);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function doSendFullReport(state: StreamState): Promise<SendResult> {
|
|
194
|
-
const report = createDebugSessionReport();
|
|
195
|
-
const response = await doPost(state.reportUrl, fetchHeaders(state), report, state.timeoutMs);
|
|
196
|
-
if (!response) return 'retry';
|
|
197
|
-
if (isAuthFailure(response.status)) return 'auth_failed';
|
|
198
|
-
if (response.status < 200 || response.status >= 300) return 'retry';
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
const body = response.json
|
|
202
|
-
? (await response.json()) as Record<string, unknown> | null
|
|
203
|
-
: null;
|
|
204
|
-
if (body?.ok !== true || typeof body.sessionId !== 'string') return 'retry';
|
|
205
|
-
state.sessionId = body.sessionId;
|
|
206
|
-
emitStatus(state, { state: 'connected', sessionId: body.sessionId });
|
|
207
|
-
} catch {
|
|
208
|
-
return 'retry';
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
state.lastSentIds.clear();
|
|
212
|
-
for (const feature of DebugToolkit.features) {
|
|
213
|
-
try {
|
|
214
|
-
const snapshot = feature.getSnapshot();
|
|
215
|
-
if (Array.isArray(snapshot)) state.lastSentIds.set(feature.name, snapshotToIds(snapshot));
|
|
216
|
-
} catch {
|
|
217
|
-
// skip
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return 'ok';
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async function sendDelta(state: StreamState): Promise<void> {
|
|
224
|
-
if (state.sending || state.dirtyFeatures.size === 0) return;
|
|
225
|
-
state.sending = true;
|
|
226
|
-
let retry = false;
|
|
227
|
-
try {
|
|
228
|
-
const delta: Record<string, unknown[]> = {};
|
|
229
|
-
const nextSentIds = new Map<string, Set<string | number>>();
|
|
230
|
-
const features = DebugToolkit.features;
|
|
231
|
-
|
|
232
|
-
for (const featureName of state.dirtyFeatures) {
|
|
233
|
-
const feature = features.find((f) => f.name === featureName);
|
|
234
|
-
if (!feature) continue;
|
|
235
|
-
|
|
236
|
-
let snapshot: unknown;
|
|
237
|
-
try {
|
|
238
|
-
snapshot = feature.getSnapshot();
|
|
239
|
-
} catch {
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (!Array.isArray(snapshot)) continue;
|
|
244
|
-
|
|
245
|
-
const prevIds = state.lastSentIds.get(featureName) || new Set<string | number>();
|
|
246
|
-
const newEntries = snapshot.filter(
|
|
247
|
-
(entry) => {
|
|
248
|
-
const id = getEntryId(entry);
|
|
249
|
-
return id != null && !prevIds.has(id);
|
|
250
|
-
},
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
if (newEntries.length > 0) {
|
|
254
|
-
delta[featureName] = newEntries;
|
|
255
|
-
nextSentIds.set(featureName, snapshotToIds(snapshot));
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
state.dirtyFeatures.clear();
|
|
260
|
-
state.debounceTimer = null;
|
|
261
|
-
|
|
262
|
-
if (Object.keys(delta).length === 0) return;
|
|
263
|
-
|
|
264
|
-
if (!state.sessionId) {
|
|
265
|
-
const result = await doSendFullReport(state);
|
|
266
|
-
retry = result === 'retry';
|
|
267
|
-
if (result !== 'ok') Object.keys(delta).forEach((featureName) => state.dirtyFeatures.add(featureName));
|
|
268
|
-
if (result === 'auth_failed') failStreaming(state, 'auth');
|
|
269
|
-
if (result === 'ok') resetRetry(state);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const response = await doPost(
|
|
274
|
-
state.ingestUrl,
|
|
275
|
-
fetchHeaders(state),
|
|
276
|
-
{ sessionId: state.sessionId, delta: { logs: delta } },
|
|
277
|
-
state.timeoutMs,
|
|
278
|
-
);
|
|
279
|
-
if (!response) {
|
|
280
|
-
Object.keys(delta).forEach((featureName) => state.dirtyFeatures.add(featureName));
|
|
281
|
-
retry = true;
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (response.status === 404) {
|
|
286
|
-
state.sessionId = null;
|
|
287
|
-
state.lastSentIds.clear();
|
|
288
|
-
const result = await doSendFullReport(state);
|
|
289
|
-
retry = result === 'retry';
|
|
290
|
-
if (result !== 'ok') Object.keys(delta).forEach((featureName) => state.dirtyFeatures.add(featureName));
|
|
291
|
-
if (result === 'auth_failed') failStreaming(state, 'auth');
|
|
292
|
-
if (result === 'ok') resetRetry(state);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (isAuthFailure(response.status)) {
|
|
297
|
-
Object.keys(delta).forEach((featureName) => state.dirtyFeatures.add(featureName));
|
|
298
|
-
failStreaming(state, 'auth');
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (response.status < 200 || response.status >= 300) {
|
|
303
|
-
Object.keys(delta).forEach((featureName) => state.dirtyFeatures.add(featureName));
|
|
304
|
-
retry = true;
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
nextSentIds.forEach((ids, featureName) => {
|
|
309
|
-
state.lastSentIds.set(featureName, ids);
|
|
310
|
-
});
|
|
311
|
-
resetRetry(state);
|
|
312
|
-
if (state.sessionId) {
|
|
313
|
-
emitStatus(state, { state: 'connected', sessionId: state.sessionId });
|
|
314
|
-
}
|
|
315
|
-
} finally {
|
|
316
|
-
state.sending = false;
|
|
317
|
-
if (active !== state) return;
|
|
318
|
-
if (retry && state.dirtyFeatures.size > 0) {
|
|
319
|
-
scheduleRetry(state);
|
|
320
|
-
} else if (state.dirtyFeatures.size > 0 && !state.debounceTimer) {
|
|
321
|
-
scheduleDelta(state);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function scheduleDelta(state: StreamState): void {
|
|
327
|
-
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
328
|
-
state.debounceTimer = setTimeout(() => {
|
|
329
|
-
state.debounceTimer = null;
|
|
330
|
-
if (active === state) sendDelta(state);
|
|
331
|
-
}, state.debounceMs);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function onFeatureChange(featureName: string): void {
|
|
335
|
-
if (!active) return;
|
|
336
|
-
active.dirtyFeatures.add(featureName);
|
|
337
|
-
if (active.retryTimer) return;
|
|
338
|
-
scheduleDelta(active);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function handleAppStateChange(nextState: AppStateStatus): void {
|
|
342
|
-
if (!active) return;
|
|
343
|
-
if (nextState === 'background') {
|
|
344
|
-
active.backgroundedAt = Date.now();
|
|
345
|
-
if (active.debounceTimer) {
|
|
346
|
-
clearTimeout(active.debounceTimer);
|
|
347
|
-
active.debounceTimer = null;
|
|
348
|
-
}
|
|
349
|
-
sendDelta(active);
|
|
350
|
-
} else if (nextState === 'active') {
|
|
351
|
-
const wasAway = active.backgroundedAt ? Date.now() - active.backgroundedAt : 0;
|
|
352
|
-
active.backgroundedAt = null;
|
|
353
|
-
|
|
354
|
-
if (wasAway > BACKGROUND_RESYNC_THRESHOLD_MS || !active.sessionId) {
|
|
355
|
-
active.sessionId = null;
|
|
356
|
-
active.lastSentIds.clear();
|
|
357
|
-
sendFullReport(active);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export function startStreaming(options: StreamToDaemonOptions = {}): void {
|
|
363
|
-
if (active) return;
|
|
364
|
-
|
|
365
|
-
const endpoint = options.endpoint || getDefaultDaemonEndpoint();
|
|
366
|
-
const reportUrl = buildDaemonUrl(endpoint, '/report');
|
|
367
|
-
const ingestUrl = buildDaemonUrl(endpoint, '/ingest');
|
|
368
|
-
|
|
369
|
-
_addDaemonEndpointToNetworkBlacklist(endpoint);
|
|
370
|
-
_addDaemonEndpointToNetworkBlacklist(reportUrl);
|
|
371
|
-
_addDaemonEndpointToNetworkBlacklist(ingestUrl);
|
|
372
|
-
|
|
373
|
-
const state: StreamState = {
|
|
374
|
-
endpoint,
|
|
375
|
-
reportUrl,
|
|
376
|
-
ingestUrl,
|
|
377
|
-
token: options.token,
|
|
378
|
-
debounceMs: options.debounceMs || DEFAULT_DEBOUNCE_MS,
|
|
379
|
-
timeoutMs: Math.max(0, options.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
380
|
-
sessionId: null,
|
|
381
|
-
sending: false,
|
|
382
|
-
debounceTimer: null,
|
|
383
|
-
retryTimer: null,
|
|
384
|
-
retryAttempt: 0,
|
|
385
|
-
dirtyFeatures: new Set(),
|
|
386
|
-
lastSentIds: new Map(),
|
|
387
|
-
featureUnsubscribes: [],
|
|
388
|
-
appStateUnsubscribe: null,
|
|
389
|
-
backgroundedAt: null,
|
|
390
|
-
onStatus: options.onStatus,
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
for (const feature of DebugToolkit.features) {
|
|
394
|
-
if (!feature.subscribe) continue;
|
|
395
|
-
const unsub = feature.subscribe(() => { onFeatureChange(feature.name); });
|
|
396
|
-
state.featureUnsubscribes.push(unsub);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
state.appStateUnsubscribe = AppState.addEventListener('change', handleAppStateChange).remove;
|
|
400
|
-
active = state;
|
|
401
|
-
|
|
402
|
-
emitStatus(active, { state: 'connecting' });
|
|
403
|
-
sendFullReport(active);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
export function stopStreaming(): void {
|
|
407
|
-
if (!active) return;
|
|
408
|
-
const state = active;
|
|
409
|
-
active = null;
|
|
410
|
-
|
|
411
|
-
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
412
|
-
if (state.retryTimer) clearTimeout(state.retryTimer);
|
|
413
|
-
state.featureUnsubscribes.forEach((fn) => fn());
|
|
414
|
-
state.appStateUnsubscribe?.();
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
export function isStreaming(): boolean {
|
|
418
|
-
return active !== null;
|
|
419
|
-
}
|