react-native-inapp-inspector 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +14 -0
  2. package/dist/commonjs/components/ConsoleLogCard.js +18 -0
  3. package/dist/commonjs/components/JsonViewer.d.ts +2 -1
  4. package/dist/commonjs/components/JsonViewer.js +6 -4
  5. package/dist/commonjs/components/LogCard.js +17 -0
  6. package/dist/commonjs/components/MetaAccordion.js +26 -6
  7. package/dist/commonjs/components/NetworkIcons.d.ts +9 -2
  8. package/dist/commonjs/components/NetworkIcons.js +59 -3
  9. package/dist/commonjs/components/ReduxTreeView.js +80 -5
  10. package/dist/commonjs/constants/version.d.ts +1 -1
  11. package/dist/commonjs/constants/version.js +1 -1
  12. package/dist/commonjs/customHooks/reduxLogger.d.ts +21 -7
  13. package/dist/commonjs/customHooks/reduxLogger.js +147 -48
  14. package/dist/commonjs/customHooks/webViewLogger.js +13 -8
  15. package/dist/commonjs/helpers/settingsStore.d.ts +24 -0
  16. package/dist/commonjs/helpers/settingsStore.js +74 -0
  17. package/dist/commonjs/index.d.ts +1 -1
  18. package/dist/commonjs/index.js +897 -170
  19. package/dist/commonjs/styles/index.d.ts +40 -0
  20. package/dist/commonjs/styles/index.js +45 -2
  21. package/dist/commonjs/types/index.d.ts +4 -0
  22. package/dist/esm/components/ConsoleLogCard.js +18 -0
  23. package/dist/esm/components/JsonViewer.d.ts +2 -1
  24. package/dist/esm/components/JsonViewer.js +6 -4
  25. package/dist/esm/components/LogCard.js +17 -0
  26. package/dist/esm/components/MetaAccordion.js +27 -7
  27. package/dist/esm/components/NetworkIcons.d.ts +9 -2
  28. package/dist/esm/components/NetworkIcons.js +51 -2
  29. package/dist/esm/components/ReduxTreeView.js +81 -6
  30. package/dist/esm/constants/version.d.ts +1 -1
  31. package/dist/esm/constants/version.js +1 -1
  32. package/dist/esm/customHooks/reduxLogger.d.ts +21 -7
  33. package/dist/esm/customHooks/reduxLogger.js +145 -47
  34. package/dist/esm/customHooks/webViewLogger.js +13 -8
  35. package/dist/esm/helpers/settingsStore.d.ts +24 -0
  36. package/dist/esm/helpers/settingsStore.js +67 -0
  37. package/dist/esm/index.d.ts +1 -1
  38. package/dist/esm/index.js +896 -172
  39. package/dist/esm/styles/index.d.ts +40 -0
  40. package/dist/esm/styles/index.js +45 -2
  41. package/dist/esm/types/index.d.ts +4 -0
  42. package/example/App.tsx +199 -61
  43. package/example/ios/example.xcodeproj/project.pbxproj +0 -8
  44. package/example/package-lock.json +4 -3
  45. package/package.json +1 -1
@@ -1,11 +1,31 @@
1
1
  "use strict";
2
+ // #8 — Redux inspector module.
3
+ //
4
+ // Two integration paths, both feeding the same timeline/state snapshot:
5
+ //
6
+ // 1. `inspectorReduxMiddleware` (recommended) — a standard Redux middleware.
7
+ // Because it sits inside the middleware chain it sees EVERY action,
8
+ // including ones dispatched from thunks, sagas, listeners and RTK Query.
9
+ //
10
+ // 2. `connectReduxStore(store)` — zero-config fallback. It wraps the outer
11
+ // `store.dispatch` AND diffs state on `store.subscribe`, so even actions
12
+ // that bypass the wrapped dispatch (thunk/saga internals capture the raw
13
+ // dispatch reference at store-creation time) still update the state tree
14
+ // and per-reducer "last action" consistently.
2
15
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.connectReduxStore = exports.subscribeReduxState = exports.setReduxState = exports.clearActionHistory = exports.getActionHistory = exports.clearLastActionForReducer = exports.getLastActionForReducer = exports.getReduxAutoRefresh = exports.setReduxAutoRefresh = exports.getReduxState = void 0;
16
+ exports.connectReduxStore = exports.inspectorReduxMiddleware = exports.subscribeReduxState = exports.setReduxState = exports.clearActionHistory = exports.getActionHistory = exports.clearLastActionForReducer = exports.getLastActionForReducer = exports.getReduxAutoRefresh = exports.setReduxAutoRefresh = exports.getReduxState = void 0;
4
17
  let currentReduxState = null;
