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.
- package/README.md +83 -65
- package/README.zh-CN.md +82 -64
- package/bin/debug-toolkit.js +10 -2
- package/lib/commonjs/core/DebugToolkit.js +118 -97
- package/lib/commonjs/core/DebugToolkit.js.map +1 -1
- package/lib/commonjs/core/initialize.js +4 -4
- package/lib/commonjs/core/initialize.js.map +1 -1
- package/lib/commonjs/features/environment/index.js +22 -24
- package/lib/commonjs/features/environment/index.js.map +1 -1
- package/lib/commonjs/features/network/NetworkLogTab.js +7 -3
- package/lib/commonjs/features/network/NetworkLogTab.js.map +1 -1
- package/lib/commonjs/features/network/index.js +25 -47
- package/lib/commonjs/features/network/index.js.map +1 -1
- package/lib/commonjs/features/network/networkInterceptor.js +3 -3
- package/lib/commonjs/features/network/networkInterceptor.js.map +1 -1
- package/lib/commonjs/index.js +0 -30
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/DaemonClient.js +37 -51
- package/lib/commonjs/utils/DaemonClient.js.map +1 -1
- package/lib/commonjs/utils/createChannelFeature.js +8 -1
- package/lib/commonjs/utils/createChannelFeature.js.map +1 -1
- package/lib/commonjs/utils/deviceReport.js +3 -1
- package/lib/commonjs/utils/deviceReport.js.map +1 -1
- package/lib/commonjs/utils/urlRewriter.js +15 -0
- package/lib/commonjs/utils/urlRewriter.js.map +1 -0
- package/lib/module/core/DebugToolkit.js +117 -96
- package/lib/module/core/DebugToolkit.js.map +1 -1
- package/lib/module/core/initialize.js +6 -7
- package/lib/module/core/initialize.js.map +1 -1
- package/lib/module/features/environment/index.js +22 -24
- package/lib/module/features/environment/index.js.map +1 -1
- package/lib/module/features/network/NetworkLogTab.js +7 -3
- package/lib/module/features/network/NetworkLogTab.js.map +1 -1
- package/lib/module/features/network/index.js +25 -46
- package/lib/module/features/network/index.js.map +1 -1
- package/lib/module/features/network/networkInterceptor.js +3 -3
- package/lib/module/features/network/networkInterceptor.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/DaemonClient.js +38 -42
- package/lib/module/utils/DaemonClient.js.map +1 -1
- package/lib/module/utils/createChannelFeature.js +8 -1
- package/lib/module/utils/createChannelFeature.js.map +1 -1
- package/lib/module/utils/deviceReport.js +4 -2
- package/lib/module/utils/deviceReport.js.map +1 -1
- package/lib/module/utils/urlRewriter.js +10 -0
- package/lib/module/utils/urlRewriter.js.map +1 -0
- package/lib/typescript/src/core/DebugToolkit.d.ts +23 -10
- package/lib/typescript/src/core/DebugToolkit.d.ts.map +1 -1
- package/lib/typescript/src/core/initialize.d.ts.map +1 -1
- package/lib/typescript/src/features/environment/index.d.ts.map +1 -1
- package/lib/typescript/src/features/network/NetworkLogTab.d.ts.map +1 -1
- package/lib/typescript/src/features/network/index.d.ts +3 -3
- package/lib/typescript/src/features/network/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/feature.d.ts +5 -0
- package/lib/typescript/src/types/feature.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +1 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts +5 -11
- package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -1
- package/lib/typescript/src/utils/createChannelFeature.d.ts +4 -0
- package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -1
- package/lib/typescript/src/utils/deviceReport.d.ts +10 -1
- package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -1
- package/lib/typescript/src/utils/urlRewriter.d.ts +5 -0
- package/lib/typescript/src/utils/urlRewriter.d.ts.map +1 -0
- package/node/daemon/src/console/console.html +197 -27
- package/node/daemon/src/server.js +32 -2
- package/node/daemon/src/store.js +45 -6
- package/node/mcp/src/logs.js +15 -4
- package/node/mcp/src/tools.js +4 -2
- package/package.json +6 -2
- package/src/core/DebugToolkit.tsx +119 -105
- package/src/core/initialize.ts +7 -8
- package/src/features/environment/index.ts +25 -27
- package/src/features/network/NetworkLogTab.tsx +6 -3
- package/src/features/network/index.ts +30 -52
- package/src/features/network/networkInterceptor.ts +3 -3
- package/src/index.ts +3 -8
- package/src/types/feature.ts +6 -0
- package/src/types/index.ts +1 -0
- package/src/utils/DaemonClient.ts +39 -56
- package/src/utils/createChannelFeature.ts +12 -1
- package/src/utils/deviceReport.ts +12 -3
- package/src/utils/urlRewriter.ts +11 -0
- package/lib/commonjs/utils/urlRewriterRegistry.js +0 -14
- package/lib/commonjs/utils/urlRewriterRegistry.js.map +0 -1
- package/lib/module/utils/urlRewriterRegistry.js +0 -10
- package/lib/module/utils/urlRewriterRegistry.js.map +0 -1
- package/lib/typescript/src/utils/urlRewriterRegistry.d.ts +0 -7
- package/lib/typescript/src/utils/urlRewriterRegistry.d.ts.map +0 -1
- 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 {
|
|
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
|
-
|
|
103
|
+
setUrlRewriter(createUrlRewriter());
|
|
106
104
|
} else {
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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)
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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,
|
package/src/types/feature.ts
CHANGED
|
@@ -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;
|
package/src/types/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { AppState, type AppStateStatus, Platform } from 'react-native';
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
|
185
|
-
this._fetch = options
|
|
186
|
-
this._AbortController = options
|
|
187
|
-
this.
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 +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 +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"}
|