@reldens/utils 0.47.0 → 0.49.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 +238 -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
|
@@ -31,7 +31,7 @@ class Shortcuts
|
|
|
31
31
|
|
|
32
32
|
isObject(obj)
|
|
33
33
|
{
|
|
34
|
-
return (obj && typeof obj
|
|
34
|
+
return (obj && 'object' === typeof obj && !this.isArray(obj));
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
isArray(obj)
|
|
@@ -93,12 +93,32 @@ class Shortcuts
|
|
|
93
93
|
return 'boolean' === typeof value;
|
|
94
94
|
}
|
|
95
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
|
+
|
|
96
113
|
deepMergeProperties(target, source)
|
|
97
114
|
{
|
|
98
115
|
if(!this.isObject(target) || !this.isObject(source)){
|
|
99
116
|
return false;
|
|
100
117
|
}
|
|
101
118
|
for(let key of Object.keys(source)){
|
|
119
|
+
if(this.hasDangerousKeys(null, key)){
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
102
122
|
if(this.isObject(source[key])){
|
|
103
123
|
if(!this.hasOwn(target, key)){
|
|
104
124
|
target[key] = source[key];
|
|
@@ -157,8 +177,8 @@ class Shortcuts
|
|
|
157
177
|
if(!sortField){
|
|
158
178
|
return collection;
|
|
159
179
|
}
|
|
160
|
-
let directionValue = '
|
|
161
|
-
let directionOpposite = '
|
|
180
|
+
let directionValue = 'asc' === direction ? 1 : -1;
|
|
181
|
+
let directionOpposite = 'asc' === direction ? -1 : 1;
|
|
162
182
|
return collection.sort((a, b) => {
|
|
163
183
|
return a[sortField] > b[sortField] ? directionValue : directionOpposite;
|
|
164
184
|
});
|
|
@@ -182,11 +202,16 @@ class Shortcuts
|
|
|
182
202
|
|
|
183
203
|
parseJson(jsonString, defaultReturn = false)
|
|
184
204
|
{
|
|
205
|
+
let parsed;
|
|
185
206
|
try {
|
|
186
|
-
|
|
207
|
+
parsed = JSON.parse(jsonString);
|
|
187
208
|
} catch (e) {
|
|
188
209
|
return defaultReturn;
|
|
189
210
|
}
|
|
211
|
+
if(this.hasDangerousKeys(parsed)){
|
|
212
|
+
return defaultReturn;
|
|
213
|
+
}
|
|
214
|
+
return parsed;
|
|
190
215
|
}
|
|
191
216
|
|
|
192
217
|
deepJsonClone(obj)
|
|
@@ -321,7 +346,7 @@ class Shortcuts
|
|
|
321
346
|
removeFromArray(valuesArray, removeValues)
|
|
322
347
|
{
|
|
323
348
|
return valuesArray.filter((value) => {
|
|
324
|
-
return removeValues.indexOf(value)
|
|
349
|
+
return -1 === removeValues.indexOf(value);
|
|
325
350
|
});
|
|
326
351
|
}
|
|
327
352
|
|
|
@@ -409,7 +434,7 @@ class Shortcuts
|
|
|
409
434
|
if(!message){
|
|
410
435
|
return '';
|
|
411
436
|
}
|
|
412
|
-
let text = message.
|
|
437
|
+
let text = message.replace(/\\/g, '').replace(/[\r\n\t]/g, ' ');
|
|
413
438
|
if(0 < characterLimit){
|
|
414
439
|
return text.substring(0, characterLimit);
|
|
415
440
|
}
|
|
@@ -418,8 +443,7 @@ class Shortcuts
|
|
|
418
443
|
|
|
419
444
|
slugify(text)
|
|
420
445
|
{
|
|
421
|
-
return (text || '')
|
|
422
|
-
.replace(/&/g, ' and ')
|
|
446
|
+
return (text || '').replace(/&/g, ' and ')
|
|
423
447
|
.replace(/[^a-zA-Z0-9]/g, ' ')
|
|
424
448
|
.trim()
|
|
425
449
|
.replace(/\s+/g, '-')
|
|
@@ -428,8 +452,7 @@ class Shortcuts
|
|
|
428
452
|
|
|
429
453
|
isValidIsoCode(isoCode)
|
|
430
454
|
{
|
|
431
|
-
|
|
432
|
-
return isoLanguageCodeRegex.test(isoCode);
|
|
455
|
+
return (/^[a-zA-Z]{2}$/).test(isoCode);
|
|
433
456
|
}
|
|
434
457
|
|
|
435
458
|
sanitize(input)
|
|
@@ -437,39 +460,45 @@ class Shortcuts
|
|
|
437
460
|
if(!input){
|
|
438
461
|
return '';
|
|
439
462
|
}
|
|
440
|
-
return input
|
|
441
|
-
.replace(/&(?![a-zA-Z0-9#]+;)/g, '&')
|
|
463
|
+
return input.replace(/&/g, '&')
|
|
442
464
|
.replace(/</g, '<')
|
|
443
465
|
.replace(/>/g, '>')
|
|
444
466
|
.replace(/"/g, '"')
|
|
445
467
|
.replace(/'/g, ''')
|
|
446
468
|
.replace(/\//g, '/')
|
|
447
|
-
.replace(/`/g, '`')
|
|
469
|
+
.replace(/`/g, '`')
|
|
470
|
+
.replace(/\(/g, '(')
|
|
471
|
+
.replace(/\)/g, ')');
|
|
448
472
|
}
|
|
449
473
|
|
|
450
474
|
sanitizeUrl(url)
|
|
451
475
|
{
|
|
452
|
-
if(!url){
|
|
476
|
+
if(!url || !this.isString(url) || 2048 < url.length){
|
|
453
477
|
return '';
|
|
454
478
|
}
|
|
455
479
|
let sanitized = url.trim();
|
|
456
|
-
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})*)?$/;
|
|
457
481
|
if(!urlPattern.test(sanitized)){
|
|
458
482
|
return '';
|
|
459
483
|
}
|
|
460
|
-
return sanitized
|
|
461
|
-
.replace(/javascript:/gi, '')
|
|
484
|
+
return sanitized.replace(/javascript:/gi, '')
|
|
462
485
|
.replace(/data:/gi, '')
|
|
463
486
|
.replace(/vbscript:/gi, '');
|
|
464
487
|
}
|
|
465
488
|
|
|
466
489
|
camelCase(str)
|
|
467
490
|
{
|
|
491
|
+
if(!this.isString(str) || 0 === str.length){
|
|
492
|
+
return str;
|
|
493
|
+
}
|
|
468
494
|
return str.replace(/[_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '');
|
|
469
495
|
}
|
|
470
496
|
|
|
471
497
|
capitalizedCamelCase(str)
|
|
472
498
|
{
|
|
499
|
+
if(!this.isString(str) || 0 === str.length){
|
|
500
|
+
return str;
|
|
501
|
+
}
|
|
473
502
|
let camelStr = this.camelCase(str);
|
|
474
503
|
return camelStr.charAt(0).toUpperCase() + camelStr.slice(1);
|
|
475
504
|
}
|
|
@@ -479,6 +508,198 @@ class Shortcuts
|
|
|
479
508
|
return str.replace(/[_\s]+/g, '-');
|
|
480
509
|
}
|
|
481
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
|
+
|
|
664
|
+
isSecurePath(filePath, dangerous = [], maxLength = 2048)
|
|
665
|
+
{
|
|
666
|
+
if(!this.isString(filePath)){
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
let normalized = filePath.replace(/\\/g, '/');
|
|
670
|
+
if(!this.isArray(dangerous) || 0 === dangerous.length){
|
|
671
|
+
dangerous = [
|
|
672
|
+
'../', '..\\', './', '.\\',
|
|
673
|
+
'/etc/', '/proc/', '/sys/',
|
|
674
|
+
'C:\\Windows\\', 'C:\\System32\\',
|
|
675
|
+
'%2e%2e%2f', '%2e%2e%5c'
|
|
676
|
+
];
|
|
677
|
+
}
|
|
678
|
+
for(let pattern of dangerous){
|
|
679
|
+
if(normalized.toLowerCase().includes(pattern.toLowerCase())){
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return maxLength >= filePath.length;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
validateInput(input, type)
|
|
687
|
+
{
|
|
688
|
+
if(!this.isString(input)){
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
let patterns = {
|
|
692
|
+
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
693
|
+
username: /^[a-zA-Z0-9_-]{3,30}$/,
|
|
694
|
+
strongPassword: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
|
|
695
|
+
alphanumeric: /^[a-zA-Z0-9]+$/,
|
|
696
|
+
numeric: /^\d+$/,
|
|
697
|
+
hexColor: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
|
|
698
|
+
ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
|
699
|
+
};
|
|
700
|
+
return patterns[type] ? patterns[type].test(input) : false;
|
|
701
|
+
}
|
|
702
|
+
|
|
482
703
|
}
|
|
483
704
|
|
|
484
705
|
module.exports = new Shortcuts();
|