react-native-debug-toolkit 3.3.8 → 3.5.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/lib/commonjs/constants/logLevels.js +19 -0
- package/lib/commonjs/constants/logLevels.js.map +1 -0
- package/lib/commonjs/core/initialize.js +30 -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/network/NetworkLogTab.js +91 -93
- 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 +1044 -0
- package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js.map +1 -0
- package/lib/commonjs/features/sessionHistory/index.js +103 -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 +20 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/DebugView.js +1 -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 +131 -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 +147 -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 +30 -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/network/NetworkLogTab.js +91 -93
- 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 +1039 -0
- package/lib/module/features/sessionHistory/SessionHistoryTab.js.map +1 -0
- package/lib/module/features/sessionHistory/index.js +99 -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 +1 -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 +126 -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 +148 -23
- 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 +4 -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/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 +4 -0
- 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 +2 -0
- package/lib/typescript/src/types/index.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 +11 -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/package.json +9 -1
- package/src/constants/logLevels.ts +13 -0
- package/src/core/initialize.ts +49 -25
- package/src/features/console/ConsoleLogTab.tsx +1 -14
- package/src/features/console/index.ts +18 -8
- package/src/features/network/NetworkLogTab.tsx +61 -71
- package/src/features/network/index.ts +12 -3
- package/src/features/sessionHistory/SessionHistoryTab.tsx +691 -0
- package/src/features/sessionHistory/index.ts +102 -0
- package/src/features/track/index.ts +10 -3
- package/src/index.ts +11 -0
- package/src/types/feature.ts +2 -1
- package/src/types/index.ts +10 -0
- package/src/ui/DebugView.tsx +1 -0
- package/src/ui/panel/DebugPanel.tsx +60 -30
- package/src/ui/panel/FeatureIntroCard.tsx +127 -0
- package/src/ui/panel/FeatureRail.tsx +165 -0
- package/src/ui/panel/FloatPanelView.tsx +154 -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
|
@@ -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' | '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', '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;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { SessionManager, type SessionManagerOptions } from './SessionManager';
|
|
2
|
+
import {
|
|
3
|
+
createDefaultLogStorage,
|
|
4
|
+
type StorageAdapter,
|
|
5
|
+
} from './StorageAdapter';
|
|
6
|
+
|
|
7
|
+
export interface LogRuntimeContext {
|
|
8
|
+
logStorage: StorageAdapter;
|
|
9
|
+
sessionManager: SessionManager;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LogRuntimeOptions extends SessionManagerOptions {
|
|
13
|
+
logStorage?: StorageAdapter;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let defaultRuntime: LogRuntimeContext | null = null;
|
|
17
|
+
|
|
18
|
+
export function createLogRuntime(options: LogRuntimeOptions = {}): LogRuntimeContext {
|
|
19
|
+
const logStorage = options.logStorage ?? createDefaultLogStorage();
|
|
20
|
+
return {
|
|
21
|
+
logStorage,
|
|
22
|
+
sessionManager: new SessionManager(logStorage, {
|
|
23
|
+
maxSessions: options.maxSessions,
|
|
24
|
+
featureKeys: options.featureKeys,
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function setDefaultLogRuntime(runtime: LogRuntimeContext | null): void {
|
|
30
|
+
defaultRuntime = runtime;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getDefaultLogRuntime(): LogRuntimeContext {
|
|
34
|
+
if (!defaultRuntime) {
|
|
35
|
+
defaultRuntime = createLogRuntime();
|
|
36
|
+
defaultRuntime.sessionManager.initialize().catch(() => {});
|
|
37
|
+
}
|
|
38
|
+
return defaultRuntime;
|
|
39
|
+
}
|