@compilacion/colleciones-clientos 2.0.29 → 2.0.32
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/dist/browser.domainModel.gtm.config.sandbox.js +6 -2
- package/dist/browser.domainModel.gtm.config.sandbox.js.map +1 -1
- package/dist/browser.domainModel.gtm.js +378 -48
- package/dist/browser.domainModel.gtm.js.map +1 -1
- package/dist/browser.domainModel.gtm.min.js +1 -1
- package/dist/browser.domainModel.gtm.min.js.map +1 -1
- package/dist/browser.domainModel.gtm.sandbox.js.map +1 -1
- package/dist/browser.domainModel.gtm.sharedWindowNames.js.map +1 -1
- package/dist/browser.gtm.js +285 -44
- package/dist/browser.gtm.js.map +1 -1
- package/dist/browser.gtm.min.js +1 -1
- package/dist/browser.gtm.min.js.map +1 -1
- package/dist/browser.js +285 -44
- package/dist/browser.js.map +1 -1
- package/dist/browser.min.js +1 -1
- package/dist/browser.min.js.map +1 -1
- package/dist/gtm/Colleciones Client/303/250s Configurationes.tpl" +18 -1
- package/dist/index.cjs +285 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +285 -44
- package/dist/index.mjs.map +1 -1
- package/docs/gtm/domain-model.md +63 -0
- package/package.json +1 -1
|
@@ -10,12 +10,13 @@ var collecionesClientosGtmConfigSandbox = (function (exports) {
|
|
|
10
10
|
'trackerName': 'trackerName',
|
|
11
11
|
'appName': 'appName',
|
|
12
12
|
'emitterEndpoint': 'emitterEndpoint',
|
|
13
|
+
'errorLoggingEndpoint': 'errorLoggingEndpoint',
|
|
13
14
|
'typeName': 'type'
|
|
14
15
|
},
|
|
15
16
|
'typeCastGtmVariableConfig': 'compilacionCollecionesClientosGtmTypeCastConfig'
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
function getConfigObject(endpoint, flushInterval, flushSize, trackerName, appName, jsLibSource, jsLibUrlInput) {
|
|
19
|
+
function getConfigObject(endpoint, flushInterval, flushSize, trackerName, appName, jsLibSource, jsLibUrlInput, errorLoggingEndpoint) {
|
|
19
20
|
let returnObject = {};
|
|
20
21
|
returnObject[constants.config.typeName] = constants.typeCastGtmVariableConfig;
|
|
21
22
|
returnObject[constants.config.emitterEndpoint] = endpoint;
|
|
@@ -25,9 +26,12 @@ var collecionesClientosGtmConfigSandbox = (function (exports) {
|
|
|
25
26
|
returnObject[constants.config.appName] = appName;
|
|
26
27
|
returnObject[constants.config.sourceName] = jsLibSource;
|
|
27
28
|
returnObject[constants.config.jsLibUrlVariable] = jsLibUrlInput;
|
|
29
|
+
// Optional: providing the URL of the server-side error-logging endpoint is what
|
|
30
|
+
// turns frontend error forwarding on. When left empty the runtime stays silent.
|
|
31
|
+
returnObject[constants.config.errorLoggingEndpoint] = errorLoggingEndpoint;
|
|
28
32
|
return returnObject;
|
|
29
33
|
}
|
|
30
|
-
/* return getConfigObject(endpoint, flushInterval, flushSize, trackerName, appName, jsLibSource, jsLibUrl);*/
|
|
34
|
+
/* return getConfigObject(endpoint, flushInterval, flushSize, trackerName, appName, jsLibSource, jsLibUrl, errorLoggingEndpoint);*/
|
|
31
35
|
|
|
32
36
|
exports.getConfigObject = getConfigObject;
|
|
33
37
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.domainModel.gtm.config.sandbox.js","sources":["../src/browser.domainModel.gtm.constants.js","../src/browser.domainModel.gtm.config.sandbox.js"],"sourcesContent":["var constants = {\n 'sharedWindowObjectNames': {\n 'queue': 'compilacionCollecionesClientosGtmQueue',\n 'configVariable': 'compilacionCollecionesClientosGtmTypeCastConfig'\n },\n 'config': {\n 'sourceName': 'jsLibSource',\n 'jsLibUrlVariable': 'jsLibSource_selfHostedUrl',\n 'selfHostedValue': 'SELF',\n 'unpkgHostedValue': 'UNPKG',\n 'flushInterval': 'flushInterval',\n 'flushSize': 'flushSize',\n 'trackerName': 'trackerName',\n 'appName': 'appName',\n 'emitterEndpoint': 'emitterEndpoint',\n 'typeName': 'type'\n },\n 'collecionesObject': 'compilacionCollecionesClientosGtm',\n 'typeCastGtmVariableConfig': 'compilacionCollecionesClientosGtmTypeCastConfig'\n};\n\nexport default constants;","import constants from './browser.domainModel.gtm.constants.js';\n\nexport function getConfigObject(endpoint, flushInterval, flushSize, trackerName, appName, jsLibSource, jsLibUrlInput) {
|
|
1
|
+
{"version":3,"file":"browser.domainModel.gtm.config.sandbox.js","sources":["../src/browser.domainModel.gtm.constants.js","../src/browser.domainModel.gtm.config.sandbox.js"],"sourcesContent":["var constants = {\n 'sharedWindowObjectNames': {\n 'queue': 'compilacionCollecionesClientosGtmQueue',\n 'configVariable': 'compilacionCollecionesClientosGtmTypeCastConfig'\n },\n 'config': {\n 'sourceName': 'jsLibSource',\n 'jsLibUrlVariable': 'jsLibSource_selfHostedUrl',\n 'selfHostedValue': 'SELF',\n 'unpkgHostedValue': 'UNPKG',\n 'flushInterval': 'flushInterval',\n 'flushSize': 'flushSize',\n 'trackerName': 'trackerName',\n 'appName': 'appName',\n 'emitterEndpoint': 'emitterEndpoint',\n 'errorLoggingEndpoint': 'errorLoggingEndpoint',\n 'typeName': 'type'\n },\n 'collecionesObject': 'compilacionCollecionesClientosGtm',\n 'typeCastGtmVariableConfig': 'compilacionCollecionesClientosGtmTypeCastConfig'\n};\n\nexport default constants;","import constants from './browser.domainModel.gtm.constants.js';\n\nexport function getConfigObject(endpoint, flushInterval, flushSize, trackerName, appName, jsLibSource, jsLibUrlInput, errorLoggingEndpoint) {\n let jsLibUrl = null;\n if(jsLibSource === constants.config.unpkgHostedValue) {\n jsLibUrl = \"https://unpkg.com/@compilacion/colleciones-clientos@latest/dist/browser.gtm.min.js\";\n } else if (jsLibSource === constants.config.selfHostedValue) {\n jsLibUrl = jsLibUrlInput\n }\n let returnObject = {};\n returnObject[constants.config.typeName] = constants.typeCastGtmVariableConfig;\n returnObject[constants.config.emitterEndpoint] = endpoint;\n returnObject[constants.config.flushInterval] = flushInterval;\n returnObject[constants.config.flushSize] = flushSize;\n returnObject[constants.config.trackerName] = trackerName;\n returnObject[constants.config.appName] = appName;\n returnObject[constants.config.sourceName] = jsLibSource;\n returnObject[constants.config.jsLibUrlVariable] = jsLibUrlInput;\n // Optional: providing the URL of the server-side error-logging endpoint is what\n // turns frontend error forwarding on. When left empty the runtime stays silent.\n returnObject[constants.config.errorLoggingEndpoint] = errorLoggingEndpoint;\n return returnObject;\n}\n/* return getConfigObject(endpoint, flushInterval, flushSize, trackerName, appName, jsLibSource, jsLibUrl, errorLoggingEndpoint);*/"],"names":[],"mappings":";;;IAAA,IAAI,SAAS,GAAG;IAChB,IAII,QAAQ,EAAE;IACd,QAAQ,YAAY,EAAE,aAAa;IACnC,QAAQ,kBAAkB,EAAE,2BAA2B;IACvD,QAEQ,eAAe,EAAE,eAAe;IACxC,QAAQ,WAAW,EAAE,WAAW;IAChC,QAAQ,aAAa,EAAE,aAAa;IACpC,QAAQ,SAAS,EAAE,SAAS;IAC5B,QAAQ,iBAAiB,EAAE,iBAAiB;IAC5C,QAAQ,sBAAsB,EAAE,sBAAsB;IACtD,QAAQ,UAAU,EAAE;IACpB,KAAK;IACL,IACI,2BAA2B,EAAE;IACjC,CAAC;;IClBM,SAAS,eAAe,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,oBAAoB,EAAE;IAO5I,IAAI,IAAI,YAAY,GAAG,EAAE;IACzB,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,yBAAyB;IACjF,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,QAAQ;IAC7D,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,aAAa;IAChE,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,SAAS;IACxD,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,WAAW;IAC5D,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,OAAO;IACpD,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,WAAW;IAC3D,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,aAAa;IACnE;IACA;IACA,IAAI,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,oBAAoB,CAAC,GAAG,oBAAoB;IAC9E,IAAI,OAAO,YAAY;IACvB;IACA;;;;;;;;;;"}
|
|
@@ -18,30 +18,6 @@
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* Collects browser-related context values like screen size, language, etc.
|
|
23
|
-
* Returns an empty object if not in browser environment.
|
|
24
|
-
* @returns {Object}
|
|
25
|
-
*/
|
|
26
|
-
const getBrowserContext = function() {
|
|
27
|
-
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
hasFocus: document.hasFocus(),
|
|
32
|
-
language: navigator.language,
|
|
33
|
-
platform: navigator.platform,
|
|
34
|
-
referrer: document.referrer,
|
|
35
|
-
screenHeight: window.screen.height,
|
|
36
|
-
screenWidth: window.screen.width,
|
|
37
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
38
|
-
url: window.location.href,
|
|
39
|
-
userAgent: navigator.userAgent,
|
|
40
|
-
viewportHeight: window.innerHeight,
|
|
41
|
-
viewportWidth: window.innerWidth,
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
|
|
45
21
|
const formatToCamelCase = function(input) {
|
|
46
22
|
return input
|
|
47
23
|
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
|
@@ -259,6 +235,27 @@
|
|
|
259
235
|
this.context[context] = value;
|
|
260
236
|
}
|
|
261
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Records a namespaced enrichment under `meta.enrichments`. A producer or
|
|
240
|
+
* processor the event passes through claims its own `name` ("I am X") and may
|
|
241
|
+
* declare what subject the data is `about` ("this tells something about Y"), so a
|
|
242
|
+
* model-aware consumer (e.g. the server) can map it onto entity state. Kept OUT
|
|
243
|
+
* of `context` on purpose: enrichments are transport/environment metadata, not
|
|
244
|
+
* part of the domain-validated event payload.
|
|
245
|
+
* @param {string} name - The enrichment namespace (the source identity).
|
|
246
|
+
* @param {string|string[]} about - The subject(s) the data describes (recommended).
|
|
247
|
+
* @param {*} data - The contributed payload.
|
|
248
|
+
*/
|
|
249
|
+
setEnrichment = function(name, about, data) {
|
|
250
|
+
if (typeof name !== 'string') {
|
|
251
|
+
throw new Error('Enrichment name must be a string');
|
|
252
|
+
}
|
|
253
|
+
if (!this.meta.enrichments) {
|
|
254
|
+
this.meta.enrichments = {};
|
|
255
|
+
}
|
|
256
|
+
this.meta.enrichments[name] = { about, data };
|
|
257
|
+
}
|
|
258
|
+
|
|
262
259
|
/**
|
|
263
260
|
* Alias for `setReference` for compatibility.
|
|
264
261
|
* Declares an external entity referenced by this event.
|
|
@@ -719,6 +716,44 @@
|
|
|
719
716
|
this.emitters = emitters;
|
|
720
717
|
this.trackerName = trackerName;
|
|
721
718
|
this.appName = appName;
|
|
719
|
+
this.handlers = [];
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Register an enrichment handler. A handler self-identifies (`name` — "I am X")
|
|
724
|
+
* and may declare what it is `about` (the subject(s) — "this tells about Y"); on
|
|
725
|
+
* track it contributes `collect(event)` into `meta.enrichments[name]`. An optional
|
|
726
|
+
* `appliesTo(event)` lets a handler contribute only for relevant events, and
|
|
727
|
+
* `collect` may return `null`/`undefined` to contribute nothing for this event.
|
|
728
|
+
* Handlers stay model-agnostic — `about` is a plain subject string (or array); a
|
|
729
|
+
* model-aware consumer (the server) does the actual mapping.
|
|
730
|
+
* @param {{name: string, about?: string|string[], collect: function, appliesTo?: function}} handler
|
|
731
|
+
* @returns {CollecionesTracker} this (chainable).
|
|
732
|
+
*/
|
|
733
|
+
use(handler) {
|
|
734
|
+
if (!handler || typeof handler.name !== 'string' || typeof handler.collect !== 'function') {
|
|
735
|
+
throw new Error('Handler must have a string `name` and a `collect(event)` function');
|
|
736
|
+
}
|
|
737
|
+
this.handlers.push(handler);
|
|
738
|
+
return this;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** Run every registered handler against the event, writing namespaced enrichments. */
|
|
742
|
+
runHandlers(collecionesEvent) {
|
|
743
|
+
this.handlers.forEach((handler) => {
|
|
744
|
+
try {
|
|
745
|
+
if (typeof handler.appliesTo === 'function' && !handler.appliesTo(collecionesEvent)) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const data = handler.collect(collecionesEvent);
|
|
749
|
+
if (data === undefined || data === null) {
|
|
750
|
+
return; // handler declined to contribute for this event
|
|
751
|
+
}
|
|
752
|
+
collecionesEvent.setEnrichment(handler.name, handler.about, data);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
// A failing handler must never drop the event; skip its enrichment.
|
|
755
|
+
}
|
|
756
|
+
});
|
|
722
757
|
}
|
|
723
758
|
|
|
724
759
|
/**
|
|
@@ -731,7 +766,8 @@
|
|
|
731
766
|
throw new Error('Event must be of type CollecionesEvent');
|
|
732
767
|
}
|
|
733
768
|
collecionesEvent.setTracker(this.trackerName);
|
|
734
|
-
collecionesEvent.setAppName(this.appName);
|
|
769
|
+
collecionesEvent.setAppName(this.appName);
|
|
770
|
+
this.runHandlers(collecionesEvent);
|
|
735
771
|
this.emitters.forEach(element => {
|
|
736
772
|
element.track(collecionesEvent, this.trackerName, this.appName);
|
|
737
773
|
});
|
|
@@ -753,31 +789,236 @@
|
|
|
753
789
|
}
|
|
754
790
|
|
|
755
791
|
/**
|
|
756
|
-
*
|
|
757
|
-
*
|
|
792
|
+
* Default browser-context enrichment handler. Contributes the page/browser
|
|
793
|
+
* environment under `meta.enrichments.browserContext`.
|
|
794
|
+
*
|
|
795
|
+
* This is transport/environment metadata — deliberately NOT part of the
|
|
796
|
+
* domain-validated `context`. A model-aware consumer (the server) routes each field
|
|
797
|
+
* onto the matching attribute of one of the declared subjects. Returns an empty
|
|
798
|
+
* payload outside a browser.
|
|
799
|
+
*/
|
|
800
|
+
const browserContextHandler = {
|
|
801
|
+
name: 'browserContext',
|
|
802
|
+
about: ['session', 'webpage', 'visitor'],
|
|
803
|
+
collect() {
|
|
804
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
805
|
+
return {};
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
hasFocus: document.hasFocus(),
|
|
809
|
+
language: navigator.language,
|
|
810
|
+
platform: navigator.platform,
|
|
811
|
+
referrer: document.referrer,
|
|
812
|
+
screenHeight: window.screen.height,
|
|
813
|
+
screenWidth: window.screen.width,
|
|
814
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
815
|
+
url: window.location.href,
|
|
816
|
+
userAgent: navigator.userAgent,
|
|
817
|
+
viewportHeight: window.innerHeight,
|
|
818
|
+
viewportWidth: window.innerWidth,
|
|
819
|
+
};
|
|
820
|
+
},
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Marketing / attribution handler. Parses the page URL's query string for the
|
|
825
|
+
* standard UTM parameters and the common ad-network click identifiers, so the
|
|
826
|
+
* model-aware consumer (the server) can attribute the session/visitor.
|
|
827
|
+
*
|
|
828
|
+
* Declares `about: ['session', 'visitor']` — attribution describes entity state,
|
|
829
|
+
* not event payload. Returns `null` when the URL carries no marketing parameters,
|
|
830
|
+
* so the handler adds nothing on the (many) events that have none.
|
|
831
|
+
*
|
|
832
|
+
* Field names are the conventional analytics names the domain model also uses
|
|
833
|
+
* (`utmSource`, `marketingNetwork`, ...) — not domain-model coupling, just the
|
|
834
|
+
* shared vocabulary of web attribution.
|
|
835
|
+
*/
|
|
836
|
+
|
|
837
|
+
// UTM query parameter -> conventional camelCase field name.
|
|
838
|
+
const UTM_FIELDS = [
|
|
839
|
+
['utm_source', 'utmSource'],
|
|
840
|
+
['utm_medium', 'utmMedium'],
|
|
841
|
+
['utm_campaign', 'utmCampaign'],
|
|
842
|
+
['utm_term', 'utmTerm'],
|
|
843
|
+
['utm_content', 'utmContent'],
|
|
844
|
+
];
|
|
845
|
+
|
|
846
|
+
// Known ad-network click identifiers, in resolution priority, mapped to their network.
|
|
847
|
+
const CLICK_ID_NETWORKS = [
|
|
848
|
+
['gclid', 'google'],
|
|
849
|
+
['wbraid', 'google'],
|
|
850
|
+
['gbraid', 'google'],
|
|
851
|
+
['dclid', 'google'],
|
|
852
|
+
['msclkid', 'microsoft'],
|
|
853
|
+
['fbclid', 'meta'],
|
|
854
|
+
['igshid', 'meta'],
|
|
855
|
+
['ttclid', 'tiktok'],
|
|
856
|
+
['li_fat_id', 'linkedin'],
|
|
857
|
+
['twclid', 'twitter'],
|
|
858
|
+
['yclid', 'yandex'],
|
|
859
|
+
['epik', 'pinterest'],
|
|
860
|
+
['scid', 'snapchat'],
|
|
861
|
+
];
|
|
862
|
+
|
|
863
|
+
const marketingHandler = {
|
|
864
|
+
name: 'marketing',
|
|
865
|
+
about: ['session', 'visitor'],
|
|
866
|
+
collect() {
|
|
867
|
+
if (typeof window === 'undefined' || !window.location) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
const params = new URLSearchParams(window.location.search || '');
|
|
871
|
+
const data = {};
|
|
872
|
+
|
|
873
|
+
UTM_FIELDS.forEach(([param, field]) => {
|
|
874
|
+
const value = params.get(param);
|
|
875
|
+
if (value) {
|
|
876
|
+
data[field] = value;
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
for (const [param, network] of CLICK_ID_NETWORKS) {
|
|
881
|
+
const value = params.get(param);
|
|
882
|
+
if (value) {
|
|
883
|
+
data.marketingNetwork = network;
|
|
884
|
+
data.marketingNetworkClickId = value;
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return Object.keys(data).length > 0 ? data : null;
|
|
890
|
+
},
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* UA Client Hints handler. Surfaces the low-entropy values from
|
|
895
|
+
* `navigator.userAgentData` (Chromium) — `mobile`, `platform`, `brands` — a more
|
|
896
|
+
* reliable device/browser signal than parsing the user-agent string server-side.
|
|
897
|
+
*
|
|
898
|
+
* Declares `about: ['visitor', 'session']`. Returns `null` where UA-CH is
|
|
899
|
+
* unavailable (Firefox/Safari) — the server then falls back to the user-agent
|
|
900
|
+
* string carried by `browserContext`. Only the synchronous low-entropy values are
|
|
901
|
+
* read; the async `getHighEntropyValues()` is intentionally not awaited.
|
|
902
|
+
*/
|
|
903
|
+
const userAgentDataHandler = {
|
|
904
|
+
name: 'userAgentData',
|
|
905
|
+
about: ['visitor', 'session'],
|
|
906
|
+
collect() {
|
|
907
|
+
if (typeof navigator === 'undefined' || !navigator.userAgentData) {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
const uaData = navigator.userAgentData;
|
|
911
|
+
const data = {
|
|
912
|
+
mobile: Boolean(uaData.mobile),
|
|
913
|
+
};
|
|
914
|
+
if (uaData.platform) {
|
|
915
|
+
data.platform = uaData.platform;
|
|
916
|
+
}
|
|
917
|
+
if (Array.isArray(uaData.brands) && uaData.brands.length > 0) {
|
|
918
|
+
data.brands = uaData.brands.map((b) => ({ brand: b.brand, version: b.version }));
|
|
919
|
+
}
|
|
920
|
+
return data;
|
|
921
|
+
},
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Consent handler. Surfaces the synchronously-available consent signals: the IAB
|
|
926
|
+
* TCF consent string (from the `euconsent-v2` cookie), the detected CMP provider,
|
|
927
|
+
* and the Global Privacy Control signal. The model-aware consumer (the server)
|
|
928
|
+
* decodes the TCF string into granted categories.
|
|
929
|
+
*
|
|
930
|
+
* Declares `about: ['consent', 'session']`. Returns `null` when no consent signal
|
|
931
|
+
* is present. NOTE: only synchronous sources are read — the async `__tcfapi`
|
|
932
|
+
* in-memory state is intentionally not awaited; the persisted cookie is the
|
|
933
|
+
* source of truth.
|
|
934
|
+
*/
|
|
935
|
+
|
|
936
|
+
// Known CMP globals -> provider name.
|
|
937
|
+
const CMP_PROVIDERS = [
|
|
938
|
+
['Cookiebot', 'cookiebot'],
|
|
939
|
+
['OneTrust', 'onetrust'],
|
|
940
|
+
['Optanon', 'onetrust'],
|
|
941
|
+
['Didomi', 'didomi'],
|
|
942
|
+
['UC_UI', 'usercentrics'],
|
|
943
|
+
];
|
|
944
|
+
|
|
945
|
+
function readCookie(name) {
|
|
946
|
+
if (typeof document === 'undefined' || typeof document.cookie !== 'string') {
|
|
947
|
+
return undefined;
|
|
948
|
+
}
|
|
949
|
+
const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
|
|
950
|
+
return match ? decodeURIComponent(match[1]) : undefined;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function detectProvider() {
|
|
954
|
+
if (typeof window === 'undefined') {
|
|
955
|
+
return undefined;
|
|
956
|
+
}
|
|
957
|
+
for (const [global, name] of CMP_PROVIDERS) {
|
|
958
|
+
if (window[global]) {
|
|
959
|
+
return name;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (typeof window.__tcfapi === 'function') {
|
|
963
|
+
return 'iab-tcf';
|
|
964
|
+
}
|
|
965
|
+
return undefined;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const consentHandler = {
|
|
969
|
+
name: 'consent',
|
|
970
|
+
about: ['consent', 'session'],
|
|
971
|
+
collect() {
|
|
972
|
+
if (typeof window === 'undefined') {
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
const data = {};
|
|
976
|
+
|
|
977
|
+
const tcfConsentString = readCookie('euconsent-v2');
|
|
978
|
+
if (tcfConsentString) {
|
|
979
|
+
data.tcfConsentString = tcfConsentString;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const provider = detectProvider();
|
|
983
|
+
if (provider) {
|
|
984
|
+
data.consentProvider = provider;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (typeof navigator !== 'undefined' && typeof navigator.globalPrivacyControl === 'boolean') {
|
|
988
|
+
data.globalPrivacyControl = navigator.globalPrivacyControl;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
return Object.keys(data).length > 0 ? data : null;
|
|
992
|
+
},
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* The default enrichment handlers registered on every web tracker. Each handler is
|
|
997
|
+
* model-agnostic: it self-identifies (`name`), declares what it is `about` (subject
|
|
998
|
+
* strings) and `collect`s its data; a model-aware consumer maps it onto entity state.
|
|
999
|
+
*/
|
|
1000
|
+
const defaultWebHandlers = [
|
|
1001
|
+
browserContextHandler,
|
|
1002
|
+
marketingHandler,
|
|
1003
|
+
userAgentDataHandler,
|
|
1004
|
+
consentHandler,
|
|
1005
|
+
];
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Web-specific tracker: the base tracker plus the default web enrichment handlers
|
|
1009
|
+
* (browser context, marketing/attribution, UA client hints, consent), so every web
|
|
1010
|
+
* event carries the client environment and attribution signals in
|
|
1011
|
+
* `meta.enrichments` — never in the domain-validated `context`.
|
|
758
1012
|
*/
|
|
759
1013
|
class CollecionesWebTracker extends CollecionesTracker {
|
|
760
1014
|
/**
|
|
761
|
-
*
|
|
762
|
-
* @param {
|
|
763
|
-
* @param {string}
|
|
764
|
-
* @param {string} appName - The name of the application generating events.
|
|
1015
|
+
* @param {Array} emitters - Emitter instances used to send events.
|
|
1016
|
+
* @param {string} trackerName - The tracker name.
|
|
1017
|
+
* @param {string} appName - The application generating events.
|
|
765
1018
|
*/
|
|
766
1019
|
constructor(emitters, trackerName, appName) {
|
|
767
1020
|
super(emitters, trackerName, appName);
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
/**
|
|
771
|
-
* Tracks an event, enriching it with browser context information.
|
|
772
|
-
* @param {CollecionesEvent} collecionesEvent - The event object to track.
|
|
773
|
-
* @throws {Error} If the event is not an instance of CollecionesEvent.
|
|
774
|
-
*/
|
|
775
|
-
track(collecionesEvent) {
|
|
776
|
-
if (!(collecionesEvent instanceof CollecionesEvent)) {
|
|
777
|
-
throw new Error('Event must be of type CollecionesEvent');
|
|
778
|
-
}
|
|
779
|
-
collecionesEvent.setContext('browserContext', getBrowserContext());
|
|
780
|
-
super.track(collecionesEvent);
|
|
1021
|
+
defaultWebHandlers.forEach((handler) => this.use(handler));
|
|
781
1022
|
}
|
|
782
1023
|
}
|
|
783
1024
|
|
|
@@ -791,7 +1032,8 @@
|
|
|
791
1032
|
'flushSize': 'flushSize',
|
|
792
1033
|
'trackerName': 'trackerName',
|
|
793
1034
|
'appName': 'appName',
|
|
794
|
-
'emitterEndpoint': 'emitterEndpoint'
|
|
1035
|
+
'emitterEndpoint': 'emitterEndpoint',
|
|
1036
|
+
'errorLoggingEndpoint': 'errorLoggingEndpoint'},
|
|
795
1037
|
'collecionesObject': 'compilacionCollecionesClientosGtm',
|
|
796
1038
|
'typeCastGtmVariableConfig': 'compilacionCollecionesClientosGtmTypeCastConfig'
|
|
797
1039
|
};
|
|
@@ -808,6 +1050,62 @@
|
|
|
808
1050
|
let queueReference = null;
|
|
809
1051
|
let processedQueueLength = 0;
|
|
810
1052
|
|
|
1053
|
+
// Identity/destination details captured from the GTM config so a caught error
|
|
1054
|
+
// can be attributed (tracker/app) and forwarded to the right endpoint.
|
|
1055
|
+
let appName = null;
|
|
1056
|
+
let trackerName = null;
|
|
1057
|
+
let errorLoggingEndpoint = null;
|
|
1058
|
+
|
|
1059
|
+
const safeStringify = (value) => {
|
|
1060
|
+
if (value === undefined) return '';
|
|
1061
|
+
if (typeof value === 'string') return value;
|
|
1062
|
+
try {
|
|
1063
|
+
return JSON.stringify(value);
|
|
1064
|
+
} catch (e) {
|
|
1065
|
+
return String(value);
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
// Forward a caught client-side error to the configured bad-event endpoint.
|
|
1070
|
+
// The whole point of this is to make the try/catch fallbacks visible server-side,
|
|
1071
|
+
// so it is strictly best-effort and must NEVER throw: a failure to report an
|
|
1072
|
+
// error can not be allowed to break the tracking flow it is meant to protect.
|
|
1073
|
+
// Reporting only happens when an errorLoggingEndpoint URL is configured.
|
|
1074
|
+
const reportError = (error, details = {}) => {
|
|
1075
|
+
if (!errorLoggingEndpoint) return;
|
|
1076
|
+
if (typeof fetch !== 'function') return;
|
|
1077
|
+
try {
|
|
1078
|
+
const payload = {
|
|
1079
|
+
errorName: (error && error.name) ? String(error.name) : 'Error',
|
|
1080
|
+
errorMessage: (error && error.message) ? String(error.message) : String(error),
|
|
1081
|
+
tracker: trackerName != null ? String(trackerName) : '',
|
|
1082
|
+
appName: appName != null ? String(appName) : '',
|
|
1083
|
+
entity: details.entity != null ? String(details.entity) : '',
|
|
1084
|
+
action: details.action != null ? String(details.action) : '',
|
|
1085
|
+
rawEvent: safeStringify(details.rawEvent),
|
|
1086
|
+
context: {
|
|
1087
|
+
// phase identifies which fallback caught the error: init, flush or group.
|
|
1088
|
+
phase: details.phase || '',
|
|
1089
|
+
stack: (error && error.stack) ? String(error.stack) : '',
|
|
1090
|
+
url: (typeof window !== 'undefined' && window.location) ? window.location.href : '',
|
|
1091
|
+
userAgent: (typeof navigator !== 'undefined' && navigator.userAgent) ? navigator.userAgent : '',
|
|
1092
|
+
language: (typeof navigator !== 'undefined' && navigator.language) ? navigator.language : '',
|
|
1093
|
+
clientTimestamp: new Date().toISOString()
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
// keepalive lets a report fired during page unload still have a chance to land.
|
|
1097
|
+
fetch(errorLoggingEndpoint, {
|
|
1098
|
+
method: 'POST',
|
|
1099
|
+
credentials: 'include',
|
|
1100
|
+
keepalive: true,
|
|
1101
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1102
|
+
body: JSON.stringify(payload)
|
|
1103
|
+
}).catch(() => {});
|
|
1104
|
+
} catch (e) {
|
|
1105
|
+
// Swallow: error reporting can never be allowed to surface its own exception.
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
|
|
811
1109
|
let init = () => {
|
|
812
1110
|
if (initialized) return true;
|
|
813
1111
|
if (window[constants.sharedWindowObjectNames.configVariable] === undefined) return false;
|
|
@@ -815,8 +1113,11 @@
|
|
|
815
1113
|
if (config.type === undefined || config.type !== constants.typeCastGtmVariableConfig) return false;
|
|
816
1114
|
let flushInterval = config[constants.config.flushInterval];
|
|
817
1115
|
let flushSize = config[constants.config.flushSize];
|
|
818
|
-
|
|
819
|
-
|
|
1116
|
+
trackerName = config[constants.config.trackerName];
|
|
1117
|
+
appName = config[constants.config.appName];
|
|
1118
|
+
// Capture the error endpoint as soon as a valid config is available so even an
|
|
1119
|
+
// emitter-init failure below can already be reported.
|
|
1120
|
+
errorLoggingEndpoint = config[constants.config.errorLoggingEndpoint] ?? null;
|
|
820
1121
|
let emitterEndpoint = config[constants.config.emitterEndpoint] ?? config.endpoint;
|
|
821
1122
|
try {
|
|
822
1123
|
emitter = new CollecionesEmitter(emitterEndpoint, flushSize, flushInterval);
|
|
@@ -825,6 +1126,7 @@
|
|
|
825
1126
|
return true;
|
|
826
1127
|
} catch (e) {
|
|
827
1128
|
console.error('Error initializing CollecionesEmitter:', e);
|
|
1129
|
+
reportError(e, { phase: 'init' });
|
|
828
1130
|
return false;
|
|
829
1131
|
}
|
|
830
1132
|
};
|
|
@@ -1059,6 +1361,12 @@
|
|
|
1059
1361
|
added++;
|
|
1060
1362
|
} catch (e) {
|
|
1061
1363
|
console.error('Colleciones: skipped malformed grouped event', e);
|
|
1364
|
+
reportError(e, {
|
|
1365
|
+
phase: 'group',
|
|
1366
|
+
rawEvent: eventArg,
|
|
1367
|
+
entity: eventArg?.entity,
|
|
1368
|
+
action: eventArg?.action
|
|
1369
|
+
});
|
|
1062
1370
|
}
|
|
1063
1371
|
});
|
|
1064
1372
|
if (added === 0) return false;
|
|
@@ -1078,7 +1386,7 @@
|
|
|
1078
1386
|
return true;
|
|
1079
1387
|
};
|
|
1080
1388
|
|
|
1081
|
-
let
|
|
1389
|
+
let flushInternal = () => {
|
|
1082
1390
|
if (!init()) {
|
|
1083
1391
|
return;
|
|
1084
1392
|
}
|
|
@@ -1103,10 +1411,32 @@
|
|
|
1103
1411
|
track(args);
|
|
1104
1412
|
} catch (e) {
|
|
1105
1413
|
console.error('Colleciones: failed to process queued event', e);
|
|
1414
|
+
reportError(e, {
|
|
1415
|
+
phase: 'flush',
|
|
1416
|
+
rawEvent: args,
|
|
1417
|
+
entity: args?.entity,
|
|
1418
|
+
action: args?.action
|
|
1419
|
+
});
|
|
1106
1420
|
}
|
|
1107
1421
|
}
|
|
1108
1422
|
};
|
|
1109
1423
|
|
|
1424
|
+
// Overarching safety net. The per-event / per-group catches above exist for
|
|
1425
|
+
// RECOVERY (they keep the loop alive), so they intentionally sit inside the loop.
|
|
1426
|
+
// This wrapper catches everything else our own runtime can throw — init, queue
|
|
1427
|
+
// bookkeeping, or any path we forgot to guard — so nothing fails silently again.
|
|
1428
|
+
// It only fires for genuinely unhandled throws (the inner catches swallow theirs),
|
|
1429
|
+
// so there is no double reporting. Scope is our runtime only: page-level errors
|
|
1430
|
+
// from other scripts are deliberately not captured here.
|
|
1431
|
+
let flush = () => {
|
|
1432
|
+
try {
|
|
1433
|
+
flushInternal();
|
|
1434
|
+
} catch (e) {
|
|
1435
|
+
console.error('Colleciones: unexpected runtime failure', e);
|
|
1436
|
+
reportError(e, { phase: 'runtime' });
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1110
1440
|
let ensureQueue = () => {
|
|
1111
1441
|
let queue = window[constants.sharedWindowObjectNames.queue];
|
|
1112
1442
|
if (!Array.isArray(queue)) {
|