coveo.analytics 2.25.2 → 2.26.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 +147 -133
- package/dist/coveoua.browser.js +1 -1
- package/dist/coveoua.browser.js.map +1 -1
- package/dist/coveoua.debug.js +291 -105
- package/dist/coveoua.debug.js.map +1 -1
- package/dist/coveoua.js +1 -1
- package/dist/coveoua.js.map +1 -1
- package/dist/definitions/client/analytics.d.ts +4 -0
- package/dist/definitions/coveoua/plugins.d.ts +2 -6
- package/dist/definitions/coveoua/simpleanalytics.d.ts +1 -1
- package/dist/definitions/plugins/BasePlugin.d.ts +8 -3
- package/dist/definitions/plugins/ec.d.ts +1 -0
- package/dist/definitions/plugins/link.d.ts +21 -0
- package/dist/definitions/plugins/svc.d.ts +1 -0
- package/dist/definitions/src/coveoua/plugins.d.ts +2 -6
- package/dist/definitions/src/coveoua/simpleanalytics.d.ts +1 -1
- package/dist/definitions/src/plugins/ec.d.ts +1 -0
- package/dist/definitions/version.d.ts +1 -1
- package/dist/library.es.js +233 -88
- package/dist/library.js +294 -108
- package/dist/react-native.es.js +238 -93
- package/package.json +1 -1
- package/src/client/analytics.spec.ts +122 -0
- package/src/client/analytics.ts +33 -1
- package/src/coveoua/plugins.ts +7 -8
- package/src/coveoua/simpleanalytics.spec.ts +15 -10
- package/src/coveoua/simpleanalytics.ts +2 -2
- package/src/plugins/BasePlugin.ts +21 -7
- package/src/plugins/ec.ts +13 -0
- package/src/plugins/link.spec.ts +171 -0
- package/src/plugins/link.ts +109 -0
- package/src/plugins/svc.ts +11 -0
package/dist/react-native.es.js
CHANGED
|
@@ -70,7 +70,7 @@ var nodePonyfill = {exports: {}};
|
|
|
70
70
|
|
|
71
71
|
var publicApi = {};
|
|
72
72
|
|
|
73
|
-
var URL$
|
|
73
|
+
var URL$3 = {exports: {}};
|
|
74
74
|
|
|
75
75
|
var conversions = {};
|
|
76
76
|
var lib$1 = conversions;
|
|
@@ -79825,9 +79825,9 @@ module.exports = {
|
|
|
79825
79825
|
Worker: { URL: URL }
|
|
79826
79826
|
}
|
|
79827
79827
|
};
|
|
79828
|
-
}(URL$
|
|
79828
|
+
}(URL$3));
|
|
79829
79829
|
|
|
79830
|
-
publicApi.URL = URL$
|
|
79830
|
+
publicApi.URL = URL$3.exports.interface;
|
|
79831
79831
|
publicApi.serializeURL = urlStateMachine.exports.serializeURL;
|
|
79832
79832
|
publicApi.serializeURLOrigin = urlStateMachine.exports.serializeURLOrigin;
|
|
79833
79833
|
publicApi.basicURLParse = urlStateMachine.exports.basicURLParse;
|
|
@@ -80968,7 +80968,7 @@ Object.defineProperty(Response.prototype, Symbol.toStringTag, {
|
|
|
80968
80968
|
});
|
|
80969
80969
|
|
|
80970
80970
|
const INTERNALS$2 = Symbol('Request internals');
|
|
80971
|
-
const URL$
|
|
80971
|
+
const URL$2 = Url.URL || publicApi.URL;
|
|
80972
80972
|
|
|
80973
80973
|
// fix an issue where "format", "parse" aren't a named export for node <10
|
|
80974
80974
|
const parse_url = Url.parse;
|
|
@@ -80987,7 +80987,7 @@ function parseURL(urlStr) {
|
|
|
80987
80987
|
Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
|
|
80988
80988
|
*/
|
|
80989
80989
|
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.exec(urlStr)) {
|
|
80990
|
-
urlStr = new URL$
|
|
80990
|
+
urlStr = new URL$2(urlStr).toString();
|
|
80991
80991
|
}
|
|
80992
80992
|
|
|
80993
80993
|
// Fallback to old implementation for arbitrary URLs
|
|
@@ -81696,7 +81696,7 @@ function stringToBytes(str) {
|
|
|
81696
81696
|
}
|
|
81697
81697
|
|
|
81698
81698
|
const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
|
|
81699
|
-
const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
|
|
81699
|
+
const URL$1 = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
|
|
81700
81700
|
function v35(name, version, hashfunc) {
|
|
81701
81701
|
function generateUUID(value, namespace, buf, offset) {
|
|
81702
81702
|
var _namespace;
|
|
@@ -81743,7 +81743,7 @@ function v35(name, version, hashfunc) {
|
|
|
81743
81743
|
|
|
81744
81744
|
|
|
81745
81745
|
generateUUID.DNS = DNS;
|
|
81746
|
-
generateUUID.URL = URL;
|
|
81746
|
+
generateUUID.URL = URL$1;
|
|
81747
81747
|
return generateUUID;
|
|
81748
81748
|
}
|
|
81749
81749
|
|
|
@@ -82158,7 +82158,197 @@ const addPageViewToHistory = (pageViewValue) => __awaiter(void 0, void 0, void 0
|
|
|
82158
82158
|
yield store.addElementAsync(historyElement);
|
|
82159
82159
|
});
|
|
82160
82160
|
|
|
82161
|
-
const libVersion = "2.
|
|
82161
|
+
const libVersion = "2.26.1" ;
|
|
82162
|
+
|
|
82163
|
+
const getFormattedLocation = (location) => `${location.protocol}//${location.hostname}${location.pathname.indexOf('/') === 0 ? location.pathname : `/${location.pathname}`}${location.search}`;
|
|
82164
|
+
|
|
82165
|
+
const BasePluginEventTypes = {
|
|
82166
|
+
pageview: 'pageview',
|
|
82167
|
+
event: 'event',
|
|
82168
|
+
};
|
|
82169
|
+
class Plugin {
|
|
82170
|
+
constructor({ client, uuidGenerator = v4 }) {
|
|
82171
|
+
this.client = client;
|
|
82172
|
+
this.uuidGenerator = uuidGenerator;
|
|
82173
|
+
}
|
|
82174
|
+
}
|
|
82175
|
+
class BasePlugin extends Plugin {
|
|
82176
|
+
constructor({ client, uuidGenerator = v4 }) {
|
|
82177
|
+
super({ client, uuidGenerator });
|
|
82178
|
+
this.actionData = {};
|
|
82179
|
+
this.pageViewId = uuidGenerator();
|
|
82180
|
+
this.nextPageViewId = this.pageViewId;
|
|
82181
|
+
this.currentLocation = getFormattedLocation(window.location);
|
|
82182
|
+
this.lastReferrer = hasDocument() ? document.referrer : '';
|
|
82183
|
+
this.addHooks();
|
|
82184
|
+
}
|
|
82185
|
+
getApi(name) {
|
|
82186
|
+
switch (name) {
|
|
82187
|
+
case 'setAction':
|
|
82188
|
+
return this.setAction;
|
|
82189
|
+
default:
|
|
82190
|
+
return null;
|
|
82191
|
+
}
|
|
82192
|
+
}
|
|
82193
|
+
setAction(action, options) {
|
|
82194
|
+
this.action = action;
|
|
82195
|
+
this.actionData = options;
|
|
82196
|
+
}
|
|
82197
|
+
clearData() {
|
|
82198
|
+
this.clearPluginData();
|
|
82199
|
+
this.action = undefined;
|
|
82200
|
+
this.actionData = {};
|
|
82201
|
+
}
|
|
82202
|
+
getLocationInformation(eventType, payload) {
|
|
82203
|
+
return Object.assign({ hitType: eventType }, this.getNextValues(eventType, payload));
|
|
82204
|
+
}
|
|
82205
|
+
updateLocationInformation(eventType, payload) {
|
|
82206
|
+
this.updateLocationForNextPageView(eventType, payload);
|
|
82207
|
+
}
|
|
82208
|
+
getDefaultContextInformation(eventType) {
|
|
82209
|
+
const documentContext = {
|
|
82210
|
+
title: hasDocument() ? document.title : '',
|
|
82211
|
+
encoding: hasDocument() ? document.characterSet : 'UTF-8',
|
|
82212
|
+
};
|
|
82213
|
+
const screenContext = {
|
|
82214
|
+
screenResolution: `${screen.width}x${screen.height}`,
|
|
82215
|
+
screenColor: `${screen.colorDepth}-bit`,
|
|
82216
|
+
};
|
|
82217
|
+
const navigatorContext = {
|
|
82218
|
+
language: navigator.language,
|
|
82219
|
+
userAgent: navigator.userAgent,
|
|
82220
|
+
};
|
|
82221
|
+
const eventContext = {
|
|
82222
|
+
time: Date.now(),
|
|
82223
|
+
eventId: this.uuidGenerator(),
|
|
82224
|
+
};
|
|
82225
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({}, eventContext), screenContext), navigatorContext), documentContext);
|
|
82226
|
+
}
|
|
82227
|
+
updateLocationForNextPageView(eventType, payload) {
|
|
82228
|
+
const { pageViewId, referrer, location } = this.getNextValues(eventType, payload);
|
|
82229
|
+
this.lastReferrer = referrer;
|
|
82230
|
+
this.pageViewId = pageViewId;
|
|
82231
|
+
this.currentLocation = location;
|
|
82232
|
+
if (eventType === BasePluginEventTypes.pageview) {
|
|
82233
|
+
this.nextPageViewId = this.uuidGenerator();
|
|
82234
|
+
this.hasSentFirstPageView = true;
|
|
82235
|
+
}
|
|
82236
|
+
}
|
|
82237
|
+
getNextValues(eventType, payload) {
|
|
82238
|
+
return {
|
|
82239
|
+
pageViewId: eventType === BasePluginEventTypes.pageview ? this.nextPageViewId : this.pageViewId,
|
|
82240
|
+
referrer: eventType === BasePluginEventTypes.pageview && this.hasSentFirstPageView
|
|
82241
|
+
? this.currentLocation
|
|
82242
|
+
: this.lastReferrer,
|
|
82243
|
+
location: eventType === BasePluginEventTypes.pageview
|
|
82244
|
+
? this.getCurrentLocationFromPayload(payload)
|
|
82245
|
+
: this.currentLocation,
|
|
82246
|
+
};
|
|
82247
|
+
}
|
|
82248
|
+
getCurrentLocationFromPayload(payload) {
|
|
82249
|
+
if (!!payload.page) {
|
|
82250
|
+
const removeStartingSlash = (page) => page.replace(/^\/?(.*)$/, '/$1');
|
|
82251
|
+
const extractHostnamePart = (location) => location.split('/').slice(0, 3).join('/');
|
|
82252
|
+
return `${extractHostnamePart(this.currentLocation)}${removeStartingSlash(payload.page)}`;
|
|
82253
|
+
}
|
|
82254
|
+
else {
|
|
82255
|
+
return getFormattedLocation(window.location);
|
|
82256
|
+
}
|
|
82257
|
+
}
|
|
82258
|
+
}
|
|
82259
|
+
|
|
82260
|
+
class CoveoLinkParam {
|
|
82261
|
+
constructor(clientId, timestamp) {
|
|
82262
|
+
if (!validate(clientId))
|
|
82263
|
+
throw Error('Not a valid uuid');
|
|
82264
|
+
this.clientId = clientId;
|
|
82265
|
+
this.creationDate = Math.floor(timestamp / 1000);
|
|
82266
|
+
}
|
|
82267
|
+
toString() {
|
|
82268
|
+
return this.clientId.replace(/-/g, '') + '.' + this.creationDate.toString();
|
|
82269
|
+
}
|
|
82270
|
+
get expired() {
|
|
82271
|
+
const age = Math.floor(Date.now() / 1000) - this.creationDate;
|
|
82272
|
+
return age < 0 || age > CoveoLinkParam.expirationTime;
|
|
82273
|
+
}
|
|
82274
|
+
validate(referrerString, referrerList) {
|
|
82275
|
+
return !this.expired && this.matchReferrer(referrerString, referrerList);
|
|
82276
|
+
}
|
|
82277
|
+
matchReferrer(referrerString, referrerList) {
|
|
82278
|
+
try {
|
|
82279
|
+
const url = new URL(referrerString);
|
|
82280
|
+
return referrerList.some((value) => {
|
|
82281
|
+
const hostRegExp = new RegExp(value.replace(/\\/g, '\\\\').replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
82282
|
+
return hostRegExp.test(url.host);
|
|
82283
|
+
});
|
|
82284
|
+
}
|
|
82285
|
+
catch (error) {
|
|
82286
|
+
return false;
|
|
82287
|
+
}
|
|
82288
|
+
}
|
|
82289
|
+
static fromString(input) {
|
|
82290
|
+
const parts = input.split('.');
|
|
82291
|
+
if (parts.length !== 2) {
|
|
82292
|
+
return null;
|
|
82293
|
+
}
|
|
82294
|
+
const [clientIdPart, creationDate] = parts;
|
|
82295
|
+
if (clientIdPart.length !== 32 || isNaN(parseInt(creationDate))) {
|
|
82296
|
+
return null;
|
|
82297
|
+
}
|
|
82298
|
+
const clientId = clientIdPart.substring(0, 8) +
|
|
82299
|
+
'-' +
|
|
82300
|
+
clientIdPart.substring(8, 12) +
|
|
82301
|
+
'-' +
|
|
82302
|
+
clientIdPart.substring(12, 16) +
|
|
82303
|
+
'-' +
|
|
82304
|
+
clientIdPart.substring(16, 20) +
|
|
82305
|
+
'-' +
|
|
82306
|
+
clientIdPart.substring(20, 32);
|
|
82307
|
+
if (validate(clientId)) {
|
|
82308
|
+
return new CoveoLinkParam(clientId, Number.parseInt(creationDate) * 1000);
|
|
82309
|
+
}
|
|
82310
|
+
else {
|
|
82311
|
+
return null;
|
|
82312
|
+
}
|
|
82313
|
+
}
|
|
82314
|
+
}
|
|
82315
|
+
CoveoLinkParam.cvo_cid = 'cvo_cid';
|
|
82316
|
+
CoveoLinkParam.expirationTime = 120;
|
|
82317
|
+
class LinkPlugin extends Plugin {
|
|
82318
|
+
constructor({ client, uuidGenerator = v4 }) {
|
|
82319
|
+
super({ client, uuidGenerator });
|
|
82320
|
+
}
|
|
82321
|
+
getApi(name) {
|
|
82322
|
+
switch (name) {
|
|
82323
|
+
case 'decorate':
|
|
82324
|
+
return this.decorate;
|
|
82325
|
+
case 'acceptFrom':
|
|
82326
|
+
return this.acceptFrom;
|
|
82327
|
+
default:
|
|
82328
|
+
return null;
|
|
82329
|
+
}
|
|
82330
|
+
}
|
|
82331
|
+
decorate(urlString) {
|
|
82332
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82333
|
+
if (!this.client.getCurrentVisitorId) {
|
|
82334
|
+
throw new Error('Could not retrieve current clientId');
|
|
82335
|
+
}
|
|
82336
|
+
try {
|
|
82337
|
+
const url = new URL(urlString);
|
|
82338
|
+
const clientId = yield this.client.getCurrentVisitorId();
|
|
82339
|
+
url.searchParams.set(CoveoLinkParam.cvo_cid, new CoveoLinkParam(clientId, Date.now()).toString());
|
|
82340
|
+
return url.toString();
|
|
82341
|
+
}
|
|
82342
|
+
catch (error) {
|
|
82343
|
+
throw new Error('Invalid URL provided');
|
|
82344
|
+
}
|
|
82345
|
+
});
|
|
82346
|
+
}
|
|
82347
|
+
acceptFrom(acceptedReferrers) {
|
|
82348
|
+
this.client.setAcceptedLinkReferrers(acceptedReferrers);
|
|
82349
|
+
}
|
|
82350
|
+
}
|
|
82351
|
+
LinkPlugin.Id = 'link';
|
|
82162
82352
|
|
|
82163
82353
|
const keysOf = Object.keys;
|
|
82164
82354
|
function isObject(o) {
|
|
@@ -82522,6 +82712,7 @@ function buildBaseUrl(endpoint = Endpoints.default, apiVersion = Version) {
|
|
|
82522
82712
|
const COVEO_NAMESPACE = '38824e1f-37f5-42d3-8372-a4b8fa9df946';
|
|
82523
82713
|
class CoveoAnalyticsClient {
|
|
82524
82714
|
constructor(opts) {
|
|
82715
|
+
this.acceptedLinkReferrers = [];
|
|
82525
82716
|
if (!opts) {
|
|
82526
82717
|
throw new Error('You have to pass options to this constructor');
|
|
82527
82718
|
}
|
|
@@ -82574,7 +82765,9 @@ class CoveoAnalyticsClient {
|
|
|
82574
82765
|
determineVisitorId() {
|
|
82575
82766
|
return __awaiter(this, void 0, void 0, function* () {
|
|
82576
82767
|
try {
|
|
82577
|
-
return (
|
|
82768
|
+
return (this.extractClientIdFromLink(window.location.href) ||
|
|
82769
|
+
(yield this.storage.getItem('visitorId')) ||
|
|
82770
|
+
v4());
|
|
82578
82771
|
}
|
|
82579
82772
|
catch (err) {
|
|
82580
82773
|
console.log('Could not get visitor ID from the current runtime environment storage. Using a random ID instead.', err);
|
|
@@ -82632,6 +82825,25 @@ class CoveoAnalyticsClient {
|
|
|
82632
82825
|
this.visitorId = visitorId;
|
|
82633
82826
|
this.storage.setItem('visitorId', visitorId);
|
|
82634
82827
|
}
|
|
82828
|
+
extractClientIdFromLink(urlString) {
|
|
82829
|
+
if (doNotTrack()) {
|
|
82830
|
+
return null;
|
|
82831
|
+
}
|
|
82832
|
+
try {
|
|
82833
|
+
const linkParam = new URL(urlString).searchParams.get(CoveoLinkParam.cvo_cid);
|
|
82834
|
+
if (linkParam == null) {
|
|
82835
|
+
return null;
|
|
82836
|
+
}
|
|
82837
|
+
const linker = CoveoLinkParam.fromString(linkParam);
|
|
82838
|
+
if (!linker || !hasDocument() || !linker.validate(document.referrer, this.acceptedLinkReferrers)) {
|
|
82839
|
+
return null;
|
|
82840
|
+
}
|
|
82841
|
+
return linker.clientId;
|
|
82842
|
+
}
|
|
82843
|
+
catch (error) {
|
|
82844
|
+
}
|
|
82845
|
+
return null;
|
|
82846
|
+
}
|
|
82635
82847
|
resolveParameters(eventType, ...payload) {
|
|
82636
82848
|
return __awaiter(this, void 0, void 0, function* () {
|
|
82637
82849
|
const { variableLengthArgumentsNames = [], addVisitorIdParameter = false, usesMeasurementProtocol = false, addClientIdParameter = false, } = this.eventTypeMapping[eventType] || {};
|
|
@@ -82799,6 +83011,12 @@ class CoveoAnalyticsClient {
|
|
|
82799
83011
|
addEventTypeMapping(eventType, eventConfig) {
|
|
82800
83012
|
this.eventTypeMapping[eventType] = eventConfig;
|
|
82801
83013
|
}
|
|
83014
|
+
setAcceptedLinkReferrers(hosts) {
|
|
83015
|
+
if (Array.isArray(hosts) && hosts.every((host) => typeof host == 'string'))
|
|
83016
|
+
this.acceptedLinkReferrers = hosts;
|
|
83017
|
+
else
|
|
83018
|
+
throw Error('Parameter should be an array of domain strings');
|
|
83019
|
+
}
|
|
82802
83020
|
parseVariableArgumentsPayload(fieldsOrder, payload) {
|
|
82803
83021
|
const parsedArguments = {};
|
|
82804
83022
|
for (let i = 0, length = payload.length; i < length; i++) {
|
|
@@ -83833,90 +84051,6 @@ class CoveoSearchPageClient {
|
|
|
83833
84051
|
}
|
|
83834
84052
|
}
|
|
83835
84053
|
|
|
83836
|
-
const getFormattedLocation = (location) => `${location.protocol}//${location.hostname}${location.pathname.indexOf('/') === 0 ? location.pathname : `/${location.pathname}`}${location.search}`;
|
|
83837
|
-
|
|
83838
|
-
const BasePluginEventTypes = {
|
|
83839
|
-
pageview: 'pageview',
|
|
83840
|
-
event: 'event',
|
|
83841
|
-
};
|
|
83842
|
-
class BasePlugin {
|
|
83843
|
-
constructor({ client, uuidGenerator = v4 }) {
|
|
83844
|
-
this.actionData = {};
|
|
83845
|
-
this.client = client;
|
|
83846
|
-
this.uuidGenerator = uuidGenerator;
|
|
83847
|
-
this.pageViewId = uuidGenerator();
|
|
83848
|
-
this.nextPageViewId = this.pageViewId;
|
|
83849
|
-
this.currentLocation = getFormattedLocation(window.location);
|
|
83850
|
-
this.lastReferrer = hasDocument() ? document.referrer : '';
|
|
83851
|
-
this.addHooks();
|
|
83852
|
-
}
|
|
83853
|
-
setAction(action, options) {
|
|
83854
|
-
this.action = action;
|
|
83855
|
-
this.actionData = options;
|
|
83856
|
-
}
|
|
83857
|
-
clearData() {
|
|
83858
|
-
this.clearPluginData();
|
|
83859
|
-
this.action = undefined;
|
|
83860
|
-
this.actionData = {};
|
|
83861
|
-
}
|
|
83862
|
-
getLocationInformation(eventType, payload) {
|
|
83863
|
-
return Object.assign({ hitType: eventType }, this.getNextValues(eventType, payload));
|
|
83864
|
-
}
|
|
83865
|
-
updateLocationInformation(eventType, payload) {
|
|
83866
|
-
this.updateLocationForNextPageView(eventType, payload);
|
|
83867
|
-
}
|
|
83868
|
-
getDefaultContextInformation(eventType) {
|
|
83869
|
-
const documentContext = {
|
|
83870
|
-
title: hasDocument() ? document.title : '',
|
|
83871
|
-
encoding: hasDocument() ? document.characterSet : 'UTF-8',
|
|
83872
|
-
};
|
|
83873
|
-
const screenContext = {
|
|
83874
|
-
screenResolution: `${screen.width}x${screen.height}`,
|
|
83875
|
-
screenColor: `${screen.colorDepth}-bit`,
|
|
83876
|
-
};
|
|
83877
|
-
const navigatorContext = {
|
|
83878
|
-
language: navigator.language,
|
|
83879
|
-
userAgent: navigator.userAgent,
|
|
83880
|
-
};
|
|
83881
|
-
const eventContext = {
|
|
83882
|
-
time: Date.now(),
|
|
83883
|
-
eventId: this.uuidGenerator(),
|
|
83884
|
-
};
|
|
83885
|
-
return Object.assign(Object.assign(Object.assign(Object.assign({}, eventContext), screenContext), navigatorContext), documentContext);
|
|
83886
|
-
}
|
|
83887
|
-
updateLocationForNextPageView(eventType, payload) {
|
|
83888
|
-
const { pageViewId, referrer, location } = this.getNextValues(eventType, payload);
|
|
83889
|
-
this.lastReferrer = referrer;
|
|
83890
|
-
this.pageViewId = pageViewId;
|
|
83891
|
-
this.currentLocation = location;
|
|
83892
|
-
if (eventType === BasePluginEventTypes.pageview) {
|
|
83893
|
-
this.nextPageViewId = this.uuidGenerator();
|
|
83894
|
-
this.hasSentFirstPageView = true;
|
|
83895
|
-
}
|
|
83896
|
-
}
|
|
83897
|
-
getNextValues(eventType, payload) {
|
|
83898
|
-
return {
|
|
83899
|
-
pageViewId: eventType === BasePluginEventTypes.pageview ? this.nextPageViewId : this.pageViewId,
|
|
83900
|
-
referrer: eventType === BasePluginEventTypes.pageview && this.hasSentFirstPageView
|
|
83901
|
-
? this.currentLocation
|
|
83902
|
-
: this.lastReferrer,
|
|
83903
|
-
location: eventType === BasePluginEventTypes.pageview
|
|
83904
|
-
? this.getCurrentLocationFromPayload(payload)
|
|
83905
|
-
: this.currentLocation,
|
|
83906
|
-
};
|
|
83907
|
-
}
|
|
83908
|
-
getCurrentLocationFromPayload(payload) {
|
|
83909
|
-
if (!!payload.page) {
|
|
83910
|
-
const removeStartingSlash = (page) => page.replace(/^\/?(.*)$/, '/$1');
|
|
83911
|
-
const extractHostnamePart = (location) => location.split('/').slice(0, 3).join('/');
|
|
83912
|
-
return `${extractHostnamePart(this.currentLocation)}${removeStartingSlash(payload.page)}`;
|
|
83913
|
-
}
|
|
83914
|
-
else {
|
|
83915
|
-
return getFormattedLocation(window.location);
|
|
83916
|
-
}
|
|
83917
|
-
}
|
|
83918
|
-
}
|
|
83919
|
-
|
|
83920
84054
|
const SVCPluginEventTypes = Object.assign({}, BasePluginEventTypes);
|
|
83921
84055
|
const allSVCEventTypes = Object.keys(SVCPluginEventTypes).map((key) => SVCPluginEventTypes[key]);
|
|
83922
84056
|
class SVCPlugin extends BasePlugin {
|
|
@@ -83924,6 +84058,17 @@ class SVCPlugin extends BasePlugin {
|
|
|
83924
84058
|
super({ client, uuidGenerator });
|
|
83925
84059
|
this.ticket = {};
|
|
83926
84060
|
}
|
|
84061
|
+
getApi(name) {
|
|
84062
|
+
const superCall = super.getApi(name);
|
|
84063
|
+
if (superCall !== null)
|
|
84064
|
+
return superCall;
|
|
84065
|
+
switch (name) {
|
|
84066
|
+
case 'setTicket':
|
|
84067
|
+
return this.setTicket;
|
|
84068
|
+
default:
|
|
84069
|
+
return null;
|
|
84070
|
+
}
|
|
84071
|
+
}
|
|
83927
84072
|
addHooks() {
|
|
83928
84073
|
this.addHooksForEvent();
|
|
83929
84074
|
this.addHooksForPageView();
|
package/package.json
CHANGED
|
@@ -7,6 +7,8 @@ import {mockFetch} from '../../tests/fetchMock';
|
|
|
7
7
|
import {BrowserRuntime} from './runtimeEnvironment';
|
|
8
8
|
import * as doNotTrack from '../donottrack';
|
|
9
9
|
import {Cookie} from '../cookieutils';
|
|
10
|
+
import {v4 as uuidv4} from 'uuid';
|
|
11
|
+
import {CoveoLinkParam} from '../plugins/link';
|
|
10
12
|
|
|
11
13
|
const aVisitorId = '123';
|
|
12
14
|
jest.mock('uuid', () => ({
|
|
@@ -483,3 +485,123 @@ describe('custom clientId', () => {
|
|
|
483
485
|
//uuid v5 specific uuid generation
|
|
484
486
|
});
|
|
485
487
|
});
|
|
488
|
+
|
|
489
|
+
describe('clientId from link', () => {
|
|
490
|
+
// note: referrer is set as http://somewhere.over/thereferrer in setup.js
|
|
491
|
+
let client: CoveoAnalyticsClient;
|
|
492
|
+
const forcedUUID: string = 'c0b48880-743e-484f-8044-d7c37910c55b';
|
|
493
|
+
|
|
494
|
+
function navigateTo(url: string) {
|
|
495
|
+
// @ts-ignore
|
|
496
|
+
delete window.location;
|
|
497
|
+
// @ts-ignore
|
|
498
|
+
window.location = new URL(url);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
beforeEach(() => {
|
|
502
|
+
client = new CoveoAnalyticsClient({});
|
|
503
|
+
// need to clear existing clientIds
|
|
504
|
+
client.clear();
|
|
505
|
+
jest.spyOn(doNotTrack, 'doNotTrack').mockImplementation(() => false);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('will extract a clientId from a query param if the referrer matches all and it is not expired', async () => {
|
|
509
|
+
client.setAcceptedLinkReferrers(['*']);
|
|
510
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
511
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
512
|
+
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('will extract a clientId from a query param if the referrer matches the current referrer exactly and it is not expired', async () => {
|
|
516
|
+
client.setAcceptedLinkReferrers(['somewhere.over']);
|
|
517
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
518
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
519
|
+
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('will extract a clientId from a query param if the referrer matches the current referrer with wildcard and it is not expired', async () => {
|
|
523
|
+
client.setAcceptedLinkReferrers(['*.over']);
|
|
524
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
525
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
526
|
+
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('will extract a clientId from a query param if one of the referrer matches the current referrer and it is not expired', async () => {
|
|
530
|
+
client.setAcceptedLinkReferrers(['*.mydomain.com', '*.over']);
|
|
531
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
532
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
533
|
+
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('will not extract a clientId from a query param if the referrer matches and it is expired', async () => {
|
|
537
|
+
client.setAcceptedLinkReferrers(['*']);
|
|
538
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now() - 180000);
|
|
539
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
540
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
541
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('will not extract a clientId from a query param if there is no accept list specified', async () => {
|
|
545
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
546
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
547
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
548
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('will not extract a clientId from a query param if there is an empty accept list', async () => {
|
|
552
|
+
client.setAcceptedLinkReferrers([]);
|
|
553
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
554
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
555
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
556
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('will not extract a clientId from a query param if the referrer list does not match', async () => {
|
|
560
|
+
client.setAcceptedLinkReferrers(['*.mydomain.com']);
|
|
561
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
562
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
563
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
564
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('will not extract a clientId from a query param if the referrer list does not match the exact port', async () => {
|
|
568
|
+
client.setAcceptedLinkReferrers(['*.over:9000']);
|
|
569
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
570
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
571
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
572
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('will not extract a clientId from a query param if the multi referrer list does not match', async () => {
|
|
576
|
+
client.setAcceptedLinkReferrers(['*.mydomain.com', 'www.example.com']);
|
|
577
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
578
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
579
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
580
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('will not extract a clientId from a query param if it is not a UUID', async () => {
|
|
584
|
+
client.setAcceptedLinkReferrers(['*']);
|
|
585
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=notauuid.' + Math.floor(Date.now() / 1000));
|
|
586
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
587
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('will not extract a clientId from a query param if DNT is enabled', async () => {
|
|
591
|
+
client.setAcceptedLinkReferrers(['*']);
|
|
592
|
+
jest.spyOn(doNotTrack, 'doNotTrack').mockImplementation(() => true);
|
|
593
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
594
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
595
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
596
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('will throw when specifying invalid hosts list', async () => {
|
|
600
|
+
//@ts-ignore
|
|
601
|
+
expect(() => client.setAcceptedLinkReferrers('*')).toThrow('Parameter should be an array of domain strings');
|
|
602
|
+
//@ts-ignore
|
|
603
|
+
expect(() => client.setAcceptedLinkReferrers({})).toThrow('Parameter should be an array of domain strings');
|
|
604
|
+
//@ts-ignore
|
|
605
|
+
expect(() => client.setAcceptedLinkReferrers([{}])).toThrow('Parameter should be an array of domain strings');
|
|
606
|
+
});
|
|
607
|
+
});
|
package/src/client/analytics.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {addDefaultValues} from '../hook/addDefaultValues';
|
|
|
24
24
|
import {enhanceViewEvent} from '../hook/enhanceViewEvent';
|
|
25
25
|
import {v4 as uuidv4, v5 as uuidv5, validate as uuidValidate} from 'uuid';
|
|
26
26
|
import {libVersion} from '../version';
|
|
27
|
+
import {CoveoLinkParam} from '../plugins/link';
|
|
27
28
|
import {
|
|
28
29
|
convertKeysToMeasurementProtocol,
|
|
29
30
|
isMeasurementProtocolKey,
|
|
@@ -105,6 +106,7 @@ export interface AnalyticsClient {
|
|
|
105
106
|
*/
|
|
106
107
|
readonly currentVisitorId: string;
|
|
107
108
|
getCurrentVisitorId?(): Promise<string>; // TODO: v3 make required
|
|
109
|
+
setAcceptedLinkReferrers?(hosts: string[]): void;
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
export interface BufferedRequest {
|
|
@@ -146,6 +148,7 @@ export class CoveoAnalyticsClient implements AnalyticsClient, VisitorIdProvider
|
|
|
146
148
|
private afterSendHooks: AnalyticsClientSendEventHook[];
|
|
147
149
|
private eventTypeMapping: {[name: string]: EventTypeConfig};
|
|
148
150
|
private options: ClientOptions;
|
|
151
|
+
private acceptedLinkReferrers: string[] = [];
|
|
149
152
|
|
|
150
153
|
constructor(opts: Partial<ClientOptions>) {
|
|
151
154
|
if (!opts) {
|
|
@@ -196,7 +199,11 @@ export class CoveoAnalyticsClient implements AnalyticsClient, VisitorIdProvider
|
|
|
196
199
|
|
|
197
200
|
private async determineVisitorId() {
|
|
198
201
|
try {
|
|
199
|
-
return (
|
|
202
|
+
return (
|
|
203
|
+
this.extractClientIdFromLink(window.location.href) ||
|
|
204
|
+
(await this.storage.getItem('visitorId')) ||
|
|
205
|
+
uuidv4()
|
|
206
|
+
);
|
|
200
207
|
} catch (err) {
|
|
201
208
|
console.log(
|
|
202
209
|
'Could not get visitor ID from the current runtime environment storage. Using a random ID instead.',
|
|
@@ -258,6 +265,26 @@ export class CoveoAnalyticsClient implements AnalyticsClient, VisitorIdProvider
|
|
|
258
265
|
this.storage.setItem('visitorId', visitorId);
|
|
259
266
|
}
|
|
260
267
|
|
|
268
|
+
private extractClientIdFromLink(urlString: string): string | null {
|
|
269
|
+
if (doNotTrack()) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const linkParam: string | null = new URL(urlString).searchParams.get(CoveoLinkParam.cvo_cid);
|
|
274
|
+
if (linkParam == null) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const linker: CoveoLinkParam | null = CoveoLinkParam.fromString(linkParam);
|
|
278
|
+
if (!linker || !hasDocument() || !linker.validate(document.referrer, this.acceptedLinkReferrers)) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
return linker.clientId;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
// Ignore any parsing errors
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
261
288
|
async resolveParameters(eventType: EventType | string, ...payload: VariableArgumentsPayload) {
|
|
262
289
|
const {
|
|
263
290
|
variableLengthArgumentsNames = [],
|
|
@@ -453,6 +480,11 @@ export class CoveoAnalyticsClient implements AnalyticsClient, VisitorIdProvider
|
|
|
453
480
|
this.eventTypeMapping[eventType] = eventConfig;
|
|
454
481
|
}
|
|
455
482
|
|
|
483
|
+
setAcceptedLinkReferrers(hosts: string[]): void {
|
|
484
|
+
if (Array.isArray(hosts) && hosts.every((host) => typeof host == 'string')) this.acceptedLinkReferrers = hosts;
|
|
485
|
+
else throw Error('Parameter should be an array of domain strings');
|
|
486
|
+
}
|
|
487
|
+
|
|
456
488
|
private parseVariableArgumentsPayload(fieldsOrder: string[], payload: VariableArgumentsPayload) {
|
|
457
489
|
const parsedArguments: {[name: string]: any} = {};
|
|
458
490
|
for (let i = 0, length = payload.length; i < length; i++) {
|
package/src/coveoua/plugins.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import {PluginClass, PluginOptions, BasePlugin} from '../plugins/BasePlugin';
|
|
1
|
+
import {PluginClass, PluginOptions, BasePlugin, Plugin} from '../plugins/BasePlugin';
|
|
2
2
|
import {EC} from '../plugins/ec';
|
|
3
|
+
import {Link} from '../plugins/link';
|
|
3
4
|
import {SVC} from '../plugins/svc';
|
|
4
5
|
|
|
5
|
-
export type UAPluginOptions = any[];
|
|
6
|
-
export type Plugin = BasePlugin & {[propName: string]: unknown};
|
|
7
|
-
|
|
8
6
|
export class Plugins {
|
|
9
|
-
public static readonly DefaultPlugins: string[] = [EC.Id, SVC.Id];
|
|
7
|
+
public static readonly DefaultPlugins: string[] = [EC.Id, SVC.Id, Link.Id];
|
|
10
8
|
private registeredPluginsMap: Record<string, PluginClass> = {
|
|
11
9
|
[EC.Id]: EC,
|
|
12
10
|
[SVC.Id]: SVC,
|
|
11
|
+
[Link.Id]: Link,
|
|
13
12
|
};
|
|
14
13
|
private requiredPlugins: Record<string, BasePlugin> = {};
|
|
15
14
|
|
|
@@ -31,18 +30,18 @@ export class Plugins {
|
|
|
31
30
|
this.requiredPlugins = {};
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
execute(name: string, fn: string, ...
|
|
33
|
+
execute(name: string, fn: string, ...args: any[]): any {
|
|
35
34
|
const plugin = this.requiredPlugins[name] as Plugin;
|
|
36
35
|
if (!plugin) {
|
|
37
36
|
throw new Error(`The plugin "${name}" is not required. Check that you required it on initialization.`);
|
|
38
37
|
}
|
|
39
|
-
const actionFunction = plugin
|
|
38
|
+
const actionFunction = plugin.getApi(fn);
|
|
40
39
|
if (!actionFunction) {
|
|
41
40
|
throw new Error(`The function "${fn}" does not exist on the plugin "${name}".`);
|
|
42
41
|
}
|
|
43
42
|
if (typeof actionFunction !== 'function') {
|
|
44
43
|
throw new Error(`"${fn}" of the plugin "${name}" is not a function.`);
|
|
45
44
|
}
|
|
46
|
-
return actionFunction.apply(plugin,
|
|
45
|
+
return actionFunction.apply(plugin, args);
|
|
47
46
|
}
|
|
48
47
|
}
|