@jitsu/js 1.9.10 → 1.9.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-clean.log +1 -1
- package/.turbo/turbo-test.log +1134 -463
- package/__tests__/node/nodejs.test.ts +27 -0
- package/__tests__/playwright/cases/anonymous-id-bug.html +26 -0
- package/__tests__/playwright/cases/basic.html +5 -0
- package/__tests__/playwright/cases/reset.html +1 -0
- package/__tests__/playwright/integration.test.ts +46 -0
- package/dist/jitsu.cjs.js +76 -14
- package/dist/jitsu.es.js +76 -14
- package/dist/web/p.js.txt +76 -14
- package/package.json +4 -4
- package/src/analytics-plugin.ts +90 -18
package/dist/web/p.js.txt
CHANGED
|
@@ -1062,10 +1062,13 @@
|
|
|
1062
1062
|
fetch: null,
|
|
1063
1063
|
echoEvents: false,
|
|
1064
1064
|
cookieDomain: undefined,
|
|
1065
|
+
cookieCapture: {},
|
|
1065
1066
|
runtime: undefined,
|
|
1066
1067
|
fetchTimeoutMs: undefined,
|
|
1067
1068
|
s2s: undefined,
|
|
1068
1069
|
idEndpoint: undefined,
|
|
1070
|
+
errorPolicy: "log",
|
|
1071
|
+
defaultPayloadContext: {},
|
|
1069
1072
|
privacy: {
|
|
1070
1073
|
dontSend: false,
|
|
1071
1074
|
disableUserIds: false,
|
|
@@ -1150,6 +1153,14 @@
|
|
|
1150
1153
|
const parts = value.split(`; ${name}=`);
|
|
1151
1154
|
return parts.length === 2 ? parts.pop().split(";").shift() : undefined;
|
|
1152
1155
|
}
|
|
1156
|
+
function getClientIds(runtime, customCookieCapture) {
|
|
1157
|
+
const cookieCapture = Object.assign({ fbc: "_fbc", fbp: "_fbp" }, customCookieCapture);
|
|
1158
|
+
const clientIds = Object.entries(cookieCapture).reduce((acc, [key, cookieName]) => {
|
|
1159
|
+
acc[key] = runtime.getCookie(cookieName);
|
|
1160
|
+
return acc;
|
|
1161
|
+
}, {});
|
|
1162
|
+
return Object.assign(Object.assign({}, clientIds), getGa4Ids(runtime));
|
|
1163
|
+
}
|
|
1153
1164
|
function getGa4Ids(runtime) {
|
|
1154
1165
|
var _a;
|
|
1155
1166
|
const allCookies = runtime.getCookies();
|
|
@@ -1227,6 +1238,10 @@
|
|
|
1227
1238
|
getItem(key) {
|
|
1228
1239
|
const cookieName = key2cookie[key] || key;
|
|
1229
1240
|
const result = getCookie(cookieName);
|
|
1241
|
+
if (key === "__anon_id") {
|
|
1242
|
+
//anonymous id must always be a string, so we don't parse it to preserve its exact value
|
|
1243
|
+
return result;
|
|
1244
|
+
}
|
|
1230
1245
|
if (typeof result === "undefined" && key === "__user_id") {
|
|
1231
1246
|
//backward compatibility with old jitsu cookie. get user id if from traits
|
|
1232
1247
|
const traits = parse(getCookie("__eventn_id_usr")) || {};
|
|
@@ -1402,18 +1417,19 @@
|
|
|
1402
1417
|
return "/" + pathMatch;
|
|
1403
1418
|
}
|
|
1404
1419
|
function adjustPayload(payload, config, storage, s2s) {
|
|
1405
|
-
var _a, _b, _c, _d, _e;
|
|
1420
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1406
1421
|
const runtime = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
|
|
1407
1422
|
const url = runtime.pageUrl();
|
|
1408
1423
|
const parsedUrl = safeCall(() => new URL(url), undefined);
|
|
1409
1424
|
const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
|
|
1410
1425
|
const properties = payload.properties || {};
|
|
1411
|
-
if (payload.type === "page" && url) {
|
|
1412
|
-
properties.url
|
|
1413
|
-
properties.
|
|
1426
|
+
if (payload.type === "page" && (properties.url || url)) {
|
|
1427
|
+
const targetUrl = properties.url || url;
|
|
1428
|
+
properties.url = targetUrl.replace(hashRegex, "");
|
|
1429
|
+
properties.path = fixPath(urlPath(targetUrl));
|
|
1414
1430
|
}
|
|
1415
|
-
const customContext = ((_a = payload.properties) === null || _a === void 0 ? void 0 : _a.context) || {};
|
|
1416
|
-
(
|
|
1431
|
+
const customContext = deepMerge(config.defaultPayloadContext, ((_a = payload.properties) === null || _a === void 0 ? void 0 : _a.context) || ((_b = payload.options) === null || _b === void 0 ? void 0 : _b.context) || {});
|
|
1432
|
+
(_c = payload.properties) === null || _c === void 0 ? true : delete _c.context;
|
|
1417
1433
|
const referrer = runtime.referrer();
|
|
1418
1434
|
const context = {
|
|
1419
1435
|
library: {
|
|
@@ -1421,7 +1437,7 @@
|
|
|
1421
1437
|
version: jitsuVersion,
|
|
1422
1438
|
env: isInBrowser() ? "browser" : "node",
|
|
1423
1439
|
},
|
|
1424
|
-
consent: ((
|
|
1440
|
+
consent: ((_d = config.privacy) === null || _d === void 0 ? void 0 : _d.consentCategories)
|
|
1425
1441
|
? {
|
|
1426
1442
|
categoryPreferences: config.privacy.consentCategories,
|
|
1427
1443
|
}
|
|
@@ -1440,14 +1456,13 @@
|
|
|
1440
1456
|
url: properties.url || url,
|
|
1441
1457
|
encoding: properties.encoding || runtime.documentEncoding(),
|
|
1442
1458
|
},
|
|
1443
|
-
clientIds: !((
|
|
1444
|
-
? Object.assign({ fbc: runtime.getCookie("_fbc"), fbp: runtime.getCookie("_fbp") }, getGa4Ids(runtime)) : undefined,
|
|
1459
|
+
clientIds: !((_e = config.privacy) === null || _e === void 0 ? void 0 : _e.disableUserIds) ? getClientIds(runtime, config.cookieCapture) : undefined,
|
|
1445
1460
|
campaign: parseUtms(query),
|
|
1446
1461
|
};
|
|
1447
1462
|
const withContext = Object.assign(Object.assign({}, payload), { timestamp: new Date().toISOString(), sentAt: new Date().toISOString(), messageId: randomId(properties.path || (parsedUrl && parsedUrl.pathname)), writeKey: maskWriteKey(config.writeKey), groupId: storage.getItem("__group_id"), context: deepMerge(context, customContext) });
|
|
1448
1463
|
delete withContext.meta;
|
|
1449
1464
|
delete withContext.options;
|
|
1450
|
-
if ((
|
|
1465
|
+
if ((_f = config.privacy) === null || _f === void 0 ? void 0 : _f.disableUserIds) {
|
|
1451
1466
|
delete withContext.userId;
|
|
1452
1467
|
delete withContext.anonymousId;
|
|
1453
1468
|
delete withContext.context.traits;
|
|
@@ -1556,6 +1571,23 @@
|
|
|
1556
1571
|
}
|
|
1557
1572
|
return writeKey;
|
|
1558
1573
|
}
|
|
1574
|
+
function getErrorHandler(opts) {
|
|
1575
|
+
const configuredHandler = opts.errorPolicy || "log";
|
|
1576
|
+
if (typeof configuredHandler === "function") {
|
|
1577
|
+
return configuredHandler;
|
|
1578
|
+
}
|
|
1579
|
+
else if (configuredHandler === "rethrow") {
|
|
1580
|
+
return (msg, ...args) => {
|
|
1581
|
+
//ignore args, not clear what to do with them
|
|
1582
|
+
throw new Error(msg);
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
else {
|
|
1586
|
+
return (msg, ...args) => {
|
|
1587
|
+
console.error(msg, ...args);
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1559
1591
|
function send(method, payload, jitsuConfig, instance, store) {
|
|
1560
1592
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
1561
1593
|
var _a, _b;
|
|
@@ -1567,6 +1599,7 @@
|
|
|
1567
1599
|
const url = s2s ? `${jitsuConfig.host}/api/s/s2s/${method}` : `${jitsuConfig.host}/api/s/${method}`;
|
|
1568
1600
|
const fetch = jitsuConfig.fetch || globalThis.fetch;
|
|
1569
1601
|
if (!fetch) {
|
|
1602
|
+
//don't run it through error handler since error is critical and should be addressed
|
|
1570
1603
|
throw new Error("Please specify fetch function in jitsu plugin initialization, fetch isn't available in global scope");
|
|
1571
1604
|
}
|
|
1572
1605
|
const debugHeader = jitsuConfig.debug ? { "X-Enable-Debug": "true" } : {};
|
|
@@ -1597,7 +1630,8 @@
|
|
|
1597
1630
|
}
|
|
1598
1631
|
}
|
|
1599
1632
|
catch (e) {
|
|
1600
|
-
|
|
1633
|
+
getErrorHandler(jitsuConfig)(`Call to ${url} failed with error ${e.message}`);
|
|
1634
|
+
return Promise.resolve();
|
|
1601
1635
|
}
|
|
1602
1636
|
let responseText;
|
|
1603
1637
|
try {
|
|
@@ -1610,14 +1644,16 @@
|
|
|
1610
1644
|
console.log(`[JITSU DEBUG] ${url} replied ${fetchResult.status}: ${responseText}. Original payload:\n${JSON.stringify(adjustedPayload, null, 2)}`);
|
|
1611
1645
|
}
|
|
1612
1646
|
if (!fetchResult.ok) {
|
|
1613
|
-
|
|
1647
|
+
getErrorHandler(jitsuConfig)(`Call to ${url} failed with error: ${fetchResult.status} - ${fetchResult.statusText}: ${responseText}`);
|
|
1648
|
+
return Promise.resolve();
|
|
1614
1649
|
}
|
|
1615
1650
|
let responseJson;
|
|
1616
1651
|
try {
|
|
1617
1652
|
responseJson = JSON.parse(responseText);
|
|
1618
1653
|
}
|
|
1619
1654
|
catch (e) {
|
|
1620
|
-
|
|
1655
|
+
getErrorHandler(jitsuConfig)(`Can't parse JSON: ${responseText}: ${e === null || e === void 0 ? void 0 : e.message}`);
|
|
1656
|
+
return Promise.resolve();
|
|
1621
1657
|
}
|
|
1622
1658
|
if (responseJson.destinations && responseJson.destinations.length > 0) {
|
|
1623
1659
|
if (jitsuConfig.s2s) {
|
|
@@ -1636,6 +1672,22 @@
|
|
|
1636
1672
|
return adjustedPayload;
|
|
1637
1673
|
});
|
|
1638
1674
|
}
|
|
1675
|
+
const controllingTraits = ["$doNotSend"];
|
|
1676
|
+
/**
|
|
1677
|
+
* Remove all members of traits that controls identify/group behavior (see analytics.d.ts), and should not be recorded. Returns
|
|
1678
|
+
* copy of the object with these members removed.
|
|
1679
|
+
*
|
|
1680
|
+
* Do not modify traits object, but creates one
|
|
1681
|
+
* @param traits
|
|
1682
|
+
*/
|
|
1683
|
+
function stripControllingTraits(traits) {
|
|
1684
|
+
const res = Object.assign({}, traits);
|
|
1685
|
+
// see Traits definition in analytics.d.ts. We cannot define const here, so here's a little code duplication
|
|
1686
|
+
for (const key of controllingTraits) {
|
|
1687
|
+
delete res[key];
|
|
1688
|
+
}
|
|
1689
|
+
return res;
|
|
1690
|
+
}
|
|
1639
1691
|
const jitsuAnalyticsPlugin = (jitsuOptions = {}, storage) => {
|
|
1640
1692
|
// just to make sure that all undefined values are replaced with defaultConfig values
|
|
1641
1693
|
mergeConfig(jitsuOptions, jitsuOptions);
|
|
@@ -1694,16 +1746,21 @@
|
|
|
1694
1746
|
return send("track", payload, config, instance, storage);
|
|
1695
1747
|
},
|
|
1696
1748
|
identify: args => {
|
|
1697
|
-
var _a, _b;
|
|
1749
|
+
var _a, _b, _c;
|
|
1698
1750
|
const { payload, config, instance } = args;
|
|
1699
1751
|
if (((_a = config.privacy) === null || _a === void 0 ? void 0 : _a.dontSend) || ((_b = config.privacy) === null || _b === void 0 ? void 0 : _b.disableUserIds)) {
|
|
1700
1752
|
return;
|
|
1701
1753
|
}
|
|
1702
1754
|
// Store traits in cache to be able to use them in page and track events that run asynchronously with current identify.
|
|
1703
1755
|
storage.setItem("__user_id", payload.userId);
|
|
1756
|
+
const doNotSend = (_c = payload.traits) === null || _c === void 0 ? void 0 : _c.$doNotSend;
|
|
1704
1757
|
if (payload.traits && typeof payload.traits === "object") {
|
|
1758
|
+
payload.traits = stripControllingTraits(payload.traits);
|
|
1705
1759
|
storage.setItem("__user_traits", payload.traits);
|
|
1706
1760
|
}
|
|
1761
|
+
if (doNotSend) {
|
|
1762
|
+
return Promise.resolve();
|
|
1763
|
+
}
|
|
1707
1764
|
return send("identify", payload, config, instance, storage);
|
|
1708
1765
|
},
|
|
1709
1766
|
reset: args => {
|
|
@@ -1748,9 +1805,14 @@
|
|
|
1748
1805
|
const userId = (options === null || options === void 0 ? void 0 : options.userId) || (user === null || user === void 0 ? void 0 : user.userId);
|
|
1749
1806
|
const anonymousId = (options === null || options === void 0 ? void 0 : options.anonymousId) || (user === null || user === void 0 ? void 0 : user.anonymousId) || storage.getItem("__anon_id");
|
|
1750
1807
|
storage.setItem("__group_id", groupId);
|
|
1808
|
+
const doNotSend = traits === null || traits === void 0 ? void 0 : traits.$doNotSend;
|
|
1751
1809
|
if (traits && typeof traits === "object") {
|
|
1810
|
+
traits = stripControllingTraits(traits);
|
|
1752
1811
|
storage.setItem("__group_traits", traits);
|
|
1753
1812
|
}
|
|
1813
|
+
if (doNotSend) {
|
|
1814
|
+
return Promise.resolve();
|
|
1815
|
+
}
|
|
1754
1816
|
return send("group", Object.assign(Object.assign({ type: "group", groupId, traits }, (anonymousId ? { anonymousId } : {})), (userId ? { userId } : {})), jitsuOptions, instance, storage);
|
|
1755
1817
|
},
|
|
1756
1818
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jitsu/js",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.12",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "Jitsu Dev Team <dev@jitsu.com>",
|
|
6
6
|
"main": "dist/jitsu.cjs.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"chalk": "^4.1.2",
|
|
28
28
|
"cookie-parser": "^1.4.7",
|
|
29
29
|
"ejs": "^3.1.8",
|
|
30
|
-
"express": "^4.21.
|
|
30
|
+
"express": "^4.21.2",
|
|
31
31
|
"jest": "^29.2.2",
|
|
32
32
|
"node-fetch-commonjs": "^3.3.2",
|
|
33
33
|
"node-forge": "^1.3.1",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"rollup": "^3.29.5",
|
|
36
36
|
"ts-jest": "29.0.5",
|
|
37
37
|
"typescript": "^5.6.3",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
38
|
+
"jsondiffpatch": "1.9.12",
|
|
39
|
+
"@jitsu/protocols": "1.9.12"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"analytics": "0.8.9"
|
package/src/analytics-plugin.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/* global analytics */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
DynamicJitsuOptions,
|
|
5
|
-
JitsuOptions,
|
|
6
|
-
PersistentStorage,
|
|
7
|
-
RuntimeFacade,
|
|
8
4
|
AnalyticsClientEvent,
|
|
9
5
|
Callback,
|
|
6
|
+
DynamicJitsuOptions,
|
|
7
|
+
ErrorHandler,
|
|
10
8
|
ID,
|
|
9
|
+
JitsuOptions,
|
|
11
10
|
JSONObject,
|
|
12
11
|
Options,
|
|
12
|
+
PersistentStorage,
|
|
13
|
+
RuntimeFacade,
|
|
14
|
+
Traits,
|
|
13
15
|
} from "@jitsu/protocols/analytics";
|
|
14
16
|
import parse from "./index";
|
|
15
17
|
|
|
@@ -31,10 +33,13 @@ const defaultConfig: Required<JitsuOptions> = {
|
|
|
31
33
|
fetch: null,
|
|
32
34
|
echoEvents: false,
|
|
33
35
|
cookieDomain: undefined,
|
|
36
|
+
cookieCapture: {},
|
|
34
37
|
runtime: undefined,
|
|
35
38
|
fetchTimeoutMs: undefined,
|
|
36
39
|
s2s: undefined,
|
|
37
40
|
idEndpoint: undefined,
|
|
41
|
+
errorPolicy: "log",
|
|
42
|
+
defaultPayloadContext: {},
|
|
38
43
|
privacy: {
|
|
39
44
|
dontSend: false,
|
|
40
45
|
disableUserIds: false,
|
|
@@ -138,6 +143,22 @@ function getCookie(name: string) {
|
|
|
138
143
|
return parts.length === 2 ? parts.pop().split(";").shift() : undefined;
|
|
139
144
|
}
|
|
140
145
|
|
|
146
|
+
function getClientIds(runtime: RuntimeFacade, customCookieCapture: Record<string, string>) {
|
|
147
|
+
const cookieCapture = {
|
|
148
|
+
fbc: "_fbc",
|
|
149
|
+
fbp: "_fbp",
|
|
150
|
+
...customCookieCapture,
|
|
151
|
+
};
|
|
152
|
+
const clientIds = Object.entries(cookieCapture).reduce((acc, [key, cookieName]) => {
|
|
153
|
+
acc[key] = runtime.getCookie(cookieName);
|
|
154
|
+
return acc;
|
|
155
|
+
}, {});
|
|
156
|
+
return {
|
|
157
|
+
...clientIds,
|
|
158
|
+
...getGa4Ids(runtime),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
141
162
|
function getGa4Ids(runtime: RuntimeFacade) {
|
|
142
163
|
const allCookies = runtime.getCookies();
|
|
143
164
|
const clientId = allCookies["_ga"]?.split(".").slice(-2).join(".");
|
|
@@ -220,6 +241,10 @@ const cookieStorage: StorageFactory = (cookieDomain, key2cookie) => {
|
|
|
220
241
|
getItem(key: string) {
|
|
221
242
|
const cookieName = key2cookie[key] || key;
|
|
222
243
|
const result = getCookie(cookieName);
|
|
244
|
+
if (key === "__anon_id") {
|
|
245
|
+
//anonymous id must always be a string, so we don't parse it to preserve its exact value
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
223
248
|
if (typeof result === "undefined" && key === "__user_id") {
|
|
224
249
|
//backward compatibility with old jitsu cookie. get user id if from traits
|
|
225
250
|
const traits = parse(getCookie("__eventn_id_usr")) || {};
|
|
@@ -415,12 +440,16 @@ function adjustPayload(
|
|
|
415
440
|
const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
|
|
416
441
|
const properties = payload.properties || {};
|
|
417
442
|
|
|
418
|
-
if (payload.type === "page" && url) {
|
|
419
|
-
properties.url
|
|
420
|
-
properties.
|
|
443
|
+
if (payload.type === "page" && (properties.url || url)) {
|
|
444
|
+
const targetUrl = properties.url || url;
|
|
445
|
+
properties.url = targetUrl.replace(hashRegex, "");
|
|
446
|
+
properties.path = fixPath(urlPath(targetUrl));
|
|
421
447
|
}
|
|
422
448
|
|
|
423
|
-
const customContext =
|
|
449
|
+
const customContext = deepMerge(
|
|
450
|
+
config.defaultPayloadContext,
|
|
451
|
+
payload.properties?.context || payload.options?.context || {}
|
|
452
|
+
);
|
|
424
453
|
delete payload.properties?.context;
|
|
425
454
|
const referrer = runtime.referrer();
|
|
426
455
|
const context: AnalyticsClientEvent["context"] = {
|
|
@@ -448,13 +477,7 @@ function adjustPayload(
|
|
|
448
477
|
url: properties.url || url,
|
|
449
478
|
encoding: properties.encoding || runtime.documentEncoding(),
|
|
450
479
|
},
|
|
451
|
-
clientIds: !config.privacy?.disableUserIds
|
|
452
|
-
? {
|
|
453
|
-
fbc: runtime.getCookie("_fbc"),
|
|
454
|
-
fbp: runtime.getCookie("_fbp"),
|
|
455
|
-
...getGa4Ids(runtime),
|
|
456
|
-
}
|
|
457
|
-
: undefined,
|
|
480
|
+
clientIds: !config.privacy?.disableUserIds ? getClientIds(runtime, config.cookieCapture) : undefined,
|
|
458
481
|
campaign: parseUtms(query),
|
|
459
482
|
};
|
|
460
483
|
const withContext = {
|
|
@@ -627,6 +650,22 @@ function maskWriteKey(writeKey?: string): string | undefined {
|
|
|
627
650
|
return writeKey;
|
|
628
651
|
}
|
|
629
652
|
|
|
653
|
+
function getErrorHandler(opts: JitsuOptions): ErrorHandler {
|
|
654
|
+
const configuredHandler = opts.errorPolicy || "log";
|
|
655
|
+
if (typeof configuredHandler === "function") {
|
|
656
|
+
return configuredHandler;
|
|
657
|
+
} else if (configuredHandler === "rethrow") {
|
|
658
|
+
return (msg, ...args) => {
|
|
659
|
+
//ignore args, not clear what to do with them
|
|
660
|
+
throw new Error(msg);
|
|
661
|
+
};
|
|
662
|
+
} else {
|
|
663
|
+
return (msg, ...args) => {
|
|
664
|
+
console.error(msg, ...args);
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
630
669
|
async function send(
|
|
631
670
|
method,
|
|
632
671
|
payload,
|
|
@@ -642,6 +681,7 @@ async function send(
|
|
|
642
681
|
const url = s2s ? `${jitsuConfig.host}/api/s/s2s/${method}` : `${jitsuConfig.host}/api/s/${method}`;
|
|
643
682
|
const fetch = jitsuConfig.fetch || globalThis.fetch;
|
|
644
683
|
if (!fetch) {
|
|
684
|
+
//don't run it through error handler since error is critical and should be addressed
|
|
645
685
|
throw new Error(
|
|
646
686
|
"Please specify fetch function in jitsu plugin initialization, fetch isn't available in global scope"
|
|
647
687
|
);
|
|
@@ -681,7 +721,8 @@ async function send(
|
|
|
681
721
|
clearTimeout(abortTimeout);
|
|
682
722
|
}
|
|
683
723
|
} catch (e: any) {
|
|
684
|
-
|
|
724
|
+
getErrorHandler(jitsuConfig)(`Call to ${url} failed with error ${e.message}`);
|
|
725
|
+
return Promise.resolve();
|
|
685
726
|
}
|
|
686
727
|
let responseText;
|
|
687
728
|
try {
|
|
@@ -701,14 +742,18 @@ async function send(
|
|
|
701
742
|
);
|
|
702
743
|
}
|
|
703
744
|
if (!fetchResult.ok) {
|
|
704
|
-
|
|
745
|
+
getErrorHandler(jitsuConfig)(
|
|
746
|
+
`Call to ${url} failed with error: ${fetchResult.status} - ${fetchResult.statusText}: ${responseText}`
|
|
747
|
+
);
|
|
748
|
+
return Promise.resolve();
|
|
705
749
|
}
|
|
706
750
|
|
|
707
751
|
let responseJson: any;
|
|
708
752
|
try {
|
|
709
753
|
responseJson = JSON.parse(responseText);
|
|
710
754
|
} catch (e) {
|
|
711
|
-
|
|
755
|
+
getErrorHandler(jitsuConfig)(`Can't parse JSON: ${responseText}: ${e?.message}`);
|
|
756
|
+
return Promise.resolve();
|
|
712
757
|
}
|
|
713
758
|
|
|
714
759
|
if (responseJson.destinations && responseJson.destinations.length > 0) {
|
|
@@ -730,6 +775,23 @@ async function send(
|
|
|
730
775
|
return adjustedPayload;
|
|
731
776
|
}
|
|
732
777
|
|
|
778
|
+
const controllingTraits = ["$doNotSend"] as const;
|
|
779
|
+
/**
|
|
780
|
+
* Remove all members of traits that controls identify/group behavior (see analytics.d.ts), and should not be recorded. Returns
|
|
781
|
+
* copy of the object with these members removed.
|
|
782
|
+
*
|
|
783
|
+
* Do not modify traits object, but creates one
|
|
784
|
+
* @param traits
|
|
785
|
+
*/
|
|
786
|
+
function stripControllingTraits(traits: Traits): Exclude<Traits, (typeof controllingTraits)[number]> {
|
|
787
|
+
const res = { ...traits };
|
|
788
|
+
// see Traits definition in analytics.d.ts. We cannot define const here, so here's a little code duplication
|
|
789
|
+
for (const key of controllingTraits) {
|
|
790
|
+
delete res[key];
|
|
791
|
+
}
|
|
792
|
+
return res;
|
|
793
|
+
}
|
|
794
|
+
|
|
733
795
|
export const jitsuAnalyticsPlugin = (jitsuOptions: JitsuOptions = {}, storage: PersistentStorage): AnalyticsPlugin => {
|
|
734
796
|
// just to make sure that all undefined values are replaced with defaultConfig values
|
|
735
797
|
mergeConfig(jitsuOptions, jitsuOptions);
|
|
@@ -792,9 +854,14 @@ export const jitsuAnalyticsPlugin = (jitsuOptions: JitsuOptions = {}, storage: P
|
|
|
792
854
|
}
|
|
793
855
|
// Store traits in cache to be able to use them in page and track events that run asynchronously with current identify.
|
|
794
856
|
storage.setItem("__user_id", payload.userId);
|
|
857
|
+
const doNotSend = payload.traits?.$doNotSend;
|
|
795
858
|
if (payload.traits && typeof payload.traits === "object") {
|
|
859
|
+
payload.traits = stripControllingTraits(payload.traits);
|
|
796
860
|
storage.setItem("__user_traits", payload.traits);
|
|
797
861
|
}
|
|
862
|
+
if (doNotSend) {
|
|
863
|
+
return Promise.resolve();
|
|
864
|
+
}
|
|
798
865
|
return send("identify", payload, config, instance, storage);
|
|
799
866
|
},
|
|
800
867
|
reset: args => {
|
|
@@ -838,9 +905,14 @@ export const jitsuAnalyticsPlugin = (jitsuOptions: JitsuOptions = {}, storage: P
|
|
|
838
905
|
const userId = options?.userId || user?.userId;
|
|
839
906
|
const anonymousId = options?.anonymousId || user?.anonymousId || storage.getItem("__anon_id");
|
|
840
907
|
storage.setItem("__group_id", groupId);
|
|
908
|
+
const doNotSend = traits?.$doNotSend;
|
|
841
909
|
if (traits && typeof traits === "object") {
|
|
910
|
+
traits = stripControllingTraits(traits);
|
|
842
911
|
storage.setItem("__group_traits", traits);
|
|
843
912
|
}
|
|
913
|
+
if (doNotSend) {
|
|
914
|
+
return Promise.resolve();
|
|
915
|
+
}
|
|
844
916
|
return send(
|
|
845
917
|
"group",
|
|
846
918
|
{ type: "group", groupId, traits, ...(anonymousId ? { anonymousId } : {}), ...(userId ? { userId } : {}) },
|