react-native-debug-toolkit 3.3.8 → 3.5.1
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 +40 -26
- package/README.zh-CN.md +52 -38
- package/android/src/main/java/com/reactnativedebugtoolkit/DebugToolkitNativeLogsModule.java +146 -0
- package/android/src/main/java/com/reactnativedebugtoolkit/ReactNativeDebugToolkitPackage.java +5 -3
- package/ios/DebugToolkitNativeLogs.mm +92 -0
- package/lib/commonjs/constants/logLevels.js +19 -0
- package/lib/commonjs/constants/logLevels.js.map +1 -0
- package/lib/commonjs/core/initialize.js +32 -19
- package/lib/commonjs/core/initialize.js.map +1 -1
- package/lib/commonjs/features/console/ConsoleLogTab.js +4 -15
- package/lib/commonjs/features/console/ConsoleLogTab.js.map +1 -1
- package/lib/commonjs/features/console/index.js +15 -8
- package/lib/commonjs/features/console/index.js.map +1 -1
- package/lib/commonjs/features/nativeLogs/NativeLogTab.js +156 -0
- package/lib/commonjs/features/nativeLogs/NativeLogTab.js.map +1 -0
- package/lib/commonjs/features/nativeLogs/index.js +97 -0
- package/lib/commonjs/features/nativeLogs/index.js.map +1 -0
- package/lib/commonjs/features/nativeLogs/nativeLogsBridge.js +71 -0
- package/lib/commonjs/features/nativeLogs/nativeLogsBridge.js.map +1 -0
- package/lib/commonjs/features/network/NetworkLogTab.js +90 -95
- package/lib/commonjs/features/network/NetworkLogTab.js.map +1 -1
- package/lib/commonjs/features/network/index.js +7 -4
- package/lib/commonjs/features/network/index.js.map +1 -1
- package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js +1046 -0
- package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js.map +1 -0
- package/lib/commonjs/features/sessionHistory/index.js +104 -0
- package/lib/commonjs/features/sessionHistory/index.js.map +1 -0
- package/lib/commonjs/features/track/index.js +4 -3
- package/lib/commonjs/features/track/index.js.map +1 -1
- package/lib/commonjs/index.js +27 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/DebugView.js +2 -0
- package/lib/commonjs/ui/DebugView.js.map +1 -1
- package/lib/commonjs/ui/panel/DebugPanel.js +67 -34
- package/lib/commonjs/ui/panel/DebugPanel.js.map +1 -1
- package/lib/commonjs/ui/panel/FeatureIntroCard.js +151 -0
- package/lib/commonjs/ui/panel/FeatureIntroCard.js.map +1 -0
- package/lib/commonjs/ui/panel/FeatureRail.js +163 -0
- package/lib/commonjs/ui/panel/FeatureRail.js.map +1 -0
- package/lib/commonjs/ui/panel/FloatPanelView.js +119 -22
- package/lib/commonjs/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/commonjs/ui/panel/buildFeatureSummary.js +207 -0
- package/lib/commonjs/ui/panel/buildFeatureSummary.js.map +1 -0
- package/lib/commonjs/ui/panel/filterFeatureSnapshot.js +43 -0
- package/lib/commonjs/ui/panel/filterFeatureSnapshot.js.map +1 -0
- package/lib/commonjs/ui/theme/colors.js +6 -0
- package/lib/commonjs/ui/theme/colors.js.map +1 -1
- package/lib/commonjs/utils/DaemonClient.js +30 -8
- package/lib/commonjs/utils/DaemonClient.js.map +1 -1
- package/lib/commonjs/utils/SessionManager.js +132 -0
- package/lib/commonjs/utils/SessionManager.js.map +1 -0
- package/lib/commonjs/utils/StorageAdapter.js +104 -0
- package/lib/commonjs/utils/StorageAdapter.js.map +1 -0
- package/lib/commonjs/utils/createChannelFeature.js +22 -5
- package/lib/commonjs/utils/createChannelFeature.js.map +1 -1
- package/lib/commonjs/utils/createPersistedObservableStore.js +14 -8
- package/lib/commonjs/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/commonjs/utils/debugPreferences.js +28 -5
- package/lib/commonjs/utils/debugPreferences.js.map +1 -1
- package/lib/commonjs/utils/deviceReport.js +5 -1
- package/lib/commonjs/utils/deviceReport.js.map +1 -1
- package/lib/commonjs/utils/logRuntime.js +32 -0
- package/lib/commonjs/utils/logRuntime.js.map +1 -0
- package/lib/module/constants/logLevels.js +15 -0
- package/lib/module/constants/logLevels.js.map +1 -0
- package/lib/module/core/initialize.js +32 -19
- package/lib/module/core/initialize.js.map +1 -1
- package/lib/module/features/console/ConsoleLogTab.js +1 -12
- package/lib/module/features/console/ConsoleLogTab.js.map +1 -1
- package/lib/module/features/console/index.js +15 -8
- package/lib/module/features/console/index.js.map +1 -1
- package/lib/module/features/nativeLogs/NativeLogTab.js +151 -0
- package/lib/module/features/nativeLogs/NativeLogTab.js.map +1 -0
- package/lib/module/features/nativeLogs/index.js +91 -0
- package/lib/module/features/nativeLogs/index.js.map +1 -0
- package/lib/module/features/nativeLogs/nativeLogsBridge.js +63 -0
- package/lib/module/features/nativeLogs/nativeLogsBridge.js.map +1 -0
- package/lib/module/features/network/NetworkLogTab.js +90 -95
- package/lib/module/features/network/NetworkLogTab.js.map +1 -1
- package/lib/module/features/network/index.js +7 -4
- package/lib/module/features/network/index.js.map +1 -1
- package/lib/module/features/sessionHistory/SessionHistoryTab.js +1041 -0
- package/lib/module/features/sessionHistory/SessionHistoryTab.js.map +1 -0
- package/lib/module/features/sessionHistory/index.js +100 -0
- package/lib/module/features/sessionHistory/index.js.map +1 -0
- package/lib/module/features/track/index.js +4 -3
- package/lib/module/features/track/index.js.map +1 -1
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/DebugView.js +2 -0
- package/lib/module/ui/DebugView.js.map +1 -1
- package/lib/module/ui/panel/DebugPanel.js +67 -34
- package/lib/module/ui/panel/DebugPanel.js.map +1 -1
- package/lib/module/ui/panel/FeatureIntroCard.js +146 -0
- package/lib/module/ui/panel/FeatureIntroCard.js.map +1 -0
- package/lib/module/ui/panel/FeatureRail.js +158 -0
- package/lib/module/ui/panel/FeatureRail.js.map +1 -0
- package/lib/module/ui/panel/FloatPanelView.js +119 -22
- package/lib/module/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/module/ui/panel/buildFeatureSummary.js +203 -0
- package/lib/module/ui/panel/buildFeatureSummary.js.map +1 -0
- package/lib/module/ui/panel/filterFeatureSnapshot.js +39 -0
- package/lib/module/ui/panel/filterFeatureSnapshot.js.map +1 -0
- package/lib/module/ui/theme/colors.js +6 -0
- package/lib/module/ui/theme/colors.js.map +1 -1
- package/lib/module/utils/DaemonClient.js +30 -8
- package/lib/module/utils/DaemonClient.js.map +1 -1
- package/lib/module/utils/SessionManager.js +127 -0
- package/lib/module/utils/SessionManager.js.map +1 -0
- package/lib/module/utils/StorageAdapter.js +96 -0
- package/lib/module/utils/StorageAdapter.js.map +1 -0
- package/lib/module/utils/createChannelFeature.js +22 -5
- package/lib/module/utils/createChannelFeature.js.map +1 -1
- package/lib/module/utils/createPersistedObservableStore.js +14 -8
- package/lib/module/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/module/utils/debugPreferences.js +27 -5
- package/lib/module/utils/debugPreferences.js.map +1 -1
- package/lib/module/utils/deviceReport.js +4 -1
- package/lib/module/utils/deviceReport.js.map +1 -1
- package/lib/module/utils/logRuntime.js +26 -0
- package/lib/module/utils/logRuntime.js.map +1 -0
- package/lib/typescript/src/constants/logLevels.d.ts +3 -0
- package/lib/typescript/src/constants/logLevels.d.ts.map +1 -0
- package/lib/typescript/src/core/initialize.d.ts +6 -0
- package/lib/typescript/src/core/initialize.d.ts.map +1 -1
- package/lib/typescript/src/features/console/ConsoleLogTab.d.ts.map +1 -1
- package/lib/typescript/src/features/console/index.d.ts +2 -1
- package/lib/typescript/src/features/console/index.d.ts.map +1 -1
- package/lib/typescript/src/features/nativeLogs/NativeLogTab.d.ts +4 -0
- package/lib/typescript/src/features/nativeLogs/NativeLogTab.d.ts.map +1 -0
- package/lib/typescript/src/features/nativeLogs/index.d.ts +12 -0
- package/lib/typescript/src/features/nativeLogs/index.d.ts.map +1 -0
- package/lib/typescript/src/features/nativeLogs/nativeLogsBridge.d.ts +11 -0
- package/lib/typescript/src/features/nativeLogs/nativeLogsBridge.d.ts.map +1 -0
- package/lib/typescript/src/features/network/NetworkLogTab.d.ts.map +1 -1
- package/lib/typescript/src/features/network/index.d.ts +2 -1
- package/lib/typescript/src/features/network/index.d.ts.map +1 -1
- package/lib/typescript/src/features/sessionHistory/SessionHistoryTab.d.ts +20 -0
- package/lib/typescript/src/features/sessionHistory/SessionHistoryTab.d.ts.map +1 -0
- package/lib/typescript/src/features/sessionHistory/index.d.ts +4 -0
- package/lib/typescript/src/features/sessionHistory/index.d.ts.map +1 -0
- package/lib/typescript/src/features/track/index.d.ts +2 -1
- package/lib/typescript/src/features/track/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +7 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/feature.d.ts +1 -1
- package/lib/typescript/src/types/feature.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +3 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/types/logs.d.ts +15 -0
- package/lib/typescript/src/types/logs.d.ts.map +1 -1
- package/lib/typescript/src/ui/DebugView.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/DebugPanel.d.ts +3 -1
- package/lib/typescript/src/ui/panel/DebugPanel.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/FeatureIntroCard.d.ts +14 -0
- package/lib/typescript/src/ui/panel/FeatureIntroCard.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/FeatureRail.d.ts +16 -0
- package/lib/typescript/src/ui/panel/FeatureRail.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/FloatPanelView.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/buildFeatureSummary.d.ts +13 -0
- package/lib/typescript/src/ui/panel/buildFeatureSummary.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/filterFeatureSnapshot.d.ts +3 -0
- package/lib/typescript/src/ui/panel/filterFeatureSnapshot.d.ts.map +1 -0
- package/lib/typescript/src/ui/theme/colors.d.ts +5 -0
- package/lib/typescript/src/ui/theme/colors.d.ts.map +1 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts +7 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -1
- package/lib/typescript/src/utils/SessionManager.d.ts +30 -0
- package/lib/typescript/src/utils/SessionManager.d.ts.map +1 -0
- package/lib/typescript/src/utils/StorageAdapter.d.ts +38 -0
- package/lib/typescript/src/utils/StorageAdapter.d.ts.map +1 -0
- package/lib/typescript/src/utils/createChannelFeature.d.ts +2 -0
- package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -1
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts +4 -1
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts.map +1 -1
- package/lib/typescript/src/utils/debugPreferences.d.ts +1 -3
- package/lib/typescript/src/utils/debugPreferences.d.ts.map +1 -1
- package/lib/typescript/src/utils/deviceReport.d.ts +1 -0
- package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -1
- package/lib/typescript/src/utils/logRuntime.d.ts +13 -0
- package/lib/typescript/src/utils/logRuntime.d.ts.map +1 -0
- package/node/daemon/src/console/console.html +18 -0
- package/node/mcp/src/logs.js +1 -1
- package/package.json +9 -1
- package/src/constants/logLevels.ts +13 -0
- package/src/core/initialize.ts +54 -25
- package/src/features/console/ConsoleLogTab.tsx +1 -14
- package/src/features/console/index.ts +18 -8
- package/src/features/nativeLogs/NativeLogTab.tsx +66 -0
- package/src/features/nativeLogs/index.ts +94 -0
- package/src/features/nativeLogs/nativeLogsBridge.ts +51 -0
- package/src/features/network/NetworkLogTab.tsx +60 -71
- package/src/features/network/index.ts +12 -3
- package/src/features/sessionHistory/SessionHistoryTab.tsx +693 -0
- package/src/features/sessionHistory/index.ts +102 -0
- package/src/features/track/index.ts +10 -3
- package/src/index.ts +16 -0
- package/src/types/feature.ts +3 -1
- package/src/types/index.ts +13 -0
- package/src/types/logs.ts +17 -0
- package/src/ui/DebugView.tsx +2 -0
- package/src/ui/panel/DebugPanel.tsx +60 -30
- package/src/ui/panel/FeatureIntroCard.tsx +147 -0
- package/src/ui/panel/FeatureRail.tsx +165 -0
- package/src/ui/panel/FloatPanelView.tsx +123 -15
- package/src/ui/panel/buildFeatureSummary.ts +288 -0
- package/src/ui/panel/filterFeatureSnapshot.ts +51 -0
- package/src/ui/theme/colors.ts +7 -0
- package/src/utils/DaemonClient.ts +33 -5
- package/src/utils/SessionManager.ts +174 -0
- package/src/utils/StorageAdapter.ts +135 -0
- package/src/utils/createChannelFeature.ts +28 -6
- package/src/utils/createPersistedObservableStore.ts +18 -10
- package/src/utils/debugPreferences.ts +38 -7
- package/src/utils/deviceReport.ts +5 -1
- package/src/utils/logRuntime.ts +39 -0
- package/lib/commonjs/ui/panel/FeatureTabBar.js +0 -182
- package/lib/commonjs/ui/panel/FeatureTabBar.js.map +0 -1
- package/lib/module/ui/panel/FeatureTabBar.js +0 -177
- package/lib/module/ui/panel/FeatureTabBar.js.map +0 -1
- package/lib/typescript/src/ui/panel/FeatureTabBar.d.ts +0 -13
- package/lib/typescript/src/ui/panel/FeatureTabBar.d.ts.map +0 -1
- package/src/ui/panel/FeatureTabBar.tsx +0 -204
package/src/ui/theme/colors.ts
CHANGED
|
@@ -14,6 +14,13 @@ export const Colors = {
|
|
|
14
14
|
|
|
15
15
|
purple: '#AF52DE',
|
|
16
16
|
|
|
17
|
+
// Panel-specific
|
|
18
|
+
railBackground: '#E8EEF6',
|
|
19
|
+
panelDivider: '#CED8E4',
|
|
20
|
+
signalRedBg: '#FFE9E7',
|
|
21
|
+
signalAmberBg: '#FFF1D6',
|
|
22
|
+
signalDefaultBg: '#E7EDF5',
|
|
23
|
+
|
|
17
24
|
get: '#007AFF',
|
|
18
25
|
post: '#34C759',
|
|
19
26
|
put: '#FF9500',
|
|
@@ -56,6 +56,7 @@ export interface ReportToDaemonOptions extends DebugDeviceReportOptions {
|
|
|
56
56
|
endpoint?: string;
|
|
57
57
|
timeoutMs?: number;
|
|
58
58
|
token?: string;
|
|
59
|
+
session?: SessionInfo;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export interface ReportResult {
|
|
@@ -89,6 +90,7 @@ type AbortControllerLike = { signal: unknown; abort: () => void };
|
|
|
89
90
|
type AbortControllerCtor = new () => AbortControllerLike;
|
|
90
91
|
|
|
91
92
|
type SendResult = 'ok' | 'retry' | 'auth_failed';
|
|
93
|
+
type SessionProvider = () => SessionInfo;
|
|
92
94
|
|
|
93
95
|
// ---- Constants ----
|
|
94
96
|
|
|
@@ -186,6 +188,7 @@ export class DaemonClient {
|
|
|
186
188
|
private _onEndpointDetected: ((url: string) => void) | undefined;
|
|
187
189
|
private _restorePromise: Promise<void> | null = null;
|
|
188
190
|
private _sessionId: SessionInfo | null = null;
|
|
191
|
+
private _sessionProvider: SessionProvider | null = null;
|
|
189
192
|
private _onConnectionChange: (() => void) | undefined;
|
|
190
193
|
|
|
191
194
|
constructor(options: DaemonClientOptions) {
|
|
@@ -268,9 +271,7 @@ export class DaemonClient {
|
|
|
268
271
|
connect(options: StreamToDaemonOptions = {}): void {
|
|
269
272
|
if (this._stream) return;
|
|
270
273
|
|
|
271
|
-
|
|
272
|
-
this._sessionId = { id: generateSessionId(), startedAt: Date.now() };
|
|
273
|
-
}
|
|
274
|
+
const session = this.resolveSession();
|
|
274
275
|
|
|
275
276
|
const endpoint = options.endpoint || this.resolveEndpoint();
|
|
276
277
|
const reportUrl = buildDaemonUrl(endpoint, '/report');
|
|
@@ -288,7 +289,7 @@ export class DaemonClient {
|
|
|
288
289
|
debounceMs: options.debounceMs || DEFAULT_DEBOUNCE_MS,
|
|
289
290
|
timeoutMs: Math.max(0, options.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
290
291
|
deviceId: null,
|
|
291
|
-
session
|
|
292
|
+
session,
|
|
292
293
|
sending: false,
|
|
293
294
|
debounceTimer: null,
|
|
294
295
|
retryTimer: null,
|
|
@@ -367,6 +368,14 @@ export class DaemonClient {
|
|
|
367
368
|
this._onConnectionChange = callback;
|
|
368
369
|
}
|
|
369
370
|
|
|
371
|
+
setSessionProvider(provider: SessionProvider): void {
|
|
372
|
+
this._sessionProvider = provider;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
clearSessionProvider(): void {
|
|
376
|
+
this._sessionProvider = null;
|
|
377
|
+
}
|
|
378
|
+
|
|
370
379
|
// --- Restore (init-time reconnect) ---
|
|
371
380
|
|
|
372
381
|
async restore(): Promise<void> {
|
|
@@ -411,7 +420,10 @@ export class DaemonClient {
|
|
|
411
420
|
async reportOnce(options: ReportToDaemonOptions = {}): Promise<ReportResult> {
|
|
412
421
|
const endpoint = options.endpoint ?? this.resolveEndpoint();
|
|
413
422
|
const reportUrl = buildDaemonUrl(endpoint, '/report');
|
|
414
|
-
const report = createDebugDeviceReport(
|
|
423
|
+
const report = createDebugDeviceReport({
|
|
424
|
+
...options,
|
|
425
|
+
session: options.session ?? this.resolveSession(),
|
|
426
|
+
});
|
|
415
427
|
const fetchImpl = this.resolveFetch();
|
|
416
428
|
|
|
417
429
|
this.notifyEndpoint(endpoint);
|
|
@@ -475,6 +487,7 @@ export class DaemonClient {
|
|
|
475
487
|
this._streamingEnabled = null;
|
|
476
488
|
this._restorePromise = null;
|
|
477
489
|
this._sessionId = null;
|
|
490
|
+
this._sessionProvider = null;
|
|
478
491
|
}
|
|
479
492
|
|
|
480
493
|
// ---- Private: Transport ----
|
|
@@ -498,6 +511,21 @@ export class DaemonClient {
|
|
|
498
511
|
this._onEndpointDetected?.(url);
|
|
499
512
|
}
|
|
500
513
|
|
|
514
|
+
private resolveSession(): SessionInfo {
|
|
515
|
+
if (this._sessionProvider) {
|
|
516
|
+
try {
|
|
517
|
+
return this._sessionProvider();
|
|
518
|
+
} catch {
|
|
519
|
+
// fall through to internal session
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (!this._sessionId) {
|
|
524
|
+
this._sessionId = { id: generateSessionId(), startedAt: Date.now() };
|
|
525
|
+
}
|
|
526
|
+
return this._sessionId;
|
|
527
|
+
}
|
|
528
|
+
|
|
501
529
|
private emitStatus(status: StreamStatus): void {
|
|
502
530
|
try {
|
|
503
531
|
this._stream?.onStatus?.(status);
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { SessionInfo } from './deviceReport';
|
|
2
|
+
import type { StorageAdapter } from './StorageAdapter';
|
|
3
|
+
|
|
4
|
+
export type LogFeatureKey = 'console_logs' | 'network_logs' | 'native_logs' | 'track_logs';
|
|
5
|
+
|
|
6
|
+
export type LogSession = SessionInfo;
|
|
7
|
+
|
|
8
|
+
export interface SessionIndex {
|
|
9
|
+
currentSessionId: string;
|
|
10
|
+
sessions: LogSession[];
|
|
11
|
+
maxSessions: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SessionManagerOptions {
|
|
15
|
+
maxSessions?: number;
|
|
16
|
+
featureKeys?: LogFeatureKey[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SESSION_INDEX_KEY = '@react_native_debug_toolkit/sessions';
|
|
20
|
+
const DEFAULT_MAX_SESSIONS = 5;
|
|
21
|
+
const DEFAULT_FEATURE_KEYS: LogFeatureKey[] = ['console_logs', 'network_logs', 'native_logs', 'track_logs'];
|
|
22
|
+
|
|
23
|
+
function randomHex(): string {
|
|
24
|
+
return Math.floor(Math.random() * 0xffffffff).toString(16).padStart(8, '0');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createSession(): LogSession {
|
|
28
|
+
const startedAt = Date.now();
|
|
29
|
+
return {
|
|
30
|
+
id: `${startedAt}-${randomHex()}`,
|
|
31
|
+
startedAt,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isLogSession(value: unknown): value is LogSession {
|
|
36
|
+
return Boolean(
|
|
37
|
+
value &&
|
|
38
|
+
typeof value === 'object' &&
|
|
39
|
+
typeof (value as Partial<LogSession>).id === 'string' &&
|
|
40
|
+
typeof (value as Partial<LogSession>).startedAt === 'number',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseSessionIndex(raw: string | null): SessionIndex | null {
|
|
45
|
+
if (!raw) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(raw) as Partial<SessionIndex>;
|
|
51
|
+
if (!parsed || !Array.isArray(parsed.sessions)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
currentSessionId: typeof parsed.currentSessionId === 'string' ? parsed.currentSessionId : '',
|
|
56
|
+
sessions: parsed.sessions.filter(isLogSession),
|
|
57
|
+
maxSessions: typeof parsed.maxSessions === 'number' ? parsed.maxSessions : DEFAULT_MAX_SESSIONS,
|
|
58
|
+
};
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function compareNewestFirst(left: LogSession, right: LogSession): number {
|
|
65
|
+
return right.startedAt - left.startedAt;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class SessionManager {
|
|
69
|
+
private readonly storage: StorageAdapter;
|
|
70
|
+
private readonly currentSession: LogSession;
|
|
71
|
+
private readonly maxSessions: number;
|
|
72
|
+
private readonly featureKeys: LogFeatureKey[];
|
|
73
|
+
|
|
74
|
+
constructor(storage: StorageAdapter, options: SessionManagerOptions = {}) {
|
|
75
|
+
this.storage = storage;
|
|
76
|
+
this.currentSession = createSession();
|
|
77
|
+
this.maxSessions = Math.max(1, Math.floor(options.maxSessions ?? DEFAULT_MAX_SESSIONS));
|
|
78
|
+
this.featureKeys = options.featureKeys?.length ? options.featureKeys : DEFAULT_FEATURE_KEYS;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async initialize(): Promise<void> {
|
|
82
|
+
const existing = parseSessionIndex(await this.storage.getItem(SESSION_INDEX_KEY));
|
|
83
|
+
const byId = new Map<string, LogSession>();
|
|
84
|
+
|
|
85
|
+
byId.set(this.currentSession.id, this.currentSession);
|
|
86
|
+
for (const session of existing?.sessions ?? []) {
|
|
87
|
+
byId.set(session.id, session);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const sessions = Array.from(byId.values()).sort(compareNewestFirst);
|
|
91
|
+
const retained = sessions.slice(0, this.maxSessions);
|
|
92
|
+
const removed = sessions.slice(this.maxSessions);
|
|
93
|
+
const index: SessionIndex = {
|
|
94
|
+
currentSessionId: this.currentSession.id,
|
|
95
|
+
sessions: retained,
|
|
96
|
+
maxSessions: this.maxSessions,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
await this.storage.setItem(SESSION_INDEX_KEY, JSON.stringify(index));
|
|
100
|
+
await this.cleanupSessionLogs(removed);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getCurrentSession(): LogSession {
|
|
104
|
+
return this.currentSession;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async getSessionHistory(): Promise<LogSession[]> {
|
|
108
|
+
const index = parseSessionIndex(await this.storage.getItem(SESSION_INDEX_KEY));
|
|
109
|
+
return (index?.sessions ?? []).sort(compareNewestFirst);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getLogStorageKey(featureKey: LogFeatureKey, sessionId = this.currentSession.id): string {
|
|
113
|
+
return `@react_native_debug_toolkit/${sessionId}/${featureKey}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async loadSessionLogs<T>(sessionId: string, featureKey: LogFeatureKey): Promise<T[]> {
|
|
117
|
+
const raw = await this.storage.getItem(this.getLogStorageKey(featureKey, sessionId));
|
|
118
|
+
if (!raw) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
124
|
+
return Array.isArray(parsed) ? parsed as T[] : [];
|
|
125
|
+
} catch {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getSessionLogCount(sessionId: string, featureKey: LogFeatureKey): Promise<number> {
|
|
131
|
+
const raw = await this.storage.getItem(this.getLogStorageKey(featureKey, sessionId));
|
|
132
|
+
if (!raw) return 0;
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(raw);
|
|
135
|
+
return Array.isArray(parsed) ? parsed.length : 0;
|
|
136
|
+
} catch {
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async clearCurrentSessionLogs(featureKey: LogFeatureKey): Promise<void> {
|
|
142
|
+
await this.storage.setItem(this.getLogStorageKey(featureKey), '[]');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async cleanupOldSessions(): Promise<number> {
|
|
146
|
+
const index = parseSessionIndex(await this.storage.getItem(SESSION_INDEX_KEY));
|
|
147
|
+
if (!index) {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const sessions = index.sessions.sort(compareNewestFirst);
|
|
152
|
+
const retained = sessions.slice(0, this.maxSessions);
|
|
153
|
+
const removed = sessions.slice(this.maxSessions);
|
|
154
|
+
if (removed.length === 0) {
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await this.storage.setItem(SESSION_INDEX_KEY, JSON.stringify({
|
|
159
|
+
currentSessionId: index.currentSessionId,
|
|
160
|
+
sessions: retained,
|
|
161
|
+
maxSessions: this.maxSessions,
|
|
162
|
+
}));
|
|
163
|
+
await this.cleanupSessionLogs(removed);
|
|
164
|
+
return removed.length;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async cleanupSessionLogs(sessions: LogSession[]): Promise<void> {
|
|
168
|
+
for (const session of sessions) {
|
|
169
|
+
for (const featureKey of this.featureKeys) {
|
|
170
|
+
await this.storage.removeItem(this.getLogStorageKey(featureKey, session.id));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export interface StorageAdapter {
|
|
2
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
3
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
4
|
+
removeItem(key: string): void | Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type AsyncStorageLike = {
|
|
8
|
+
getItem: (key: string) => Promise<string | null>;
|
|
9
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
10
|
+
removeItem: (key: string) => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type MMKVLike = {
|
|
14
|
+
getString: (key: string) => string | undefined;
|
|
15
|
+
set: (key: string, value: string) => void;
|
|
16
|
+
delete: (key: string) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type MMKVLikeV4 = {
|
|
20
|
+
getString: (key: string) => string | undefined;
|
|
21
|
+
set: (key: string, value: string | number | boolean | ArrayBuffer) => void;
|
|
22
|
+
remove: (key: string) => boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class MemoryStorageAdapter implements StorageAdapter {
|
|
26
|
+
private readonly store = new Map<string, string>();
|
|
27
|
+
|
|
28
|
+
getItem(key: string): string | null {
|
|
29
|
+
return this.store.get(key) ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setItem(key: string, value: string): void {
|
|
33
|
+
this.store.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
removeItem(key: string): void {
|
|
37
|
+
this.store.delete(key);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class AsyncStorageAdapter implements StorageAdapter {
|
|
42
|
+
constructor(private readonly storage: AsyncStorageLike) {}
|
|
43
|
+
|
|
44
|
+
getItem(key: string): Promise<string | null> {
|
|
45
|
+
return this.storage.getItem(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setItem(key: string, value: string): Promise<void> {
|
|
49
|
+
return this.storage.setItem(key, value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
removeItem(key: string): Promise<void> {
|
|
53
|
+
return this.storage.removeItem(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class MMKVStorageAdapter implements StorageAdapter {
|
|
58
|
+
private readonly storage: MMKVLike;
|
|
59
|
+
|
|
60
|
+
constructor(storage: MMKVLike) {
|
|
61
|
+
this.storage = storage;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getItem(key: string): string | null {
|
|
65
|
+
return this.storage.getString(key) ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setItem(key: string, value: string): void {
|
|
69
|
+
this.storage.set(key, value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
removeItem(key: string): void {
|
|
73
|
+
if ('delete' in this.storage) {
|
|
74
|
+
(this.storage as MMKVLike).delete(key);
|
|
75
|
+
} else {
|
|
76
|
+
(this.storage as MMKVLikeV4).remove(key);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isAsyncStorageLike(value: unknown): value is AsyncStorageLike {
|
|
82
|
+
return Boolean(
|
|
83
|
+
value &&
|
|
84
|
+
typeof value === 'object' &&
|
|
85
|
+
typeof (value as Partial<AsyncStorageLike>).getItem === 'function' &&
|
|
86
|
+
typeof (value as Partial<AsyncStorageLike>).setItem === 'function' &&
|
|
87
|
+
typeof (value as Partial<AsyncStorageLike>).removeItem === 'function',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function loadMMKVStorage(): StorageAdapter | null {
|
|
92
|
+
try {
|
|
93
|
+
const mod = require('react-native-mmkv');
|
|
94
|
+
console.warn('[StorageAdapter] MMKV module loaded, keys:', Object.keys(mod).join(','));
|
|
95
|
+
|
|
96
|
+
// v4+: createMMKV factory function
|
|
97
|
+
if (typeof mod?.createMMKV === 'function') {
|
|
98
|
+
console.warn('[StorageAdapter] Found createMMKV (v4+)');
|
|
99
|
+
const instance = mod.createMMKV({ id: 'debug-toolkit-logs' });
|
|
100
|
+
console.warn('[StorageAdapter] createMMKV returned:', typeof instance, instance ? 'has getString:' + typeof instance.getString : 'null');
|
|
101
|
+
if (instance && typeof instance.getString === 'function' && typeof instance.set === 'function') {
|
|
102
|
+
return new MMKVStorageAdapter(instance as unknown as MMKVLike);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// v3 and earlier: MMKV class
|
|
107
|
+
const MMKV = mod?.MMKV ?? mod?.default?.MMKV ?? mod?.default;
|
|
108
|
+
if (typeof MMKV === 'function') {
|
|
109
|
+
return new MMKVStorageAdapter(new MMKV({ id: 'debug-toolkit-logs' }));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.warn('[StorageAdapter] No MMKV constructor found');
|
|
113
|
+
return null;
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.warn('[StorageAdapter] loadMMKVStorage failed:', e);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function loadAsyncStorage(): StorageAdapter | null {
|
|
121
|
+
try {
|
|
122
|
+
const mod = require('@react-native-async-storage/async-storage');
|
|
123
|
+
const storage = isAsyncStorageLike(mod?.default) ? mod.default : mod;
|
|
124
|
+
if (!isAsyncStorageLike(storage)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return new AsyncStorageAdapter(storage);
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function createDefaultLogStorage(): StorageAdapter {
|
|
134
|
+
return loadMMKVStorage() ?? loadAsyncStorage() ?? new MemoryStorageAdapter();
|
|
135
|
+
}
|
|
@@ -2,11 +2,16 @@ import type { ComponentType } from 'react';
|
|
|
2
2
|
import type { DebugFeature, DebugFeatureListener, DebugFeatureRenderProps } from '../types';
|
|
3
3
|
import type { EventChannel } from './createEventChannel';
|
|
4
4
|
import { createObservableStore, type ObservableStore } from './createObservableStore';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createPersistedObservableStore,
|
|
7
|
+
type PersistedObservableStore,
|
|
8
|
+
} from './createPersistedObservableStore';
|
|
9
|
+
import type { StorageAdapter } from './StorageAdapter';
|
|
6
10
|
|
|
7
11
|
const DEFAULT_MAX_LOGS = 200;
|
|
8
12
|
|
|
9
13
|
export interface ChannelFeaturePersistConfig<TEntry> {
|
|
14
|
+
storage: StorageAdapter;
|
|
10
15
|
storageKey: string;
|
|
11
16
|
maxPersist: number;
|
|
12
17
|
debounceMs?: number;
|
|
@@ -39,15 +44,18 @@ export function createChannelFeature<TPayload, TEntry extends { id?: string }>(
|
|
|
39
44
|
const maxLogs = options.maxLogs ?? DEFAULT_MAX_LOGS;
|
|
40
45
|
let nextId = 0;
|
|
41
46
|
let logStore: ObservableStore<TEntry>;
|
|
47
|
+
let persistedStore: PersistedObservableStore<TEntry> | null = null;
|
|
42
48
|
let getId: () => string;
|
|
43
49
|
|
|
44
50
|
if (options.persist) {
|
|
45
51
|
const persisted = createPersistedObservableStore<TEntry>({
|
|
52
|
+
storage: options.persist.storage,
|
|
46
53
|
storageKey: options.persist.storageKey,
|
|
47
54
|
maxPersist: options.persist.maxPersist,
|
|
48
55
|
debounceMs: options.persist.debounceMs,
|
|
49
56
|
serialize: options.persist.serialize,
|
|
50
57
|
});
|
|
58
|
+
persistedStore = persisted;
|
|
51
59
|
logStore = persisted;
|
|
52
60
|
getId = () => persisted.nextId();
|
|
53
61
|
} else {
|
|
@@ -64,26 +72,40 @@ export function createChannelFeature<TPayload, TEntry extends { id?: string }>(
|
|
|
64
72
|
label: options.label,
|
|
65
73
|
renderContent: options.renderContent,
|
|
66
74
|
setup: () => {
|
|
67
|
-
if (initialized)
|
|
75
|
+
if (initialized) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
68
78
|
unsubscribe = getChannel().subscribe((payload) => {
|
|
69
79
|
const filtered = options.beforePush ? options.beforePush(payload) : payload;
|
|
70
|
-
if (filtered == null)
|
|
80
|
+
if (filtered == null) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
71
83
|
logStore.push(toEntry(filtered, getId()), maxLogs);
|
|
72
84
|
});
|
|
73
85
|
const cleanup = options.onSetup?.();
|
|
74
|
-
if (cleanup)
|
|
86
|
+
if (cleanup) {
|
|
87
|
+
customCleanup = cleanup;
|
|
88
|
+
}
|
|
75
89
|
initialized = true;
|
|
76
90
|
},
|
|
77
91
|
getSnapshot: () => logStore.getData(),
|
|
78
92
|
clear: () => {
|
|
79
|
-
|
|
93
|
+
if (persistedStore) {
|
|
94
|
+
persistedStore.clearPersisted();
|
|
95
|
+
} else {
|
|
96
|
+
logStore.clear();
|
|
97
|
+
}
|
|
80
98
|
},
|
|
81
99
|
cleanup: () => {
|
|
82
100
|
customCleanup?.();
|
|
83
101
|
customCleanup = null;
|
|
84
102
|
unsubscribe?.();
|
|
85
103
|
unsubscribe = null;
|
|
86
|
-
|
|
104
|
+
if (persistedStore) {
|
|
105
|
+
persistedStore.dispose();
|
|
106
|
+
} else {
|
|
107
|
+
logStore.clear();
|
|
108
|
+
}
|
|
87
109
|
initialized = false;
|
|
88
110
|
},
|
|
89
111
|
subscribe: (listener: DebugFeatureListener) => logStore.subscribe(listener),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createObservableStore, type ObservableStore } from './createObservableStore';
|
|
2
|
-
import {
|
|
2
|
+
import type { StorageAdapter } from './StorageAdapter';
|
|
3
3
|
|
|
4
4
|
export interface PersistedStoreOptions<T> {
|
|
5
|
+
storage: StorageAdapter;
|
|
5
6
|
storageKey: string;
|
|
6
7
|
maxPersist: number;
|
|
7
8
|
debounceMs?: number;
|
|
@@ -11,13 +12,14 @@ export interface PersistedStoreOptions<T> {
|
|
|
11
12
|
export interface PersistedObservableStore<T> extends ObservableStore<T> {
|
|
12
13
|
nextId: () => string;
|
|
13
14
|
ready: Promise<void>;
|
|
14
|
-
|
|
15
|
+
clearPersisted: () => void;
|
|
16
|
+
dispose: () => void;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export function createPersistedObservableStore<T extends { id?: string }>(
|
|
18
20
|
options: PersistedStoreOptions<T>,
|
|
19
21
|
): PersistedObservableStore<T> {
|
|
20
|
-
const { storageKey, maxPersist, debounceMs = 2000, serialize } = options;
|
|
22
|
+
const { storage, storageKey, maxPersist, debounceMs = 2000, serialize } = options;
|
|
21
23
|
const store = createObservableStore<T>();
|
|
22
24
|
let writeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
23
25
|
let idCounter = 0;
|
|
@@ -25,7 +27,7 @@ export function createPersistedObservableStore<T extends { id?: string }>(
|
|
|
25
27
|
const ready = new Promise<void>((resolve) => { resolveReady = resolve; });
|
|
26
28
|
|
|
27
29
|
// Restore from storage (single notify via pushBatch)
|
|
28
|
-
|
|
30
|
+
Promise.resolve(storage.getItem(storageKey)).then((raw) => {
|
|
29
31
|
if (!raw) { resolveReady(); return; }
|
|
30
32
|
try {
|
|
31
33
|
const entries = JSON.parse(raw) as T[];
|
|
@@ -36,7 +38,9 @@ export function createPersistedObservableStore<T extends { id?: string }>(
|
|
|
36
38
|
let max = 0;
|
|
37
39
|
for (const e of restored) {
|
|
38
40
|
const n = parseInt(e.id ?? '', 10);
|
|
39
|
-
if (!isNaN(n) && n >= max)
|
|
41
|
+
if (!isNaN(n) && n >= max) {
|
|
42
|
+
max = n + 1;
|
|
43
|
+
}
|
|
40
44
|
}
|
|
41
45
|
idCounter = max;
|
|
42
46
|
} catch {
|
|
@@ -46,13 +50,15 @@ export function createPersistedObservableStore<T extends { id?: string }>(
|
|
|
46
50
|
});
|
|
47
51
|
|
|
48
52
|
function scheduleWrite(): void {
|
|
49
|
-
if (writeTimer !== null)
|
|
53
|
+
if (writeTimer !== null) {
|
|
54
|
+
clearTimeout(writeTimer);
|
|
55
|
+
}
|
|
50
56
|
writeTimer = setTimeout(() => {
|
|
51
57
|
writeTimer = null;
|
|
52
58
|
const data = store.getData().slice(-maxPersist);
|
|
53
59
|
const toStore = serialize ? data.map(serialize) : data;
|
|
54
60
|
try {
|
|
55
|
-
|
|
61
|
+
Promise.resolve(storage.setItem(storageKey, JSON.stringify(toStore))).catch(() => {});
|
|
56
62
|
} catch {
|
|
57
63
|
// stringify failed (circular refs, etc) — skip write
|
|
58
64
|
}
|
|
@@ -68,22 +74,24 @@ export function createPersistedObservableStore<T extends { id?: string }>(
|
|
|
68
74
|
pushBatch: store.pushBatch,
|
|
69
75
|
clear: () => {
|
|
70
76
|
store.clear();
|
|
77
|
+
},
|
|
78
|
+
clearPersisted: () => {
|
|
79
|
+
store.clear();
|
|
71
80
|
if (writeTimer !== null) {
|
|
72
81
|
clearTimeout(writeTimer);
|
|
73
82
|
writeTimer = null;
|
|
74
83
|
}
|
|
75
|
-
|
|
84
|
+
Promise.resolve(storage.setItem(storageKey, '[]')).catch(() => {});
|
|
76
85
|
},
|
|
77
86
|
subscribe: store.subscribe,
|
|
78
87
|
nextId: () => String(idCounter++),
|
|
79
88
|
ready,
|
|
80
|
-
|
|
89
|
+
dispose: () => {
|
|
81
90
|
if (writeTimer !== null) {
|
|
82
91
|
clearTimeout(writeTimer);
|
|
83
92
|
writeTimer = null;
|
|
84
93
|
}
|
|
85
94
|
store.clear();
|
|
86
|
-
setPreference(storageKey, '[]');
|
|
87
95
|
},
|
|
88
96
|
};
|
|
89
97
|
}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
type AsyncStorageLike = {
|
|
2
|
-
|
|
1
|
+
type AsyncStorageLike = {
|
|
2
|
+
getItem: (k: string) => Promise<string | null>;
|
|
3
|
+
setItem: (k: string, v: string) => Promise<void>;
|
|
4
|
+
removeItem?: (k: string) => Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
type NativePreferencesLike = {
|
|
7
|
+
getPreference: (k: string) => Promise<string | null>;
|
|
8
|
+
setPreference: (k: string, v: string) => Promise<void>;
|
|
9
|
+
};
|
|
3
10
|
|
|
4
11
|
const memoryStore = new Map<string, string>();
|
|
5
12
|
|
|
6
13
|
function loadAsyncStorage(): AsyncStorageLike | null {
|
|
7
14
|
try {
|
|
8
15
|
const mod = require('@react-native-async-storage/async-storage');
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
const storage = mod?.default && typeof mod.default.getItem === 'function' ? mod.default : mod;
|
|
17
|
+
if (storage && typeof storage.getItem === 'function' && typeof storage.setItem === 'function') {
|
|
18
|
+
return storage;
|
|
11
19
|
}
|
|
12
20
|
return null;
|
|
13
21
|
} catch {
|
|
@@ -82,12 +90,35 @@ export async function getPreference(key: string): Promise<string | null> {
|
|
|
82
90
|
return memoryStore.get(key) ?? null;
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
export async function removePreference(key: string): Promise<void> {
|
|
94
|
+
memoryStore.delete(key);
|
|
95
|
+
const AsyncStorage = loadAsyncStorage();
|
|
96
|
+
if (AsyncStorage) {
|
|
97
|
+
try {
|
|
98
|
+
if (typeof AsyncStorage.removeItem === 'function') {
|
|
99
|
+
await AsyncStorage.removeItem(key);
|
|
100
|
+
} else {
|
|
101
|
+
await AsyncStorage.setItem(key, '');
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
} catch {
|
|
105
|
+
// degrade to memory only
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const nativePreferences = loadNativePreferences();
|
|
110
|
+
if (nativePreferences) {
|
|
111
|
+
try {
|
|
112
|
+
await nativePreferences.setPreference(key, '');
|
|
113
|
+
} catch {
|
|
114
|
+
// degrade to memory only
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
85
119
|
export const KEYS = {
|
|
86
120
|
fabPosition: '@react_native_debug_toolkit/fab_position',
|
|
87
121
|
lastTab: '@react_native_debug_toolkit/last_tab',
|
|
88
|
-
consoleLogs: '@react_native_debug_toolkit/console_logs',
|
|
89
|
-
networkLogs: '@react_native_debug_toolkit/network_logs',
|
|
90
|
-
trackLogs: '@react_native_debug_toolkit/track_logs',
|
|
91
122
|
computerHost: '@react_native_debug_toolkit/computer_host',
|
|
92
123
|
daemonPort: '@react_native_debug_toolkit/daemon_port',
|
|
93
124
|
} as const;
|
|
@@ -33,6 +33,10 @@ export interface DebugDeviceReport {
|
|
|
33
33
|
logs: Record<string, unknown[] | undefined>;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export function sanitizeDebugLogEntry(value: unknown, maxBodyBytes = DEFAULT_MAX_BODY_BYTES): unknown {
|
|
37
|
+
return sanitizeValue(value, Math.max(256, Math.floor(maxBodyBytes)));
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
interface TruncatedValue {
|
|
37
41
|
__debugToolkitTruncated: true;
|
|
38
42
|
originalBytes: number;
|
|
@@ -193,7 +197,7 @@ export function createDebugDeviceReport(
|
|
|
193
197
|
|
|
194
198
|
logs[feature.name] = snapshot
|
|
195
199
|
.slice(-maxPerType)
|
|
196
|
-
.map((entry) =>
|
|
200
|
+
.map((entry) => sanitizeDebugLogEntry(entry, maxBodyBytes));
|
|
197
201
|
});
|
|
198
202
|
|
|
199
203
|
const constants = Platform.constants as Record<string, unknown> | undefined;
|