@not-true/devtools
Version:
Remote debugging and development tools client library for React Native applications
187 lines (158 loc) • 4.77 kB
text/typescript
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));
}
};
}
}