@compilacion/colleciones-clientos 1.0.12 → 2.0.1
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/README.md +45 -83
- package/dist/collecionesClientos.iife.js +433 -345
- package/dist/collecionesClientos.iife.js.map +1 -1
- package/dist/index.cjs +433 -345
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +433 -344
- package/dist/index.mjs.map +1 -1
- package/docs/architecture.md +50 -0
- package/docs/concept.md +65 -0
- package/docs/dsl.md +92 -0
- package/docs/emitterAndTracker.md +63 -0
- package/docs/events.md +68 -0
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -1,58 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// helper 'private' functions
|
|
4
|
-
/**
|
|
5
|
-
* Converts a snake_case string to camelCase.
|
|
6
|
-
* Removes non-ASCII characters.
|
|
7
|
-
* @param {string} str
|
|
8
|
-
* @returns {string}
|
|
9
|
-
*/
|
|
10
|
-
const underscoreToCamelCase = function (str) {
|
|
11
|
-
str = str.replace(/[^\x00-\x7F_]/g, '');
|
|
12
|
-
return str
|
|
13
|
-
.split('_')
|
|
14
|
-
.map((word, index) => {
|
|
15
|
-
if (index === 0) {
|
|
16
|
-
return word;
|
|
17
|
-
}
|
|
18
|
-
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
19
|
-
})
|
|
20
|
-
.join('');
|
|
21
|
-
};
|
|
22
4
|
|
|
23
|
-
/**
|
|
24
|
-
* Formats adjectives by converting them to camelCase strings.
|
|
25
|
-
* Accepts a single string or an array of strings.
|
|
26
|
-
* @param {string|string[]|undefined} adjectiveParameter
|
|
27
|
-
* @returns {string[]|undefined}
|
|
28
|
-
*/
|
|
29
|
-
const formatAdjectives = function (adjectiveParameter) {
|
|
30
|
-
const errorMsg = new Error('Adjective must be a string, an array of strings, or undefined');
|
|
31
|
-
if (typeof adjectiveParameter === 'undefined') {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
if (typeof adjectiveParameter === 'string') {
|
|
35
|
-
return [underscoreToCamelCase(adjectiveParameter)];
|
|
36
|
-
}
|
|
37
|
-
if (Array.isArray(adjectiveParameter) && adjectiveParameter.every(item => typeof item === 'string')) {
|
|
38
|
-
return adjectiveParameter.map(adjective => underscoreToCamelCase(adjective));
|
|
39
|
-
}
|
|
40
|
-
throw errorMsg;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Converts an array of adjective strings to a dot-prefixed, dot-separated string.
|
|
45
|
-
* Removes duplicates and sorts alphabetically.
|
|
46
|
-
* @param {string[]} adjective
|
|
47
|
-
* @returns {string|undefined}
|
|
48
|
-
*/
|
|
49
|
-
const stringifyAdjectives = function (adjective) {
|
|
50
|
-
if (!Array.isArray(adjective) || adjective.length === 0) {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
const uniqueSorted = [...new Set(adjective)].sort();
|
|
54
|
-
return '.' + uniqueSorted.join('.');
|
|
55
|
-
};
|
|
56
5
|
|
|
57
6
|
/**
|
|
58
7
|
* Encodes a string to Base64.
|
|
@@ -94,171 +43,264 @@ const getBrowserContext = function() {
|
|
|
94
43
|
|
|
95
44
|
/**
|
|
96
45
|
* Base class representing a structured event with timestamp, identifiers, and data fields.
|
|
46
|
+
* This class is used to model events in a semantic and structured format for tracking purposes.
|
|
97
47
|
*/
|
|
98
|
-
class
|
|
48
|
+
class CollecionesEvent {
|
|
99
49
|
|
|
100
50
|
/**
|
|
101
51
|
* Constructs a new event with default metadata and timestamps.
|
|
102
52
|
*/
|
|
103
53
|
constructor() {
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
this.
|
|
107
|
-
|
|
54
|
+
// initialize event properties
|
|
55
|
+
this.entity = '';
|
|
56
|
+
this.adjevtives = [];
|
|
57
|
+
this.identifiers = {};
|
|
58
|
+
this.action = '';
|
|
59
|
+
this.actor = {
|
|
60
|
+
name:'',
|
|
61
|
+
identifiers: []
|
|
62
|
+
};
|
|
63
|
+
this.context = {};
|
|
64
|
+
this.references = {};
|
|
65
|
+
// set default values
|
|
66
|
+
this.meta = {
|
|
67
|
+
eventFormat: 'CollecionesEvent',
|
|
108
68
|
eventFormatVersion: '1'
|
|
109
69
|
};
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
70
|
+
this.meta.tracker = '';
|
|
71
|
+
this.meta.app = '';
|
|
72
|
+
this.meta.timestamps = {};
|
|
73
|
+
this.meta.timestamps.clientDatetimeUtc = new Date().toISOString();
|
|
112
74
|
if (typeof window !== 'undefined' && typeof navigator !== 'undefined' && navigator.language) {
|
|
113
|
-
this.
|
|
114
|
-
this.
|
|
115
|
-
this.
|
|
75
|
+
this.meta.timestamps.clientDatetimeLocal = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString();
|
|
76
|
+
this.meta.timestamps.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
77
|
+
this.meta.timestamps.timeZoneOffset = new Date().getTimezoneOffset();
|
|
116
78
|
} else {
|
|
117
|
-
this.
|
|
118
|
-
this.
|
|
79
|
+
this.meta.timestamps.clientDatetimeLocal = new Date().toISOString();
|
|
80
|
+
this.meta.timestamps.timeZone = 'UTC';
|
|
119
81
|
}
|
|
120
82
|
}
|
|
121
83
|
|
|
122
84
|
/**
|
|
123
|
-
*
|
|
124
|
-
* @
|
|
85
|
+
* Returns the declared event format.
|
|
86
|
+
* @returns {string} The format name (e.g. 'CollecionesEvent').
|
|
125
87
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
88
|
+
getEventFormat() {
|
|
89
|
+
let v = this.meta?.eventFormat;
|
|
90
|
+
return (typeof v !== 'undefined') ? v : '1';
|
|
128
91
|
}
|
|
129
92
|
|
|
130
93
|
/**
|
|
131
|
-
* Returns the event
|
|
132
|
-
* @returns {string}
|
|
94
|
+
* Returns the version of the event format.
|
|
95
|
+
* @returns {string} The format version string.
|
|
133
96
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
97
|
+
getEventFormatVersion() {
|
|
98
|
+
let v = this?.meta?.eventFormatVersion;
|
|
99
|
+
return (typeof v !== 'undefined') ? v : 'CollecionesEvent';
|
|
136
100
|
}
|
|
137
101
|
|
|
138
102
|
/**
|
|
139
|
-
*
|
|
140
|
-
* @
|
|
103
|
+
* Overrides or supplements the event's timestamp fields with custom values.
|
|
104
|
+
* @param {object} dateTimeObject - Key-value pairs to merge into the existing timestamp object.
|
|
141
105
|
*/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
106
|
+
overrideDatetime(dateTimeObject = {}) {
|
|
107
|
+
for (const [key, value] of Object.entries(dateTimeObject)) {
|
|
108
|
+
this.meta.timestamps[key] = value;
|
|
109
|
+
}
|
|
145
110
|
}
|
|
146
111
|
|
|
147
112
|
/**
|
|
148
|
-
*
|
|
149
|
-
* @
|
|
113
|
+
* Sets the name of the tracker responsible for generating this event.
|
|
114
|
+
* @param {string} name - Tracker name.
|
|
150
115
|
*/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return (typeof v !== 'undefined') ? v : 'CollecionesBaseEvent';
|
|
116
|
+
setTracker(name) {
|
|
117
|
+
this.meta.tracker = name;
|
|
154
118
|
}
|
|
155
119
|
|
|
156
120
|
/**
|
|
157
|
-
*
|
|
158
|
-
* @param {
|
|
121
|
+
* Sets the name of the application that generated the event.
|
|
122
|
+
* @param {string} name - Application name.
|
|
159
123
|
*/
|
|
160
|
-
|
|
161
|
-
this.
|
|
124
|
+
setAppName(name) {
|
|
125
|
+
this.meta.appName = name;
|
|
162
126
|
}
|
|
163
127
|
|
|
164
128
|
/**
|
|
165
|
-
*
|
|
166
|
-
* @param {string} name
|
|
167
|
-
* @param {*} value
|
|
129
|
+
* Sets the expected schema name for this event.
|
|
130
|
+
* @param {string} schema - The name of the schema.
|
|
168
131
|
*/
|
|
169
|
-
|
|
170
|
-
|
|
132
|
+
setSchema(schema) {
|
|
133
|
+
if (typeof schema !== 'string') {
|
|
134
|
+
throw new Error('Schema must be a string');
|
|
135
|
+
}
|
|
136
|
+
this.meta.schema = schema;
|
|
171
137
|
}
|
|
172
138
|
|
|
173
139
|
/**
|
|
174
|
-
*
|
|
175
|
-
* @param {string} name
|
|
176
|
-
* @param {*} value
|
|
140
|
+
* Sets the entity (subject) of the event.
|
|
141
|
+
* @param {string} entity - The entity name.
|
|
177
142
|
*/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
143
|
+
setEntity = function(entity) {
|
|
144
|
+
this.entity = entity;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Defines the main action of the event (e.g., 'clicked').
|
|
149
|
+
* @param {string} action - The action string.
|
|
150
|
+
*/
|
|
151
|
+
setAction = function(action) {
|
|
152
|
+
this.action = action;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Adds an adjective that describes the entity in more detail.
|
|
157
|
+
* @param {string} adjective - An adjective string.
|
|
158
|
+
*/
|
|
159
|
+
addAdjective = function(adjective) {
|
|
160
|
+
if (typeof adjective !== 'string') {
|
|
161
|
+
throw new Error('Adjective must be a string');
|
|
181
162
|
}
|
|
182
|
-
this.
|
|
163
|
+
this.adjevtives.push(adjective);
|
|
183
164
|
}
|
|
184
165
|
|
|
185
166
|
/**
|
|
186
|
-
*
|
|
187
|
-
* @param {string} name
|
|
167
|
+
* Adds or updates an identifier for the primary entity.
|
|
168
|
+
* @param {string} name - The identifier key.
|
|
169
|
+
* @param {*} identifier - The identifier value.
|
|
188
170
|
*/
|
|
189
|
-
|
|
190
|
-
|
|
171
|
+
setIdentifier = function(name, identifier) {
|
|
172
|
+
if (typeof name !== 'string') {
|
|
173
|
+
throw new Error('Identifier name must be a string');
|
|
174
|
+
}
|
|
175
|
+
this.identifiers[name] = identifier;
|
|
191
176
|
}
|
|
192
177
|
|
|
193
178
|
/**
|
|
194
|
-
*
|
|
195
|
-
* @param {string} name
|
|
179
|
+
* Defines the name of the actor (who or what performed the action).
|
|
180
|
+
* @param {string} name - Actor name (e.g., 'user').
|
|
196
181
|
*/
|
|
197
|
-
|
|
198
|
-
|
|
182
|
+
setActor = function(name) {
|
|
183
|
+
if (typeof name !== 'string') {
|
|
184
|
+
throw new Error('Actor name must be a string');
|
|
185
|
+
}
|
|
186
|
+
this.actor.name = name;
|
|
199
187
|
}
|
|
200
188
|
|
|
201
189
|
/**
|
|
202
|
-
* Adds
|
|
203
|
-
* @param {
|
|
190
|
+
* Adds or updates an identifier for the actor.
|
|
191
|
+
* @param {string} name - Identifier name.
|
|
192
|
+
* @param {*} identifier - Identifier value.
|
|
204
193
|
*/
|
|
205
|
-
|
|
206
|
-
if (typeof
|
|
207
|
-
|
|
208
|
-
this.addIdentifier(key, value);
|
|
209
|
-
}
|
|
194
|
+
setActorIdentifier = function(name, identifier) {
|
|
195
|
+
if (typeof name !== 'string') {
|
|
196
|
+
throw new Error('Actor Identifier name must be a string');
|
|
210
197
|
}
|
|
198
|
+
this.actor.identifiers[name] = identifier;
|
|
211
199
|
}
|
|
212
200
|
|
|
213
201
|
/**
|
|
214
|
-
* Adds
|
|
215
|
-
* @param {string}
|
|
216
|
-
* @param {*} value
|
|
202
|
+
* Adds contextual information to the event.
|
|
203
|
+
* @param {string} context - The key of the context field.
|
|
204
|
+
* @param {*} value - The value of the context field.
|
|
217
205
|
*/
|
|
218
|
-
|
|
219
|
-
if (typeof
|
|
220
|
-
|
|
206
|
+
setContext = function(context, value) {
|
|
207
|
+
if (typeof context !== 'string') {
|
|
208
|
+
throw new Error('Context must be a string');
|
|
221
209
|
}
|
|
222
|
-
this.
|
|
210
|
+
this.context[context] = value;
|
|
223
211
|
}
|
|
224
212
|
|
|
225
213
|
/**
|
|
226
|
-
*
|
|
227
|
-
* @
|
|
214
|
+
* Declares an entity referenced by this event.
|
|
215
|
+
* @param {string} entity - The referenced entity name.
|
|
228
216
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
payload: toBase64(this.getPayload())
|
|
237
|
-
};
|
|
217
|
+
setRefence = function(entity) {
|
|
218
|
+
if (typeof entity !== 'string') {
|
|
219
|
+
throw new Error('Referenced entity must be a string');
|
|
220
|
+
}
|
|
221
|
+
if(this.references[entity] === undefined) {
|
|
222
|
+
this.references[entity] = {identifiers: {}};
|
|
223
|
+
}
|
|
238
224
|
}
|
|
239
225
|
|
|
240
226
|
/**
|
|
241
|
-
*
|
|
242
|
-
* @param {
|
|
227
|
+
* Adds or updates an identifier for a referenced entity.
|
|
228
|
+
* @param {string} entity - The referenced entity name.
|
|
229
|
+
* @param {string} name - The identifier key.
|
|
230
|
+
* @param {*} identifier - The identifier value.
|
|
243
231
|
*/
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
232
|
+
setRefenceIdentifier = function(entity, name, identifier) {
|
|
233
|
+
if (typeof entity !== 'string') {
|
|
234
|
+
throw new Error('Referenced entity name must be a string');
|
|
235
|
+
}
|
|
236
|
+
if (typeof name !== 'string') {
|
|
237
|
+
throw new Error('Actor Identifier name must be a string');
|
|
247
238
|
}
|
|
239
|
+
this.setRefence(entity);
|
|
240
|
+
this.references[entity].identifiers[name] = identifier;
|
|
248
241
|
}
|
|
249
242
|
|
|
250
243
|
/**
|
|
251
|
-
* Serializes the event
|
|
252
|
-
* @returns {
|
|
244
|
+
* Serializes the event to a plain object suitable for JSON.stringify().
|
|
245
|
+
* @returns {object}
|
|
253
246
|
*/
|
|
254
|
-
|
|
255
|
-
return
|
|
247
|
+
toJSON() {
|
|
248
|
+
return {
|
|
249
|
+
class: 'CollecionesEvent',
|
|
250
|
+
entity: this.entity,
|
|
251
|
+
adjectives: this.adjevtives,
|
|
252
|
+
identifiers: this.identifiers,
|
|
253
|
+
action: this.action,
|
|
254
|
+
actor: this.actor,
|
|
255
|
+
context: this.context,
|
|
256
|
+
references: this.references,
|
|
257
|
+
meta: this.meta
|
|
258
|
+
};
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Recreates an instance of CollecionesEvent from a plain object.
|
|
263
|
+
* @param {object} obj
|
|
264
|
+
* @returns {CollecionesEvent}
|
|
265
|
+
*/
|
|
266
|
+
static fromJSON(obj) {
|
|
267
|
+
if (!obj || obj.class !== 'CollecionesEvent') {
|
|
268
|
+
throw new Error('Invalid or missing event class type');
|
|
269
|
+
}
|
|
270
|
+
const instance = new CollecionesEvent();
|
|
271
|
+
instance.entity = obj.entity;
|
|
272
|
+
instance.adjevtives = obj.adjectives || [];
|
|
273
|
+
instance.identifiers = obj.identifiers || {};
|
|
274
|
+
instance.action = obj.action;
|
|
275
|
+
instance.actor = obj.actor || { name: '', identifiers: {} };
|
|
276
|
+
instance.context = obj.context || {};
|
|
277
|
+
instance.references = obj.references || {};
|
|
278
|
+
instance.meta = obj.meta || {};
|
|
279
|
+
return instance;
|
|
280
|
+
}
|
|
281
|
+
|
|
258
282
|
}
|
|
259
283
|
|
|
260
284
|
/**
|
|
261
|
-
*
|
|
285
|
+
* CollecionesEmitter
|
|
286
|
+
* -------------------
|
|
287
|
+
* This class collects and sends event objects to a remote endpoint (e.g., an event collector API).
|
|
288
|
+
* It is used in both client- and server-side tracking scenarios, typically in combination with
|
|
289
|
+
* CollecionesEvent or subclasses like CollecionesBaseEvent.
|
|
290
|
+
*
|
|
291
|
+
* Behavior:
|
|
292
|
+
* - Events are buffered via `.track()` or `.trackAsync()`.
|
|
293
|
+
* - If the number of buffered events reaches `flushSize`, the buffer is automatically flushed.
|
|
294
|
+
* - If `flushInterval` is provided, the buffer is flushed at a fixed interval.
|
|
295
|
+
* - Flushing serializes each event using `.toJSON()`, encodes it with base64, and posts
|
|
296
|
+
* the resulting array to the configured endpoint.
|
|
297
|
+
*
|
|
298
|
+
* Example usage:
|
|
299
|
+
* const emitter = new CollecionesEmitter('/track', 5, 10000);
|
|
300
|
+
* emitter.track(new CollecionesEvent());
|
|
301
|
+
*
|
|
302
|
+
* Note:
|
|
303
|
+
* This class is stateful. To stop periodic flushing, call `.stopTimer()` when the emitter is no longer in use.
|
|
262
304
|
*/
|
|
263
305
|
class CollecionesEmitter {
|
|
264
306
|
|
|
@@ -278,6 +320,7 @@ class CollecionesEmitter {
|
|
|
278
320
|
|
|
279
321
|
/**
|
|
280
322
|
* Starts the flush timer if a valid interval is set.
|
|
323
|
+
* @returns {void}
|
|
281
324
|
*/
|
|
282
325
|
startTimer() {
|
|
283
326
|
this.stopTimer();
|
|
@@ -288,6 +331,7 @@ class CollecionesEmitter {
|
|
|
288
331
|
|
|
289
332
|
/**
|
|
290
333
|
* Starts the flush timer only if not already running.
|
|
334
|
+
* @returns {void}
|
|
291
335
|
*/
|
|
292
336
|
startTimerIfStopped() {
|
|
293
337
|
if (!this.timer) {
|
|
@@ -297,6 +341,7 @@ class CollecionesEmitter {
|
|
|
297
341
|
|
|
298
342
|
/**
|
|
299
343
|
* Stops the active flush timer.
|
|
344
|
+
* @returns {void}
|
|
300
345
|
*/
|
|
301
346
|
stopTimer() {
|
|
302
347
|
if (this.timer) {
|
|
@@ -307,23 +352,28 @@ class CollecionesEmitter {
|
|
|
307
352
|
|
|
308
353
|
/**
|
|
309
354
|
* Adds an event to the buffer and flushes if threshold is reached.
|
|
310
|
-
*
|
|
355
|
+
* Validates that the event is an instance of CollecionesEvent to ensure correct event type.
|
|
356
|
+
* @param {CollecionesEvent} event - Event instance to be tracked.
|
|
357
|
+
* @throws {Error} Throws if event is not a CollecionesEvent instance.
|
|
358
|
+
* @returns {void}
|
|
311
359
|
*/
|
|
312
360
|
track(event) {
|
|
313
|
-
if (!(event instanceof
|
|
314
|
-
throw new Error('Event must be an instance of
|
|
361
|
+
if (!(event instanceof CollecionesEvent)) {
|
|
362
|
+
throw new Error('Event must be an instance of CollecionesEvent');
|
|
315
363
|
}
|
|
316
364
|
this.trackAsync(event);
|
|
317
365
|
}
|
|
318
366
|
|
|
319
367
|
/**
|
|
320
368
|
* Asynchronously adds an event and flushes if the buffer size is exceeded.
|
|
321
|
-
*
|
|
322
|
-
* @
|
|
369
|
+
* Validates that the event is an instance of CollecionesEvent to ensure correct event type.
|
|
370
|
+
* @param {CollecionesEvent} event - Event instance to be tracked asynchronously.
|
|
371
|
+
* @throws {Error} Throws if event is not a CollecionesEvent instance.
|
|
372
|
+
* @returns {Promise<void>} Resolves when event is added and flush triggered if needed.
|
|
323
373
|
*/
|
|
324
374
|
async trackAsync(event) {
|
|
325
|
-
if (!(event instanceof
|
|
326
|
-
throw new Error('Event must be an instance of
|
|
375
|
+
if (!(event instanceof CollecionesEvent)) {
|
|
376
|
+
throw new Error('Event must be an instance of CollecionesEvent');
|
|
327
377
|
}
|
|
328
378
|
this.startTimerIfStopped();
|
|
329
379
|
this.buffer.push(event);
|
|
@@ -331,18 +381,16 @@ class CollecionesEmitter {
|
|
|
331
381
|
}
|
|
332
382
|
|
|
333
383
|
/**
|
|
334
|
-
* Sends
|
|
335
|
-
*
|
|
384
|
+
* Sends all buffered events in a single POST request to the server.
|
|
385
|
+
* Each event is serialized via `.toJSON()` and encoded as a base64 string.
|
|
386
|
+
* Response status is checked for HTTP success; errors are logged but not thrown.
|
|
387
|
+
*
|
|
388
|
+
* @returns {Promise<boolean|undefined>} Resolves true if flush was triggered, or undefined if buffer was empty.
|
|
336
389
|
*/
|
|
337
390
|
async flush() {
|
|
338
391
|
if (this.buffer.length === 0) return;
|
|
339
|
-
const eventsToSend = [...this.buffer];
|
|
340
|
-
this.buffer = [];
|
|
341
|
-
let postPayload = [];
|
|
342
|
-
eventsToSend.forEach((e) => {
|
|
343
|
-
postPayload.push( e.getPostObject() );
|
|
344
|
-
});
|
|
345
392
|
this.stopTimer();
|
|
393
|
+
let body = this.getBody();
|
|
346
394
|
try {
|
|
347
395
|
const response = await fetch(this.endpoint, {
|
|
348
396
|
method: 'POST',
|
|
@@ -350,7 +398,7 @@ class CollecionesEmitter {
|
|
|
350
398
|
headers: {
|
|
351
399
|
'Content-Type': 'application/json'
|
|
352
400
|
},
|
|
353
|
-
body
|
|
401
|
+
body
|
|
354
402
|
});
|
|
355
403
|
if (!response.ok) {
|
|
356
404
|
console.log(`Failed to send events: ${response.statusText}`);
|
|
@@ -360,6 +408,16 @@ class CollecionesEmitter {
|
|
|
360
408
|
console.log(`Network error sending events: ${error.message}`);
|
|
361
409
|
}
|
|
362
410
|
}
|
|
411
|
+
|
|
412
|
+
getBody() {
|
|
413
|
+
const eventsToSend = [...this.buffer];
|
|
414
|
+
this.buffer = [];
|
|
415
|
+
let postPayload = [];
|
|
416
|
+
eventsToSend.forEach((e) => {
|
|
417
|
+
postPayload.push( toBase64( JSON.stringify(e.toJSON())) );
|
|
418
|
+
});
|
|
419
|
+
return JSON.stringify(postPayload);
|
|
420
|
+
}
|
|
363
421
|
}
|
|
364
422
|
|
|
365
423
|
/**
|
|
@@ -380,22 +438,13 @@ class CollecionesTracker {
|
|
|
380
438
|
this.identifiers = {};
|
|
381
439
|
}
|
|
382
440
|
|
|
383
|
-
/**
|
|
384
|
-
* Adds a global identifier to be included with every event.
|
|
385
|
-
* @param {string} name - Identifier key.
|
|
386
|
-
* @param {*} value - Identifier value.
|
|
387
|
-
*/
|
|
388
|
-
addIdentifier(name, value) {
|
|
389
|
-
this.identifiers[name] = value;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
441
|
/**
|
|
393
442
|
* Sends an event to all emitters after enriching it with identifiers and metadata.
|
|
394
|
-
* @param {
|
|
395
|
-
* @throws {Error} If the input is not an instance of
|
|
443
|
+
* @param {CollecionesEvent} collecionesEvent - The event to be sent.
|
|
444
|
+
* @throws {Error} If the input is not an instance of CollecionesEvent.
|
|
396
445
|
*/
|
|
397
446
|
track(collecionesEvent) {
|
|
398
|
-
if (!(collecionesEvent instanceof
|
|
447
|
+
if (!(collecionesEvent instanceof CollecionesEvent)) {
|
|
399
448
|
throw new Error('Event must be of type CollecionesEvent');
|
|
400
449
|
}
|
|
401
450
|
for (const [key, value] of Object.entries(this.identifiers)) {
|
|
@@ -426,214 +475,253 @@ class CollecionesWebTracker extends CollecionesTracker {
|
|
|
426
475
|
|
|
427
476
|
/**
|
|
428
477
|
* Tracks an event, enriching it with browser context information.
|
|
429
|
-
* @param {
|
|
430
|
-
* @throws {Error} If the event is not an instance of
|
|
478
|
+
* @param {CollecionesEvent} collecionesEvent - The event object to track.
|
|
479
|
+
* @throws {Error} If the event is not an instance of CollecionesEvent.
|
|
431
480
|
*/
|
|
432
481
|
track(collecionesEvent) {
|
|
433
|
-
if (!(collecionesEvent instanceof
|
|
482
|
+
if (!(collecionesEvent instanceof CollecionesEvent)) {
|
|
434
483
|
throw new Error('Event must be of type CollecionesEvent');
|
|
435
484
|
}
|
|
436
|
-
collecionesEvent.
|
|
485
|
+
collecionesEvent.setContext('browserContext', getBrowserContext());
|
|
437
486
|
super.track(collecionesEvent);
|
|
438
487
|
}
|
|
439
488
|
}
|
|
440
489
|
|
|
441
490
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
491
|
+
* Colleciones DSL
|
|
492
|
+
* ---------------
|
|
493
|
+
* This module provides a fluent, human-readable DSL (domain-specific language) for constructing
|
|
494
|
+
* structured tracking events based on CollecionesBaseEvent. Each step in the chain represents
|
|
495
|
+
* a semantically meaningful piece of the event: the entity, the action, the actor, references, etc.
|
|
496
|
+
*
|
|
497
|
+
* Entry Point:
|
|
498
|
+
* collecionesDsl.the('dark')
|
|
499
|
+
*
|
|
500
|
+
* Chaining:
|
|
501
|
+
* - .and('...') : add additional adjectives
|
|
502
|
+
* - ._('entity') : specify the entity being acted on
|
|
503
|
+
* - .identified.by('field') : declare primary identifier for the subject
|
|
504
|
+
* - .has.been('action') : declare what happened
|
|
505
|
+
* - .by('actor') : describe the actor performing the action
|
|
506
|
+
* - .identified.by('id') : add identifiers for the actor
|
|
507
|
+
* - .with('key').set.to('val') : add contextual data fields
|
|
508
|
+
* - .referring.to('entity') : describe referenced/related entities
|
|
509
|
+
* - .identified.by('refId') : add identifiers for the reference
|
|
510
|
+
* - .conform.to('SchemaName') : attach schema metadata
|
|
511
|
+
* - .then.track.with(emitter) : finalize and emit the event
|
|
512
|
+
*
|
|
513
|
+
* Each function in the chain passes forward a scoped version of the CollecionesBaseEvent instance,
|
|
514
|
+
* and enforces semantic constraints (e.g. `.as()` is required after `.by()`).
|
|
515
|
+
*
|
|
516
|
+
* Internal:
|
|
517
|
+
* - Uses setRefence / setRefenceIdentifier for references
|
|
518
|
+
* - Uses helpers.getEvent() for test access
|
|
519
|
+
* - Uses instanceof CollecionesEmitter for validation
|
|
520
|
+
*
|
|
521
|
+
* This file is designed for internal use by developers building with the Colleciones tracking model.
|
|
444
522
|
*/
|
|
445
|
-
class CollecionesEvent extends CollecionesBaseEvent {
|
|
446
523
|
|
|
447
|
-
/**
|
|
448
|
-
* Constructs a new CollecionesEvent instance.
|
|
449
|
-
* @param {string} name - The name of the event.
|
|
450
|
-
* @param {object} data - The main data payload of the event.
|
|
451
|
-
* @param {object} identifiers - A set of identifiers to associate with the event.
|
|
452
|
-
*/
|
|
453
|
-
constructor(name, data, identifiers) {
|
|
454
|
-
super();
|
|
455
|
-
this.setEventName(name);
|
|
456
|
-
Object.assign(this.data, data);
|
|
457
|
-
this.convertParamIdentifiers(identifiers);
|
|
458
|
-
}
|
|
459
524
|
|
|
460
|
-
|
|
525
|
+
let init = (entity) => {
|
|
526
|
+
return ((entity)=>{
|
|
527
|
+
let eventInstance = new CollecionesEvent(); // Core event object
|
|
528
|
+
eventInstance.setEntity(entity);
|
|
461
529
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return (byteToHex[arr[offset + 0]] +
|
|
468
|
-
byteToHex[arr[offset + 1]] +
|
|
469
|
-
byteToHex[arr[offset + 2]] +
|
|
470
|
-
byteToHex[arr[offset + 3]] +
|
|
471
|
-
'-' +
|
|
472
|
-
byteToHex[arr[offset + 4]] +
|
|
473
|
-
byteToHex[arr[offset + 5]] +
|
|
474
|
-
'-' +
|
|
475
|
-
byteToHex[arr[offset + 6]] +
|
|
476
|
-
byteToHex[arr[offset + 7]] +
|
|
477
|
-
'-' +
|
|
478
|
-
byteToHex[arr[offset + 8]] +
|
|
479
|
-
byteToHex[arr[offset + 9]] +
|
|
480
|
-
'-' +
|
|
481
|
-
byteToHex[arr[offset + 10]] +
|
|
482
|
-
byteToHex[arr[offset + 11]] +
|
|
483
|
-
byteToHex[arr[offset + 12]] +
|
|
484
|
-
byteToHex[arr[offset + 13]] +
|
|
485
|
-
byteToHex[arr[offset + 14]] +
|
|
486
|
-
byteToHex[arr[offset + 15]]).toLowerCase();
|
|
487
|
-
}
|
|
530
|
+
let helpers = {
|
|
531
|
+
getEvent: () => {
|
|
532
|
+
return eventInstance;
|
|
533
|
+
},
|
|
534
|
+
};
|
|
488
535
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
536
|
+
// DSL function groups
|
|
537
|
+
let andEntity = () => {}; // .and(...) chaining after the()
|
|
538
|
+
let setEntityAfterAnd = () => {}; // ._('entity') after .and
|
|
539
|
+
let setEntityIdentifiers = () => {}; // .identified.by(...) for main subject
|
|
540
|
+
let setAction = () => {}; // .has.been(...) for action
|
|
541
|
+
let setActor = () => {}; // .by('actor')
|
|
542
|
+
let setActorIdentifier = () => {}; // .identified.by(...) inside .by(...)
|
|
543
|
+
let addContext = () => {}; // .with(...).set.to(...)
|
|
544
|
+
let addReference = () => {}; // .referring.to(...)
|
|
545
|
+
let getAddReference = () => {}; // .identified.by().as() inside .referring
|
|
546
|
+
let addReferenceIdentifier = () => {}; // .and(...) after reference
|
|
547
|
+
let conformingTo = () => {}; // .conform.to('SchemaName')
|
|
548
|
+
let track = () => {}; // .then.track.with(emitter)
|
|
549
|
+
|
|
550
|
+
// Adjective chaining: .and(...)._('entity')
|
|
551
|
+
setEntityAfterAnd = (entity) => {
|
|
552
|
+
eventInstance.addAdjective(eventInstance.entity);
|
|
553
|
+
eventInstance.setEntity(entity);
|
|
554
|
+
return {
|
|
555
|
+
as: () => {
|
|
556
|
+
return { helpers }
|
|
557
|
+
},
|
|
558
|
+
identified: {
|
|
559
|
+
by: setEntityIdentifiers
|
|
560
|
+
},
|
|
561
|
+
has: { been: setAction },
|
|
562
|
+
helpers
|
|
563
|
+
};
|
|
564
|
+
};
|
|
500
565
|
|
|
501
|
-
|
|
502
|
-
|
|
566
|
+
// Identifier setup: .identified.by().as()
|
|
567
|
+
setEntityIdentifiers = (name) => {
|
|
568
|
+
eventInstance.setIdentifier(name, null);
|
|
569
|
+
return {
|
|
570
|
+
as: (value) => {
|
|
571
|
+
eventInstance.setIdentifier(name, value);
|
|
572
|
+
return {
|
|
573
|
+
helpers,
|
|
574
|
+
and: {
|
|
575
|
+
by: setEntityIdentifiers
|
|
576
|
+
},
|
|
577
|
+
has: { been: setAction },
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
helpers
|
|
581
|
+
};
|
|
582
|
+
};
|
|
503
583
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
rnds[8] = (rnds[8] & 0x3f) | 0x80;
|
|
515
|
-
return unsafeStringify(rnds);
|
|
516
|
-
}
|
|
584
|
+
// Additional adjectives: .and('...')._()
|
|
585
|
+
andEntity = (entity) => {
|
|
586
|
+
eventInstance.addAdjective(eventInstance.entity);
|
|
587
|
+
eventInstance.setEntity(entity);
|
|
588
|
+
return {
|
|
589
|
+
and: andEntity,
|
|
590
|
+
_ : setEntityAfterAnd,
|
|
591
|
+
helpers
|
|
592
|
+
}
|
|
593
|
+
};
|
|
517
594
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
595
|
+
// Action: .has.been('...')
|
|
596
|
+
setAction = (action) => {
|
|
597
|
+
eventInstance.setAction(action);
|
|
598
|
+
return {
|
|
599
|
+
by: setActor,
|
|
600
|
+
referring: { to: addReference },
|
|
601
|
+
with: addContext,
|
|
602
|
+
conform: {to: conformingTo},
|
|
603
|
+
then: {track: {with: track}},
|
|
604
|
+
helpers
|
|
605
|
+
}
|
|
606
|
+
};
|
|
524
607
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
this.action = underscoreToCamelCase(action);
|
|
536
|
-
this.adjective = formatAdjectives(adjective);
|
|
537
|
-
let adjectiveString = stringifyAdjectives(this.adjective);
|
|
538
|
-
this.eventName = `${this.entity}${(adjectiveString !== undefined) ? adjectiveString : ''}__${this.action}`;
|
|
539
|
-
this.convertParamIdentifiers(identifiers);
|
|
540
|
-
this.data.entityIdentifiers = {};
|
|
541
|
-
this.data.attributes = {};
|
|
542
|
-
this.data.clientEventId = v4();
|
|
543
|
-
this.data.meta = {
|
|
544
|
-
eventFormat: 'CollecionesSemanticEvent',
|
|
545
|
-
eventFormatVersion: '1'
|
|
608
|
+
// Actor: .by('user')
|
|
609
|
+
setActor = (name) => {
|
|
610
|
+
eventInstance.setActor(name);
|
|
611
|
+
return {
|
|
612
|
+
identified: {
|
|
613
|
+
by: setActorIdentifier
|
|
614
|
+
},
|
|
615
|
+
with: addContext,
|
|
616
|
+
helpers
|
|
617
|
+
}
|
|
546
618
|
};
|
|
547
|
-
}
|
|
548
619
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
620
|
+
// Actor identifier: .identified.by().as()
|
|
621
|
+
setActorIdentifier = (name) => {
|
|
622
|
+
return {
|
|
623
|
+
as: (value) => {
|
|
624
|
+
eventInstance.setActorIdentifier(name, value);
|
|
625
|
+
return {
|
|
626
|
+
helpers,
|
|
627
|
+
and: setActorIdentifier,
|
|
628
|
+
with: addContext,
|
|
629
|
+
referring: { to: addReference },
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
helpers
|
|
633
|
+
}
|
|
634
|
+
};
|
|
556
635
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
636
|
+
// Contextual field: .with('key').set.to('value')
|
|
637
|
+
addContext = (context) => {
|
|
638
|
+
return {
|
|
639
|
+
helpers,
|
|
640
|
+
set: {
|
|
641
|
+
to: (value) => {
|
|
642
|
+
eventInstance.setContext(context, value);
|
|
643
|
+
return {
|
|
644
|
+
helpers,
|
|
645
|
+
and: addContext,
|
|
646
|
+
referring: { to: addReference },
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
572
652
|
|
|
573
|
-
|
|
653
|
+
// Reference: .referring.to('...')
|
|
654
|
+
addReference = (entity) => {
|
|
655
|
+
eventInstance.setRefence(entity);
|
|
656
|
+
return {
|
|
657
|
+
identified: {
|
|
658
|
+
by: getAddReference(entity)
|
|
659
|
+
},
|
|
660
|
+
conform: {to: conformingTo},
|
|
661
|
+
then: {track: {with: track}},
|
|
662
|
+
helpers
|
|
663
|
+
}
|
|
664
|
+
};
|
|
574
665
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
666
|
+
// Reference identifier setup: .identified.by().as()
|
|
667
|
+
getAddReference = (entity) => {
|
|
668
|
+
return (name) => {
|
|
669
|
+
return {
|
|
670
|
+
as: (value) => {
|
|
671
|
+
eventInstance.setRefenceIdentifier(entity, name, value);
|
|
672
|
+
return {
|
|
673
|
+
helpers,
|
|
674
|
+
and: addReferenceIdentifier,
|
|
675
|
+
conform: {to: conformingTo},
|
|
676
|
+
then: {track: {with: track}},
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
helpers,
|
|
680
|
+
};
|
|
681
|
+
};
|
|
682
|
+
};
|
|
580
683
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
*/
|
|
589
|
-
constructor(itemEntity, action, adjective, identifiers, collectionEntity) {
|
|
590
|
-
if (typeof collectionEntity == 'undefined') {
|
|
591
|
-
collectionEntity = `${itemEntity}Collection`;
|
|
592
|
-
}
|
|
593
|
-
super(collectionEntity, action, undefined, identifiers);
|
|
594
|
-
this.data.meta = {
|
|
595
|
-
eventFormat: 'CollecionesSemanticCollectionEvent',
|
|
596
|
-
eventFormatVersion: '1'
|
|
684
|
+
// Schema declaration: .conform.to('...')
|
|
685
|
+
conformingTo = (name) => {
|
|
686
|
+
eventInstance.setSchema(name);
|
|
687
|
+
return {
|
|
688
|
+
then: {track: {with: track}},
|
|
689
|
+
helpers
|
|
690
|
+
}
|
|
597
691
|
};
|
|
598
|
-
this.adjective = formatAdjectives(adjective);
|
|
599
|
-
let adjectiveString = stringifyAdjectives(this.adjective);
|
|
600
|
-
itemEntity = underscoreToCamelCase(itemEntity);
|
|
601
|
-
this.data.itemEntity = itemEntity;
|
|
602
|
-
this.data.itemEventName = `${itemEntity}${(adjectiveString !== undefined) ? adjectiveString : ''}__${this.action}`;
|
|
603
|
-
this.data.entityItemIdentifiers = {};
|
|
604
|
-
}
|
|
605
692
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
} else {
|
|
627
|
-
this.data.entityItemIdentifiers[key].push(value);
|
|
693
|
+
// Final dispatch: .then.track.with(emitter)
|
|
694
|
+
track = (emitter) => {
|
|
695
|
+
if(emitter instanceof CollecionesEmitter){
|
|
696
|
+
emitter.track(eventInstance);
|
|
697
|
+
}
|
|
698
|
+
return {
|
|
699
|
+
and: track,
|
|
700
|
+
helpers,
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
and: andEntity,
|
|
706
|
+
_: setEntityAfterAnd,
|
|
707
|
+
identified: {
|
|
708
|
+
by: setEntityIdentifiers
|
|
709
|
+
},
|
|
710
|
+
has: { been: setAction},
|
|
711
|
+
node: '_base',
|
|
712
|
+
helpers
|
|
628
713
|
}
|
|
629
|
-
}
|
|
630
714
|
|
|
631
|
-
}
|
|
715
|
+
})(entity);
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
let collecionesDsl = {
|
|
719
|
+
the: init
|
|
720
|
+
};
|
|
632
721
|
|
|
633
722
|
exports.CollecionesEmitter = CollecionesEmitter;
|
|
634
723
|
exports.CollecionesEvent = CollecionesEvent;
|
|
635
|
-
exports.CollecionesSemanticCollectionEvent = CollecionesSemanticCollectionEvent;
|
|
636
|
-
exports.CollecionesSemanticEvent = CollecionesSemanticEvent;
|
|
637
724
|
exports.CollecionesTracker = CollecionesTracker;
|
|
638
725
|
exports.CollecionesWebTracker = CollecionesWebTracker;
|
|
726
|
+
exports.collecionesDsl = collecionesDsl;
|
|
639
727
|
//# sourceMappingURL=index.cjs.map
|