@compilacion/colleciones-clientos 1.0.10 → 1.0.12

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,6 +2,12 @@
2
2
  'use strict';
3
3
 
4
4
  // helper 'private' functions
5
+ /**
6
+ * Converts a snake_case string to camelCase.
7
+ * Removes non-ASCII characters.
8
+ * @param {string} str
9
+ * @returns {string}
10
+ */
5
11
  const underscoreToCamelCase = function (str) {
6
12
  str = str.replace(/[^\x00-\x7F_]/g, '');
7
13
  return str
@@ -15,8 +21,14 @@
15
21
  .join('');
16
22
  };
17
23
 
24
+ /**
25
+ * Formats adjectives by converting them to camelCase strings.
26
+ * Accepts a single string or an array of strings.
27
+ * @param {string|string[]|undefined} adjectiveParameter
28
+ * @returns {string[]|undefined}
29
+ */
18
30
  const formatAdjectives = function (adjectiveParameter) {
19
- let errorMsg = new Error('Adjective must be a string, an array of strings, or undefined');
31
+ const errorMsg = new Error('Adjective must be a string, an array of strings, or undefined');
20
32
  if (typeof adjectiveParameter === 'undefined') {
21
33
  return undefined;
22
34
  }
@@ -24,18 +36,17 @@
24
36
  return [underscoreToCamelCase(adjectiveParameter)];
25
37
  }
26
38
  if (Array.isArray(adjectiveParameter) && adjectiveParameter.every(item => typeof item === 'string')) {
27
- let returnValue = [];
28
- adjectiveParameter.forEach((adjectiveParameter) => {
29
- if (!typeof item === 'string') {
30
- throw errorMsg;
31
- }
32
- returnValue.push(underscoreToCamelCase(adjectiveParameter));
33
- });
34
- return returnValue;
39
+ return adjectiveParameter.map(adjective => underscoreToCamelCase(adjective));
35
40
  }
36
41
  throw errorMsg;
37
42
  };
38
43
 
