@reldens/utils 0.46.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/env-var.js +54 -6
- package/lib/events-manager.js +49 -2
- package/lib/logger.js +12 -9
- package/lib/schema-validator.js +63 -0
- package/lib/shortcuts.js +220 -17
- package/package.json +1 -1
package/lib/env-var.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const sc = require('./shortcuts');
|
|
8
|
+
|
|
7
9
|
class EnvVar
|
|
8
10
|
{
|
|
9
11
|
|
|
10
12
|
string(obj, key, defaultValue)
|
|
11
13
|
{
|
|
12
|
-
|
|
14
|
+
let val = obj[key];
|
|
13
15
|
if('string' === typeof val){
|
|
14
16
|
return val;
|
|
15
17
|
}
|
|
@@ -18,7 +20,7 @@ class EnvVar
|
|
|
18
20
|
|
|
19
21
|
nonEmptyString(obj, key, defaultValue)
|
|
20
22
|
{
|
|
21
|
-
|
|
23
|
+
let val = obj[key];
|
|
22
24
|
if('string' === typeof val && '' !== val.trim()){
|
|
23
25
|
return val;
|
|
24
26
|
}
|
|
@@ -27,16 +29,17 @@ class EnvVar
|
|
|
27
29
|
|
|
28
30
|
number(obj, key, defaultValue)
|
|
29
31
|
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
let val = obj[key];
|
|
33
|
+
let parsed = sc.parseNumber(val);
|
|
34
|
+
if(parsed){
|
|
35
|
+
return parsed;
|
|
33
36
|
}
|
|
34
37
|
return defaultValue;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
boolean(obj, key, defaultValue)
|
|
38
41
|
{
|
|
39
|
-
|
|
42
|
+
let val = obj[key];
|
|
40
43
|
if('true' === val || '1' === val){
|
|
41
44
|
return true;
|
|
42
45
|
}
|
|
@@ -46,6 +49,51 @@ class EnvVar
|
|
|
46
49
|
return defaultValue;
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
array(obj, key, defaultValue, separator = ',')
|
|
53
|
+
{
|
|
54
|
+
let val = obj[key];
|
|
55
|
+
let parsed = sc.splitToArray(val, separator);
|
|
56
|
+
if(parsed){
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
return defaultValue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
url(obj, key, defaultValue)
|
|
63
|
+
{
|
|
64
|
+
let val = obj[key];
|
|
65
|
+
if(!sc.isString(val)){
|
|
66
|
+
return defaultValue;
|
|
67
|
+
}
|
|
68
|
+
if(sc.isValidUrl(val)){
|
|
69
|
+
return val;
|
|
70
|
+
}
|
|
71
|
+
return defaultValue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
json(obj, key, defaultValue)
|
|
75
|
+
{
|
|
76
|
+
let val = obj[key];
|
|
77
|
+
if(!sc.isString(val)){
|
|
78
|
+
return defaultValue;
|
|
79
|
+
}
|
|
80
|
+
return sc.parseJson(val, defaultValue);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
integer(obj, key, defaultValue, min, max)
|
|
84
|
+
{
|
|
85
|
+
let val = this.number(obj, key, defaultValue);
|
|
86
|
+
if(val !== defaultValue && sc.isValidInteger(val, min, max)){
|
|
87
|
+
return val;
|
|
88
|
+
}
|
|
89
|
+
return defaultValue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
port(obj, key, defaultValue)
|
|
93
|
+
{
|
|
94
|
+
return this.integer(obj, key, defaultValue, 1, 65535);
|
|
95
|
+
}
|
|
96
|
+
|
|
49
97
|
}
|
|
50
98
|
|
|
51
99
|
module.exports = new EnvVar();
|
package/lib/events-manager.js
CHANGED
|
@@ -28,8 +28,21 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
|
|
|
28
28
|
this.debug = false;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
validateEventKey(eventKey)
|
|
32
|
+
{
|
|
33
|
+
return !sc.hasDangerousKeys(null, eventKey);
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
onWithKey(eventName, callback, uniqueRemoveKey, masterKey)
|
|
32
37
|
{
|
|
38
|
+
if(!this.validateEventKey(eventName) || !this.validateEventKey(uniqueRemoveKey)){
|
|
39
|
+
Logger.critical('Invalid event key detected: '+eventName+' or '+uniqueRemoveKey);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if(masterKey && !this.validateEventKey(masterKey)){
|
|
43
|
+
Logger.critical('Invalid master key detected: '+masterKey);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
33
46
|
if(
|
|
34
47
|
sc.hasOwn(this.eventsByRemoveKeys, uniqueRemoveKey)
|
|
35
48
|
|| (
|
|
@@ -62,6 +75,14 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
|
|
|
62
75
|
*/
|
|
63
76
|
offWithKey(uniqueRemoveKey, masterKey)
|
|
64
77
|
{
|
|
78
|
+
if(!this.validateEventKey(uniqueRemoveKey)){
|
|
79
|
+
Logger.critical('Invalid remove key detected: '+uniqueRemoveKey);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if(masterKey && !this.validateEventKey(masterKey)){
|
|
83
|
+
Logger.critical('Invalid master key detected: '+masterKey);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
65
86
|
if(masterKey && !sc.hasOwn(this.eventsByRemoveKeys, masterKey)){
|
|
66
87
|
Logger.debug('Event not found by masterKey "'+masterKey+'".');
|
|
67
88
|
return false;
|
|
@@ -74,8 +95,16 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
|
|
|
74
95
|
? this.eventsByRemoveKeys[masterKey][uniqueRemoveKey]
|
|
75
96
|
: this.eventsByRemoveKeys[uniqueRemoveKey];
|
|
76
97
|
|
|
98
|
+
if(!this.validateEventKey(eventToRemove.eventName)){
|
|
99
|
+
Logger.critical('Invalid event name in stored event: '+eventToRemove.eventName);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
77
102
|
let dataArr = this.listeners(eventToRemove.eventName);
|
|
78
103
|
let currentListenerIndex = dataArr.indexOf(eventToRemove.callback);
|
|
104
|
+
if(-1 === currentListenerIndex){
|
|
105
|
+
Logger.debug('Event listener not found in _events array.');
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
79
108
|
this._events[eventToRemove.eventName].splice(currentListenerIndex, 1);
|
|
80
109
|
if(0 === this._events[eventToRemove.eventName].length){
|
|
81
110
|
delete this._events[eventToRemove.eventName];
|
|
@@ -95,6 +124,10 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
|
|
|
95
124
|
*/
|
|
96
125
|
offByMasterKey(masterKey)
|
|
97
126
|
{
|
|
127
|
+
if(!this.validateEventKey(masterKey)){
|
|
128
|
+
Logger.critical('Invalid master key detected: '+masterKey);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
98
131
|
if(!sc.hasOwn(this.eventsByRemoveKeys, masterKey)){
|
|
99
132
|
Logger.debug('Events not found by masterKey "'+masterKey+'".');
|
|
100
133
|
return false;
|
|
@@ -104,8 +137,14 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
|
|
|
104
137
|
Logger.debug('Removing events by masterKey: '+masterKey, Object.keys(this.eventsByRemoveKeys[masterKey]));
|
|
105
138
|
for(let uniqueRemoveKey of Object.keys(this.eventsByRemoveKeys[masterKey])){
|
|
106
139
|
let eventToRemove = this.eventsByRemoveKeys[masterKey][uniqueRemoveKey];
|
|
140
|
+
if(!this.validateEventKey(eventToRemove.eventName)){
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
107
143
|
let dataArr = this.listeners(eventToRemove.eventName);
|
|
108
144
|
let currentListenerIndex = dataArr.indexOf(eventToRemove.callback);
|
|
145
|
+
if(-1 === currentListenerIndex){
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
109
148
|
this._events[eventToRemove.eventName].splice(currentListenerIndex, 1);
|
|
110
149
|
if(0 === this._events[eventToRemove.eventName].length){
|
|
111
150
|
delete this._events[eventToRemove.eventName];
|
|
@@ -116,7 +155,11 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
|
|
|
116
155
|
|
|
117
156
|
on(key, callback)
|
|
118
157
|
{
|
|
119
|
-
if(this.
|
|
158
|
+
if(!this.validateEventKey(key)){
|
|
159
|
+
Logger.critical('Invalid event key detected: '+key);
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if(false !== this.debug){
|
|
120
163
|
this.logDebugEvent(key, 'Listen');
|
|
121
164
|
}
|
|
122
165
|
super.on(key, callback);
|
|
@@ -124,7 +167,11 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
|
|
|
124
167
|
|
|
125
168
|
async emit(key, ...args)
|
|
126
169
|
{
|
|
127
|
-
if(this.
|
|
170
|
+
if(!this.validateEventKey(key)){
|
|
171
|
+
Logger.critical('Invalid event key detected: '+key);
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if(false !== this.debug){
|
|
128
175
|
this.logDebugEvent(key, 'Fire');
|
|
129
176
|
}
|
|
130
177
|
await super.emit(key, ...args);
|
package/lib/logger.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const sc = require('./shortcuts');
|
|
8
|
+
|
|
7
9
|
class Logger
|
|
8
10
|
{
|
|
9
11
|
|
|
@@ -21,15 +23,14 @@ class Logger
|
|
|
21
23
|
|
|
22
24
|
constructor()
|
|
23
25
|
{
|
|
24
|
-
// @TODO - BETA
|
|
25
|
-
// - Implement different log systems (console.log, files logs, db log?).
|
|
26
|
-
// - Implement notifications system (email?), and make it configurable for the different log levels.
|
|
27
26
|
let context = this.context();
|
|
28
|
-
this.enableTraceBack = '';
|
|
29
|
-
this.logLevelBack = 3;
|
|
30
|
-
this.forcedDisabled = Boolean(context.RELDENS_FORCED_DISABLED_LOGS || false);
|
|
31
|
-
this.addTimeStamp = Boolean(context.RELDENS_INCLUDE_LOGS_TIMESTAMP || true);
|
|
27
|
+
this.enableTraceBack = sc.get(context, 'RELDENS_LOGS_ENABLE_TRACE_BACK', '');
|
|
28
|
+
this.logLevelBack = sc.get(context, 'RELDENS_LOGS_LEVEL_BACK', 3);
|
|
32
29
|
this.callback = false;
|
|
30
|
+
this.forcedDisabled = Boolean(sc.get(context, 'RELDENS_LOGS_FORCED_DISABLED', false));
|
|
31
|
+
this.addTimeStamp = Boolean(sc.get(context, 'RELDENS_LOGS_INCLUDE_TIMESTAMP', true));
|
|
32
|
+
this.maxLogArgLength = sc.get(context, 'RELDENS_LOGS_MAX_ARGUMENT_LENGTH', 1000);
|
|
33
|
+
this.maxStackTraceLength = sc.get(context, 'RELDENS_LOGS_MAX_STACK_TRACE_LENGTH', 2000);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
context()
|
|
@@ -94,7 +95,8 @@ class Logger
|
|
|
94
95
|
log(levelLabel, ...args)
|
|
95
96
|
{
|
|
96
97
|
let date = !this.addTimeStamp ? '' : (new Date()).toISOString().slice(0, 19).replace('T', ' ')+' - ';
|
|
97
|
-
|
|
98
|
+
let sanitizedArgs = args.map(arg => sc.isString(arg) ? sc.cleanMessage(arg, this.maxLogArgLength) : arg);
|
|
99
|
+
this.logWithCallback(date+levelLabel.toUpperCase()+' -', ...sanitizedArgs);
|
|
98
100
|
if(-1 !== this.enableTraceFor().indexOf('all') || -1 !== this.enableTraceFor().indexOf(levelLabel)){
|
|
99
101
|
if('function' !== typeof Error?.captureStackTrace){
|
|
100
102
|
this.logWithCallback('Error.captureStackTrace is not available.', typeof Error?.captureStackTrace);
|
|
@@ -102,7 +104,8 @@ class Logger
|
|
|
102
104
|
}
|
|
103
105
|
let stackHolder = {};
|
|
104
106
|
Error.captureStackTrace(stackHolder, levelLabel);
|
|
105
|
-
|
|
107
|
+
let sanitizedStack = sc.cleanMessage(stackHolder.stack, this.maxStackTraceLength);
|
|
108
|
+
this.logWithCallback(sanitizedStack);
|
|
106
109
|
}
|
|
107
110
|
return this;
|
|
108
111
|
}
|
package/lib/schema-validator.js
CHANGED
|
@@ -43,6 +43,13 @@ class SchemaValidator extends ValidatorInterface
|
|
|
43
43
|
}
|
|
44
44
|
for(let i of Object.keys(schema)){
|
|
45
45
|
let validate = schema[i];
|
|
46
|
+
if(validate.required && !sc.hasOwn(obj, i)){
|
|
47
|
+
Logger.debug('Required property missing.', i);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if(!sc.hasOwn(obj, i) && !validate.required){
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
46
53
|
if(!this.isValidSchema(obj[i], validate, i)){
|
|
47
54
|
return false;
|
|
48
55
|
}
|
|
@@ -61,12 +68,36 @@ class SchemaValidator extends ValidatorInterface
|
|
|
61
68
|
Logger.debug('No schema type provided.', schema, obj, objectKey);
|
|
62
69
|
return false;
|
|
63
70
|
}
|
|
71
|
+
if(schema.custom && sc.isFunction(schema.custom)){
|
|
72
|
+
if(!schema.custom(obj)){
|
|
73
|
+
Logger.debug('Custom validation failed.', objectKey, obj, schema);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if(schema.enum && sc.isArray(schema.enum)){
|
|
78
|
+
if(-1 === schema.enum.indexOf(obj)){
|
|
79
|
+
Logger.debug('Value not in enum.', objectKey, obj, schema.enum);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
64
83
|
switch(schema.type){
|
|
65
84
|
case 'string':
|
|
66
85
|
if(!sc.isString(obj)){
|
|
67
86
|
Logger.debug('Object is not a string.', objectKey, obj, schema);
|
|
68
87
|
return false;
|
|
69
88
|
}
|
|
89
|
+
if(schema.min && obj.length < schema.min){
|
|
90
|
+
Logger.debug('String too short.', objectKey, obj.length, schema.min);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if(schema.max && obj.length > schema.max){
|
|
94
|
+
Logger.debug('String too long.', objectKey, obj.length, schema.max);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if(schema.pattern && !schema.pattern.test(obj)){
|
|
98
|
+
Logger.debug('String pattern mismatch.', objectKey, obj, schema.pattern);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
70
101
|
break;
|
|
71
102
|
case 'number':
|
|
72
103
|
if(!sc.isInt(obj)){
|
|
@@ -75,18 +106,42 @@ class SchemaValidator extends ValidatorInterface
|
|
|
75
106
|
return false;
|
|
76
107
|
}
|
|
77
108
|
}
|
|
109
|
+
if(schema.min && obj < schema.min){
|
|
110
|
+
Logger.debug('Number too small.', objectKey, obj, schema.min);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if(schema.max && obj > schema.max){
|
|
114
|
+
Logger.debug('Number too large.', objectKey, obj, schema.max);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
78
117
|
break;
|
|
79
118
|
case 'int':
|
|
80
119
|
if(!sc.isInt(obj)){
|
|
81
120
|
Logger.debug('Object is not an integer.', objectKey, obj, schema);
|
|
82
121
|
return false;
|
|
83
122
|
}
|
|
123
|
+
if(schema.min && obj < schema.min){
|
|
124
|
+
Logger.debug('Integer too small.', objectKey, obj, schema.min);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if(schema.max && obj > schema.max){
|
|
128
|
+
Logger.debug('Integer too large.', objectKey, obj, schema.max);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
84
131
|
break;
|
|
85
132
|
case 'float':
|
|
86
133
|
if(!sc.isFloat(obj)){
|
|
87
134
|
Logger.debug('Object is not a float.', objectKey, obj, schema);
|
|
88
135
|
return false;
|
|
89
136
|
}
|
|
137
|
+
if(schema.min && obj < schema.min){
|
|
138
|
+
Logger.debug('Float too small.', objectKey, obj, schema.min);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if(schema.max && obj > schema.max){
|
|
142
|
+
Logger.debug('Float too large.', objectKey, obj, schema.max);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
90
145
|
break;
|
|
91
146
|
case 'boolean':
|
|
92
147
|
if(!sc.isBoolean(obj)){
|
|
@@ -105,6 +160,14 @@ class SchemaValidator extends ValidatorInterface
|
|
|
105
160
|
Logger.debug('Object is not an array.', objectKey, obj, schema);
|
|
106
161
|
return false;
|
|
107
162
|
}
|
|
163
|
+
if(schema.min && obj.length < schema.min){
|
|
164
|
+
Logger.debug('Array too short.', objectKey, obj.length, schema.min);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if(schema.max && obj.length > schema.max){
|
|
168
|
+
Logger.debug('Array too long.', objectKey, obj.length, schema.max);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
108
171
|
if(schema.valuesType){
|
|
109
172
|
for(let i of obj){
|
|
110
173
|
if(!this.isValidSchema(i, {type: schema.valuesType}, objectKey)){
|
package/lib/shortcuts.js
CHANGED
|
@@ -47,6 +47,11 @@ class Shortcuts
|
|
|
47
47
|
return -1 !== dataArray.indexOf(value);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
isNotEmptyArray(dataArray)
|
|
51
|
+
{
|
|
52
|
+
return (this.isArray(dataArray) && 0 < dataArray.length);
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
isFunction(callback)
|
|
51
56
|
{
|
|
52
57
|
return (callback && 'function' === typeof callback);
|
|
@@ -88,12 +93,32 @@ class Shortcuts
|
|
|
88
93
|
return 'boolean' === typeof value;
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
hasDangerousKeys(obj, key = null)
|
|
97
|
+
{
|
|
98
|
+
let dangerousKeys = ['__proto__', 'constructor', 'prototype'];
|
|
99
|
+
if(key){
|
|
100
|
+
return -1 !== dangerousKeys.indexOf(key);
|
|
101
|
+
}
|
|
102
|
+
if(!this.isObject(obj)){
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
for(let dangerousKey of dangerousKeys){
|
|
106
|
+
if(this.hasOwn(obj, dangerousKey)){
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
deepMergeProperties(target, source)
|
|
92
114
|
{
|
|
93
115
|
if(!this.isObject(target) || !this.isObject(source)){
|
|
94
116
|
return false;
|
|
95
117
|
}
|
|
96
118
|
for(let key of Object.keys(source)){
|
|
119
|
+
if(this.hasDangerousKeys(null, key)){
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
97
122
|
if(this.isObject(source[key])){
|
|
98
123
|
if(!this.hasOwn(target, key)){
|
|
99
124
|
target[key] = source[key];
|
|
@@ -152,8 +177,8 @@ class Shortcuts
|
|
|
152
177
|
if(!sortField){
|
|
153
178
|
return collection;
|
|
154
179
|
}
|
|
155
|
-
let directionValue = '
|
|
156
|
-
let directionOpposite = '
|
|
180
|
+
let directionValue = 'asc' === direction ? 1 : -1;
|
|
181
|
+
let directionOpposite = 'asc' === direction ? -1 : 1;
|
|
157
182
|
return collection.sort((a, b) => {
|
|
158
183
|
return a[sortField] > b[sortField] ? directionValue : directionOpposite;
|
|
159
184
|
});
|
|
@@ -177,11 +202,16 @@ class Shortcuts
|
|
|
177
202
|
|
|
178
203
|
parseJson(jsonString, defaultReturn = false)
|
|
179
204
|
{
|
|
205
|
+
let parsed;
|
|
180
206
|
try {
|
|
181
|
-
|
|
207
|
+
parsed = JSON.parse(jsonString);
|
|
182
208
|
} catch (e) {
|
|
183
209
|
return defaultReturn;
|
|
184
210
|
}
|
|
211
|
+
if(this.hasDangerousKeys(parsed)){
|
|
212
|
+
return defaultReturn;
|
|
213
|
+
}
|
|
214
|
+
return parsed;
|
|
185
215
|
}
|
|
186
216
|
|
|
187
217
|
deepJsonClone(obj)
|
|
@@ -302,7 +332,7 @@ class Shortcuts
|
|
|
302
332
|
let obj = {};
|
|
303
333
|
for(let [key, value] of formData){
|
|
304
334
|
if(obj[key] !== undefined){
|
|
305
|
-
if(!
|
|
335
|
+
if(!this.isArray(obj[key])){
|
|
306
336
|
obj[key] = [obj[key]];
|
|
307
337
|
}
|
|
308
338
|
obj[key].push(value);
|
|
@@ -316,7 +346,7 @@ class Shortcuts
|
|
|
316
346
|
removeFromArray(valuesArray, removeValues)
|
|
317
347
|
{
|
|
318
348
|
return valuesArray.filter((value) => {
|
|
319
|
-
return removeValues.indexOf(value)
|
|
349
|
+
return -1 === removeValues.indexOf(value);
|
|
320
350
|
});
|
|
321
351
|
}
|
|
322
352
|
|
|
@@ -404,7 +434,7 @@ class Shortcuts
|
|
|
404
434
|
if(!message){
|
|
405
435
|
return '';
|
|
406
436
|
}
|
|
407
|
-
let text = message.
|
|
437
|
+
let text = message.replace(/\\/g, '').replace(/[\r\n\t]/g, ' ');
|
|
408
438
|
if(0 < characterLimit){
|
|
409
439
|
return text.substring(0, characterLimit);
|
|
410
440
|
}
|
|
@@ -413,8 +443,7 @@ class Shortcuts
|
|
|
413
443
|
|
|
414
444
|
slugify(text)
|
|
415
445
|
{
|
|
416
|
-
return (text || '')
|
|
417
|
-
.replace(/&/g, ' and ')
|
|
446
|
+
return (text || '').replace(/&/g, ' and ')
|
|
418
447
|
.replace(/[^a-zA-Z0-9]/g, ' ')
|
|
419
448
|
.trim()
|
|
420
449
|
.replace(/\s+/g, '-')
|
|
@@ -423,8 +452,7 @@ class Shortcuts
|
|
|
423
452
|
|
|
424
453
|
isValidIsoCode(isoCode)
|
|
425
454
|
{
|
|
426
|
-
|
|
427
|
-
return isoLanguageCodeRegex.test(isoCode);
|
|
455
|
+
return (/^[a-zA-Z]{2}$/).test(isoCode);
|
|
428
456
|
}
|
|
429
457
|
|
|
430
458
|
sanitize(input)
|
|
@@ -432,32 +460,207 @@ class Shortcuts
|
|
|
432
460
|
if(!input){
|
|
433
461
|
return '';
|
|
434
462
|
}
|
|
435
|
-
return input
|
|
436
|
-
.replace(/&(?![a-zA-Z0-9#]+;)/g, '&')
|
|
463
|
+
return input.replace(/&/g, '&')
|
|
437
464
|
.replace(/</g, '<')
|
|
438
465
|
.replace(/>/g, '>')
|
|
439
466
|
.replace(/"/g, '"')
|
|
440
467
|
.replace(/'/g, ''')
|
|
441
468
|
.replace(/\//g, '/')
|
|
442
|
-
.replace(/`/g, '`')
|
|
469
|
+
.replace(/`/g, '`')
|
|
470
|
+
.replace(/\(/g, '(')
|
|
471
|
+
.replace(/\)/g, ')');
|
|
443
472
|
}
|
|
444
473
|
|
|
445
474
|
sanitizeUrl(url)
|
|
446
475
|
{
|
|
447
|
-
if(!url){
|
|
476
|
+
if(!url || !this.isString(url) || 2048 < url.length){
|
|
448
477
|
return '';
|
|
449
478
|
}
|
|
450
479
|
let sanitized = url.trim();
|
|
451
|
-
let urlPattern = /^(?:
|
|
480
|
+
let urlPattern = /^https?:\/\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(?::[1-9][0-9]{0,4})?(?:\/(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*(?:\?(?:[\w\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:#(?:[\w\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?$/;
|
|
452
481
|
if(!urlPattern.test(sanitized)){
|
|
453
482
|
return '';
|
|
454
483
|
}
|
|
455
|
-
return sanitized
|
|
456
|
-
.replace(/javascript:/gi, '')
|
|
484
|
+
return sanitized.replace(/javascript:/gi, '')
|
|
457
485
|
.replace(/data:/gi, '')
|
|
458
486
|
.replace(/vbscript:/gi, '');
|
|
459
487
|
}
|
|
460
488
|
|
|
489
|
+
camelCase(str)
|
|
490
|
+
{
|
|
491
|
+
if(!this.isString(str) || 0 === str.length){
|
|
492
|
+
return str;
|
|
493
|
+
}
|
|
494
|
+
return str.replace(/[_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
capitalizedCamelCase(str)
|
|
498
|
+
{
|
|
499
|
+
if(!this.isString(str) || 0 === str.length){
|
|
500
|
+
return str;
|
|
501
|
+
}
|
|
502
|
+
let camelStr = this.camelCase(str);
|
|
503
|
+
return camelStr.charAt(0).toUpperCase() + camelStr.slice(1);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
kebabCase(str)
|
|
507
|
+
{
|
|
508
|
+
return str.replace(/[_\s]+/g, '-');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
capitalize(str)
|
|
512
|
+
{
|
|
513
|
+
if(!this.isString(str) || 0 === str.length){
|
|
514
|
+
return str;
|
|
515
|
+
}
|
|
516
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
chunk(array, size)
|
|
520
|
+
{
|
|
521
|
+
if(!this.isArray(array) || 0 >= size){
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
let result = [];
|
|
525
|
+
for(let i = 0; i < array.length; i += size){
|
|
526
|
+
result.push(array.slice(i, i + size));
|
|
527
|
+
}
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
flatten(array, depth = 1)
|
|
532
|
+
{
|
|
533
|
+
if(!this.isArray(array)){
|
|
534
|
+
return [];
|
|
535
|
+
}
|
|
536
|
+
return array.flat(depth);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
unique(array)
|
|
540
|
+
{
|
|
541
|
+
if(!this.isArray(array)){
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
return [...new Set(array)];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
clamp(value, min, max)
|
|
548
|
+
{
|
|
549
|
+
if(!this.isNumber(value) || !this.isNumber(min) || !this.isNumber(max)){
|
|
550
|
+
return value;
|
|
551
|
+
}
|
|
552
|
+
return Math.min(Math.max(value, min), max);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
truncate(str, length, suffix = '...')
|
|
556
|
+
{
|
|
557
|
+
if(!this.isString(str) || str.length <= length){
|
|
558
|
+
return str;
|
|
559
|
+
}
|
|
560
|
+
return str.substring(0, length) + suffix;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
pickProps(obj, props)
|
|
564
|
+
{
|
|
565
|
+
if(!this.isObject(obj) || !this.isArray(props)){
|
|
566
|
+
return {};
|
|
567
|
+
}
|
|
568
|
+
let result = {};
|
|
569
|
+
for(let prop of props){
|
|
570
|
+
if(this.hasDangerousKeys(null, prop)){
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if(this.hasOwn(obj, prop)){
|
|
574
|
+
result[prop] = obj[prop];
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
omitProps(obj, props)
|
|
581
|
+
{
|
|
582
|
+
if(!this.isObject(obj) || !this.isArray(props)){
|
|
583
|
+
return obj;
|
|
584
|
+
}
|
|
585
|
+
let result = {};
|
|
586
|
+
for(let key of Object.keys(obj)){
|
|
587
|
+
if(-1 === props.indexOf(key)){
|
|
588
|
+
result[key] = obj[key];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
debounce(func, wait)
|
|
595
|
+
{
|
|
596
|
+
if(!this.isFunction(func) || !this.isNumber(wait)){
|
|
597
|
+
return func;
|
|
598
|
+
}
|
|
599
|
+
let timeout;
|
|
600
|
+
return function executedFunction(...args) {
|
|
601
|
+
let later = () => {
|
|
602
|
+
clearTimeout(timeout);
|
|
603
|
+
func.apply(this, args);
|
|
604
|
+
};
|
|
605
|
+
clearTimeout(timeout);
|
|
606
|
+
timeout = setTimeout(later, wait);
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
throttle(func, limit)
|
|
611
|
+
{
|
|
612
|
+
if(!this.isFunction(func) || !this.isNumber(limit)){
|
|
613
|
+
return func;
|
|
614
|
+
}
|
|
615
|
+
let inThrottle;
|
|
616
|
+
return function executedFunction(...args) {
|
|
617
|
+
if(!inThrottle){
|
|
618
|
+
func.apply(this, args);
|
|
619
|
+
inThrottle = true;
|
|
620
|
+
setTimeout(() => inThrottle = false, limit);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
isValidUrl(url)
|
|
626
|
+
{
|
|
627
|
+
if(!this.isString(url) || 2048 < url.length){
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
let urlPattern = /^https?:\/\/(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(?::[1-9][0-9]{0,4})?(?:\/(?:[\w\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*(?:\?(?:[\w\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:#(?:[\w\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?$/;
|
|
631
|
+
return urlPattern.test(url);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
isValidInteger(value, min, max)
|
|
635
|
+
{
|
|
636
|
+
if(!this.isNumber(value) || !Number.isInteger(value)){
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
if('number' === typeof min && value < min){
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
if('number' === typeof max && value > max){
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
parseNumber(value)
|
|
649
|
+
{
|
|
650
|
+
if(!isNaN(Number(value)) && '' !== value && null !== value){
|
|
651
|
+
return Number(value);
|
|
652
|
+
}
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
splitToArray(str, separator = ',')
|
|
657
|
+
{
|
|
658
|
+
if(!this.isString(str) || '' === str.trim()){
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
return str.split(separator).map(item => item.trim()).filter(item => '' !== item);
|
|
662
|
+
}
|
|
663
|
+
|
|
461
664
|
}
|
|
462
665
|
|
|
463
666
|
module.exports = new Shortcuts();
|