| /* | 
| Copyright (c) 2014, Yahoo! Inc. All rights reserved. | 
| Copyrights licensed under the New BSD License. | 
| See the accompanying LICENSE file for terms. | 
| */ | 
|   | 
| 'use strict'; | 
|   | 
| var randomBytes = require('randombytes'); | 
|   | 
| // Generate an internal UID to make the regexp pattern harder to guess. | 
| var UID_LENGTH          = 16; | 
| var UID                 = generateUID(); | 
| var PLACE_HOLDER_REGEXP = new RegExp('(\\\\)?"@__(F|R|D|M|S|U|I|B)-' + UID + '-(\\d+)__@"', 'g'); | 
|   | 
| var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g; | 
| var IS_PURE_FUNCTION = /function.*?\(/; | 
| var IS_ARROW_FUNCTION = /.*?=>.*?/; | 
| var UNSAFE_CHARS_REGEXP   = /[<>\/\u2028\u2029]/g; | 
|   | 
| var RESERVED_SYMBOLS = ['*', 'async']; | 
|   | 
| // Mapping of unsafe HTML and invalid JavaScript line terminator chars to their | 
| // Unicode char counterparts which are safe to use in JavaScript strings. | 
| var ESCAPED_CHARS = { | 
|     '<'     : '\\u003C', | 
|     '>'     : '\\u003E', | 
|     '/'     : '\\u002F', | 
|     '\u2028': '\\u2028', | 
|     '\u2029': '\\u2029' | 
| }; | 
|   | 
| function escapeUnsafeChars(unsafeChar) { | 
|     return ESCAPED_CHARS[unsafeChar]; | 
| } | 
|   | 
| function generateUID() { | 
|     var bytes = randomBytes(UID_LENGTH); | 
|     var result = ''; | 
|     for(var i=0; i<UID_LENGTH; ++i) { | 
|         result += bytes[i].toString(16); | 
|     } | 
|     return result; | 
| } | 
|   | 
| function deleteFunctions(obj){ | 
|     var functionKeys = []; | 
|     for (var key in obj) { | 
|         if (typeof obj[key] === "function") { | 
|             functionKeys.push(key); | 
|         } | 
|     } | 
|     for (var i = 0; i < functionKeys.length; i++) { | 
|         delete obj[functionKeys[i]]; | 
|     } | 
| } | 
|   | 
| module.exports = function serialize(obj, options) { | 
|     options || (options = {}); | 
|   | 
|     // Backwards-compatibility for `space` as the second argument. | 
|     if (typeof options === 'number' || typeof options === 'string') { | 
|         options = {space: options}; | 
|     } | 
|   | 
|     var functions = []; | 
|     var regexps   = []; | 
|     var dates     = []; | 
|     var maps      = []; | 
|     var sets      = []; | 
|     var undefs    = []; | 
|     var infinities= []; | 
|     var bigInts = []; | 
|   | 
|     // Returns placeholders for functions and regexps (identified by index) | 
|     // which are later replaced by their string representation. | 
|     function replacer(key, value) { | 
|   | 
|         // For nested function | 
|         if(options.ignoreFunction){ | 
|             deleteFunctions(value); | 
|         } | 
|   | 
|         if (!value && value !== undefined) { | 
|             return value; | 
|         } | 
|   | 
|         // If the value is an object w/ a toJSON method, toJSON is called before | 
|         // the replacer runs, so we use this[key] to get the non-toJSONed value. | 
|         var origValue = this[key]; | 
|         var type = typeof origValue; | 
|   | 
|         if (type === 'object') { | 
|             if(origValue instanceof RegExp) { | 
|                 return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@'; | 
|             } | 
|   | 
|             if(origValue instanceof Date) { | 
|                 return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@'; | 
|             } | 
|   | 
|             if(origValue instanceof Map) { | 
|                 return '@__M-' + UID + '-' + (maps.push(origValue) - 1) + '__@'; | 
|             } | 
|   | 
|             if(origValue instanceof Set) { | 
|                 return '@__S-' + UID + '-' + (sets.push(origValue) - 1) + '__@'; | 
|             } | 
|         } | 
|   | 
|         if (type === 'function') { | 
|             return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@'; | 
|         } | 
|   | 
|         if (type === 'undefined') { | 
|             return '@__U-' + UID + '-' + (undefs.push(origValue) - 1) + '__@'; | 
|         } | 
|   | 
|         if (type === 'number' && !isNaN(origValue) && !isFinite(origValue)) { | 
|             return '@__I-' + UID + '-' + (infinities.push(origValue) - 1) + '__@'; | 
|         } | 
|   | 
|         if (type === 'bigint') { | 
|             return '@__B-' + UID + '-' + (bigInts.push(origValue) - 1) + '__@'; | 
|         } | 
|   | 
|         return value; | 
|     } | 
|   | 
|     function serializeFunc(fn) { | 
|       var serializedFn = fn.toString(); | 
|       if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) { | 
|           throw new TypeError('Serializing native function: ' + fn.name); | 
|       } | 
|   | 
|       // pure functions, example: {key: function() {}} | 
|       if(IS_PURE_FUNCTION.test(serializedFn)) { | 
|           return serializedFn; | 
|       } | 
|   | 
|       // arrow functions, example: arg1 => arg1+5 | 
|       if(IS_ARROW_FUNCTION.test(serializedFn)) { | 
|           return serializedFn; | 
|       } | 
|   | 
|       var argsStartsAt = serializedFn.indexOf('('); | 
|       var def = serializedFn.substr(0, argsStartsAt) | 
|         .trim() | 
|         .split(' ') | 
|         .filter(function(val) { return val.length > 0 }); | 
|   | 
|       var nonReservedSymbols = def.filter(function(val) { | 
|         return RESERVED_SYMBOLS.indexOf(val) === -1 | 
|       }); | 
|   | 
|       // enhanced literal objects, example: {key() {}} | 
|       if(nonReservedSymbols.length > 0) { | 
|           return (def.indexOf('async') > -1 ? 'async ' : '') + 'function' | 
|             + (def.join('').indexOf('*') > -1 ? '*' : '') | 
|             + serializedFn.substr(argsStartsAt); | 
|       } | 
|   | 
|       // arrow functions | 
|       return serializedFn; | 
|     } | 
|   | 
|     // Check if the parameter is function | 
|     if (options.ignoreFunction && typeof obj === "function") { | 
|         obj = undefined; | 
|     } | 
|     // Protects against `JSON.stringify()` returning `undefined`, by serializing | 
|     // to the literal string: "undefined". | 
|     if (obj === undefined) { | 
|         return String(obj); | 
|     } | 
|   | 
|     var str; | 
|   | 
|     // Creates a JSON string representation of the value. | 
|     // NOTE: Node 0.12 goes into slow mode with extra JSON.stringify() args. | 
|     if (options.isJSON && !options.space) { | 
|         str = JSON.stringify(obj); | 
|     } else { | 
|         str = JSON.stringify(obj, options.isJSON ? null : replacer, options.space); | 
|     } | 
|   | 
|     // Protects against `JSON.stringify()` returning `undefined`, by serializing | 
|     // to the literal string: "undefined". | 
|     if (typeof str !== 'string') { | 
|         return String(str); | 
|     } | 
|   | 
|     // Replace unsafe HTML and invalid JavaScript line terminator chars with | 
|     // their safe Unicode char counterpart. This _must_ happen before the | 
|     // regexps and functions are serialized and added back to the string. | 
|     if (options.unsafe !== true) { | 
|         str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars); | 
|     } | 
|   | 
|     if (functions.length === 0 && regexps.length === 0 && dates.length === 0 && maps.length === 0 && sets.length === 0 && undefs.length === 0 && infinities.length === 0 && bigInts.length === 0) { | 
|         return str; | 
|     } | 
|   | 
|     // Replaces all occurrences of function, regexp, date, map and set placeholders in the | 
|     // JSON string with their string representations. If the original value can | 
|     // not be found, then `undefined` is used. | 
|     return str.replace(PLACE_HOLDER_REGEXP, function (match, backSlash, type, valueIndex) { | 
|         // The placeholder may not be preceded by a backslash. This is to prevent | 
|         // replacing things like `"a\"@__R-<UID>-0__@"` and thus outputting | 
|         // invalid JS. | 
|         if (backSlash) { | 
|             return match; | 
|         } | 
|   | 
|         if (type === 'D') { | 
|             return "new Date(\"" + dates[valueIndex].toISOString() + "\")"; | 
|         } | 
|   | 
|         if (type === 'R') { | 
|             return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")"; | 
|         } | 
|   | 
|         if (type === 'M') { | 
|             return "new Map(" + serialize(Array.from(maps[valueIndex].entries()), options) + ")"; | 
|         } | 
|   | 
|         if (type === 'S') { | 
|             return "new Set(" + serialize(Array.from(sets[valueIndex].values()), options) + ")"; | 
|         } | 
|   | 
|         if (type === 'U') { | 
|             return 'undefined' | 
|         } | 
|   | 
|         if (type === 'I') { | 
|             return infinities[valueIndex]; | 
|         } | 
|   | 
|         if (type === 'B') { | 
|             return "BigInt(\"" + bigInts[valueIndex] + "\")"; | 
|         } | 
|   | 
|         var fn = functions[valueIndex]; | 
|   | 
|         return serializeFunc(fn); | 
|     }); | 
| } |