5
18
  const listeners = new Set();
6
19
  let globalReduxAutoRefresh = true;
7
20
  let lastActionForReducer = {};
8
21
  let actionHistory = [];
22
+ const MAX_HISTORY = 50;
23
+ let historyIdSeq = 0;
24
+ // Guards against double-instrumentation (e.g. connectReduxStore called twice,
25
+ // or middleware + connect used together) which previously produced duplicate
26
+ // timeline entries and inconsistent counts.
27
+ const connectedStores = new WeakSet();
28
+ let middlewareAttached = false;
9
29
  const getReduxState = () => currentReduxState;
10
30
  exports.getReduxState = getReduxState;
11
31
  const setReduxAutoRefresh = (val) => {
@@ -18,19 +38,19 @@ const getLastActionForReducer = () => lastActionForReducer;
18
38
  exports.getLastActionForReducer = getLastActionForReducer;
19
39
  const clearLastActionForReducer = () => {
20
40
  lastActionForReducer = {};
21
- listeners.forEach(cb => cb());
41
+ notify();
22
42
  };
23
43
  exports.clearLastActionForReducer = clearLastActionForReducer;
24
44
  const getActionHistory = () => actionHistory;
25
45
  exports.getActionHistory = getActionHistory;
26
46
  const clearActionHistory = () => {
27
47
  actionHistory = [];
28
- listeners.forEach(cb => cb());
48
+ notify();
29
49
  };
30
50
  exports.clearActionHistory = clearActionHistory;
31
51
  const setReduxState = (state) => {
32
52
  currentReduxState = state;
33
- listeners.forEach(cb => cb());
53
+ notify();
34
54
  };
35
55
  exports.setReduxState = setReduxState;
36
56
  const subscribeReduxState = (cb) => {
@@ -40,6 +60,83 @@ const subscribeReduxState = (cb) => {
40
60
  };
41
61
  };
42
62
  exports.subscribeReduxState = subscribeReduxState;
63
+ function notify() {
64
+ listeners.forEach(cb => cb());
65
+ }
66
+ function actionTypeOf(action) {
67
+ if (typeof action === 'string')
68
+ return action;
69
+ if (action && typeof action === 'object' && action.type != null) {
70
+ return String(action.type);
71
+ }
72
+ if (typeof action === 'function') {
73
+ return action.name ? `thunk: ${action.name}` : 'thunk';
74
+ }
75
+ return 'UNKNOWN_ACTION';
76
+ }
77
+ function recordAction(action, prevState, nextState) {
78
+ const type = actionTypeOf(action);
79
+ const payload = action && typeof action === 'object' && action.payload !== undefined
80
+ ? action.payload
81
+ : null;
82
+ const timestamp = new Date().toLocaleTimeString();
83
+ const affectedSlices = [];
84
+ if (prevState &&
85
+ nextState &&
86
+ typeof prevState === 'object' &&
87
+ typeof nextState === 'object') {
88
+ Object.keys(nextState).forEach(key => {
89
+ if (prevState[key] !== nextState[key]) {
90
+ lastActionForReducer[key] = { type, payload, timestamp };
91
+ affectedSlices.push(key);
92
+ }
93
+ });
94
+ }
95
+ actionHistory.unshift({
96
+ id: ++historyIdSeq,
97
+ type,
98
+ payload,
99
+ timestamp,
100
+ affectedSlices,
101
+ });
102
+ if (actionHistory.length > MAX_HISTORY) {
103
+ actionHistory.length = MAX_HISTORY;
104
+ }
105
+ if (globalReduxAutoRefresh) {
106
+ currentReduxState = nextState;
107
+ }
108
+ // Timeline / last-action map always changed — notify even when the state
109
+ // tree snapshot is paused so those panels stay live.
110
+ notify();
111
+ }
112
+ /**
113
+ * Standard Redux middleware — add it to your store to capture every action,
114
+ * including those dispatched from thunks, sagas and RTK Query:
115
+ *
116
+ * const store = configureStore({
117
+ * reducer,
118
+ * middleware: gDM => gDM().concat(inspectorReduxMiddleware),
119
+ * });
120
+ *
121
+ * Pair with `connectReduxStore(store)` (safe — they de-duplicate) or rely on
122
+ * the middleware alone; the initial snapshot is taken on the first action.
123
+ */
124
+ const inspectorReduxMiddleware = (storeApi) => (next) => (action) => {
125
+ middlewareAttached = true;
126
+ // Thunks are functions — let them run; their inner plain-action dispatches
127
+ // pass back through this same middleware, so nothing is lost.
128
+ if (typeof action === 'function') {
129
+ return next(action);
130
+ }
131
+ const prevState = storeApi.getState();
132
+ const result = next(action);
133
+ const nextState = storeApi.getState();
134
+ if (currentReduxState == null)
135
+ currentReduxState = nextState;
136
+ recordAction(action, prevState, nextState);
137
+ return result;
138
+ };
139
+ exports.inspectorReduxMiddleware = inspectorReduxMiddleware;
43
140
  const connectReduxStore = (store) => {
44
141
  if (!store ||
45
142
  typeof store.getState !== 'function' ||
@@ -47,61 +144,63 @@ const connectReduxStore = (store) => {
47
144
  console.warn('[NetworkInspector] Invalid Redux store passed to connectReduxStore.');
48
145
  return;
49
146
  }
50
- // Intercept dispatch calls to log actions and tie them to modified state slices
147
+ // Idempotent connecting the same store twice must not double-wrap
148
+ // dispatch or double-record the timeline.
149
+ if (connectedStores.has(store)) {
150
+ return;
151
+ }
152
+ connectedStores.add(store);
153
+ // Wrap the outer dispatch so directly dispatched actions get full
154
+ // type/payload attribution. Skipped when the middleware is already
155
+ // attached, otherwise every direct dispatch would be recorded twice.
51
156
  const originalDispatch = store.dispatch.bind(store);
157
+ let inWrappedDispatch = false;
52
158
  store.dispatch = (action) => {
53
- const prevState = store.getState();
54
- const result = originalDispatch(action);
55
- const nextState = store.getState();
56
- // Map the dispatched action to state slices that actually changed
57
- const affectedSlices = [];
58
- if (prevState &&
59
- nextState &&
60
- typeof prevState === 'object' &&
61
- typeof nextState === 'object' &&
62
- action &&
63
- typeof action === 'object') {
64
- Object.keys(nextState).forEach(key => {
65
- if (prevState[key] !== nextState[key]) {
66
- const actionObj = {
67
- type: action.type || 'UNKNOWN_ACTION',
68
- payload: action.payload !== undefined ? action.payload : null,
69
- timestamp: new Date().toLocaleTimeString(),
70
- };
71
- lastActionForReducer[key] = actionObj;
72
- affectedSlices.push(key);
73
- }
74
- });
75
- // Push to history
76
- actionHistory.unshift({
77
- id: Date.now() + Math.random(),
78
- type: action.type || 'UNKNOWN_ACTION',
79
- payload: action.payload !== undefined ? action.payload : null,
80
- timestamp: new Date().toLocaleTimeString(),
81
- affectedSlices,
82
- });
83
- // Cap size at 50
84
- if (actionHistory.length > 50) {
85
- actionHistory.pop();
159
+ if (middlewareAttached || typeof action === 'function') {
160
+ // Middleware handles recording, or it's a thunk whose inner dispatches
161
+ // will be picked up individually.
162
+ inWrappedDispatch = true;
163
+ try {
164
+ return originalDispatch(action);
165
+ }
166
+ finally {
167
+ inWrappedDispatch = false;
86
168
  }
87
169
  }
88
- if (globalReduxAutoRefresh) {
89
- // Refresh the displayed state tree snapshot.
90
- (0, exports.setReduxState)(nextState);
170
+ const prevState = store.getState();
171
+ inWrappedDispatch = true;
172
+ let result;
173
+ try {
174
+ result = originalDispatch(action);
91
175
  }
92
- else {
93
- // Tree is paused, but the action timeline / last-action map still changed,
94
- // so notify subscribers to re-render those without moving the tree snapshot.
95
- listeners.forEach(cb => cb());
176
+ finally {
177
+ inWrappedDispatch = false;
96
178
  }
179
+ recordAction(action, prevState, store.getState());
97
180
  return result;
98
181
  };
99
182
  (0, exports.setReduxState)(store.getState());
100
- // Listen to subscription for devtools updates or any other state changes
183
+ // Subscribe-diff fallback: catches state changes whose dispatch bypassed
184
+ // the wrapper above (thunk/saga internals hold the raw dispatch reference).
185
+ // Without this, the tree and per-reducer last-action drifted out of sync.
186
+ let lastSeenState = store.getState();
101
187
  store.subscribe(() => {
102
- if (globalReduxAutoRefresh) {
103
- (0, exports.setReduxState)(store.getState());
188
+ const nextState = store.getState();
189
+ if (nextState === lastSeenState)
190
+ return;
191
+ const prevState = lastSeenState;
192
+ lastSeenState = nextState;
193
+ if (inWrappedDispatch || middlewareAttached) {
194
+ // Already recorded with proper attribution; just refresh the snapshot.
195
+ if (globalReduxAutoRefresh) {
196
+ currentReduxState = nextState;
197
+ notify();
198
+ }
199
+ return;
104
200
  }
201
+ // Change arrived outside the wrapped dispatch — record it so the
202
+ // timeline stays consistent, even without the original action type.
203
+ recordAction({ type: '@@inspector/EXTERNAL_STATE_CHANGE' }, prevState, nextState);
105
204
  });
106
205
  };
107
206
  exports.connectReduxStore = connectReduxStore;
@@ -323,14 +323,19 @@ exports.WebView = (0, react_1.forwardRef)((props, ref) => {
323
323
  onNavigationStateChange: handleNavigationStateChange,
324
324
  onLoadStart: handleLoadStart,
325
325
  onLoadEnd: handleLoadEnd,
326
- }), loading && showLoader && react_1.default.createElement(react_native_1.View, {
327
- style: {
328
- ...react_native_1.StyleSheet.absoluteFill,
329
- justifyContent: 'center',
330
- alignItems: 'center',
331
- backgroundColor: 'rgba(255, 255, 255, 0.4)',
332
- },
333
- }, react_1.default.createElement(react_native_1.ActivityIndicator, { size: 'large', color: '#684B9B' })));
326
+ }), loading &&
327
+ showLoader &&
328
+ react_1.default.createElement(react_native_1.View, {
329
+ style: {
330
+ ...react_native_1.StyleSheet.absoluteFill,
331
+ justifyContent: 'center',
332
+ alignItems: 'center',
333
+ backgroundColor: 'rgba(255, 255, 255, 0.4)',
334
+ },
335
+ }, react_1.default.createElement(react_native_1.ActivityIndicator, {
336
+ size: 'large',
337
+ color: '#684B9B',
338
+ })));
334
339
  });
335
340
  // Perform monkey-patching to intercept react-native-webview exports globally
336
341
  try {
@@ -0,0 +1,24 @@
1
+ export interface PersistedSettings {
2
+ isDark?: boolean;
3
+ modalHeightPercent?: number;
4
+ tabVisibility?: Record<string, boolean>;
5
+ defaultTab?: string;
6
+ maxNetworkLogs?: number;
7
+ maxConsoleLogs?: number;
8
+ showConsoleLevels?: {
9
+ info: boolean;
10
+ warn: boolean;
11
+ error: boolean;
12
+ };
13
+ webViewCaptureCssJs?: boolean;
14
+ reduxAutoRefresh?: boolean;
15
+ reduxExpandDepth?: number;
16
+ slowRequestThreshold?: number;
17
+ insightsShowConsoleAlerts?: boolean;
18
+ showDuplicateLogs?: boolean;
19
+ }
20
+ export declare function loadSettings(): Promise<PersistedSettings>;
21
+ /** Debounced save so rapid toggling doesn't hammer storage. */
22
+ export declare function saveSettings(settings: PersistedSettings): void;
23
+ export declare function clearPersistedSettings(): Promise<void>;
24
+ export declare const isPersistentStorageAvailable: () => boolean;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ // #5 — Persistence layer for the inspector's settings selections.
3
+ //
4
+ // Backed by @react-native-async-storage/async-storage when the host app has it
5
+ // installed (most RN apps do), with a transparent in-memory fallback so this
6
+ // library never crashes and never forces a new native dependency.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.isPersistentStorageAvailable = void 0;
9
+ exports.loadSettings = loadSettings;
10
+ exports.saveSettings = saveSettings;
11
+ exports.clearPersistedSettings = clearPersistedSettings;
12
+ let storage = null;
13
+ try {
14
+ // Optional dependency — resolved only if the host app already ships it.
15
+ const mod = require('@react-native-async-storage/async-storage');
16
+ storage = mod?.default ?? mod ?? null;
17
+ if (storage && typeof storage.getItem !== 'function')
18
+ storage = null;
19
+ }
20
+ catch {
21
+ storage = null;
22
+ }
23
+ // In-memory fallback (settings survive for the app session only).
24
+ const memory = new Map();
25
+ const SETTINGS_KEY = 'rn-inapp-inspector.settings.v1';
26
+ async function loadSettings() {
27
+ try {
28
+ const raw = storage
29
+ ? await storage.getItem(SETTINGS_KEY)
30
+ : memory.get(SETTINGS_KEY) ?? null;
31
+ if (!raw)
32
+ return {};
33
+ const parsed = JSON.parse(raw);
34
+ return parsed && typeof parsed === 'object' ? parsed : {};
35
+ }
36
+ catch {
37
+ return {};
38
+ }
39
+ }
40
+ let saveTimer = null;
41
+ /** Debounced save so rapid toggling doesn't hammer storage. */
42
+ function saveSettings(settings) {
43
+ if (saveTimer)
44
+ clearTimeout(saveTimer);
45
+ saveTimer = setTimeout(async () => {
46
+ try {
47
+ const raw = JSON.stringify(settings);
48
+ if (storage) {
49
+ await storage.setItem(SETTINGS_KEY, raw);
50
+ }
51
+ else {
52
+ memory.set(SETTINGS_KEY, raw);
53
+ }
54
+ }
55
+ catch {
56
+ // Persistence is best-effort — never crash the host app over it.
57
+ }
58
+ }, 250);
59
+ }
60
+ async function clearPersistedSettings() {
61
+ try {
62
+ if (storage) {
63
+ await storage.removeItem(SETTINGS_KEY);
64
+ }
65
+ else {
66
+ memory.delete(SETTINGS_KEY);
67
+ }
68
+ }
69
+ catch {
70
+ // ignore
71
+ }
72
+ }
73
+ const isPersistentStorageAvailable = () => storage != null;
74
+ exports.isPersistentStorageAvailable = isPersistentStorageAvailable;
@@ -6,4 +6,4 @@ export { setupConsoleLogger, clearConsoleLogs, subscribeConsoleLogs, } from './c
6
6
  export { setupAnalyticsLogger, logAnalyticsEvent, subscribeAnalyticsEvents, clearAnalyticsEvents, } from './customHooks/analyticsLogger';
7
7
  export { WebView, getWebViewLogs, getWebViewNavHistory, getWebViewHtml, getWebViewCss, getWebViewJs, getWebViewHtmlUrl, clearWebViewData, subscribeWebView, } from './customHooks/webViewLogger';
8
8
  export { default as ErrorBoundary } from './components/ErrorBoundary';
9
- export { connectReduxStore, getReduxState, subscribeReduxState, } from './customHooks/reduxLogger';
9
+ export { connectReduxStore, inspectorReduxMiddleware, getReduxState, subscribeReduxState, getActionHistory, clearActionHistory, } from './customHooks/reduxLogger';