| 'use strict'; |
| |
| // Load modules |
| |
| const Assert = require('assert'); |
| const Crypto = require('crypto'); |
| const Path = require('path'); |
| const Util = require('util'); |
| |
| const Escape = require('./escape'); |
| |
| |
| // Declare internals |
| |
| const internals = {}; |
| |
| |
| // Clone object or array |
| |
| exports.clone = function (obj, seen) { |
| |
| if (typeof obj !== 'object' || |
| obj === null) { |
| |
| return obj; |
| } |
| |
| seen = seen || new Map(); |
| |
| const lookup = seen.get(obj); |
| if (lookup) { |
| return lookup; |
| } |
| |
| let newObj; |
| let cloneDeep = false; |
| |
| if (!Array.isArray(obj)) { |
| if (Buffer.isBuffer(obj)) { |
| newObj = Buffer.from(obj); |
| } |
| else if (obj instanceof Date) { |
| newObj = new Date(obj.getTime()); |
| } |
| else if (obj instanceof RegExp) { |
| newObj = new RegExp(obj); |
| } |
| else { |
| const proto = Object.getPrototypeOf(obj); |
| if (proto && |
| proto.isImmutable) { |
| |
| newObj = obj; |
| } |
| else { |
| newObj = Object.create(proto); |
| cloneDeep = true; |
| } |
| } |
| } |
| else { |
| newObj = []; |
| cloneDeep = true; |
| } |
| |
| seen.set(obj, newObj); |
| |
| if (cloneDeep) { |
| const keys = Object.getOwnPropertyNames(obj); |
| for (let i = 0; i < keys.length; ++i) { |
| const key = keys[i]; |
| const descriptor = Object.getOwnPropertyDescriptor(obj, key); |
| if (descriptor && |
| (descriptor.get || |
| descriptor.set)) { |
| |
| Object.defineProperty(newObj, key, descriptor); |
| } |
| else { |
| newObj[key] = exports.clone(obj[key], seen); |
| } |
| } |
| } |
| |
| return newObj; |
| }; |
| |
| |
| // Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied |
| |
| /*eslint-disable */ |
| exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) { |
| /*eslint-enable */ |
| |
| exports.assert(target && typeof target === 'object', 'Invalid target value: must be an object'); |
| exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object'); |
| |
| if (!source) { |
| return target; |
| } |
| |
| if (Array.isArray(source)) { |
| exports.assert(Array.isArray(target), 'Cannot merge array onto an object'); |
| if (isMergeArrays === false) { // isMergeArrays defaults to true |
| target.length = 0; // Must not change target assignment |
| } |
| |
| for (let i = 0; i < source.length; ++i) { |
| target.push(exports.clone(source[i])); |
| } |
| |
| return target; |
| } |
| |
| const keys = Object.keys(source); |
| for (let i = 0; i < keys.length; ++i) { |
| const key = keys[i]; |
| if (key === '__proto__') { |
| continue; |
| } |
| |
| const value = source[key]; |
| if (value && |
| typeof value === 'object') { |
| |
| if (!target[key] || |
| typeof target[key] !== 'object' || |
| (Array.isArray(target[key]) !== Array.isArray(value)) || |
| value instanceof Date || |
| Buffer.isBuffer(value) || |
| value instanceof RegExp) { |
| |
| target[key] = exports.clone(value); |
| } |
| else { |
| exports.merge(target[key], value, isNullOverride, isMergeArrays); |
| } |
| } |
| else { |
| if (value !== null && |
| value !== undefined) { // Explicit to preserve empty strings |
| |
| target[key] = value; |
| } |
| else if (isNullOverride !== false) { // Defaults to true |
| target[key] = value; |
| } |
| } |
| } |
| |
| return target; |
| }; |
| |
| |
| // Apply options to a copy of the defaults |
| |
| exports.applyToDefaults = function (defaults, options, isNullOverride) { |
| |
| exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object'); |
| exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object'); |
| |
| if (!options) { // If no options, return null |
| return null; |
| } |
| |
| const copy = exports.clone(defaults); |
| |
| if (options === true) { // If options is set to true, use defaults |
| return copy; |
| } |
| |
| return exports.merge(copy, options, isNullOverride === true, false); |
| }; |
| |
| |
| // Clone an object except for the listed keys which are shallow copied |
| |
| exports.cloneWithShallow = function (source, keys) { |
| |
| if (!source || |
| typeof source !== 'object') { |
| |
| return source; |
| } |
| |
| const storage = internals.store(source, keys); // Move shallow copy items to storage |
| const copy = exports.clone(source); // Deep copy the rest |
| internals.restore(copy, source, storage); // Shallow copy the stored items and restore |
| return copy; |
| }; |
| |
| |
| internals.store = function (source, keys) { |
| |
| const storage = {}; |
| for (let i = 0; i < keys.length; ++i) { |
| const key = keys[i]; |
| const value = exports.reach(source, key); |
| if (value !== undefined) { |
| storage[key] = value; |
| internals.reachSet(source, key, undefined); |
| } |
| } |
| |
| return storage; |
| }; |
| |
| |
| internals.restore = function (copy, source, storage) { |
| |
| const keys = Object.keys(storage); |
| for (let i = 0; i < keys.length; ++i) { |
| const key = keys[i]; |
| internals.reachSet(copy, key, storage[key]); |
| internals.reachSet(source, key, storage[key]); |
| } |
| }; |
| |
| |
| internals.reachSet = function (obj, key, value) { |
| |
| const path = key.split('.'); |
| let ref = obj; |
| for (let i = 0; i < path.length; ++i) { |
| const segment = path[i]; |
| if (i + 1 === path.length) { |
| ref[segment] = value; |
| } |
| |
| ref = ref[segment]; |
| } |
| }; |
| |
| |
| // Apply options to defaults except for the listed keys which are shallow copied from option without merging |
| |
| exports.applyToDefaultsWithShallow = function (defaults, options, keys) { |
| |
| exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object'); |
| exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object'); |
| exports.assert(keys && Array.isArray(keys), 'Invalid keys'); |
| |
| if (!options) { // If no options, return null |
| return null; |
| } |
| |
| const copy = exports.cloneWithShallow(defaults, keys); |
| |
| if (options === true) { // If options is set to true, use defaults |
| return copy; |
| } |
| |
| const storage = internals.store(options, keys); // Move shallow copy items to storage |
| exports.merge(copy, options, false, false); // Deep copy the rest |
| internals.restore(copy, options, storage); // Shallow copy the stored items and restore |
| return copy; |
| }; |
| |
| |
| // Deep object or array comparison |
| |
| exports.deepEqual = function (obj, ref, options, seen) { |
| |
| options = options || { prototype: true }; |
| |
| const type = typeof obj; |
| |
| if (type !== typeof ref) { |
| return false; |
| } |
| |
| if (type !== 'object' || |
| obj === null || |
| ref === null) { |
| |
| if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql |
| return obj !== 0 || 1 / obj === 1 / ref; // -0 / +0 |
| } |
| |
| return obj !== obj && ref !== ref; // NaN |
| } |
| |
| seen = seen || []; |
| if (seen.indexOf(obj) !== -1) { |
| return true; // If previous comparison failed, it would have stopped execution |
| } |
| |
| seen.push(obj); |
| |
| if (Array.isArray(obj)) { |
| if (!Array.isArray(ref)) { |
| return false; |
| } |
| |
| if (!options.part && obj.length !== ref.length) { |
| return false; |
| } |
| |
| for (let i = 0; i < obj.length; ++i) { |
| if (options.part) { |
| let found = false; |
| for (let j = 0; j < ref.length; ++j) { |
| if (exports.deepEqual(obj[i], ref[j], options)) { |
| found = true; |
| break; |
| } |
| } |
| |
| return found; |
| } |
| |
| if (!exports.deepEqual(obj[i], ref[i], options)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| if (Buffer.isBuffer(obj)) { |
| if (!Buffer.isBuffer(ref)) { |
| return false; |
| } |
| |
| if (obj.length !== ref.length) { |
| return false; |
| } |
| |
| for (let i = 0; i < obj.length; ++i) { |
| if (obj[i] !== ref[i]) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| if (obj instanceof Date) { |
| return (ref instanceof Date && obj.getTime() === ref.getTime()); |
| } |
| |
| if (obj instanceof RegExp) { |
| return (ref instanceof RegExp && obj.toString() === ref.toString()); |
| } |
| |
| if (options.prototype) { |
| if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) { |
| return false; |
| } |
| } |
| |
| const keys = Object.getOwnPropertyNames(obj); |
| |
| if (!options.part && keys.length !== Object.getOwnPropertyNames(ref).length) { |
| return false; |
| } |
| |
| for (let i = 0; i < keys.length; ++i) { |
| const key = keys[i]; |
| const descriptor = Object.getOwnPropertyDescriptor(obj, key); |
| if (descriptor.get) { |
| if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), options, seen)) { |
| return false; |
| } |
| } |
| else if (!exports.deepEqual(obj[key], ref[key], options, seen)) { |
| return false; |
| } |
| } |
| |
| return true; |
| }; |
| |
| |
| // Remove duplicate items from array |
| |
| exports.unique = (array, key) => { |
| |
| let result; |
| if (key) { |
| result = []; |
| const index = new Set(); |
| array.forEach((item) => { |
| |
| const identifier = item[key]; |
| if (!index.has(identifier)) { |
| index.add(identifier); |
| result.push(item); |
| } |
| }); |
| } |
| else { |
| result = Array.from(new Set(array)); |
| } |
| |
| return result; |
| }; |
| |
| |
| // Convert array into object |
| |
| exports.mapToObject = function (array, key) { |
| |
| if (!array) { |
| return null; |
| } |
| |
| const obj = {}; |
| for (let i = 0; i < array.length; ++i) { |
| if (key) { |
| if (array[i][key]) { |
| obj[array[i][key]] = true; |
| } |
| } |
| else { |
| obj[array[i]] = true; |
| } |
| } |
| |
| return obj; |
| }; |
| |
| |
| // Find the common unique items in two arrays |
| |
| exports.intersect = function (array1, array2, justFirst) { |
| |
| if (!array1 || !array2) { |
| return []; |
| } |
| |
| const common = []; |
| const hash = (Array.isArray(array1) ? exports.mapToObject(array1) : array1); |
| const found = {}; |
| for (let i = 0; i < array2.length; ++i) { |
| if (hash[array2[i]] && !found[array2[i]]) { |
| if (justFirst) { |
| return array2[i]; |
| } |
| |
| common.push(array2[i]); |
| found[array2[i]] = true; |
| } |
| } |
| |
| return (justFirst ? null : common); |
| }; |
| |
| |
| // Test if the reference contains the values |
| |
| exports.contain = function (ref, values, options) { |
| |
| /* |
| string -> string(s) |
| array -> item(s) |
| object -> key(s) |
| object -> object (key:value) |
| */ |
| |
| let valuePairs = null; |
| if (typeof ref === 'object' && |
| typeof values === 'object' && |
| !Array.isArray(ref) && |
| !Array.isArray(values)) { |
| |
| valuePairs = values; |
| values = Object.keys(values); |
| } |
| else { |
| values = [].concat(values); |
| } |
| |
| options = options || {}; // deep, once, only, part |
| |
| exports.assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object'); |
| exports.assert(values.length, 'Values array cannot be empty'); |
| |
| let compare; |
| let compareFlags; |
| if (options.deep) { |
| compare = exports.deepEqual; |
| |
| const hasOnly = options.hasOwnProperty('only'); |
| const hasPart = options.hasOwnProperty('part'); |
| |
| compareFlags = { |
| prototype: hasOnly ? options.only : hasPart ? !options.part : false, |
| part: hasOnly ? !options.only : hasPart ? options.part : true |
| }; |
| } |
| else { |
| compare = (a, b) => a === b; |
| } |
| |
| let misses = false; |
| const matches = new Array(values.length); |
| for (let i = 0; i < matches.length; ++i) { |
| matches[i] = 0; |
| } |
| |
| if (typeof ref === 'string') { |
| let pattern = '('; |
| for (let i = 0; i < values.length; ++i) { |
| const value = values[i]; |
| exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value'); |
| pattern += (i ? '|' : '') + exports.escapeRegex(value); |
| } |
| |
| const regex = new RegExp(pattern + ')', 'g'); |
| const leftovers = ref.replace(regex, ($0, $1) => { |
| |
| const index = values.indexOf($1); |
| ++matches[index]; |
| return ''; // Remove from string |
| }); |
| |
| misses = !!leftovers; |
| } |
| else if (Array.isArray(ref)) { |
| for (let i = 0; i < ref.length; ++i) { |
| let matched = false; |
| for (let j = 0; j < values.length && matched === false; ++j) { |
| matched = compare(values[j], ref[i], compareFlags) && j; |
| } |
| |
| if (matched !== false) { |
| ++matches[matched]; |
| } |
| else { |
| misses = true; |
| } |
| } |
| } |
| else { |
| const keys = Object.getOwnPropertyNames(ref); |
| for (let i = 0; i < keys.length; ++i) { |
| const key = keys[i]; |
| const pos = values.indexOf(key); |
| if (pos !== -1) { |
| if (valuePairs && |
| !compare(valuePairs[key], ref[key], compareFlags)) { |
| |
| return false; |
| } |
| |
| ++matches[pos]; |
| } |
| else { |
| misses = true; |
| } |
| } |
| } |
| |
| let result = false; |
| for (let i = 0; i < matches.length; ++i) { |
| result = result || !!matches[i]; |
| if ((options.once && matches[i] > 1) || |
| (!options.part && !matches[i])) { |
| |
| return false; |
| } |
| } |
| |
| if (options.only && |
| misses) { |
| |
| return false; |
| } |
| |
| return result; |
| }; |
| |
| |
| // Flatten array |
| |
| exports.flatten = function (array, target) { |
| |
| const result = target || []; |
| |
| for (let i = 0; i < array.length; ++i) { |
| if (Array.isArray(array[i])) { |
| exports.flatten(array[i], result); |
| } |
| else { |
| result.push(array[i]); |
| } |
| } |
| |
| return result; |
| }; |
| |
| |
| // Convert an object key chain string ('a.b.c') to reference (object[a][b][c]) |
| |
| exports.reach = function (obj, chain, options) { |
| |
| if (chain === false || |
| chain === null || |
| typeof chain === 'undefined') { |
| |
| return obj; |
| } |
| |
| options = options || {}; |
| if (typeof options === 'string') { |
| options = { separator: options }; |
| } |
| |
| const path = chain.split(options.separator || '.'); |
| let ref = obj; |
| for (let i = 0; i < path.length; ++i) { |
| let key = path[i]; |
| if (key[0] === '-' && Array.isArray(ref)) { |
| key = key.slice(1, key.length); |
| key = ref.length - key; |
| } |
| |
| if (!ref || |
| !((typeof ref === 'object' || typeof ref === 'function') && key in ref) || |
| (typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties |
| |
| exports.assert(!options.strict || i + 1 === path.length, 'Missing segment', key, 'in reach path ', chain); |
| exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain); |
| ref = options.default; |
| break; |
| } |
| |
| ref = ref[key]; |
| } |
| |
| return ref; |
| }; |
| |
| |
| exports.reachTemplate = function (obj, template, options) { |
| |
| return template.replace(/{([^}]+)}/g, ($0, chain) => { |
| |
| const value = exports.reach(obj, chain, options); |
| return (value === undefined || value === null ? '' : value); |
| }); |
| }; |
| |
| |
| exports.formatStack = function (stack) { |
| |
| const trace = []; |
| for (let i = 0; i < stack.length; ++i) { |
| const item = stack[i]; |
| trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]); |
| } |
| |
| return trace; |
| }; |
| |
| |
| exports.formatTrace = function (trace) { |
| |
| const display = []; |
| |
| for (let i = 0; i < trace.length; ++i) { |
| const row = trace[i]; |
| display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')'); |
| } |
| |
| return display; |
| }; |
| |
| |
| exports.callStack = function (slice) { |
| |
| // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi |
| |
| const v8 = Error.prepareStackTrace; |
| Error.prepareStackTrace = function (_, stack) { |
| |
| return stack; |
| }; |
| |
| const capture = {}; |
| Error.captureStackTrace(capture, this); |
| const stack = capture.stack; |
| |
| Error.prepareStackTrace = v8; |
| |
| const trace = exports.formatStack(stack); |
| |
| return trace.slice(1 + slice); |
| }; |
| |
| |
| exports.displayStack = function (slice) { |
| |
| const trace = exports.callStack(slice === undefined ? 1 : slice + 1); |
| |
| return exports.formatTrace(trace); |
| }; |
| |
| |
| exports.abortThrow = false; |
| |
| |
| exports.abort = function (message, hideStack) { |
| |
| if (process.env.NODE_ENV === 'test' || exports.abortThrow === true) { |
| throw new Error(message || 'Unknown error'); |
| } |
| |
| let stack = ''; |
| if (!hideStack) { |
| stack = exports.displayStack(1).join('\n\t'); |
| } |
| console.log('ABORT: ' + message + '\n\t' + stack); |
| process.exit(1); |
| }; |
| |
| |
| exports.assert = function (condition, ...args) { |
| |
| if (condition) { |
| return; |
| } |
| |
| if (args.length === 1 && args[0] instanceof Error) { |
| throw args[0]; |
| } |
| |
| const msgs = args |
| .filter((arg) => arg !== '') |
| .map((arg) => { |
| |
| return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : exports.stringify(arg); |
| }); |
| |
| throw new Assert.AssertionError({ |
| message: msgs.join(' ') || 'Unknown error', |
| actual: false, |
| expected: true, |
| operator: '==', |
| stackStartFunction: exports.assert |
| }); |
| }; |
| |
| |
| exports.Bench = function () { |
| |
| this.ts = 0; |
| this.reset(); |
| }; |
| |
| |
| exports.Bench.prototype.reset = function () { |
| |
| this.ts = exports.Bench.now(); |
| }; |
| |
| |
| exports.Bench.prototype.elapsed = function () { |
| |
| return exports.Bench.now() - this.ts; |
| }; |
| |
| |
| exports.Bench.now = function () { |
| |
| const ts = process.hrtime(); |
| return (ts[0] * 1e3) + (ts[1] / 1e6); |
| }; |
| |
| |
| // Escape string for Regex construction |
| |
| exports.escapeRegex = function (string) { |
| |
| // Escape ^$.*+-?=!:|\/()[]{}, |
| return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&'); |
| }; |
| |
| |
| // Base64url (RFC 4648) encode |
| |
| exports.base64urlEncode = function (value, encoding) { |
| |
| exports.assert(typeof value === 'string' || Buffer.isBuffer(value), 'value must be string or buffer'); |
| const buf = (Buffer.isBuffer(value) ? value : Buffer.from(value, encoding || 'binary')); |
| return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); |
| }; |
| |
| |
| // Base64url (RFC 4648) decode |
| |
| exports.base64urlDecode = function (value, encoding) { |
| |
| if (typeof value !== 'string') { |
| |
| throw new Error('Value not a string'); |
| } |
| |
| if (!/^[\w\-]*$/.test(value)) { |
| |
| throw new Error('Invalid character'); |
| } |
| |
| const buf = Buffer.from(value, 'base64'); |
| return (encoding === 'buffer' ? buf : buf.toString(encoding || 'binary')); |
| }; |
| |
| |
| // Escape attribute value for use in HTTP header |
| |
| exports.escapeHeaderAttribute = function (attribute) { |
| |
| // Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, " |
| |
| exports.assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')'); |
| |
| return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash |
| }; |
| |
| |
| exports.escapeHtml = function (string) { |
| |
| return Escape.escapeHtml(string); |
| }; |
| |
| |
| exports.escapeJavaScript = function (string) { |
| |
| return Escape.escapeJavaScript(string); |
| }; |
| |
| |
| exports.escapeJson = function (string) { |
| |
| return Escape.escapeJson(string); |
| }; |
| |
| |
| exports.once = function (method) { |
| |
| if (method._hoekOnce) { |
| return method; |
| } |
| |
| let once = false; |
| const wrapped = function (...args) { |
| |
| if (!once) { |
| once = true; |
| method.apply(null, args); |
| } |
| }; |
| |
| wrapped._hoekOnce = true; |
| return wrapped; |
| }; |
| |
| |
| exports.isInteger = Number.isSafeInteger; |
| |
| |
| exports.ignore = function () { }; |
| |
| |
| exports.inherits = Util.inherits; |
| |
| |
| exports.format = Util.format; |
| |
| |
| exports.transform = function (source, transform, options) { |
| |
| exports.assert(source === null || source === undefined || typeof source === 'object' || Array.isArray(source), 'Invalid source object: must be null, undefined, an object, or an array'); |
| const separator = (typeof options === 'object' && options !== null) ? (options.separator || '.') : '.'; |
| |
| if (Array.isArray(source)) { |
| const results = []; |
| for (let i = 0; i < source.length; ++i) { |
| results.push(exports.transform(source[i], transform, options)); |
| } |
| return results; |
| } |
| |
| const result = {}; |
| const keys = Object.keys(transform); |
| |
| for (let i = 0; i < keys.length; ++i) { |
| const key = keys[i]; |
| const path = key.split(separator); |
| const sourcePath = transform[key]; |
| |
| exports.assert(typeof sourcePath === 'string', 'All mappings must be "." delineated strings'); |
| |
| let segment; |
| let res = result; |
| |
| while (path.length > 1) { |
| segment = path.shift(); |
| if (!res[segment]) { |
| res[segment] = {}; |
| } |
| res = res[segment]; |
| } |
| segment = path.shift(); |
| res[segment] = exports.reach(source, sourcePath, options); |
| } |
| |
| return result; |
| }; |
| |
| |
| exports.uniqueFilename = function (path, extension) { |
| |
| if (extension) { |
| extension = extension[0] !== '.' ? '.' + extension : extension; |
| } |
| else { |
| extension = ''; |
| } |
| |
| path = Path.resolve(path); |
| const name = [Date.now(), process.pid, Crypto.randomBytes(8).toString('hex')].join('-') + extension; |
| return Path.join(path, name); |
| }; |
| |
| |
| exports.stringify = function (...args) { |
| |
| try { |
| return JSON.stringify.apply(null, args); |
| } |
| catch (err) { |
| return '[Cannot display object: ' + err.message + ']'; |
| } |
| }; |
| |
| |
| exports.shallow = function (source) { |
| |
| const target = {}; |
| const keys = Object.keys(source); |
| for (let i = 0; i < keys.length; ++i) { |
| const key = keys[i]; |
| target[key] = source[key]; |
| } |
| |
| return target; |
| }; |
| |
| |
| exports.wait = function (timeout) { |
| |
| return new Promise((resolve) => setTimeout(resolve, timeout)); |
| }; |
| |
| |
| exports.block = function () { |
| |
| return new Promise(exports.ignore); |
| }; |