react-native-debug-toolkit 3.1.3 → 3.1.5

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.
Files changed (94) hide show
  1. package/README.md +83 -65
  2. package/README.zh-CN.md +82 -64
  3. package/bin/debug-toolkit.js +10 -2
  4. package/lib/commonjs/core/DebugToolkit.js +118 -97
  5. package/lib/commonjs/core/DebugToolkit.js.map +1 -1
  6. package/lib/commonjs/core/initialize.js +4 -4
  7. package/lib/commonjs/core/initialize.js.map +1 -1
  8. package/lib/commonjs/features/environment/index.js +22 -24
  9. package/lib/commonjs/features/environment/index.js.map +1 -1
  10. package/lib/commonjs/features/network/NetworkLogTab.js +7 -3
  11. package/lib/commonjs/features/network/NetworkLogTab.js.map +1 -1
  12. package/lib/commonjs/features/network/index.js +25 -47
  13. package/lib/commonjs/features/network/index.js.map +1 -1
  14. package/lib/commonjs/features/network/networkInterceptor.js +3 -3
  15. package/lib/commonjs/features/network/networkInterceptor.js.map +1 -1
  16. package/lib/commonjs/index.js +0 -30
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/utils/DaemonClient.js +37 -51
  19. package/lib/commonjs/utils/DaemonClient.js.map +1 -1
  20. package/lib/commonjs/utils/createChannelFeature.js +8 -1
  21. package/lib/commonjs/utils/createChannelFeature.js.map +1 -1
  22. package/lib/commonjs/utils/deviceReport.js +3 -1
  23. package/lib/commonjs/utils/deviceReport.js.map +1 -1
  24. package/lib/commonjs/utils/urlRewriter.js +15 -0
  25. package/lib/commonjs/utils/urlRewriter.js.map +1 -0
  26. package/lib/module/core/DebugToolkit.js +117 -96
  27. package/lib/module/core/DebugToolkit.js.map +1 -1
  28. package/lib/module/core/initialize.js +6 -7
  29. package/lib/module/core/initialize.js.map +1 -1
  30. package/lib/module/features/environment/index.js +22 -24
  31. package/lib/module/features/environment/index.js.map +1 -1
  32. package/lib/module/features/network/NetworkLogTab.js +7 -3
  33. package/lib/module/features/network/NetworkLogTab.js.map +1 -1
  34. package/lib/module/features/network/index.js +25 -46
  35. package/lib/module/features/network/index.js.map +1 -1
  36. package/lib/module/features/network/networkInterceptor.js +3 -3
  37. package/lib/module/features/network/networkInterceptor.js.map +1 -1
  38. package/lib/module/index.js +1 -1
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/utils/DaemonClient.js +38 -42
  41. package/lib/module/utils/DaemonClient.js.map +1 -1
  42. package/lib/module/utils/createChannelFeature.js +8 -1
  43. package/lib/module/utils/createChannelFeature.js.map +1 -1
  44. package/lib/module/utils/deviceReport.js +4 -2
  45. package/lib/module/utils/deviceReport.js.map +1 -1
  46. package/lib/module/utils/urlRewriter.js +10 -0
  47. package/lib/module/utils/urlRewriter.js.map +1 -0
  48. package/lib/typescript/src/core/DebugToolkit.d.ts +23 -10
  49. package/lib/typescript/src/core/DebugToolkit.d.ts.map +1 -1
  50. package/lib/typescript/src/core/initialize.d.ts.map +1 -1
  51. package/lib/typescript/src/features/environment/index.d.ts.map +1 -1
  52. package/lib/typescript/src/features/network/NetworkLogTab.d.ts.map +1 -1
  53. package/lib/typescript/src/features/network/index.d.ts +3 -3
  54. package/lib/typescript/src/features/network/index.d.ts.map +1 -1
  55. package/lib/typescript/src/index.d.ts +2 -2
  56. package/lib/typescript/src/index.d.ts.map +1 -1
  57. package/lib/typescript/src/types/feature.d.ts +5 -0
  58. package/lib/typescript/src/types/feature.d.ts.map +1 -1
  59. package/lib/typescript/src/types/index.d.ts +1 -1
  60. package/lib/typescript/src/types/index.d.ts.map +1 -1
  61. package/lib/typescript/src/utils/DaemonClient.d.ts +5 -11
  62. package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -1
  63. package/lib/typescript/src/utils/createChannelFeature.d.ts +4 -0
  64. package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -1
  65. package/lib/typescript/src/utils/deviceReport.d.ts +10 -1
  66. package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -1
  67. package/lib/typescript/src/utils/urlRewriter.d.ts +5 -0
  68. package/lib/typescript/src/utils/urlRewriter.d.ts.map +1 -0
  69. package/node/daemon/src/console/console.html +197 -27
  70. package/node/daemon/src/server.js +32 -2
  71. package/node/daemon/src/store.js +45 -6
  72. package/node/mcp/src/logs.js +15 -4
  73. package/node/mcp/src/tools.js +4 -2
  74. package/package.json +6 -2
  75. package/src/core/DebugToolkit.tsx +119 -105
  76. package/src/core/initialize.ts +7 -8
  77. package/src/features/environment/index.ts +25 -27
  78. package/src/features/network/NetworkLogTab.tsx +6 -3
  79. package/src/features/network/index.ts +30 -52
  80. package/src/features/network/networkInterceptor.ts +3 -3
  81. package/src/index.ts +3 -8
  82. package/src/types/feature.ts +6 -0
  83. package/src/types/index.ts +1 -0
  84. package/src/utils/DaemonClient.ts +39 -56
  85. package/src/utils/createChannelFeature.ts +12 -1
  86. package/src/utils/deviceReport.ts +12 -3
  87. package/src/utils/urlRewriter.ts +11 -0
  88. package/lib/commonjs/utils/urlRewriterRegistry.js +0 -14
  89. package/lib/commonjs/utils/urlRewriterRegistry.js.map +0 -1
  90. package/lib/module/utils/urlRewriterRegistry.js +0 -10
  91. package/lib/module/utils/urlRewriterRegistry.js.map +0 -1
  92. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts +0 -7
  93. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts.map +0 -1
  94. package/src/utils/urlRewriterRegistry.ts +0 -10
