react-native-debug-toolkit 2.2.0 → 3.0.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/README.md +5 -8
- package/README.zh-CN.md +5 -8
- package/bin/debug-toolkit.js +114 -0
- package/lib/commonjs/features/network/index.js +32 -12
- package/lib/commonjs/features/network/index.js.map +1 -1
- package/lib/commonjs/features/network/networkInterceptor.js +164 -201
- package/lib/commonjs/features/network/networkInterceptor.js.map +1 -1
- package/lib/commonjs/index.js +59 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/panel/DebugPanel.js +25 -0
- package/lib/commonjs/ui/panel/DebugPanel.js.map +1 -1
- package/lib/commonjs/ui/panel/FloatPanelView.js +15 -62
- package/lib/commonjs/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js +529 -0
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js.map +1 -0
- package/lib/commonjs/ui/panel/useTabAnimation.js +71 -0
- package/lib/commonjs/ui/panel/useTabAnimation.js.map +1 -0
- package/lib/commonjs/utils/autoDetectDaemon.js +141 -0
- package/lib/commonjs/utils/autoDetectDaemon.js.map +1 -0
- package/lib/commonjs/utils/createPersistedObservableStore.js +23 -3
- package/lib/commonjs/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/commonjs/utils/daemonConnection.js +81 -0
- package/lib/commonjs/utils/daemonConnection.js.map +1 -0
- package/lib/commonjs/utils/daemonSettings.js +110 -0
- package/lib/commonjs/utils/daemonSettings.js.map +1 -0
- package/lib/commonjs/utils/reportToDaemon.js +112 -0
- package/lib/commonjs/utils/reportToDaemon.js.map +1 -0
- package/lib/commonjs/utils/sessionReport.js +132 -0
- package/lib/commonjs/utils/sessionReport.js.map +1 -0
- package/lib/commonjs/utils/streamToDaemon.js +334 -0
- package/lib/commonjs/utils/streamToDaemon.js.map +1 -0
- package/lib/module/features/network/index.js +30 -12
- package/lib/module/features/network/index.js.map +1 -1
- package/lib/module/features/network/networkInterceptor.js +163 -200
- package/lib/module/features/network/networkInterceptor.js.map +1 -1
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/panel/DebugPanel.js +26 -1
- package/lib/module/ui/panel/DebugPanel.js.map +1 -1
- package/lib/module/ui/panel/FloatPanelView.js +16 -63
- package/lib/module/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/module/ui/panel/StreamingSettingsModal.js +524 -0
- package/lib/module/ui/panel/StreamingSettingsModal.js.map +1 -0
- package/lib/module/ui/panel/useTabAnimation.js +67 -0
- package/lib/module/ui/panel/useTabAnimation.js.map +1 -0
- package/lib/module/utils/autoDetectDaemon.js +136 -0
- package/lib/module/utils/autoDetectDaemon.js.map +1 -0
- package/lib/module/utils/createPersistedObservableStore.js +23 -3
- package/lib/module/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/module/utils/daemonConnection.js +77 -0
- package/lib/module/utils/daemonConnection.js.map +1 -0
- package/lib/module/utils/daemonSettings.js +102 -0
- package/lib/module/utils/daemonSettings.js.map +1 -0
- package/lib/module/utils/reportToDaemon.js +105 -0
- package/lib/module/utils/reportToDaemon.js.map +1 -0
- package/lib/module/utils/sessionReport.js +128 -0
- package/lib/module/utils/sessionReport.js.map +1 -0
- package/lib/module/utils/streamToDaemon.js +328 -0
- package/lib/module/utils/streamToDaemon.js.map +1 -0
- package/lib/typescript/src/features/network/index.d.ts +2 -4
- package/lib/typescript/src/features/network/index.d.ts.map +1 -1
- package/lib/typescript/src/features/network/networkInterceptor.d.ts +2 -15
- package/lib/typescript/src/features/network/networkInterceptor.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +11 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/DebugPanel.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/FloatPanelView.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts +8 -0
- package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/useTabAnimation.d.ts +14 -0
- package/lib/typescript/src/ui/panel/useTabAnimation.d.ts.map +1 -0
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts +15 -0
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts.map +1 -0
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts +2 -1
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts.map +1 -1
- package/lib/typescript/src/utils/daemonConnection.d.ts +18 -0
- package/lib/typescript/src/utils/daemonConnection.d.ts.map +1 -0
- package/lib/typescript/src/utils/daemonSettings.d.ts +19 -0
- package/lib/typescript/src/utils/daemonSettings.d.ts.map +1 -0
- package/lib/typescript/src/utils/reportToDaemon.d.ts +34 -0
- package/lib/typescript/src/utils/reportToDaemon.d.ts.map +1 -0
- package/lib/typescript/src/utils/sessionReport.d.ts +18 -0
- package/lib/typescript/src/utils/sessionReport.d.ts.map +1 -0
- package/lib/typescript/src/utils/streamToDaemon.d.ts +23 -0
- package/lib/typescript/src/utils/streamToDaemon.d.ts.map +1 -0
- package/node/daemon/src/cli.js +75 -0
- package/node/daemon/src/console/console.html +936 -0
- package/node/daemon/src/console/index.js +47 -0
- package/node/daemon/src/constants.js +32 -0
- package/node/daemon/src/index.js +11 -0
- package/node/daemon/src/server.js +365 -0
- package/node/daemon/src/store.js +110 -0
- package/node/mcp/src/cli.js +31 -0
- package/node/mcp/src/constants.js +13 -0
- package/node/mcp/src/daemonClient.js +132 -0
- package/node/mcp/src/httpClient.js +49 -0
- package/node/mcp/src/index.js +15 -0
- package/node/mcp/src/logs.js +95 -0
- package/node/mcp/src/server.js +144 -0
- package/node/mcp/src/tools.js +84 -0
- package/package.json +10 -5
- package/src/features/network/index.ts +35 -19
- package/src/features/network/networkInterceptor.ts +224 -236
- package/src/index.ts +15 -1
- package/src/ui/panel/DebugPanel.tsx +23 -1
- package/src/ui/panel/FloatPanelView.tsx +10 -68
- package/src/ui/panel/StreamingSettingsModal.tsx +566 -0
- package/src/ui/panel/useTabAnimation.ts +77 -0
- package/src/utils/autoDetectDaemon.ts +175 -0
- package/src/utils/createPersistedObservableStore.ts +16 -3
- package/src/utils/daemonConnection.ts +133 -0
- package/src/utils/daemonSettings.ts +134 -0
- package/src/utils/reportToDaemon.ts +172 -0
- package/src/utils/sessionReport.ts +203 -0
- package/src/utils/streamToDaemon.ts +419 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { _addDaemonEndpointToNetworkBlacklist } from '../features/network';
|
|
4
|
+
import {
|
|
5
|
+
createDebugSessionReport,
|
|
6
|
+
type DebugSessionReport,
|
|
7
|
+
type DebugSessionReportOptions,
|
|
8
|
+
} from './sessionReport';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 3000;
|
|
11
|
+
|
|
12
|
+
type FetchResponseLike = {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
status: number;
|
|
15
|
+
json?: () => Promise<unknown>;
|
|
16
|
+
text?: () => Promise<string>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type FetchLike = (
|
|
20
|
+
url: string,
|
|
21
|
+
init: {
|
|
22
|
+
method: string;
|
|
23
|
+
headers: Record<string, string>;
|
|
24
|
+
body?: string;
|
|
25
|
+
signal?: unknown;
|
|
26
|
+
},
|
|
27
|
+
) => Promise<FetchResponseLike>;
|
|
28
|
+
|
|
29
|
+
type AbortControllerLike = {
|
|
30
|
+
signal: unknown;
|
|
31
|
+
abort: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export interface ReportToDaemonOptions extends DebugSessionReportOptions {
|
|
35
|
+
endpoint?: string;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
token?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ReportResult {
|
|
41
|
+
ok: boolean;
|
|
42
|
+
endpoint: string;
|
|
43
|
+
report: DebugSessionReport;
|
|
44
|
+
status?: number;
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
receivedAt?: string;
|
|
47
|
+
logCount?: Record<string, number>;
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getGlobalFetch(): FetchLike | undefined {
|
|
52
|
+
return (globalThis as { fetch?: FetchLike }).fetch;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createAbortController(): AbortControllerLike | undefined {
|
|
56
|
+
const AbortControllerCtor = (globalThis as {
|
|
57
|
+
AbortController?: new () => AbortControllerLike;
|
|
58
|
+
}).AbortController;
|
|
59
|
+
return AbortControllerCtor ? new AbortControllerCtor() : undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getDefaultDaemonEndpoint(): string {
|
|
63
|
+
if (Platform.OS === 'android') {
|
|
64
|
+
return 'http://10.0.2.2:3799';
|
|
65
|
+
}
|
|
66
|
+
return 'http://localhost:3799';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function buildDaemonUrl(endpoint: string, path: string): string {
|
|
70
|
+
const trimmed = endpoint.replace(/\/+$/, '');
|
|
71
|
+
return trimmed.endsWith(path) ? trimmed : `${trimmed}${path}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function readResponseBody(response: FetchResponseLike): Promise<unknown> {
|
|
75
|
+
try {
|
|
76
|
+
if (response.json) {
|
|
77
|
+
return await response.json();
|
|
78
|
+
}
|
|
79
|
+
if (response.text) {
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
return text ? JSON.parse(text) : null;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readLogCount(value: unknown): Record<string, number> | undefined {
|
|
90
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Object.entries(value as Record<string, unknown>).reduce<Record<string, number>>(
|
|
95
|
+
(acc, [key, count]) => {
|
|
96
|
+
if (typeof count === 'number') {
|
|
97
|
+
acc[key] = count;
|
|
98
|
+
}
|
|
99
|
+
return acc;
|
|
100
|
+
},
|
|
101
|
+
{},
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function reportDebugSessionToDaemon(
|
|
106
|
+
options: ReportToDaemonOptions = {},
|
|
107
|
+
): Promise<ReportResult> {
|
|
108
|
+
const endpoint = options.endpoint ?? getDefaultDaemonEndpoint();
|
|
109
|
+
const reportUrl = buildDaemonUrl(endpoint, '/report');
|
|
110
|
+
const report = createDebugSessionReport(options);
|
|
111
|
+
const fetchImpl = getGlobalFetch();
|
|
112
|
+
|
|
113
|
+
_addDaemonEndpointToNetworkBlacklist(endpoint);
|
|
114
|
+
_addDaemonEndpointToNetworkBlacklist(reportUrl);
|
|
115
|
+
|
|
116
|
+
if (!fetchImpl) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
endpoint,
|
|
120
|
+
report,
|
|
121
|
+
error: 'global fetch is not available',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
126
|
+
const controller = createAbortController();
|
|
127
|
+
const timeout = controller && timeoutMs > 0
|
|
128
|
+
? setTimeout(() => controller.abort(), timeoutMs)
|
|
129
|
+
: undefined;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const headers: Record<string, string> = {
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
};
|
|
135
|
+
if (options.token) {
|
|
136
|
+
headers.Authorization = `Bearer ${options.token}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const response = await fetchImpl(reportUrl, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers,
|
|
142
|
+
body: JSON.stringify(report),
|
|
143
|
+
signal: controller?.signal,
|
|
144
|
+
});
|
|
145
|
+
const responseBody = await readResponseBody(response);
|
|
146
|
+
const bodyObject = responseBody && typeof responseBody === 'object'
|
|
147
|
+
? responseBody as Record<string, unknown>
|
|
148
|
+
: {};
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
ok: response.ok && bodyObject.ok === true,
|
|
152
|
+
endpoint,
|
|
153
|
+
report,
|
|
154
|
+
status: response.status,
|
|
155
|
+
sessionId: typeof bodyObject.sessionId === 'string' ? bodyObject.sessionId : undefined,
|
|
156
|
+
receivedAt: typeof bodyObject.receivedAt === 'string' ? bodyObject.receivedAt : undefined,
|
|
157
|
+
logCount: readLogCount(bodyObject.logCount),
|
|
158
|
+
error: response.ok ? undefined : typeof bodyObject.error === 'string' ? bodyObject.error : 'Report failed',
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
endpoint,
|
|
164
|
+
report,
|
|
165
|
+
error: error instanceof Error ? error.message : String(error),
|
|
166
|
+
};
|
|
167
|
+
} finally {
|
|
168
|
+
if (timeout) {
|
|
169
|
+
clearTimeout(timeout);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { DebugToolkit } from '../core/DebugToolkit';
|
|
4
|
+
import { safeStringify } from './safeStringify';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MAX_PER_TYPE = 50;
|
|
7
|
+
const DEFAULT_MAX_BODY_BYTES = 16 * 1024;
|
|
8
|
+
const MAX_DEPTH = 8;
|
|
9
|
+
|
|
10
|
+
export interface DebugSessionReportOptions {
|
|
11
|
+
maxPerType?: number;
|
|
12
|
+
maxBodyBytes?: number;
|
|
13
|
+
includeTypes?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DeviceInfo {
|
|
17
|
+
platform: string;
|
|
18
|
+
model: string;
|
|
19
|
+
osVersion: string;
|
|
20
|
+
appVersion: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DebugSessionReport {
|
|
24
|
+
version: 2;
|
|
25
|
+
device: DeviceInfo;
|
|
26
|
+
logs: Record<string, unknown[] | undefined>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface TruncatedValue {
|
|
30
|
+
__debugToolkitTruncated: true;
|
|
31
|
+
originalBytes: number;
|
|
32
|
+
preview: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function utf8ByteLength(value: string): number {
|
|
36
|
+
let bytes = 0;
|
|
37
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
38
|
+
const code = value.charCodeAt(index);
|
|
39
|
+
if (code <= 0x7f) {
|
|
40
|
+
bytes += 1;
|
|
41
|
+
} else if (code <= 0x7ff) {
|
|
42
|
+
bytes += 2;
|
|
43
|
+
} else if (code >= 0xd800 && code <= 0xdbff) {
|
|
44
|
+
bytes += 4;
|
|
45
|
+
index += 1;
|
|
46
|
+
} else {
|
|
47
|
+
bytes += 3;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return bytes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function truncateUtf8(value: string, maxBytes: number): string {
|
|
54
|
+
if (utf8ByteLength(value) <= maxBytes) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let bytes = 0;
|
|
59
|
+
let result = '';
|
|
60
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
61
|
+
const code = value.charCodeAt(index);
|
|
62
|
+
const char = value[index]!;
|
|
63
|
+
const charBytes = code <= 0x7f
|
|
64
|
+
? 1
|
|
65
|
+
: code <= 0x7ff
|
|
66
|
+
? 2
|
|
67
|
+
: code >= 0xd800 && code <= 0xdbff
|
|
68
|
+
? 4
|
|
69
|
+
: 3;
|
|
70
|
+
|
|
71
|
+
if (bytes + charBytes > maxBytes) {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
result += char;
|
|
76
|
+
bytes += charBytes;
|
|
77
|
+
|
|
78
|
+
if (charBytes === 4) {
|
|
79
|
+
index += 1;
|
|
80
|
+
result += value[index] ?? '';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return `${result}...[truncated]`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function truncateLargeValue(value: unknown, maxBytes: number): unknown | TruncatedValue {
|
|
88
|
+
const serialized = safeStringify(value);
|
|
89
|
+
const originalBytes = utf8ByteLength(serialized);
|
|
90
|
+
if (originalBytes <= maxBytes) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
__debugToolkitTruncated: true,
|
|
96
|
+
originalBytes,
|
|
97
|
+
preview: truncateUtf8(serialized, maxBytes),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function sanitizeValue(
|
|
102
|
+
value: unknown,
|
|
103
|
+
maxBodyBytes: number,
|
|
104
|
+
depth = 0,
|
|
105
|
+
seen = new WeakSet<object>(),
|
|
106
|
+
key = '',
|
|
107
|
+
): unknown {
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
return truncateUtf8(value, maxBodyBytes);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
value === null ||
|
|
114
|
+
typeof value === 'number' ||
|
|
115
|
+
typeof value === 'boolean' ||
|
|
116
|
+
typeof value === 'undefined'
|
|
117
|
+
) {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof value === 'function' || typeof value === 'symbol' || typeof value === 'bigint') {
|
|
122
|
+
return String(value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (value instanceof Date) {
|
|
126
|
+
return value.toISOString();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (typeof value !== 'object') {
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const normalizedKey = key.toLowerCase();
|
|
134
|
+
if (normalizedKey === 'body' || normalizedKey === 'data') {
|
|
135
|
+
const normalized = sanitizeValue(value, maxBodyBytes, depth + 1, seen);
|
|
136
|
+
return truncateLargeValue(normalized, maxBodyBytes);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (seen.has(value)) {
|
|
140
|
+
return '[Circular]';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (depth >= MAX_DEPTH) {
|
|
144
|
+
return '[MaxDepth]';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
seen.add(value);
|
|
148
|
+
|
|
149
|
+
if (Array.isArray(value)) {
|
|
150
|
+
return value.map((item) => sanitizeValue(item, maxBodyBytes, depth + 1, seen));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Object.entries(value as Record<string, unknown>).reduce<Record<string, unknown>>(
|
|
154
|
+
(acc, [entryKey, entryValue]) => {
|
|
155
|
+
acc[entryKey] = sanitizeValue(entryValue, maxBodyBytes, depth + 1, seen, entryKey);
|
|
156
|
+
return acc;
|
|
157
|
+
},
|
|
158
|
+
{},
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function createDebugSessionReport(
|
|
163
|
+
options: DebugSessionReportOptions = {},
|
|
164
|
+
): DebugSessionReport {
|
|
165
|
+
const maxPerType = Math.max(1, Math.floor(options.maxPerType ?? DEFAULT_MAX_PER_TYPE));
|
|
166
|
+
const maxBodyBytes = Math.max(256, Math.floor(options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES));
|
|
167
|
+
const includeTypes = options.includeTypes?.length ? new Set(options.includeTypes) : null;
|
|
168
|
+
const logs: DebugSessionReport['logs'] = {};
|
|
169
|
+
|
|
170
|
+
DebugToolkit.features.forEach((feature) => {
|
|
171
|
+
if (includeTypes && !includeTypes.has(feature.name)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let snapshot: unknown;
|
|
176
|
+
try {
|
|
177
|
+
snapshot = feature.getSnapshot();
|
|
178
|
+
} catch {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!Array.isArray(snapshot)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
logs[feature.name] = snapshot
|
|
187
|
+
.slice(-maxPerType)
|
|
188
|
+
.map((entry) => sanitizeValue(entry, maxBodyBytes));
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const constants = Platform.constants as Record<string, unknown> | undefined;
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
version: 2,
|
|
195
|
+
device: {
|
|
196
|
+
platform: Platform.OS,
|
|
197
|
+
model: (constants?.model as string) || 'unknown',
|
|
198
|
+
osVersion: Platform.Version == null ? 'unknown' : String(Platform.Version),
|
|
199
|
+
appVersion: (constants?.appVersion as string) || 'unknown',
|
|
200
|
+
},
|
|
201
|
+
logs,
|
|
202
|
+
};
|
|
203
|
+
}
|