mirror of
				https://github.com/elyby/accounts-frontend.git
				synced 2025-05-31 14:11:58 +05:30 
			
		
		
		
	
		
			
				
	
	
		
			189 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
const STRING_MAX_LENGTH = 128 * 1024;
 | 
						|
 | 
						|
type Filter = (key: string | undefined, value: Record<string, any>) => any;
 | 
						|
 | 
						|
interface State {
 | 
						|
    sizeLeft: number;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create a copy of any object without non-serializable elements to make result safe for JSON.stringify().
 | 
						|
 * Guaranteed to never throw.
 | 
						|
 *
 | 
						|
 * @param {any}    obj     Any data structure
 | 
						|
 * @param {object} options
 | 
						|
 * @param {Function=} options.filter - callback that is called on every object's key with (key,value) and should return
 | 
						|
 *        value to use (may return undefined to remove unwanted keys). See nodeFilter and browserFilter.
 | 
						|
 * @param {number=} options.depth - maximum recursion depth. Elements deeper than that are stringified with util.inspect()
 | 
						|
 * @param {number=} options.maxSize - roughly maximum allowed size of data after JSON serialisation (but it's not guaranteed
 | 
						|
 *        that it won't exceed the limit)
 | 
						|
 *
 | 
						|
 * @see https://github.com/ftlabs/js-abbreviate
 | 
						|
 *
 | 
						|
 * @returns {object}
 | 
						|
 */
 | 
						|
function abbreviate(
 | 
						|
    obj: Record<string, any>,
 | 
						|
    options: {
 | 
						|
        filter?: Filter;
 | 
						|
        depth?: number;
 | 
						|
        maxSize?: number;
 | 
						|
    } = {},
 | 
						|
): string {
 | 
						|
    const filter: Filter = options.filter || ((key, value) => value);
 | 
						|
    const maxDepth: number = options.depth || 10;
 | 
						|
    const maxSize: number = options.maxSize || 1 * 1024 * 1024;
 | 
						|
 | 
						|
    return abbreviateRecursive(undefined, obj, filter, { sizeLeft: maxSize }, maxDepth);
 | 
						|
}
 | 
						|
 | 
						|
function limitStringLength(str: string): string {
 | 
						|
    if (str.length > STRING_MAX_LENGTH) {
 | 
						|
        return `${str.substring(0, STRING_MAX_LENGTH / 2)} … ${str.substring(str.length - STRING_MAX_LENGTH / 2)}`;
 | 
						|
    }
 | 
						|
 | 
						|
    return str;
 | 
						|
}
 | 
						|
 | 
						|
function abbreviateRecursive(key: string | undefined, obj: any, filter: Filter, state: State, maxDepth: number): any {
 | 
						|
    // TODO: return type
 | 
						|
    if (state.sizeLeft < 0) {
 | 
						|
        return '**skipped**';
 | 
						|
    }
 | 
						|
 | 
						|
    state.sizeLeft -= 5; // rough approximation of JSON overhead
 | 
						|
 | 
						|
    obj = filter(key, obj);
 | 
						|
 | 
						|
    try {
 | 
						|
        switch (typeof obj) {
 | 
						|
            case 'object': {
 | 
						|
                if (obj === null) {
 | 
						|
                    return null;
 | 
						|
                }
 | 
						|
 | 
						|
                if (maxDepth < 0) {
 | 
						|
                    break; // fall back to stringification
 | 
						|
                }
 | 
						|
 | 
						|
                const newobj: Record<string, any> | Array<any> = Array.isArray(obj) ? [] : {};
 | 
						|
 | 
						|
                for (const i in obj) {
 | 
						|
                    if (!obj.hasOwnProperty(i)) {
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // @ts-ignore
 | 
						|
                    newobj[i] = abbreviateRecursive(i, obj[i], filter, state, maxDepth - 1);
 | 
						|
 | 
						|
                    if (state.sizeLeft < 0) {
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                return newobj;
 | 
						|
            }
 | 
						|
 | 
						|
            case 'string':
 | 
						|
                obj = limitStringLength(obj);
 | 
						|
                state.sizeLeft -= obj.length;
 | 
						|
 | 
						|
                return obj;
 | 
						|
 | 
						|
            case 'number':
 | 
						|
            case 'boolean':
 | 
						|
            case 'undefined':
 | 
						|
            default:
 | 
						|
                return obj;
 | 
						|
        }
 | 
						|
    } catch (err) {
 | 
						|
        /* fall back to inspect*/
 | 
						|
    }
 | 
						|
 | 
						|
    try {
 | 
						|
        obj = limitStringLength(`${obj}`);
 | 
						|
        state.sizeLeft -= obj.length;
 | 
						|
 | 
						|
        return obj;
 | 
						|
    } catch (err) {
 | 
						|
        return '**non-serializable**';
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
function commonFilter(key: any, val: any): any {
 | 
						|
    if (typeof val === 'function') {
 | 
						|
        return undefined;
 | 
						|
    }
 | 
						|
 | 
						|
    if (val instanceof Date) {
 | 
						|
        return `**Date** ${val}`;
 | 
						|
    }
 | 
						|
 | 
						|
    if (val instanceof Error) {
 | 
						|
        const err = {
 | 
						|
            // These properties are implemented as magical getters and don't show up in for in
 | 
						|
            stack: val.stack,
 | 
						|
            message: val.message,
 | 
						|
            name: val.name,
 | 
						|
        };
 | 
						|
 | 
						|
        for (const i in val) {
 | 
						|
            if (!val.hasOwnProperty(i)) {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            // @ts-ignore
 | 
						|
            err[i] = val[i];
 | 
						|
        }
 | 
						|
 | 
						|
        return err;
 | 
						|
    }
 | 
						|
 | 
						|
    return val;
 | 
						|
}
 | 
						|
 | 
						|
function nodeFilter(key: any, val: any): any {
 | 
						|
    // domain objects are huge and have circular references
 | 
						|
    if (key === 'domain' && typeof val === 'object' && val._events) {
 | 
						|
        return '**domain ignored**';
 | 
						|
    }
 | 
						|
 | 
						|
    if (key === 'domainEmitter') {
 | 
						|
        return '**domainEmitter ignored**';
 | 
						|
    }
 | 
						|
 | 
						|
    if (val === global) {
 | 
						|
        return '**global**';
 | 
						|
    }
 | 
						|
 | 
						|
    return commonFilter(key, val);
 | 
						|
}
 | 
						|
 | 
						|
function browserFilter(key: any, val: any): any {
 | 
						|
    if (val === window) {
 | 
						|
        return '**window**';
 | 
						|
    }
 | 
						|
 | 
						|
    if (val === document) {
 | 
						|
        return '**document**';
 | 
						|
    }
 | 
						|
 | 
						|
    if (val instanceof HTMLElement) {
 | 
						|
        const { outerHTML } = val;
 | 
						|
 | 
						|
        if (typeof outerHTML !== 'undefined') {
 | 
						|
            return `**HTMLElement** ${outerHTML}`;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return commonFilter(key, val);
 | 
						|
}
 | 
						|
 | 
						|
export { abbreviate, nodeFilter, browserFilter };
 | 
						|
 | 
						|
export default function (obj: any): string {
 | 
						|
    return abbreviate(obj, {
 | 
						|
        filter: browserFilter,
 | 
						|
    });
 | 
						|
}
 |