@reldens/utils 0.49.0 → 0.51.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.
@@ -2,55 +2,336 @@
2
2
  *
3
3
  * Reldens - EventsManager
4
4
  *
5
- * This extension includes a few new features:
6
- *
7
- * - The onWithKey and offWithKey methods will allow you to specify a "group" key for the event listener, the related
8
- * event will be referenced in a new property called "eventsByRemoveKey" so you can detach multiple listeners at the
9
- * time.
10
- *
11
- * - We override the "on" and "off" aliases override to include a debug check with a log. Using the debug property,
12
- * you can specify to log "all" the fire and listeners, or just by key. Note the key search will be applied in both
13
- * directions, it will check if the debug value exists in the event key or if the event key exists in the debug value.
14
- *
15
5
  */
16
6
 
17
- const AwaitEventEmitter = require('await-event-emitter').default;
18
7
  const Logger = require('./logger');
19
8
  const sc = require('./shortcuts');
20
9
 
21
- class AwaitEventEmitterExtended extends AwaitEventEmitter
10
+ class EventsManager
22
11
  {
23
12
 
24
13
  constructor()
25
14
  {
26
- super();
15
+ this._events = {};
27
16
  this.eventsByRemoveKeys = {};
28
17
  this.debug = false;
18
+ this._listenersCache = {};
19
+ this._validationCache = new Map();
20
+ this._debugPatterns = null;
21
+ this.maxEventKeyLength = 1000;
22
+ this.maxListeners = 10000;
23
+ this.maxEventArgs = 50;
24
+ this.sensitiveFields = ['password', 'token', 'secret', 'key', 'auth', 'credential'];
25
+ this.hasLoggedMaxListeners = false;
26
+ this.symbolString = '--[[await-event-emitter]]--';
27
+ this.typeKeyName = sc.isFunction(Symbol) ? Symbol.for(this.symbolString) : this.symbolString;
28
+ }
29
+
30
+ assertType(type)
31
+ {
32
+ if(!sc.isString(type) && !sc.isSymbol(type)){
33
+ throw new TypeError('type is not type of string or symbol!');
34
+ }
35
+ }
36
+
37
+ assertFn(fn)
38
+ {
39
+ if(!sc.isFunction(fn)){
40
+ throw new TypeError('fn is not type of Function!');
41
+ }
42
+ }
43
+
44
+ alwaysListener(fn)
45
+ {
46
+ return {
47
+ [this.typeKeyName]: 'always',
48
+ fn
49
+ };
50
+ }
51
+
52
+ onceListener(fn)
53
+ {
54
+ return {
55
+ [this.typeKeyName]: 'once',
56
+ fn
57
+ };
29
58
  }
30
59
 
31
60
  validateEventKey(eventKey)
32
61
  {
33
- return !sc.hasDangerousKeys(null, eventKey);
62
+ if(this._validationCache.has(eventKey)){
63
+ return this._validationCache.get(eventKey);
64
+ }
65
+ if(sc.isString(eventKey) && eventKey.length > this.maxEventKeyLength){
66
+ Logger.critical('Event key exceeds maximum length: '+eventKey.length);
67
+ this._validationCache.set(eventKey, false);
68
+ return false;
69
+ }
70
+ let isValid = !sc.hasDangerousKeys(null, eventKey);
71
+ this._validationCache.set(eventKey, isValid);
72
+ return isValid;
73
+ }
74
+
75
+ sanitizeEventArgs(args)
76
+ {
77
+ if(!sc.isArray(args)){
78
+ return [];
79
+ }
80
+ if(args.length > this.maxEventArgs){
81
+ Logger.warning('Event arguments exceed maximum: '+args.length);
82
+ return [];
83
+ }
84
+ return args.map(arg => {
85
+ if(sc.isObject(arg)){
86
+ return this.filterSensitiveData(arg);
87
+ }
88
+ return arg;
89
+ });
90
+ }
91
+
92
+ filterSensitiveData(obj)
93
+ {
94
+ if(!sc.isObject(obj)){
95
+ return obj;
96
+ }
97
+ let filtered = {};
98
+ for(let key of Object.keys(obj)){
99
+ if(sc.hasDangerousKeys(null, key)){
100
+ continue;
101
+ }
102
+ let isKeywordSensitive = this.sensitiveFields.some(sensitive =>
103
+ key.toLowerCase().includes(sensitive.toLowerCase())
104
+ );
105
+ if(isKeywordSensitive){
106
+ filtered[key] = '[FILTERED]';
107
+ continue;
108
+ }
109
+ if(sc.isObject(obj[key])){
110
+ filtered[key] = this.filterSensitiveData(obj[key]);
111
+ continue;
112
+ }
113
+ filtered[key] = obj[key];
114
+ }
115
+ return filtered;
116
+ }
117
+
118
+ checkMemoryLeaks()
119
+ {
120
+ if(this.hasLoggedMaxListeners){
121
+ return true;
122
+ }
123
+ let totalListeners = 0;
124
+ for(let eventType of Object.keys(this._events)){
125
+ totalListeners += this._events[eventType].length;
126
+ }
127
+ if(totalListeners > this.maxListeners){
128
+ Logger.debug('High listener count detected: '+totalListeners+' total listeners');
129
+ this.hasLoggedMaxListeners = true;
130
+ }
131
+ return true;
132
+ }
133
+
134
+ addListener(type, fn)
135
+ {
136
+ return this.on(type, fn);
137
+ }
138
+
139
+ on(type, fn)
140
+ {
141
+ this.assertType(type);
142
+ this.assertFn(fn);
143
+ if(!this.validateEventKey(type)){
144
+ Logger.critical('Invalid event key detected: '+type);
145
+ return false;
146
+ }
147
+ if(false !== this.debug){
148
+ this.logDebugEvent(type, 'Listen');
149
+ }
150
+ this._events[type] = this._events[type] || [];
151
+ this._events[type].push(this.alwaysListener(fn));
152
+ delete this._listenersCache[type];
153
+ this.checkMemoryLeaks();
154
+ return this;
155
+ }
156
+
157
+ prependListener(type, fn)
158
+ {
159
+ return this.prepend(type, fn);
160
+ }
161
+
162
+ prepend(type, fn)
163
+ {
164
+ this.assertType(type);
165
+ this.assertFn(fn);
166
+ if(!this.validateEventKey(type)){
167
+ Logger.critical('Invalid event key detected: '+type);
168
+ return false;
169
+ }
170
+ this._events[type] = this._events[type] || [];
171
+ this._events[type].unshift(this.alwaysListener(fn));
172
+ delete this._listenersCache[type];
173
+ this.checkMemoryLeaks();
174
+ return this;
175
+ }
176
+
177
+ prependOnceListener(type, fn)
178
+ {
179
+ return this.prependOnce(type, fn);
180
+ }
181
+
182
+ prependOnce(type, fn)
183
+ {
184
+ this.assertType(type);
185
+ this.assertFn(fn);
186
+ if(!this.validateEventKey(type)){
187
+ Logger.critical('Invalid event key detected: '+type);
188
+ return false;
189
+ }
190
+ this._events[type] = this._events[type] || [];
191
+ this._events[type].unshift(this.onceListener(fn));
192
+ delete this._listenersCache[type];
193
+ this.checkMemoryLeaks();
194
+ return this;
195
+ }
196
+
197
+ listeners(type)
198
+ {
199
+ if(this._listenersCache[type]){
200
+ return this._listenersCache[type];
201
+ }
202
+ let result = (this._events[type] || []).map((x) => x.fn);
203
+ this._listenersCache[type] = result;
204
+ return result;
205
+ }
206
+
207
+ once(type, fn)
208
+ {
209
+ this.assertType(type);
210
+ this.assertFn(fn);
211
+ if(!this.validateEventKey(type)){
212
+ Logger.critical('Invalid event key detected: '+type);
213
+ return false;
214
+ }
215
+ this._events[type] = this._events[type] || [];
216
+ this._events[type].push(this.onceListener(fn));
217
+ delete this._listenersCache[type];
218
+ this.checkMemoryLeaks();
219
+ return this;
220
+ }
221
+
222
+ removeAllListeners()
223
+ {
224
+ this._events = {};
225
+ this._listenersCache = {};
226
+ }
227
+
228
+ off(type, nullOrFn)
229
+ {
230
+ return this.removeListener(type, nullOrFn);
231
+ }
232
+
233
+ removeListener(type, nullOrFn)
234
+ {
235
+ this.assertType(type);
236
+ if(!this.validateEventKey(type)){
237
+ Logger.critical('Invalid event key detected: '+type);
238
+ return false;
239
+ }
240
+ let listeners = this.listeners(type);
241
+ if(sc.isFunction(nullOrFn)){
242
+ let index = -1;
243
+ let found = false;
244
+ while(-1 < (index = listeners.indexOf(nullOrFn))){
245
+ listeners.splice(index, 1);
246
+ this._events[type].splice(index, 1);
247
+ found = true;
248
+ }
249
+ delete this._listenersCache[type];
250
+ return found;
251
+ }
252
+ delete this._events[type];
253
+ delete this._listenersCache[type];
254
+ return true;
255
+ }
256
+
257
+ async emit(type, ...args)
258
+ {
259
+ this.assertType(type);
260
+ if(!this.validateEventKey(type)){
261
+ Logger.critical('Invalid event key detected: '+type);
262
+ return false;
263
+ }
264
+ let sanitizedArgs = this.sanitizeEventArgs(args);
265
+ if(false !== this.debug){
266
+ this.logDebugEvent(type, 'Fire', sanitizedArgs);
267
+ }
268
+ this.checkMemoryLeaks();
269
+ let listeners = this.listeners(type);
270
+ let onceListeners = [];
271
+ if(listeners && listeners.length){
272
+ for(let i = 0; i < listeners.length; i++){
273
+ let event = listeners[i];
274
+ let rlt = event.apply(this, sanitizedArgs);
275
+ if(sc.isPromise(rlt)){
276
+ await rlt;
277
+ }
278
+ if(this._events[type] && this._events[type][i] && 'once' === this._events[type][i][this.typeKeyName]){
279
+ onceListeners.push(event);
280
+ }
281
+ }
282
+ for(let event of onceListeners){
283
+ this.removeListener(type, event);
284
+ }
285
+ return true;
286
+ }
287
+ return false;
288
+ }
289
+
290
+ emitSync(type, ...args)
291
+ {
292
+ this.assertType(type);
293
+ if(!this.validateEventKey(type)){
294
+ Logger.critical('Invalid event key detected: '+type);
295
+ return false;
296
+ }
297
+ let sanitizedArgs = this.sanitizeEventArgs(args);
298
+ let listeners = this.listeners(type);
299
+ let onceListeners = [];
300
+ if(listeners && listeners.length){
301
+ for(let i = 0; i < listeners.length; i++){
302
+ let event = listeners[i];
303
+ event.apply(this, sanitizedArgs);
304
+ if(this._events[type] && this._events[type][i] && 'once' === this._events[type][i][this.typeKeyName]){
305
+ onceListeners.push(event);
306
+ }
307
+ }
308
+ for(let event of onceListeners){
309
+ this.removeListener(type, event);
310
+ }
311
+ return true;
312
+ }
313
+ return false;
34
314
  }
35
315
 
36
316
  onWithKey(eventName, callback, uniqueRemoveKey, masterKey)
37
317
  {
38
- if(!this.validateEventKey(eventName) || !this.validateEventKey(uniqueRemoveKey)){
39
- Logger.critical('Invalid event key detected: '+eventName+' or '+uniqueRemoveKey);
318
+ if(!this.validateEventKey(eventName)){
319
+ Logger.critical('Invalid event key detected: '+eventName);
320
+ return false;
321
+ }
322
+ if(!this.validateEventKey(uniqueRemoveKey)){
323
+ Logger.critical('Invalid remove key detected: '+uniqueRemoveKey);
40
324
  return false;
41
325
  }
42
326
  if(masterKey && !this.validateEventKey(masterKey)){
43
327
  Logger.critical('Invalid master key detected: '+masterKey);
44
328
  return false;
45
329
  }
46
- if(
47
- sc.hasOwn(this.eventsByRemoveKeys, uniqueRemoveKey)
48
- || (
49
- masterKey
50
- && sc.hasOwn(this.eventsByRemoveKeys, masterKey)
51
- && sc.hasOwn(this.eventsByRemoveKeys[masterKey], uniqueRemoveKey)
52
- )
53
- ){
330
+ if(sc.hasOwn(this.eventsByRemoveKeys, uniqueRemoveKey)){
331
+ Logger.debug('Event "'+eventName+'" exists with key "'+uniqueRemoveKey+'".');
332
+ return false;
333
+ }
334
+ if(masterKey && sc.hasOwn(this.eventsByRemoveKeys, masterKey) && sc.hasOwn(this.eventsByRemoveKeys[masterKey], uniqueRemoveKey)){
54
335
  Logger.debug('Event "'+eventName+'" exists with key "'+uniqueRemoveKey+'" and masterKey "'+masterKey+'".');
55
336
  return false;
56
337
  }
@@ -69,10 +350,6 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
69
350
  return currentListener;
70
351
  }
71
352
 
72
- /**
73
- * This method will remove a SINGLE event using the unique remove key.
74
- * If the event listener was assigned using a masterKey then we need to pass it as well to find the event.
75
- */
76
353
  offWithKey(uniqueRemoveKey, masterKey)
77
354
  {
78
355
  if(!this.validateEventKey(uniqueRemoveKey)){
@@ -109,6 +386,7 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
109
386
  if(0 === this._events[eventToRemove.eventName].length){
110
387
  delete this._events[eventToRemove.eventName];
111
388
  }
389
+ delete this._listenersCache[eventToRemove.eventName];
112
390
  if(masterKey){
113
391
  delete this.eventsByRemoveKeys[masterKey][uniqueRemoveKey];
114
392
  Logger.debug('Deleted event by removeKey "'+uniqueRemoveKey+'" and masterKey "'+masterKey+'".');
@@ -119,9 +397,6 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
119
397
  return true;
120
398
  }
121
399
 
122
- /**
123
- * This method will remove ALL the events below the specified masterKey.
124
- */
125
400
  offByMasterKey(masterKey)
126
401
  {
127
402
  if(!this.validateEventKey(masterKey)){
@@ -132,14 +407,15 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
132
407
  Logger.debug('Events not found by masterKey "'+masterKey+'".');
133
408
  return false;
134
409
  }
135
- // @NOTE: we loop all the events related to the masterKey and remove them one by one since the _events property
136
- // is an object with multiple listeners attached.
137
410
  Logger.debug('Removing events by masterKey: '+masterKey, Object.keys(this.eventsByRemoveKeys[masterKey]));
138
- for(let uniqueRemoveKey of Object.keys(this.eventsByRemoveKeys[masterKey])){
411
+ let eventsToRemove = Object.keys(this.eventsByRemoveKeys[masterKey]);
412
+ let affectedEvents = new Set();
413
+ for(let uniqueRemoveKey of eventsToRemove){
139
414
  let eventToRemove = this.eventsByRemoveKeys[masterKey][uniqueRemoveKey];
140
415
  if(!this.validateEventKey(eventToRemove.eventName)){
141
416
  continue;
142
417
  }
418
+ affectedEvents.add(eventToRemove.eventName);
143
419
  let dataArr = this.listeners(eventToRemove.eventName);
144
420
  let currentListenerIndex = dataArr.indexOf(eventToRemove.callback);
145
421
  if(-1 === currentListenerIndex){
@@ -150,44 +426,28 @@ class AwaitEventEmitterExtended extends AwaitEventEmitter
150
426
  delete this._events[eventToRemove.eventName];
151
427
  }
152
428
  }
153
- delete this.eventsByRemoveKeys[masterKey];
154
- }
155
-
156
- on(key, callback)
157
- {
158
- if(!this.validateEventKey(key)){
159
- Logger.critical('Invalid event key detected: '+key);
160
- return false;
161
- }
162
- if(false !== this.debug){
163
- this.logDebugEvent(key, 'Listen');
429
+ for(let eventName of affectedEvents){
430
+ delete this._listenersCache[eventName];
164
431
  }
165
- super.on(key, callback);
432
+ delete this.eventsByRemoveKeys[masterKey];
166
433
  }
167
434
 
168
- async emit(key, ...args)
435
+ logDebugEvent(key, type, args = null)
169
436
  {
170
- if(!this.validateEventKey(key)){
171
- Logger.critical('Invalid event key detected: '+key);
172
- return false;
437
+ if(!this._debugPatterns){
438
+ this._debugPatterns = new Set(this.debug.split(','));
173
439
  }
174
- if(false !== this.debug){
175
- this.logDebugEvent(key, 'Fire');
440
+ if(!this._debugPatterns.has('all') && !this._debugPatterns.has(key) && -1 === key.indexOf(this.debug)){
441
+ return;
176
442
  }
177
- await super.emit(key, ...args);
178
- }
179
-
180
- logDebugEvent(key, type)
181
- {
182
- if(
183
- -1 !== this.debug.indexOf('all')
184
- || -1 !== this.debug.indexOf(key)
185
- || -1 !== key.indexOf(this.debug)
186
- ){
187
- Logger.debug(type+' Event:', key);
443
+ let logMessage = type+' Event: '+key;
444
+ if(args && 0 < args.length){
445
+ let filteredArgs = args.map(arg => this.filterSensitiveData(arg));
446
+ logMessage += ' with '+filteredArgs.length+' arguments';
188
447
  }
448
+ Logger.debug(logMessage);
189
449
  }
190
450
 
191
451
  }
192
452
 
193
- module.exports = AwaitEventEmitterExtended;
453
+ module.exports = EventsManager;
package/lib/logger.js CHANGED
@@ -29,14 +29,13 @@ class Logger
29
29
  this.callback = false;
30
30
  this.forcedDisabled = Boolean(sc.get(context, 'RELDENS_LOGS_FORCED_DISABLED', false));
31
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);
32
+ this.maxLogArgLength = sc.get(context, 'RELDENS_LOGS_MAX_ARGUMENT_LENGTH', 0);
33
+ this.maxStackTraceLength = sc.get(context, 'RELDENS_LOGS_MAX_STACK_TRACE_LENGTH', 0);
34
+ this.applySanitizer = Boolean(sc.get(context, 'RELDENS_LOGS_APPLY_SANITIZER', false));
34
35
  }
35
36
 
36
37
  context()
37
38
  {
38
- // @NOTE: any change on this method could break Parcel and you will end up with the following error.
39
- // Failed to resolve module specifier "process"
40
39
  let context = process.env;
41
40
  if('undefined' !== typeof window){
42
41
  return window;
@@ -95,8 +94,11 @@ class Logger
95
94
  log(levelLabel, ...args)
96
95
  {
97
96
  let date = !this.addTimeStamp ? '' : (new Date()).toISOString().slice(0, 19).replace('T', ' ')+' - ';
98
- let sanitizedArgs = args.map(arg => sc.isString(arg) ? sc.cleanMessage(arg, this.maxLogArgLength) : arg);
99
- this.logWithCallback(date+levelLabel.toUpperCase()+' -', ...sanitizedArgs);
97
+ let processedArgs = args;
98
+ if(this.applySanitizer){
99
+ processedArgs = args.map(arg => sc.isString(arg) ? sc.cleanMessage(arg, this.maxLogArgLength) : arg);
100
+ }
101
+ this.logWithCallback(date+levelLabel.toUpperCase()+' -', ...processedArgs);
100
102
  if(-1 !== this.enableTraceFor().indexOf('all') || -1 !== this.enableTraceFor().indexOf(levelLabel)){
101
103
  if('function' !== typeof Error?.captureStackTrace){
102
104
  this.logWithCallback('Error.captureStackTrace is not available.', typeof Error?.captureStackTrace);
@@ -104,8 +106,11 @@ class Logger
104
106
  }
105
107
  let stackHolder = {};
106
108
  Error.captureStackTrace(stackHolder, levelLabel);
107
- let sanitizedStack = sc.cleanMessage(stackHolder.stack, this.maxStackTraceLength);
108
- this.logWithCallback(sanitizedStack);
109
+ let processedStack = stackHolder.stack;
110
+ if(this.applySanitizer){
111
+ processedStack = sc.cleanMessage(stackHolder.stack, this.maxStackTraceLength);
112
+ }
113
+ this.logWithCallback(processedStack);
109
114
  }
110
115
  return this;
111
116
  }
package/lib/shortcuts.js CHANGED
@@ -62,6 +62,11 @@ class Shortcuts
62
62
  return this.isObject(obj) && property && 'function' === typeof obj[property];
63
63
  }
64
64
 
65
+ isSymbol(value)
66
+ {
67
+ return 'symbol' === typeof value;
68
+ }
69
+
65
70
  isString(value)
66
71
  {
67
72
  return 'string' === typeof value;
@@ -93,6 +98,11 @@ class Shortcuts
93
98
  return 'boolean' === typeof value;
94
99
  }
95
100
 
101
+ isPromise(value)
102
+ {
103
+ return value && 'function' === typeof value.then;
104
+ }
105
+
96
106
  hasDangerousKeys(obj, key = null)
97
107
  {
98
108
  let dangerousKeys = ['__proto__', 'constructor', 'prototype'];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/utils",
3
3
  "scope": "@reldens",
4
- "version": "0.49.0",
4
+ "version": "0.51.0",
5
5
  "description": "Reldens - Utils",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -34,7 +34,8 @@
34
34
  "bugs": {
35
35
  "url": "https://github.com/damian-pastorini/reldens-utils/issues"
36
36
  },
37
- "dependencies": {
38
- "await-event-emitter": "2.0.2"
39
- }
37
+ "scripts": {
38
+ "test": "node tests/run.js"
39
+ },
40
+ "dependencies": {}
40
41
  }