1 | 'use strict';
|
2 |
|
3 | const crypto = require('crypto');
|
4 |
|
5 | function deepClone(object, seen = new WeakMap()) {
|
6 |
|
7 | if (Object(object) !== object || object instanceof Function) {
|
8 | return object;
|
9 | }
|
10 |
|
11 |
|
12 | if (seen.has(object)) {
|
13 | return seen.get(object);
|
14 | }
|
15 |
|
16 | let result;
|
17 | if (object instanceof Buffer) {
|
18 | result = Buffer.from(object);
|
19 | } else if (object instanceof Date) {
|
20 | result = new Date(object);
|
21 | } else if (object instanceof RegExp) {
|
22 | result = new RegExp(object.source, object.flags);
|
23 | } else if (object.constructor) {
|
24 | result = new object.constructor();
|
25 | } else {
|
26 | result = Object.create(null);
|
27 | }
|
28 |
|
29 | seen.set(object, result);
|
30 |
|
31 | if (object instanceof Buffer) {
|
32 | return result;
|
33 | } else if (object instanceof Map) {
|
34 | object.forEach((value, key) => { return result.set(key, deepClone(value, seen)); });
|
35 | } else if (object instanceof Set) {
|
36 | object.forEach(value => { return result.add(deepClone(value, seen)); });
|
37 | } else {
|
38 | for (const key in object) {
|
39 | result[key] = deepClone(object[key], seen);
|
40 | }
|
41 | }
|
42 |
|
43 | return result;
|
44 | }
|
45 |
|
46 | function merge(objectA, objectB, createNew = false, seen) {
|
47 | if (createNew) {
|
48 | objectA = deepClone(objectA);
|
49 | }
|
50 |
|
51 | seen = new Set(seen);
|
52 |
|
53 | const keys = Object.getOwnPropertyNames(objectB);
|
54 | for (const key of keys) {
|
55 | if (typeof objectB[key] === 'object' && !seen.has(objectB[key])) {
|
56 | if (typeof objectA[key] === 'object') {
|
57 | objectA[key] = merge(objectA[key], objectB[key], createNew, seen);
|
58 | } else if (createNew) {
|
59 | objectA[key] = deepClone(objectB[key]);
|
60 | } else {
|
61 | objectA[key] = objectB[key];
|
62 | }
|
63 |
|
64 | seen.add(objectB[key]);
|
65 | } else {
|
66 | objectA[key] = objectB[key];
|
67 | }
|
68 | }
|
69 | return objectA;
|
70 | }
|
71 |
|
72 | const arrayPartRegExp = /([^]+)\[(\d+)\]$/;
|
73 |
|
74 | function resolve(object, path = '', delimiter = '.') {
|
75 | if (!object || !path) {
|
76 | return undefined;
|
77 | }
|
78 |
|
79 | const parts = path.trim().split(delimiter);
|
80 |
|
81 | for (const part of parts) {
|
82 | if (arrayPartRegExp.test(part)) {
|
83 | const match = part.match(arrayPartRegExp);
|
84 | const subpart = match[1];
|
85 | const index = Number(match[2]);
|
86 |
|
87 | object = object[subpart];
|
88 | if (!object) {
|
89 | return object;
|
90 | }
|
91 |
|
92 | object = object[index];
|
93 | if (!object) {
|
94 | return object;
|
95 | }
|
96 | } else {
|
97 | object = object[part];
|
98 | if (!object) {
|
99 | return object;
|
100 | }
|
101 | }
|
102 | }
|
103 | return object;
|
104 | }
|
105 |
|
106 | function resolves(object, path = '', delimiter = '.') {
|
107 | if (!object || !path) {
|
108 | return false;
|
109 | }
|
110 |
|
111 | const parts = path.trim().split(delimiter);
|
112 |
|
113 | for (const part of parts) {
|
114 | if (arrayPartRegExp.test(part)) {
|
115 | const match = part.match(arrayPartRegExp);
|
116 | const subpart = match[1];
|
117 | const index = Number(match[2]);
|
118 |
|
119 | object = object[subpart];
|
120 | if (!object) {
|
121 | return false;
|
122 | }
|
123 |
|
124 | object = object[index];
|
125 | if (!object) {
|
126 | return false;
|
127 | }
|
128 | } else {
|
129 | object = object[part];
|
130 | if (!object) {
|
131 | return false;
|
132 | }
|
133 | }
|
134 | }
|
135 | return true;
|
136 | }
|
137 |
|
138 | function formatBytes(bytes, decimals = 2) {
|
139 | if (bytes === 0) {
|
140 | return '0 Bytes';
|
141 | }
|
142 | const kilobyte = 1024;
|
143 | const places = decimals + 1;
|
144 | const sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
|
145 | const index = Math.floor(Math.log(bytes) / Math.log(kilobyte));
|
146 | return `${ parseFloat((bytes / Math.pow(kilobyte, index)).toFixed(places)) } ${ sizes[index] }`;
|
147 | }
|
148 |
|
149 | function camelize(string) {
|
150 | return string.replace(/^.*?:+/, '').
|
151 | replace(/[-:]/g, ' ').
|
152 | replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => {
|
153 | if (Number(match) === 0) {
|
154 | return '';
|
155 | }
|
156 | return index === 0 ? match.toLowerCase() : match.toUpperCase();
|
157 | });
|
158 | }
|
159 |
|
160 | function formatNumber(value) {
|
161 | value = Number(value);
|
162 | if (Number.isNaN(value) || Number === Infinity) {
|
163 | return value.toString();
|
164 | }
|
165 |
|
166 | const words = [ 'zero', 'one', 'two', 'three', 'four',
|
167 | 'five', 'six', 'seven', 'eight', 'nine', 'ten' ];
|
168 |
|
169 | if (Number.isInteger(value)) {
|
170 | if (words[value]) {
|
171 | return words[value];
|
172 | }
|
173 | } else {
|
174 | value = precisionRound(value);
|
175 | }
|
176 |
|
177 | return value.toString().
|
178 | split(/(?=(?:\d{3})+(?:\.|$))/g).
|
179 | join( ',' );
|
180 | }
|
181 |
|
182 | function sha1(input) {
|
183 | if (typeof input !== 'string') {
|
184 | input = JSON.stringify(input);
|
185 | }
|
186 | return crypto.createHash('sha1').update(input).
|
187 | digest('hex');
|
188 | }
|
189 |
|
190 | function sha256(input) {
|
191 | if (typeof input !== 'string') {
|
192 | input = JSON.stringify(input);
|
193 | }
|
194 | return crypto.createHash('sha256').update(input).
|
195 | digest('hex');
|
196 | }
|
197 |
|
198 | function encrypt(text, secret, algorithm = 'aes-256-cbc', ivLength = 16) {
|
199 | let encrypted = null;
|
200 | secret = secret.replace(/-/g, '').substring(0, 32);
|
201 | const iv = crypto.randomBytes(ivLength);
|
202 | const cipher = crypto.createCipheriv(algorithm, secret, iv);
|
203 | encrypted = cipher.update(text);
|
204 | encrypted = Buffer.concat([ encrypted, cipher.final() ]);
|
205 | encrypted = `${ iv.toString('hex') }:${ encrypted.toString('hex') }`;
|
206 |
|
207 | return encrypted;
|
208 | }
|
209 |
|
210 | function decrypt(text, secret, algorithm = 'aes-256-cbc') {
|
211 | let decrypted = null;
|
212 | secret = secret.replace(/-/g, '').substring(0, 32);
|
213 | const textParts = text.split(':');
|
214 | const iv = Buffer.from(textParts.shift(), 'hex');
|
215 | const encryptedText = Buffer.from(textParts.join(':'), 'hex');
|
216 | const decipher = crypto.createDecipheriv(algorithm, secret, iv);
|
217 | decrypted = decipher.update(encryptedText);
|
218 |
|
219 | decrypted = Buffer.concat([ decrypted, decipher.final() ]).toString();
|
220 |
|
221 | return decrypted;
|
222 | }
|
223 |
|
224 | const noop = () => { return undefined; };
|
225 |
|
226 | function callback(next, nextTick = false) {
|
227 | if (typeof next === 'function') {
|
228 | if (nextTick) {
|
229 | return function(...args) {
|
230 | process.nextTick(() => {
|
231 | next(...args);
|
232 | });
|
233 | };
|
234 | }
|
235 | return next;
|
236 | }
|
237 | return noop;
|
238 | }
|
239 |
|
240 | function once(func) {
|
241 | if (typeof func !== 'function') {
|
242 | return noop;
|
243 | }
|
244 |
|
245 | let invoked = false;
|
246 |
|
247 | return (...args) => {
|
248 | if (!invoked) {
|
249 | invoked = true;
|
250 | return func(...args);
|
251 | }
|
252 | return false;
|
253 | };
|
254 | }
|
255 |
|
256 | function timestamp(date) {
|
257 | if (date) {
|
258 | return new Date(date).getTime();
|
259 | }
|
260 | return Date.now();
|
261 | }
|
262 |
|
263 | function precisionRound(number, precision = 2) {
|
264 | const factor = Math.pow(10, precision);
|
265 | return Math.round(number * factor) / factor;
|
266 | }
|
267 |
|
268 | function functionType(func) {
|
269 | const flags = {
|
270 | function: func instanceof Function,
|
271 | name: undefined,
|
272 | native: false,
|
273 | bound: false,
|
274 | plain: false,
|
275 | arrow: false
|
276 | };
|
277 |
|
278 | if (flags.function) {
|
279 | flags.name = func.name || '(anonymous)';
|
280 | flags.native = func.toString().trim().
|
281 | endsWith('() { [native code] }');
|
282 | flags.bound = flags.native && flags.name.startsWith('bound ');
|
283 | flags.plain = !flags.native && func.hasOwnProperty('prototype');
|
284 | flags.arrow = !(flags.native || flags.plain);
|
285 | }
|
286 |
|
287 | return flags;
|
288 | }
|
289 |
|
290 | function set(object, propertyPath, value) {
|
291 | const parts = propertyPath.trim().split(/\./);
|
292 | const key = parts.pop();
|
293 |
|
294 | for (const part of parts) {
|
295 | if (object[part] === undefined) {
|
296 | object[part] = {};
|
297 | }
|
298 |
|
299 | object = object[part];
|
300 |
|
301 | if (!object) {
|
302 | return false;
|
303 | }
|
304 | }
|
305 |
|
306 | object[key] = value;
|
307 |
|
308 | return true;
|
309 | }
|
310 |
|
311 | function remove(object, propertyPath, removeEmptyContainer = false) {
|
312 | const parts = propertyPath.trim().split(/\./);
|
313 | const key = parts.pop();
|
314 |
|
315 | let parent;
|
316 | let parentKey;
|
317 |
|
318 | for (const part of parts) {
|
319 | parent = object;
|
320 | parentKey = part;
|
321 |
|
322 | object = object[part];
|
323 |
|
324 | if (!object) {
|
325 | return false;
|
326 | }
|
327 | }
|
328 |
|
329 | delete object[key];
|
330 |
|
331 | if (removeEmptyContainer && size(object) === 0) {
|
332 | delete parent[parentKey];
|
333 | }
|
334 |
|
335 | return true;
|
336 | }
|
337 |
|
338 | function flatten(object, prefix = '', container = {}) {
|
339 | if (typeof object !== 'object') {
|
340 | container[prefix] = object;
|
341 | return container;
|
342 | }
|
343 |
|
344 | if (prefix.length) {
|
345 | prefix += '.';
|
346 | }
|
347 |
|
348 | for (const key in object) {
|
349 | const pathKey = prefix + key;
|
350 |
|
351 | if (Array.isArray(object[key])) {
|
352 | container[`${ pathKey }$type`] = 'Array';
|
353 | const array = object[key];
|
354 | for (let i = 0; i < array.length; i++) {
|
355 | flatten(array[i], `${ pathKey }.${ i }`, container);
|
356 | }
|
357 | } else if (typeof object[key] === 'object' && object[key] !== null) {
|
358 | container[`${ pathKey }$type`] = 'Object';
|
359 | flatten(object[key], pathKey, container);
|
360 | } else {
|
361 | container[ pathKey ] = object[key];
|
362 | }
|
363 | }
|
364 | return container;
|
365 | }
|
366 |
|
367 | function expand(container, object = {}) {
|
368 | for (const key in container) {
|
369 | const parts = key.split(/\./);
|
370 | const property = parts.pop();
|
371 |
|
372 | let chunk = object;
|
373 | for (const part of parts) {
|
374 | if (!chunk[part]) {
|
375 | chunk[part] = {};
|
376 | }
|
377 |
|
378 | chunk = chunk[part];
|
379 | }
|
380 |
|
381 | if (property.endsWith('$type')) {
|
382 | const name = property.replace(/\$type$/, '');
|
383 | if (container[key] === 'Object') {
|
384 | chunk[name] = {};
|
385 | } else if (container[key] === 'Array') {
|
386 | chunk[name] = [];
|
387 | } else {
|
388 |
|
389 | }
|
390 | } else {
|
391 | chunk[property] = container[key];
|
392 | }
|
393 | }
|
394 | return object;
|
395 | }
|
396 |
|
397 | function setTypes(object) {
|
398 | for (const key in object) {
|
399 | const value = object[key];
|
400 |
|
401 | if (parseInt(value, 10).toString() === value) {
|
402 | object[key] = parseInt(value, 10);
|
403 | } else if (parseFloat(value, 10).toString() === value) {
|
404 | object[key] = parseFloat(value, 10);
|
405 | } else if (value === 'true') {
|
406 | object[key] = true;
|
407 | } else if (value === 'false') {
|
408 | object[key] = false;
|
409 | }
|
410 | }
|
411 | return object;
|
412 | }
|
413 |
|
414 | function size(object) {
|
415 | if (typeof object === 'object') {
|
416 | return Object.getOwnPropertyNames(object).length;
|
417 | }
|
418 | return 0;
|
419 | }
|
420 |
|
421 | function project(object, projection) {
|
422 | let sum = 0;
|
423 | for (const key in projection) {
|
424 | sum += projection[key] ? 1 : 0;
|
425 | }
|
426 |
|
427 | if (sum === 0) {
|
428 | const result = deepClone(object);
|
429 | for (const key in projection) {
|
430 | remove(result, key, true);
|
431 | }
|
432 | return result;
|
433 | }
|
434 |
|
435 | const result = {};
|
436 |
|
437 | for (const key in projection) {
|
438 | if (projection[key]) {
|
439 | if (typeof projection[key] === 'string') {
|
440 | set(result, projection[key], resolve(object, key));
|
441 | } else if (typeof projection[key] === 'function') {
|
442 | set(result, key, projection[key](resolve(object, key)));
|
443 | } else {
|
444 | set(result, key, resolve(object, key)); // simple projection
|
445 | }
|
446 | }
|
447 | }
|
448 | return result;
|
449 | }
|
450 |
|
451 | function filter(object, check, include = true, path) {
|
452 | if (typeof object !== 'object') {
|
453 | return object;
|
454 | }
|
455 |
|
456 | function test(fullpath) {
|
457 | if (Array.isArray(check)) {
|
458 | if (check.includes(fullpath)) {
|
459 | return include;
|
460 | }
|
461 | return !include;
|
462 | } else if (check instanceof Map || check instanceof Set ||
|
463 | check instanceof WeakMap || check instanceof WeakSet) {
|
464 | if (check.has(fullpath)) {
|
465 | return include;
|
466 | }
|
467 | return !include;
|
468 | } else if (typeof check === 'function') {
|
469 | if (check(fullpath)) {
|
470 | return include;
|
471 | } return !include;
|
472 | } else if (check instanceof RegExp) {
|
473 | if (check.test(fullpath)) {
|
474 | return include;
|
475 | }
|
476 | return !include;
|
477 | } else if (typeof check === 'object') {
|
478 | if (resolve(check, fullpath)) {
|
479 | return include;
|
480 | }
|
481 | return !include;
|
482 | }
|
483 | return !include;
|
484 | }
|
485 |
|
486 | include = Boolean(include);
|
487 |
|
488 | const clone = Array.isArray(object) ? [ ] : { };
|
489 | for (const prop in object) {
|
490 | const fullpath = path ? `${ path }.${ prop }` : prop;
|
491 |
|
492 | let value = object[prop];
|
493 |
|
494 | if (test(fullpath)) {
|
495 | if (typeof value === 'object') {
|
496 | value = filter(value, check, include, fullpath);
|
497 | if (Array.isArray(value) && value.length !== 0 ||
|
498 | Object.keys(value).length !== 0) {
|
499 | clone[prop] = value;
|
500 | }
|
501 | } else {
|
502 | clone[prop] = value;
|
503 | }
|
504 | }
|
505 | }
|
506 |
|
507 | return clone;
|
508 | }
|
509 |
|
510 | function deepEqual(actual, expected) {
|
511 | if (actual === null || actual === undefined ||
|
512 | expected === null || expected === undefined) {
|
513 | return actual === expected;
|
514 | }
|
515 |
|
516 | if (actual.constructor !== expected.constructor) {
|
517 | return false;
|
518 | }
|
519 |
|
520 | if (actual instanceof Function) {
|
521 | return actual === expected;
|
522 | }
|
523 |
|
524 | if (actual instanceof RegExp) {
|
525 | return actual === expected;
|
526 | }
|
527 |
|
528 | if (actual instanceof Set) {
|
529 | if (actual.size !== expected.size) {
|
530 | return false;
|
531 | }
|
532 |
|
533 | for (const entry of actual) {
|
534 | if (!expected.has(entry)) {
|
535 | return false;
|
536 | }
|
537 | }
|
538 |
|
539 | return true;
|
540 | }
|
541 |
|
542 | if (actual instanceof Map) {
|
543 | if (actual.size !== expected.size) {
|
544 | return false;
|
545 | }
|
546 |
|
547 | for (const [ key, value ] of actual) {
|
548 | if (!expected.has(key) &&
|
549 | deepEqual(value, expected.get(key))) {
|
550 | return false;
|
551 | }
|
552 | }
|
553 |
|
554 | return true;
|
555 | }
|
556 |
|
557 | if (actual === expected || actual.valueOf() === expected.valueOf()) {
|
558 | return true;
|
559 | }
|
560 |
|
561 | if (Array.isArray(actual) && actual.length !== expected.length) {
|
562 | return false;
|
563 | }
|
564 |
|
565 | if (actual instanceof Date) {
|
566 | return false;
|
567 | }
|
568 |
|
569 | if (!(actual instanceof Object)) {
|
570 | return false;
|
571 | }
|
572 |
|
573 | if (!(expected instanceof Object)) {
|
574 | return false;
|
575 | }
|
576 |
|
577 | const properties = Object.keys(actual);
|
578 | return Object.keys(expected).every((i) => {
|
579 | return properties.indexOf(i) !== -1;
|
580 | }) && properties.every((i) => {
|
581 | return deepEqual(actual[i], expected[i]);
|
582 | });
|
583 | }
|
584 |
|
585 | function debounce(func, wait) {
|
586 | let timeout;
|
587 |
|
588 | return (...args) => {
|
589 | if (timeout) {
|
590 | clearTimeout(timeout);
|
591 | }
|
592 |
|
593 | timeout = setTimeout(() => {
|
594 | func(...args);
|
595 | }, wait || 500);
|
596 | };
|
597 | }
|
598 |
|
599 | function milliseconds(value) {
|
600 | if (typeof value === 'number') {
|
601 | return value;
|
602 | } else if (typeof value === 'string') {
|
603 | let millis = 0;
|
604 | value.replace(/(\d+\.?\d*)\s*([mshd]+)/g, (match, time, unit) => {
|
605 | time = Number(time) || 0;
|
606 | if (unit === 'ms') {
|
607 | time *= 1;
|
608 | } else if (unit === 's') {
|
609 | time *= 1000;
|
610 | } else if (unit === 'm') {
|
611 | time *= 1000 * 60;
|
612 | } else if (unit === 'h') {
|
613 | time *= 1000 * 60 * 60;
|
614 | } else if (unit === 'd') {
|
615 | time *= 1000 * 60 * 60 * 24;
|
616 | }
|
617 |
|
618 | millis += time;
|
619 | });
|
620 |
|
621 | return Math.ceil(millis);
|
622 | }
|
623 | return 0;
|
624 | }
|
625 |
|
626 | function duration(diff, {
|
627 | units = 'd h m', separator = ', ', empty = 'less than a minute'
|
628 | } = {}) {
|
629 | const days = Math.floor(diff / 86400000);
|
630 | diff = diff % 86400000;
|
631 | const hours = Math.floor(diff / 3600000);
|
632 | diff = diff % 3600000;
|
633 | const minutes = Math.floor(diff / 60000);
|
634 | diff = diff % 60000;
|
635 | const seconds = Math.floor(diff / 1000);
|
636 |
|
637 | const parts = [];
|
638 | if (days > 0 && units.includes('d')) {
|
639 | if (days === 1) {
|
640 | parts.push(`${ days } day`);
|
641 | } else {
|
642 | parts.push(`${ days } day`);
|
643 | }
|
644 | }
|
645 | if (hours > 0 && units.includes('h')) {
|
646 | if (hours === 1) {
|
647 | parts.push(`${ hours } hour`);
|
648 | } else {
|
649 | parts.push(`${ hours } hours`);
|
650 | }
|
651 | }
|
652 | if (minutes > 0 && units.includes('m')) {
|
653 | if (minutes === 1) {
|
654 | parts.push(`${ minutes } minute`);
|
655 | } else {
|
656 | parts.push(`${ minutes } minutes`);
|
657 | }
|
658 | }
|
659 |
|
660 | if (seconds > 0 && units.includes('s')) {
|
661 | if (seconds === 1) {
|
662 | parts.push(`${ seconds } second`);
|
663 | } else {
|
664 | parts.push(`${ seconds } seconds`);
|
665 | }
|
666 | }
|
667 |
|
668 | if (parts.length) {
|
669 | return parts.join(separator);
|
670 | }
|
671 | return empty;
|
672 | }
|
673 |
|
674 | function ordinal (value) {
|
675 | value = Number(value);
|
676 |
|
677 | const tens = value % 10;
|
678 | const hundreds = value % 100;
|
679 |
|
680 | if (tens === 1 && hundreds !== 11) {
|
681 | return `${ value }st`;
|
682 | } else if (tens === 2 && hundreds !== 12) {
|
683 | return `${ value }nd`;
|
684 | } else if (tens === 3 && hundreds !== 13) {
|
685 | return `${ value }rd`;
|
686 | }
|
687 | return `${ value }th`;
|
688 | }
|
689 |
|
690 | module.exports = {
|
691 | callback,
|
692 | camelize,
|
693 | debounce,
|
694 | decrypt,
|
695 | deepClone,
|
696 | deepEqual,
|
697 | duration,
|
698 | encrypt,
|
699 | expand,
|
700 | filter,
|
701 | flatten,
|
702 | formatBytes,
|
703 | formatNumber,
|
704 | functionType,
|
705 | merge,
|
706 | milliseconds,
|
707 | noop,
|
708 | nop: noop,
|
709 | once,
|
710 | ordinal,
|
711 | precisionRound,
|
712 | project,
|
713 | remove,
|
714 | resolve,
|
715 | resolves,
|
716 | set,
|
717 | setTypes,
|
718 | sha1,
|
719 | sha256,
|
720 | size,
|
721 | timestamp
|
722 | };
|