44
+ /**
45
+ * Converts an array of adjective strings to a dot-prefixed, dot-separated string.
46
+ * Removes duplicates and sorts alphabetically.
47
+ * @param {string[]} adjective
48
+ * @returns {string|undefined}
49
+ */
39
50
  const stringifyAdjectives = function (adjective) {
40
51
  if (!Array.isArray(adjective) || adjective.length === 0) {
41
52
  return undefined;
@@ -44,14 +55,25 @@
44
55
  return '.' + uniqueSorted.join('.');
45
56
  };
46
57
 
58
+ /**
59
+ * Encodes a string to Base64.
60
+ * Uses browser or Node.js method depending on environment.
61
+ * @param {string} str
62
+ * @returns {string}
63
+ */
47
64
  const toBase64 = function (str) {
48
- if (typeof window !== 'undefined' && window.btoa) {
65
+ if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
49
66
  return window.btoa(unescape(encodeURIComponent(str)));
50
67
  } else {
51
68
  return Buffer.from(str, 'utf-8').toString('base64');
52
69
  }
53
70
  };
54
71
 
72
+ /**
73
+ * Collects browser-related context values like screen size, language, etc.
74
+ * Returns an empty object if not in browser environment.
75
+ * @returns {Object}
76
+ */
55
77
  const getBrowserContext = function() {
56
78
  if (typeof window === 'undefined' || typeof navigator === 'undefined') {
57
79
  return {};
@@ -71,8 +93,14 @@
71
93
  };
72
94
  };
73
95
 
96
+ /**
97
+ * Base class representing a structured event with timestamp, identifiers, and data fields.
98
+ */
74
99
  class CollecionesBaseEvent {
75
100
 
101
+ /**
102
+ * Constructs a new event with default metadata and timestamps.
103
+ */
76
104
  constructor() {
77
105
  this.eventName = '';
78
106
  this.data = {};
@@ -80,59 +108,74 @@
80
108
  eventFormat: 'CollecionesBaseEvent',
81
109
  eventFormatVersion: '1'
82
110
  };
83
- const now = new Date();
84
111
  this.data.timestamps = {};
85
- this.data.timestamps.clientDatetimeUtc = now.toISOString();
112
+ this.data.timestamps.clientDatetimeUtc = new Date().toISOString();
86
113
  if (typeof window !== 'undefined' && typeof navigator !== 'undefined' && navigator.language) {
87
- this.data.timestamps.clientDatetimeLocal = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString();
114
+ this.data.timestamps.clientDatetimeLocal = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString();
88
115
  this.data.timestamps.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
116
+ this.data.timestamps.timeZoneOffset = new Date().getTimezoneOffset();
89
117
  } else {
90
- this.data.timestamps.clientDatetimeLocal = now.toISOString();
118
+ this.data.timestamps.clientDatetimeLocal = new Date().toISOString();
91
119
  this.data.timestamps.timeZone = 'UTC';
92
120
  }
93
121
  }
94
122
 
123
+ /**
124
+ * Sets the event name.
125
+ * @param {string} name
126
+ */
95
127
  setEventName(name) {
96
128
  this.eventName = name;
97
129
  }
98
130
 
131
+ /**
132
+ * Returns the event name.
133
+ * @returns {string}
134
+ */
99
135
  getEventName() {
100
136
  return this.eventName;
101
137
  }
102
138
 
139
+ /**
140
+ * Gets the format of the event.
141
+ * @returns {string}
142
+ */
103
143
  getEventFormat() {
104
144
  let v = this.data?.meta?.eventFormat;
105
145
  return (typeof v !== 'undefined') ? v : '1';
106
146
  }
107
147
 
148
+ /**
149
+ * Gets the version of the event format.
150
+ * @returns {string}
151
+ */
108
152
  getEventFormatVersion() {
109
153
  let v = this.data?.meta?.eventFormatVersion;
110
154
  return (typeof v !== 'undefined') ? v : 'CollecionesBaseEvent';
111
155
  }
112
156
 
113
- getPostObject() {
114
- const now = new Date();
115
- if (typeof navigator !== 'undefined' && navigator.language) {
116
- this.data.timestamps.sentDatetime = now.toLocaleString(navigator.language);
117
- } else {
118
- this.data.timestamps.sentDatetime = now.toISOString();
119
- }
120
- return {
121
- eventname: this.getEventName(),
122
- eventFormat: this.getEventFormat(),
123
- eventFormatVersion: this.getEventFormatVersion(),
124
- payload: toBase64(this.getPayload())
125
- };
126
- }
127
-
157
+ /**
158
+ * Replaces the entire data object.
159
+ * @param {object} data
160
+ */
128
161
  setData(data) {
129
162
  this.data = data;
130
163
  }
131
164
 
165
+ /**
166
+ * Adds a field to the event's custom fields section.
167
+ * @param {string} name
168
+ * @param {*} value
169
+ */
132
170
  addAttribute(name, value) {
133
171
  return this.addField(name, value);
134
172
  }
135
173
 
174
+ /**
175
+ * Adds a key-value pair to the custom fields.
176
+ * @param {string} name
177
+ * @param {*} value
178
+ */
136
179
  addField(name, value) {
137
180
  if (typeof this.data.fields !== 'object' || this.data.fields === null) {
138
181
  this.data.fields = {};
@@ -140,14 +183,26 @@
140
183
  this.data.fields[name] = value;
141
184
  }
142
185
 
186
+ /**
187
+ * Sets the name of the tracker used to generate the event.
188
+ * @param {string} name
189
+ */
143
190
  setTracker(name) {
144
191
  this.data.tracker = name;
145
192
  }
146
193
 
194
+ /**
195
+ * Sets the name of the application that created the event.
196
+ * @param {string} name
197
+ */
147
198
  setAppName(name) {
148
199
  this.data.appName = name;
149
200
  }
150
201
 
202
+ /**
203
+ * Adds multiple identifiers from an object.
204
+ * @param {object} param
205
+ */
151
206
  convertParamIdentifiers(param) {
152
207
  if (typeof param === 'object' && param !== null && !Array.isArray(param)) {
153
208
  for (const [key, value] of Object.entries(param)) {
@@ -156,6 +211,11 @@
156
211
  }
157
212
  }
158
213
 
214
+ /**
215
+ * Adds a single identifier to the event.
216
+ * @param {string} name
217
+ * @param {*} value
218
+ */
159
219
  addIdentifier(name, value) {
160
220
  if (typeof this.data.identifiers !== 'object' || this.data.identifiers === null) {
161
221
  this.data.identifiers = {};
@@ -163,14 +223,52 @@
163
223
  this.data.identifiers[name] = value;
164
224
  }
165
225
 
226
+ /**
227
+ * Constructs the postable event object with metadata and encoded payload.
228
+ * @returns {object}
229
+ */
230
+ getPostObject() {
231
+ this.data.timestamps.sendDatetimeUtc = new Date().toISOString();
232
+ this.data.timestamps.sendDatetimeLocal = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString();
233
+ return {
234
+ eventname: this.getEventName(),
235
+ eventFormat: this.getEventFormat(),
236
+ eventFormatVersion: this.getEventFormatVersion(),
237
+ payload: toBase64(this.getPayload())
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Overrides or extends the timestamp fields of the event.
243
+ * @param {object} dateTimeObject
244
+ */
245
+ overrideDatetime(dateTimeObject = {}) {
246
+ for (const [key, value] of Object.entries(dateTimeObject)) {
247
+ this.data.timestamps[key] = value;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Serializes the event data to a JSON string.
253
+ * @returns {string}
254
+ */
166
255
  getPayload() {
167
256
  return JSON.stringify(this.data);
168
257
  }
169
258
 
170
259
  }
171
260
 
261
+ /**
262
+ * Emitter class responsible for batching and sending events to a configured endpoint.
263
+ */
172
264
  class CollecionesEmitter {
173
265
 
266
+ /**
267
+ * Initializes the emitter with buffering settings.
268
+ * @param {string} [endpoint='/collect'] - The URL to send events to.
269
+ * @param {number} [flushSize=10] - Number of events to buffer before flushing.
270
+ * @param {number|boolean} [flushInterval=false] - Time in ms to flush events periodically.
271
+ */
174
272
  constructor(endpoint = '/collect', flushSize = 10, flushInterval = false) {
175
273
  this.endpoint = endpoint;
176
274
  this.flushInterval = flushInterval;
@@ -179,6 +277,9 @@
179
277
  this.timer = null;
180
278
  }
181
279
 
280
+ /**
281
+ * Starts the flush timer if a valid interval is set.
282
+ */
182
283
  startTimer() {
183
284
  this.stopTimer();
184
285
  if (typeof this.flushInterval == 'number' && this.flushInterval > 0) {
@@ -186,12 +287,18 @@
186
287
  }
187
288
  }
188
289
 
290
+ /**
291
+ * Starts the flush timer only if not already running.
292
+ */
189
293
  startTimerIfStopped() {
190
294
  if (!this.timer) {
191
295
  this.startTimer();
192
296
  }
193
297
  }
194
298
 
299
+ /**
300
+ * Stops the active flush timer.
301
+ */
195
302
  stopTimer() {
196
303
  if (this.timer) {
197
304
  clearInterval(this.timer);
@@ -199,6 +306,10 @@
199
306
  this.timer = null;
200
307
  }
201
308
 
309
+ /**
310
+ * Adds an event to the buffer and flushes if threshold is reached.
311
+ * @param {CollecionesBaseEvent} event
312
+ */
202
313
  track(event) {
203
314
  if (!(event instanceof CollecionesBaseEvent)) {
204
315
  throw new Error('Event must be an instance of CollecionesBaseEvent');
@@ -206,6 +317,11 @@
206
317
  this.trackAsync(event);
207
318
  }
208
319
 
320
+ /**
321
+ * Asynchronously adds an event and flushes if the buffer size is exceeded.
322
+ * @param {CollecionesBaseEvent} event
323
+ * @returns {Promise<void>}
324
+ */
209
325
  async trackAsync(event) {
210
326
  if (!(event instanceof CollecionesBaseEvent)) {
211
327
  throw new Error('Event must be an instance of CollecionesBaseEvent');
@@ -215,6 +331,10 @@
215
331
  return (this.buffer.length >= this.flushSize) ? this.flush() : Promise.resolve();
216
332
  }
217
333
 
334
+ /**
335
+ * Sends the buffered events to the server and clears the buffer.
336
+ * @returns {Promise<boolean|undefined>}
337
+ */
218
338
  async flush() {
219
339
  if (this.buffer.length === 0) return;
220
340
  const eventsToSend = [...this.buffer];
@@ -243,8 +363,17 @@
243
363
  }
244
364
  }
245
365
 
366
+ /**
367
+ * Tracks events by enriching them with shared identifiers and routing through configured emitters.
368
+ */
246
369
  class CollecionesTracker {
247
-
370
+
371
+ /**
372
+ * Constructs a new tracker instance.
373
+ * @param {Array} emitters - Array of emitter instances responsible for sending events.
374
+ * @param {string} trackerName - Name identifying this tracker.
375
+ * @param {string} appName - Name of the application generating events.
376
+ */
248
377
  constructor(emitters, trackerName, appName) {
249
378
  this.emitters = emitters;
250
379
  this.trackerName = trackerName;
@@ -252,10 +381,20 @@
252
381
  this.identifiers = {};
253
382
  }
254
383
 
384
+ /**
385
+ * Adds a global identifier to be included with every event.
386
+ * @param {string} name - Identifier key.
387
+ * @param {*} value - Identifier value.
388
+ */
255
389
  addIdentifier(name, value) {
256
390
  this.identifiers[name] = value;
257
391
  }
258
392
 
393
+ /**
394
+ * Sends an event to all emitters after enriching it with identifiers and metadata.
395
+ * @param {CollecionesBaseEvent} collecionesEvent - The event to be sent.
396
+ * @throws {Error} If the input is not an instance of CollecionesBaseEvent.
397
+ */
259
398
  track(collecionesEvent) {
260
399
  if (!(collecionesEvent instanceof CollecionesBaseEvent)) {
261
400
  throw new Error('Event must be of type CollecionesEvent');
@@ -268,16 +407,29 @@
268
407
  this.emitters.forEach(element => {
269
408
  element.track(collecionesEvent, this.trackerName, this.appName);
270
409
  });
271
-
272
410
  }
273
411
  }
274
412
 
413
+ /**
414
+ * Web-specific tracker that enriches events with browser context before sending them.
415
+ * Extends the base CollecionesTracker.
416
+ */
275
417
  class CollecionesWebTracker extends CollecionesTracker {
276
-
418
+ /**
419
+ * Creates a new instance of CollecionesWebTracker.
420
+ * @param {Array} emitters - A list of emitter instances used to send the event.
421
+ * @param {string} trackerName - The name of the tracker.
422
+ * @param {string} appName - The name of the application generating events.
423
+ */
277
424
  constructor(emitters, trackerName, appName) {
278
425
  super(emitters, trackerName, appName);
279
426
  }
280
427
 
428
+ /**
429
+ * Tracks an event, enriching it with browser context information.
430
+ * @param {CollecionesBaseEvent} collecionesEvent - The event object to track.
431
+ * @throws {Error} If the event is not an instance of CollecionesBaseEvent.
432
+ */
281
433
  track(collecionesEvent) {
282
434
  if (!(collecionesEvent instanceof CollecionesBaseEvent)) {
283
435
  throw new Error('Event must be of type CollecionesEvent');
@@ -287,8 +439,18 @@
287
439
  }
288
440
  }
289
441
 
442
+ /**
443
+ * Represents a semantic event with a name, data payload, and identifiers.
444
+ * Extends CollecionesBaseEvent.
445
+ */
290
446
  class CollecionesEvent extends CollecionesBaseEvent {
291
447
 
448
+ /**
449
+ * Constructs a new CollecionesEvent instance.
450
+ * @param {string} name - The name of the event.
451
+ * @param {object} data - The main data payload of the event.
452
+ * @param {object} identifiers - A set of identifiers to associate with the event.
453
+ */
292
454
  constructor(name, data, identifiers) {
293
455
  super();
294
456
  this.setEventName(name);
@@ -354,8 +516,20 @@
354
516
  return unsafeStringify(rnds);
355
517
  }
356
518
 
519
+ /**
520
+ * Represents a structured semantic event with entity, action, and optional adjective.
521
+ * Includes automatic naming and identifier mapping.
522
+ * Extends CollecionesBaseEvent.
523
+ */
357
524
  class CollecionesSemanticEvent extends CollecionesBaseEvent {
358
525
 
526
+ /**
527
+ * Constructs a new semantic event.
528
+ * @param {string} entity - The entity involved in the event.
529
+ * @param {string} action - The action performed on the entity.
530
+ * @param {string|string[]|undefined} adjective - Optional adjective(s) modifying the event.
531
+ * @param {object} identifiers - Key-value pairs to be added as identifiers.
532
+ */
359
533
  constructor(entity, action, adjective, identifiers) {
360
534
  super();
361
535
  this.entity = underscoreToCamelCase(entity);
@@ -373,10 +547,20 @@
373
547
  };
374
548
  }
375
549
 
550
+ /**
551
+ * This method is deprecated in semantic events and will throw if called.
552
+ * @throws {Error}
553
+ */
376
554
  setData() {
377
555
  throw new Error('setData deprecated in semantic events');
378
556
  }
379
557
 
558
+ /**
559
+ * Adds a key-value pair as an identifier for the semantic entity.
560
+ * @param {string} key - The identifier name.
561
+ * @param {string|number} value - The identifier value.
562
+ * @throws Will throw if key is not a string or value is not a string or number.
563
+ */
380
564
  addEntityIdentifier(key, value) {
381
565
  if (typeof key !== 'string') {
382
566
  throw new Error('Entity identifier key must be a string');
@@ -389,10 +573,22 @@
389
573
 
390
574
  }
391
575
 
576
+ /**
577
+ * Represents a semantic event related to a collection of entities.
578
+ * Extends CollecionesSemanticEvent and adds item-level detail and identifiers.
579
+ */
392
580
  class CollecionesSemanticCollectionEvent extends CollecionesSemanticEvent {
393
581
 
582
+ /**
583
+ * Constructs a new CollecionesSemanticCollectionEvent.
584
+ * @param {string} itemEntity - The name of the item entity being acted on.
585
+ * @param {string} action - The action performed.
586
+ * @param {string|string[]|undefined} adjective - Optional adjective(s) describing the action.
587
+ * @param {object} identifiers - Identifiers associated with the event.
588
+ * @param {string} [collectionEntity] - Optional collection name, defaults to `${itemEntity}Collection`.
589
+ */
394
590
  constructor(itemEntity, action, adjective, identifiers, collectionEntity) {
395
- if(typeof collectionEntity == 'undefined') {
591
+ if (typeof collectionEntity == 'undefined') {
396
592
  collectionEntity = `${itemEntity}Collection`;
397
593
  }
398
594
  super(collectionEntity, action, undefined, identifiers);
@@ -408,6 +604,12 @@
408
604
  this.data.entityItemIdentifiers = {};
409
605
  }
410
606
 
607
+ /**
608
+ * Adds an identifier value (or values) for a specific entity item key.
609
+ * @param {string} key - The identifier name.
610
+ * @param {string|number|Array<string|number>} value - The identifier value(s).
611
+ * @throws Will throw if key is not a string or value is of invalid type.
612
+ */
411
613
  addEntityItemIdentifier(key, value) {
412
614
  if (typeof key !== 'string') {
413
615
  throw new Error('Entity identifier key must be a string');
@@ -415,11 +617,11 @@
415
617
  if (typeof value !== 'string' && typeof value !== 'number' && !Array.isArray(value)) {
416
618
  throw new Error('Entity identifier value must be a string or number');
417
619
  }
418
- if(typeof this.data.entityItemIdentifiers[key] == 'undefined') {
620
+ if (typeof this.data.entityItemIdentifiers[key] == 'undefined') {
419
621
  this.data.entityItemIdentifiers[key] = [];
420
622
  }
421
- if(Array.isArray(value)) {
422
- value.forEach((v)=> {
623
+ if (Array.isArray(value)) {
624
+ value.forEach((v) => {
423
625
  this.data.entityItemIdentifiers[key].push(v);
424
626
  });
425
627
  } else {
@@ -427,7 +629,6 @@
427
629
  }
428
630
  }
429
631
 
430
-
431
632
  }
432
633
 
433
634
  (function(global) {