UNPKG

@not-true/devtools

Version:

Remote debugging and development tools client library for React Native applications

187 lines (158 loc) 4.77 kB
import { StateUpdate } from '../types'; export class StateUtils { /** * Create a state update for Redux store */ static createReduxStateUpdate(state: any, statePath?: string): StateUpdate { return { type: 'redux', data: state, statePath, }; } /** * Create a state update for React Context */ static createContextStateUpdate(contextValue: any, contextName?: string): StateUpdate { return { type: 'context', data: contextValue, statePath: contextName, }; } /** * Create a state update for AsyncStorage */ static createAsyncStorageUpdate(key: string, value: any): StateUpdate { return { type: 'async-storage', data: { [key]: value }, statePath: key, }; } /** * Create a custom state update */ static createCustomStateUpdate(data: any, identifier?: string): StateUpdate { return { type: 'custom', data, statePath: identifier, }; } /** * Sanitize state data to prevent circular references and large objects */ static sanitizeStateData(data: any, maxDepth = 10): any { const seen = new WeakSet(); const sanitize = (obj: any, depth: number): any => { if (depth > maxDepth) { return '[Max depth reached]'; } if (obj === null || typeof obj !== 'object') { return obj; } if (seen.has(obj)) { return '[Circular reference]'; } seen.add(obj); if (Array.isArray(obj)) { const sanitizedArray = obj.slice(0, 100).map(item => sanitize(item, depth + 1)); if (obj.length > 100) { sanitizedArray.push(`[... ${obj.length - 100} more items]`); } return sanitizedArray; } const sanitizedObj: any = {}; const keys = Object.keys(obj).slice(0, 100); for (const key of keys) { try { sanitizedObj[key] = sanitize(obj[key], depth + 1); } catch (error) { sanitizedObj[key] = '[Error accessing property]'; } } if (Object.keys(obj).length > 100) { sanitizedObj['[truncated]'] = `${Object.keys(obj).length - 100} more properties`; } return sanitizedObj; }; return sanitize(data, 0); } /** * Extract state diff between two state objects */ static createStateDiff(oldState: any, newState: any): any { const diff: any = {}; const compare = (old: any, current: any, path: string = ''): void => { if (old === current) return; if (typeof old !== typeof current) { diff[path || 'root'] = { old, new: current }; return; } if (current === null || typeof current !== 'object') { diff[path || 'root'] = { old, new: current }; return; } if (Array.isArray(current)) { if (!Array.isArray(old) || old.length !== current.length) { diff[path || 'root'] = { old, new: current }; return; } for (let i = 0; i < current.length; i++) { const newPath = path ? `${path}[${i}]` : `[${i}]`; compare(old[i], current[i], newPath); } return; } // Object comparison const allKeys = new Set([...Object.keys(old || {}), ...Object.keys(current)]); for (const key of Array.from(allKeys)) { const newPath = path ? `${path}.${key}` : key; if (!(key in (old || {}))) { diff[newPath] = { old: undefined, new: current[key] }; } else if (!(key in current)) { diff[newPath] = { old: old[key], new: undefined }; } else { compare(old[key], current[key], newPath); } } }; compare(oldState, newState); return diff; } /** * Create a throttled state update function */ static createThrottledUpdater( updateFn: (stateUpdate: StateUpdate) => void, throttleMs: number = 500 ): (stateUpdate: StateUpdate) => void { let lastUpdate = 0; let timeoutId: NodeJS.Timeout | null = null; let pendingUpdate: StateUpdate | null = null; return (stateUpdate: StateUpdate) => { pendingUpdate = stateUpdate; const now = Date.now(); if (now - lastUpdate >= throttleMs) { // Execute immediately lastUpdate = now; updateFn(stateUpdate); pendingUpdate = null; } else { // Schedule for later if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { if (pendingUpdate) { lastUpdate = Date.now(); updateFn(pendingUpdate); pendingUpdate = null; timeoutId = null; } }, throttleMs - (now - lastUpdate)); } }; } }