@@ -5,7 +5,7 @@ import type {
5
5
  EnvironmentConfig,
6
6
  EnvironmentState,
7
7
  } from '../../types';
8
- import { urlRewriter } from '../../utils/urlRewriterRegistry';
8
+ import { setUrlRewriter } from '../../utils/urlRewriter';
9
9
 
10
10
  // Lazy AsyncStorage loader
11
11
  type AsyncStorageModule = {
@@ -32,10 +32,6 @@ function getAsyncStorage(): AsyncStorageModule | null {
32
32
 
33
33
  const STORAGE_KEY = 'debug_toolkit_env_id';
34
34
 
35
- // Module-level state for synchronous URL rewriting
36
- let currentHostsMap: Map<string, string> | null = null;
37
- let activeEnvironmentId: string | null = null;
38
-
39
35
  function buildHostsMap(
40
36
  environments: EnvironmentConfig[],
41
37
  targetId: string | null,
@@ -53,24 +49,6 @@ function buildHostsMap(
53
49
  return map;
54
50
  }
55
51
 
56
- function createUrlRewriter(): (url: string) => string {
57
- return (url: string): string => {
58
- if (!currentHostsMap) return url;
59
-
60
- try {
61
- const parsed = new URL(url);
62
- const targetHost = currentHostsMap.get(parsed.host);
63
- if (targetHost) {
64
- return url.replace(parsed.host, targetHost);
65
- }
66
- } catch {
67
- // Not a valid URL or relative URL — return unchanged
68
- }
69
-
70
- return url;
71
- };
72
- }
73
-
74
52
  export interface EnvironmentFeatureAPI extends DebugFeature<EnvironmentState> {
75
53
  registerEnvironments: (environments: EnvironmentConfig[]) => void;
76
54
  switchEnvironment: (environmentId: string | null) => void;
@@ -83,6 +61,26 @@ export const createEnvironmentFeature = (
83
61
  const listeners = new Set<DebugFeatureListener>();
84
62
  let environments: EnvironmentConfig[] = initialEnvironments ?? [];
85
63
  let initialized = false;
64
+ let currentHostsMap: Map<string, string> | null = null;
65
+ let activeEnvironmentId: string | null = null;
66
+
67
+ const createUrlRewriter = (): ((url: string) => string) => {
68
+ return (url: string): string => {
69
+ if (!currentHostsMap) return url;
70
+
71
+ try {
72
+ const parsed = new URL(url);
73
+ const targetHost = currentHostsMap.get(parsed.host);
74
+ if (targetHost) {
75
+ return url.replace(parsed.host, targetHost);
76
+ }
77
+ } catch {
78
+ // Not a valid URL or relative URL — return unchanged
79
+ }
80
+
81
+ return url;
82
+ };
83
+ };
86
84
 
87
85
  const getCurrentState = (): EnvironmentState => ({
88
86
  environments,
@@ -102,9 +100,9 @@ export const createEnvironmentFeature = (
102
100
  if (initialized) {
103
101
  try {
104
102
  if (envId && environments.length > 0) {
105
- urlRewriter.set(createUrlRewriter());
103
+ setUrlRewriter(createUrlRewriter());
106
104
  } else {
107
- urlRewriter.set(null);
105
+ setUrlRewriter(null);
108
106
  }
109
107
  } catch (err) {
110
108
  if (__DEV__) console.warn('[DebugToolkit] Failed to set URL rewriter:', err);
@@ -149,7 +147,7 @@ export const createEnvironmentFeature = (
149
147
 
150
148
  // Install rewriter if an environment is already selected
151
149
  if (activeEnvironmentId && environments.length > 0) {
152
- urlRewriter.set(createUrlRewriter());
150
+ setUrlRewriter(createUrlRewriter());
153
151
  }
154
152
 
155
153
  // Async persistence load (will override if a preference exists)
@@ -162,7 +160,7 @@ export const createEnvironmentFeature = (
162
160
  },
163
161
  cleanup: () => {
164
162
  if (!initialized) return;
165
- urlRewriter.set(null);
163
+ setUrlRewriter(null);
166
164
  activeEnvironmentId = null;
167
165
  currentHostsMap = null;
168
166
  notify();
@@ -26,13 +26,16 @@ const formatSize = (data: unknown): string => {
26
26
  }
27
27
  };
28
28
 
29
+ // Keep in sync with console.html buildCurlCommand()
29
30
  const buildCurl = (log: NetworkLogEntry): string => {
30
- let c = `curl -X ${log.request.method} '${log.request.url}'`;
31
+ const q = (s: string) => s.replace(/'/g, "'\\''");
32
+ let c = `curl -X ${log.request.method} '${q(log.request.url)}'`;
31
33
  if (log.request.headers) {
32
- Object.entries(log.request.headers).forEach(([k, v]) => (c += ` \\\n -H '${k}: ${v}'`));
34
+ Object.entries(log.request.headers).forEach(([k, v]) => (c += ` \\\n -H '${q(k)}: ${q(String(v))}'`));
33
35
  }
34
36
  if (log.request.body) {
35
- c += ` \\\n -d '${typeof log.request.body === 'string' ? log.request.body : JSON.stringify(log.request.body)}'`;
37
+ const bodyStr = typeof log.request.body === 'string' ? log.request.body : JSON.stringify(log.request.body);
38
+ c += ` \\\n -d '${q(bodyStr)}'`;
36
39
  }
37
40
  return c;
38
41
  };
@@ -1,15 +1,15 @@
1
1
  import { NetworkLogTab } from './NetworkLogTab';
2
2
 
3
- import type { DebugFeature, NetworkLogEntry } from '../../types';
3
+ import type { NetworkLogEntry } from '../../types';
4
+ import { createChannelFeature } from '../../utils/createChannelFeature';
4
5
  import { createEventChannel } from '../../utils/createEventChannel';
5
- import { createPersistedObservableStore } from '../../utils/createPersistedObservableStore';
6
6
  import { KEYS } from '../../utils/debugPreferences';
7
- import { urlRewriter } from '../../utils/urlRewriterRegistry';
8
7
  import {
9
8
  startXMLHttpRequest,
10
9
  resetInterceptors,
11
10
  } from './networkInterceptor';
12
11
  import type { NetworkLogPayload } from './networkInterceptor';
12
+ import { setUrlRewriter as setInterceptorUrlRewriter } from '../../utils/urlRewriter';
13
13
 
14
14
  // ─── Utilities ────────────────────────────────────────
15
15
 
@@ -35,7 +35,6 @@ function emitNetworkLog(entry: NetworkLogPayload): void {
35
35
 
36
36
  // ─── Feature factory ──────────────────────────────────
37
37
 
38
- const DEFAULT_MAX_LOGS = 200;
39
38
  const daemonEndpointBlacklist: Array<string | RegExp> = [];
40
39
 
41
40
  export interface NetworkFeatureConfig {
@@ -45,54 +44,33 @@ export interface NetworkFeatureConfig {
45
44
  blacklist?: Array<string | RegExp>;
46
45
  }
47
46
 
48
- export const createNetworkFeature = (config?: NetworkFeatureConfig): DebugFeature<NetworkLogEntry[]> => {
49
- const maxLogs = config?.maxLogs ?? DEFAULT_MAX_LOGS;
50
- const blacklist: Array<string | RegExp> = config?.blacklist ? [...config.blacklist] : [];
51
- const logStore = createPersistedObservableStore<NetworkLogEntry>({
52
- storageKey: KEYS.networkLogs,
53
- maxPersist: 30,
54
- });
55
- let initialized = false;
56
- let unsubscribeLogs: (() => void) | null = null;
57
- let stopXhrFn: (() => void) | null = null;
58
-
59
- const handleLog = (entry: NetworkLogPayload) => {
60
- if (isUrlBlacklisted(entry.request.url, [...blacklist, ...daemonEndpointBlacklist])) {
61
- return;
62
- }
63
- logStore.push({ ...entry, id: logStore.nextId() }, maxLogs);
64
- };
65
-
66
- return {
67
- name: 'network',
68
- label: 'Network',
69
- renderContent: NetworkLogTab,
70
- setup: () => {
71
- if (initialized) {
72
- return;
73
- }
74
- unsubscribeLogs = networkChannel.subscribe(handleLog);
75
- stopXhrFn = startXMLHttpRequest(emitNetworkLog);
76
- initialized = true;
47
+ export const createNetworkFeature = (config?: NetworkFeatureConfig) => {
48
+ const userBlacklist = config?.blacklist ? [...config.blacklist] : [];
49
+
50
+ return createChannelFeature<NetworkLogPayload, NetworkLogEntry>(
51
+ () => networkChannel,
52
+ (payload, id) => ({ ...payload, id }),
53
+ {
54
+ name: 'network',
55
+ label: 'Network',
56
+ renderContent: NetworkLogTab,
57
+ maxLogs: config?.maxLogs,
58
+ persist: { storageKey: KEYS.networkLogs, maxPersist: 30 },
59
+ beforePush: (payload) => {
60
+ if (isUrlBlacklisted(payload.request.url, [...userBlacklist, ...daemonEndpointBlacklist])) {
61
+ return null;
62
+ }
63
+ return payload;
64
+ },
65
+ onSetup: () => {
66
+ const stopXhr = startXMLHttpRequest(emitNetworkLog);
67
+ return () => {
68
+ setInterceptorUrlRewriter(null);
69
+ stopXhr();
70
+ };
71
+ },
77
72
  },
78
- getSnapshot: () => logStore.getData(),
79
- clear: () => {
80
- logStore.clear();
81
- },
82
- cleanup: () => {
83
- if (!initialized) {
84
- return;
85
- }
86
- urlRewriter.set(null);
87
- unsubscribeLogs?.();
88
- unsubscribeLogs = null;
89
- stopXhrFn?.();
90
- stopXhrFn = null;
91
- logStore.clear();
92
- initialized = false;
93
- },
94
- subscribe: (listener) => logStore.subscribe(listener),
95
- };
73
+ );
96
74
  };
97
75
 
98
76
  function normalizeDaemonEndpoint(endpoint: string): string {
@@ -109,7 +87,7 @@ function normalizeDaemonEndpoint(endpoint: string): string {
109
87
  }
110
88
  }
111
89
 
112
- export function _addDaemonEndpointToNetworkBlacklist(endpoint: string): void {
90
+ export function addToBlacklist(endpoint: string): void {
113
91
  const normalized = normalizeDaemonEndpoint(endpoint);
114
92
  if (!normalized || daemonEndpointBlacklist.includes(normalized)) {
115
93
  return;
@@ -1,5 +1,5 @@
1
1
  import type { NetworkLogEntry } from '../../types';
2
- import { urlRewriter } from '../../utils/urlRewriterRegistry';
2
+ import { getUrlRewriter } from '../../utils/urlRewriter';
3
3
 
4
4
  type NetworkLogPayload = Omit<NetworkLogEntry, 'id'>;
5
5
 
@@ -11,7 +11,7 @@ export type { NetworkLogPayload };
11
11
  // ─── Shared helpers ────────────────────────────────────
12
12
 
13
13
  function rewriteUrl(url: string): string {
14
- const rewriter = urlRewriter.get();
14
+ const rewriter = getUrlRewriter();
15
15
  if (!rewriter) {
16
16
  return url;
17
17
  }
@@ -193,7 +193,7 @@ export function startXMLHttpRequest(
193
193
  url: string,
194
194
  ...args: unknown[]
195
195
  ) {
196
- const rewrittenUrl = urlRewriter.get() ? rewriteUrl(url) : url;
196
+ const rewrittenUrl = rewriteUrl(url);
197
197
  if (shouldIgnoreUrl(rewrittenUrl)) {
198
198
  return originalXhrOpen!.call(this, method, rewrittenUrl, ...args);
199
199
  }
package/src/index.ts CHANGED
@@ -42,21 +42,16 @@ export type {
42
42
  ReportResult,
43
43
  ReportToDaemonOptions,
44
44
  } from './utils/DaemonClient';
45
- export {
46
- getDefaultDaemonEndpoint,
47
- reportDebugDeviceToDaemon,
48
- checkDaemonConnection,
49
- startStreaming,
50
- stopStreaming,
51
- isStreaming,
52
- } from './utils/DaemonClient';
45
+ export { getDefaultDaemonEndpoint } from './utils/DaemonClient';
53
46
 
54
47
  // Types
55
48
  export type {
56
49
  AnyDebugFeature,
57
50
  BuiltInFeatureName,
58
51
  DebugFeature,
52
+ DebugFeatureListener,
59
53
  DebugFeatureRenderProps,
54
+ FeatureDataProvider,
60
55
  NetworkLogEntry,
61
56
  ConsoleLogEntry,
62
57
  ZustandLogEntry,
@@ -19,6 +19,12 @@ export interface DebugFeatureRenderProps<TSnapshot = unknown> {
19
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
20
  export type AnyDebugFeature = DebugFeature<any>;
21
21
 
22
+ /** Provides feature list and change notifications to consumers (e.g., DaemonClient). */
23
+ export interface FeatureDataProvider {
24
+ readonly features: AnyDebugFeature[];
25
+ subscribe(listener: DebugFeatureListener): () => void;
26
+ }
27
+
22
28
  export interface DebugFeature<TSnapshot = unknown> {
23
29
  name: string;
24
30
  label: string;
@@ -4,6 +4,7 @@ export type {
4
4
  DebugFeature,
5
5
  DebugFeatureListener,
6
6
  DebugFeatureRenderProps,
7
+ FeatureDataProvider,
7
8
  } from './feature';
8
9
 
9
10
  export type {
@@ -1,10 +1,12 @@
1
1
  import { AppState, type AppStateStatus, Platform } from 'react-native';
2
2
 
3
- import { DebugToolkit } from '../core/DebugToolkit';
3
+ import { debugToolkit } from '../core/DebugToolkit';
4
+ import type { FeatureDataProvider } from '../types';
4
5
  import {
5
6
  createDebugDeviceReport,
6
7
  type DebugDeviceReport,
7
8
  type DebugDeviceReportOptions,
9
+ type SessionInfo,
8
10
  } from './deviceReport';
9
11
  import { safeStringify } from './safeStringify';
10
12
 
@@ -146,6 +148,7 @@ interface StreamState {
146
148
  debounceMs: number;
147
149
  timeoutMs: number;
148
150
  deviceId: string | null;
151
+ session: SessionInfo;
149
152
  sending: boolean;
150
153
  debounceTimer: ReturnType<typeof setTimeout> | null;
151
154
  retryTimer: ReturnType<typeof setTimeout> | null;
@@ -164,6 +167,7 @@ interface StreamState {
164
167
  export interface DaemonClientOptions {
165
168
  fetch?: FetchLike;
166
169
  AbortController?: AbortControllerCtor;
170
+ featureProvider: FeatureDataProvider;
167
171
  onEndpointDetected?: (url: string) => void;
168
172
  }
169
173
 
@@ -178,13 +182,16 @@ export class DaemonClient {
178
182
  private _stream: StreamState | null = null;
179
183
  private _fetch: FetchLike | undefined;
180
184
  private _AbortController: AbortControllerCtor | undefined;
185
+ private _featureProvider: FeatureDataProvider;
181
186
  private _onEndpointDetected: ((url: string) => void) | undefined;
182
187
  private _restorePromise: Promise<void> | null = null;
188
+ private _sessionId: SessionInfo | null = null;
183
189
 
184
- constructor(options?: DaemonClientOptions) {
185
- this._fetch = options?.fetch;
186
- this._AbortController = options?.AbortController;
187
- this._onEndpointDetected = options?.onEndpointDetected;
190
+ constructor(options: DaemonClientOptions) {
191
+ this._fetch = options.fetch;
192
+ this._AbortController = options.AbortController;
193
+ this._featureProvider = options.featureProvider;
194
+ this._onEndpointDetected = options.onEndpointDetected;
188
195
  }
189
196
 
190
197
  // --- Settings ---
@@ -260,6 +267,10 @@ export class DaemonClient {
260
267
  connect(options: StreamToDaemonOptions = {}): void {
261
268
  if (this._stream) return;
262
269
 
270
+ if (!this._sessionId) {
271
+ this._sessionId = { id: generateSessionId(), startedAt: Date.now() };
272
+ }
273
+
263
274
  const endpoint = options.endpoint || this.resolveEndpoint();
264
275
  const reportUrl = buildDaemonUrl(endpoint, '/report');
265
276
  const ingestUrl = buildDaemonUrl(endpoint, '/ingest');
@@ -276,6 +287,7 @@ export class DaemonClient {
276
287
  debounceMs: options.debounceMs || DEFAULT_DEBOUNCE_MS,
277
288
  timeoutMs: Math.max(0, options.timeoutMs ?? DEFAULT_TIMEOUT_MS),
278
289
  deviceId: null,
290
+ session: this._sessionId,
279
291
  sending: false,
280
292
  debounceTimer: null,
281
293
  retryTimer: null,
@@ -291,7 +303,7 @@ export class DaemonClient {
291
303
  onStatus: options.onStatus,
292
304
  };
293
305
 
294
- for (const feature of DebugToolkit.features) {
306
+ for (const feature of this._featureProvider.features) {
295
307
  if (!feature.subscribe) continue;
296
308
  const unsub = feature.subscribe(() => { this.onFeatureChange(feature.name); });
297
309
  state.featureUnsubscribes.push(unsub);
@@ -311,6 +323,7 @@ export class DaemonClient {
311
323
  if (!this._stream) return;
312
324
  const state = this._stream;
313
325
  this._stream = null;
326
+ this._sessionId = null;
314
327
 
315
328
  if (state.debounceTimer) clearTimeout(state.debounceTimer);
316
329
  if (state.retryTimer) clearTimeout(state.retryTimer);
@@ -454,6 +467,7 @@ export class DaemonClient {
454
467
  this._settings = { mode: 'simulator', endpoint: '', deviceHost: '', token: '' };
455
468
  this._streamingEnabled = null;
456
469
  this._restorePromise = null;
470
+ this._sessionId = null;
457
471
  }
458
472
 
459
473
  // ---- Private: Transport ----
@@ -625,7 +639,7 @@ export class DaemonClient {
625
639
  }
626
640
 
627
641
  private async doSendFullReport(state: StreamState): Promise<SendResult> {
628
- const report = createDebugDeviceReport();
642
+ const report = createDebugDeviceReport({ featureProvider: this._featureProvider, session: state.session });
629
643
  const response = await this.doPost(
630
644
  state.reportUrl,
631
645
  this.fetchHeaders(state),
@@ -648,7 +662,7 @@ export class DaemonClient {
648
662
  }
649
663
 
650
664
  state.lastSentIds.clear();
651
- for (const feature of DebugToolkit.features) {
665
+ for (const feature of this._featureProvider.features) {
652
666
  try {
653
667
  const snapshot = feature.getSnapshot();
654
668
  if (Array.isArray(snapshot)) {
@@ -673,7 +687,7 @@ export class DaemonClient {
673
687
  try {
674
688
  const delta: Record<string, unknown[]> = {};
675
689
  const nextSentIds = new Map<string, Set<string | number>>();
676
- const features = DebugToolkit.features;
690
+ const features = this._featureProvider.features;
677
691
 
678
692
  for (const featureName of state.dirtyFeatures) {
679
693
  const feature = features.find((f) => f.name === featureName);
@@ -769,53 +783,7 @@ export class DaemonClient {
769
783
 
770
784
  // ---- Module-level Singleton ----
771
785
 
772
- export const daemonClient = new DaemonClient();
773
-
774
- // ---- Backward-compatible Function Exports ----
775
-
776
- export async function loadDaemonSettings(): Promise<DaemonSettings> {
777
- return daemonClient.getSettings();
778
- }
779
-
780
- export async function saveDaemonSettings(settings: DaemonSettings): Promise<void> {
781
- daemonClient.configure(settings);
782
- }
783
-
784
- export async function loadDaemonStreamingEnabled(): Promise<boolean | null> {
785
- return null;
786
- }
787
-
788
- export async function saveDaemonStreamingEnabled(enabled: boolean): Promise<void> {
789
- daemonClient.setStreamingEnabled(enabled);
790
- }
791
-
792
- export function startStreaming(options: StreamToDaemonOptions = {}): void {
793
- daemonClient.connect(options);
794
- }
795
-
796
- export function stopStreaming(): void {
797
- daemonClient.disconnect();
798
- }
799
-
800
- export function isStreaming(): boolean {
801
- return daemonClient.isConnected();
802
- }
803
-
804
- export function checkDaemonConnection(
805
- options: DaemonConnectionOptions = {},
806
- ): Promise<DaemonConnectionResult> {
807
- return daemonClient.checkConnection(options);
808
- }
809
-
810
- export function reportDebugDeviceToDaemon(
811
- options: ReportToDaemonOptions = {},
812
- ): Promise<ReportResult> {
813
- return daemonClient.reportOnce(options);
814
- }
815
-
816
- export function restoreDaemonStreaming(): Promise<void> {
817
- return daemonClient.restore();
818
- }
786
+ export const daemonClient = new DaemonClient({ featureProvider: debugToolkit });
819
787
 
820
788
  // ---- Internal Helpers ----
821
789
 
@@ -885,3 +853,18 @@ function readLogCount(value: unknown): Record<string, number> | undefined {
885
853
  export function _resetDaemonClientForTesting(): void {
886
854
  daemonClient._resetForTesting();
887
855
  }
856
+
857
+ function generateSessionId(): string {
858
+ try {
859
+ return (globalThis as { crypto?: { randomUUID?: () => string } }).crypto?.randomUUID?.() ?? fallbackSessionId();
860
+ } catch {
861
+ return fallbackSessionId();
862
+ }
863
+ }
864
+
865
+ function fallbackSessionId(): string {
866
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
867
+ const r = Math.random() * 16 | 0;
868
+ return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
869
+ });
870
+ }
@@ -30,6 +30,10 @@ export function createChannelFeature<TPayload, TEntry extends { id?: string }>(
30
30
  renderContent?: ComponentType<DebugFeatureRenderProps<TEntry[]>>;
31
31
  maxLogs?: number;
32
32
  persist?: ChannelFeaturePersistConfig<TEntry>;
33
+ /** Return null to skip, or the (possibly modified) payload to proceed. */
34
+ beforePush?: (payload: TPayload) => TPayload | null;
35
+ /** Called after channel subscription in setup. Return a cleanup function. */
36
+ onSetup?: () => (() => void) | void;
33
37
  },
34
38
  ): DebugFeature<TEntry[]> {
35
39
  const maxLogs = options.maxLogs ?? DEFAULT_MAX_LOGS;
@@ -53,6 +57,7 @@ export function createChannelFeature<TPayload, TEntry extends { id?: string }>(
53
57
 
54
58
  let initialized = false;
55
59
  let unsubscribe: (() => void) | null = null;
60
+ let customCleanup: (() => void) | null = null;
56
61
 
57
62
  return {
58
63
  name: options.name,
@@ -61,8 +66,12 @@ export function createChannelFeature<TPayload, TEntry extends { id?: string }>(
61
66
  setup: () => {
62
67
  if (initialized) return;
63
68
  unsubscribe = getChannel().subscribe((payload) => {
64
- logStore.push(toEntry(payload, getId()), maxLogs);
69
+ const filtered = options.beforePush ? options.beforePush(payload) : payload;
70
+ if (filtered == null) return;
71
+ logStore.push(toEntry(filtered, getId()), maxLogs);
65
72
  });
73
+ const cleanup = options.onSetup?.();
74
+ if (cleanup) customCleanup = cleanup;
66
75
  initialized = true;
67
76
  },
68
77
  getSnapshot: () => logStore.getData(),
@@ -70,6 +79,8 @@ export function createChannelFeature<TPayload, TEntry extends { id?: string }>(
70
79
  logStore.clear();
71
80
  },
72
81
  cleanup: () => {
82
+ customCleanup?.();
83
+ customCleanup = null;
73
84
  unsubscribe?.();
74
85
  unsubscribe = null;
75
86
  logStore.clear();
@@ -1,6 +1,7 @@
1
1
  import { Platform } from 'react-native';
2
2
 
3
- import { DebugToolkit } from '../core/DebugToolkit';
3
+ import { debugToolkit } from '../core/DebugToolkit';
4
+ import type { FeatureDataProvider } from '../types';
4
5
  import { safeStringify } from './safeStringify';
5
6
 
6
7
  const DEFAULT_MAX_PER_TYPE = 50;
@@ -20,9 +21,15 @@ export interface DeviceInfo {
20
21
  appVersion: string;
21
22
  }
22
23
 
24
+ export interface SessionInfo {
25
+ id: string;
26
+ startedAt: number;
27
+ }
28
+
23
29
  export interface DebugDeviceReport {
24
30
  version: 2;
25
31
  device: DeviceInfo;
32
+ session?: SessionInfo;
26
33
  logs: Record<string, unknown[] | undefined>;
27
34
  }
28
35
 
@@ -160,14 +167,15 @@ function sanitizeValue(
160
167
  }
161
168
 
162
169
  export function createDebugDeviceReport(
163
- options: DebugDeviceReportOptions = {},
170
+ options: DebugDeviceReportOptions & { featureProvider?: FeatureDataProvider; session?: SessionInfo } = {},
164
171
  ): DebugDeviceReport {
172
+ const provider = options.featureProvider ?? debugToolkit;
165
173
  const maxPerType = Math.max(1, Math.floor(options.maxPerType ?? DEFAULT_MAX_PER_TYPE));
166
174
  const maxBodyBytes = Math.max(256, Math.floor(options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES));
167
175
  const includeTypes = options.includeTypes?.length ? new Set(options.includeTypes) : null;
168
176
  const logs: DebugDeviceReport['logs'] = {};
169
177
 
170
- DebugToolkit.features.forEach((feature) => {
178
+ provider.features.forEach((feature) => {
171
179
  if (includeTypes && !includeTypes.has(feature.name)) {
172
180
  return;
173
181
  }
@@ -198,6 +206,7 @@ export function createDebugDeviceReport(
198
206
  osVersion: Platform.Version == null ? 'unknown' : String(Platform.Version),
199
207
  appVersion: (constants?.appVersion as string) || 'unknown',
200
208
  },
209
+ session: options.session,
201
210
  logs,
202
211
  };
203
212
  }
@@ -0,0 +1,11 @@
1
+ type UrlRewriter = (url: string) => string;
2
+
3
+ let _urlRewriter: UrlRewriter | null = null;
4
+
5
+ export function getUrlRewriter(): UrlRewriter | null {
6
+ return _urlRewriter;
7
+ }
8
+
9
+ export function setUrlRewriter(rewriter: UrlRewriter | null): void {
10
+ _urlRewriter = rewriter;
11
+ }
@@ -1,14 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.urlRewriter = void 0;
7
- let current = null;
8
- const urlRewriter = exports.urlRewriter = {
9
- get: () => current,
10
- set: rewriter => {
11
- current = rewriter;
12
- }
13
- };
14
- //# sourceMappingURL=urlRewriterRegistry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["current","urlRewriter","exports","get","set","rewriter"],"sourceRoot":"../../../src","sources":["utils/urlRewriterRegistry.ts"],"mappings":";;;;;;AAEA,IAAIA,OAA2B,GAAG,IAAI;AAE/B,MAAMC,WAAW,GAAAC,OAAA,CAAAD,WAAA,GAAG;EACzBE,GAAG,EAAEA,CAAA,KAA0BH,OAAO;EACtCI,GAAG,EAAGC,QAA4B,IAAW;IAC3CL,OAAO,GAAGK,QAAQ;EACpB;AACF,CAAC","ignoreList":[]}
@@ -1,10 +0,0 @@
1
- "use strict";
2
-
3
- let current = null;
4
- export const urlRewriter = {
5
- get: () => current,
6
- set: rewriter => {
7
- current = rewriter;
8
- }
9
- };
10
- //# sourceMappingURL=urlRewriterRegistry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["current","urlRewriter","get","set","rewriter"],"sourceRoot":"../../../src","sources":["utils/urlRewriterRegistry.ts"],"mappings":";;AAEA,IAAIA,OAA2B,GAAG,IAAI;AAEtC,OAAO,MAAMC,WAAW,GAAG;EACzBC,GAAG,EAAEA,CAAA,KAA0BF,OAAO;EACtCG,GAAG,EAAGC,QAA4B,IAAW;IAC3CJ,OAAO,GAAGI,QAAQ;EACpB;AACF,CAAC","ignoreList":[]}
@@ -1,7 +0,0 @@
1
- type UrlRewriter = (url: string) => string;
2
- export declare const urlRewriter: {
3
- get: () => UrlRewriter | null;
4
- set: (rewriter: UrlRewriter | null) => void;
5
- };
6
- export {};
7
- //# sourceMappingURL=urlRewriterRegistry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"urlRewriterRegistry.d.ts","sourceRoot":"","sources":["../../../../src/utils/urlRewriterRegistry.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;AAI3C,eAAO,MAAM,WAAW;eACb,WAAW,GAAG,IAAI;oBACX,WAAW,GAAG,IAAI,KAAG,IAAI;CAG1C,CAAC"}
@@ -1,10 +0,0 @@
1
- type UrlRewriter = (url: string) => string;
2
-
3
- let current: UrlRewriter | null = null;
4
-
5
- export const urlRewriter = {
6
- get: (): UrlRewriter | null => current,
7
- set: (rewriter: UrlRewriter | null): void => {
8
- current = rewriter;
9
- },
10
- };