@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
|
@@ -214,6 +214,33 @@ describe("Test Jitsu NodeJS client", () => {
|
|
|
214
214
|
expect((p.body.anonymousId ?? "").length).toBeGreaterThan(0);
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
+
test("test defaultPayloadContext", async () => {
|
|
218
|
+
const config = {
|
|
219
|
+
host: server.baseUrl,
|
|
220
|
+
writeKey: "key:secret",
|
|
221
|
+
debug: true,
|
|
222
|
+
defaultPayloadContext: {
|
|
223
|
+
awesomeIdentifier: "awesome-identifier",
|
|
224
|
+
awesome: {
|
|
225
|
+
nestedKey: "awesome-key",
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
const client = jitsuAnalytics(config);
|
|
230
|
+
expect(requestLog.length).toBe(0);
|
|
231
|
+
await client.identify("myUserId", { email: "myUserId@example.com" });
|
|
232
|
+
await client.group("myGroupId", { name: "myGroupId" });
|
|
233
|
+
await client.track("myEvent", { prop1: "value1" });
|
|
234
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
235
|
+
expect(requestLog.length).toBe(3);
|
|
236
|
+
expect(requestLog[0].body.context.awesomeIdentifier).toBe("awesome-identifier");
|
|
237
|
+
expect(requestLog[0].body.context.awesome.nestedKey).toBe("awesome-key");
|
|
238
|
+
expect(requestLog[1].body.context.awesomeIdentifier).toBe("awesome-identifier");
|
|
239
|
+
expect(requestLog[1].body.context.awesome.nestedKey).toBe("awesome-key");
|
|
240
|
+
expect(requestLog[2].body.context.awesomeIdentifier).toBe("awesome-identifier");
|
|
241
|
+
expect(requestLog[2].body.context.awesome.nestedKey).toBe("awesome-key");
|
|
242
|
+
});
|
|
243
|
+
|
|
217
244
|
test("node js", async () => {
|
|
218
245
|
const jitsu: AnalyticsInterface = jitsuAnalytics({
|
|
219
246
|
writeKey: "key:secret",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
|
|
8
|
+
<title>Tracking page</title>
|
|
9
|
+
<script>
|
|
10
|
+
window.testOnload = async j => {
|
|
11
|
+
j.track("pageLoaded");
|
|
12
|
+
};
|
|
13
|
+
</script>
|
|
14
|
+
<script
|
|
15
|
+
type="text/javascript"
|
|
16
|
+
src="<%=trackingBase%>/p.js"
|
|
17
|
+
data-onload="testOnload"
|
|
18
|
+
data-debug="true"
|
|
19
|
+
defer
|
|
20
|
+
></script>
|
|
21
|
+
</head>
|
|
22
|
+
|
|
23
|
+
<body>
|
|
24
|
+
<h1>Test</h1>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
<title>Tracking page</title>
|
|
9
9
|
<script>
|
|
10
|
+
window.jitsuConfig = {
|
|
11
|
+
cookieCapture: {
|
|
12
|
+
ttp: "_ttp",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
10
15
|
window.testOnload = async j => {
|
|
11
16
|
j.identify("john-doe-id-1", { email: "john.doe@gmail.com" });
|
|
12
17
|
j.track("pageLoaded", { trackParam: "trackValue" });
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
window.testOnload = async j => {
|
|
11
11
|
await j.setAnonymousId("john-doe-id-1");
|
|
12
12
|
await j.identify("john-nondoe", { email: "john@example.com" });
|
|
13
|
+
await j.identify("john-nondoe", { email: "john2@example.com", $doNotSend: true });
|
|
13
14
|
await j.track("pageLoaded", { trackParam: "trackValue" });
|
|
14
15
|
await j.reset();
|
|
15
16
|
await j.track("pageLoaded", { trackParam: "trackValue" });
|
|
@@ -273,6 +273,9 @@ test("reset", async ({ browser }) => {
|
|
|
273
273
|
const newAnonymousId = cookies[0].value;
|
|
274
274
|
console.log(`🍪Cookies`, cookies);
|
|
275
275
|
|
|
276
|
+
//second identify call should not reach the server, but it should change the traits
|
|
277
|
+
expect(firstTrack.body.context.traits?.email).toEqual("john2@example.com");
|
|
278
|
+
|
|
276
279
|
expect(secondTrack.body.anonymousId).not.toBeNull();
|
|
277
280
|
expect(secondTrack.body.anonymousId).toBeDefined();
|
|
278
281
|
expect(secondTrack.body.anonymousId).toEqual(newAnonymousId);
|
|
@@ -447,9 +450,46 @@ test("disable-user-ids-then-consent", async ({ browser }) => {
|
|
|
447
450
|
expect((p.body.anonymousId ?? "").length).toBeGreaterThan(0);
|
|
448
451
|
});
|
|
449
452
|
|
|
453
|
+
test("anonymous-id-bug", async ({ browser }) => {
|
|
454
|
+
clearRequestLog();
|
|
455
|
+
const anonymousId = "1724633695283.638279";
|
|
456
|
+
const browserContext = await browser.newContext();
|
|
457
|
+
await browserContext.addCookies([{ name: "__eventn_id", value: anonymousId, url: server.baseUrl }]);
|
|
458
|
+
const { page, uncaughtErrors } = await createLoggingPage(browserContext);
|
|
459
|
+
const [pageResult] = await Promise.all([page.goto(`${server.baseUrl}/anonymous-id-bug.html`)]);
|
|
460
|
+
await page.waitForFunction(() => window["jitsu"] !== undefined, undefined, {
|
|
461
|
+
timeout: 1000,
|
|
462
|
+
polling: 100,
|
|
463
|
+
});
|
|
464
|
+
expect(pageResult.status()).toBe(200);
|
|
465
|
+
const cookies = (await browserContext.cookies()).reduce(
|
|
466
|
+
(res, cookie) => ({
|
|
467
|
+
...res,
|
|
468
|
+
[cookie.name]: cookie.value,
|
|
469
|
+
}),
|
|
470
|
+
{}
|
|
471
|
+
);
|
|
472
|
+
console.log("🍪 Jitsu Cookies", cookies);
|
|
473
|
+
//wait for some time since the server has an artificial latency of 30ms
|
|
474
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
475
|
+
expect(uncaughtErrors.length).toEqual(0);
|
|
476
|
+
console.log(
|
|
477
|
+
`📝 Request log size of ${requestLog.length}`,
|
|
478
|
+
requestLog.map(x => describeEvent(x.type, x.body))
|
|
479
|
+
);
|
|
480
|
+
const p = requestLog[0];
|
|
481
|
+
console.log(chalk.bold("📝 Checking page event"), JSON.stringify(p, null, 3));
|
|
482
|
+
expect(p.body.anonymousId).toEqual(anonymousId);
|
|
483
|
+
});
|
|
484
|
+
|
|
450
485
|
test("basic", async ({ browser }) => {
|
|
451
486
|
clearRequestLog();
|
|
452
487
|
const browserContext = await browser.newContext();
|
|
488
|
+
await browserContext.addCookies([
|
|
489
|
+
{ name: "_fbc", value: "fbc-id", url: server.baseUrl },
|
|
490
|
+
{ name: "_fbp", value: "fbp-id", url: server.baseUrl },
|
|
491
|
+
{ name: "_ttp", value: "ttp-id", url: server.baseUrl },
|
|
492
|
+
]);
|
|
453
493
|
|
|
454
494
|
const { page: firstPage, uncaughtErrors: firstPageErrors } = await createLoggingPage(browserContext);
|
|
455
495
|
const [pageResult] = await Promise.all([
|
|
@@ -495,6 +535,9 @@ test("basic", async ({ browser }) => {
|
|
|
495
535
|
console.log(chalk.bold("📝 Checking track event"), JSON.stringify(track, null, 3));
|
|
496
536
|
expect(track.properties.trackParam).toEqual("trackValue");
|
|
497
537
|
expect(track.type).toEqual("track");
|
|
538
|
+
expect(track.context.clientIds).toHaveProperty("fbc", "fbc-id");
|
|
539
|
+
expect(track.context.clientIds).toHaveProperty("fbp", "fbp-id");
|
|
540
|
+
expect(track.context.clientIds).toHaveProperty("ttp", "ttp-id");
|
|
498
541
|
expect(track.context.traits.email).toEqual("john.doe@gmail.com");
|
|
499
542
|
expect(track.userId).toEqual("john-doe-id-1");
|
|
500
543
|
expect(track.event).toEqual("pageLoaded");
|
|
@@ -506,6 +549,9 @@ test("basic", async ({ browser }) => {
|
|
|
506
549
|
|
|
507
550
|
console.log(chalk.bold("📝 Checking page event"), JSON.stringify(page, null, 3));
|
|
508
551
|
expect(page.anonymousId).toEqual(anonymousId);
|
|
552
|
+
expect(page.context.clientIds).toHaveProperty("fbc", "fbc-id");
|
|
553
|
+
expect(page.context.clientIds).toHaveProperty("fbp", "fbp-id");
|
|
554
|
+
expect(page.context.clientIds).toHaveProperty("ttp", "ttp-id");
|
|
509
555
|
expect(page.context.traits.email).toEqual("john.doe@gmail.com");
|
|
510
556
|
expect(page.userId).toEqual("john-doe-id-1");
|
|
511
557
|
|
package/dist/jitsu.cjs.js
CHANGED
|
@@ -1061,10 +1061,13 @@ const defaultConfig = {
|
|
|
1061
1061
|
fetch: null,
|
|
1062
1062
|
echoEvents: false,
|
|
1063
1063
|
cookieDomain: undefined,
|
|
1064
|
+
cookieCapture: {},
|
|
1064
1065
|
runtime: undefined,
|
|
1065
1066
|
fetchTimeoutMs: undefined,
|
|
1066
1067
|
s2s: undefined,
|
|
1067
1068
|
idEndpoint: undefined,
|
|
1069
|
+
errorPolicy: "log",
|
|
1070
|
+
defaultPayloadContext: {},
|
|
1068
1071
|
privacy: {
|
|
1069
1072
|
dontSend: false,
|
|
1070
1073
|
disableUserIds: false,
|
|
@@ -1149,6 +1152,14 @@ function getCookie(name) {
|
|
|
1149
1152
|
const parts = value.split(`; ${name}=`);
|
|
1150
1153
|
return parts.length === 2 ? parts.pop().split(";").shift() : undefined;
|
|
1151
1154
|
}
|
|
1155
|
+
function getClientIds(runtime, customCookieCapture) {
|
|
1156
|
+
const cookieCapture = Object.assign({ fbc: "_fbc", fbp: "_fbp" }, customCookieCapture);
|
|
1157
|
+
const clientIds = Object.entries(cookieCapture).reduce((acc, [key, cookieName]) => {
|
|
1158
|
+
acc[key] = runtime.getCookie(cookieName);
|
|
1159
|
+
return acc;
|
|
1160
|
+
}, {});
|
|
1161
|
+
return Object.assign(Object.assign({}, clientIds), getGa4Ids(runtime));
|
|
1162
|
+
}
|
|
1152
1163
|
function getGa4Ids(runtime) {
|
|
1153
1164
|
var _a;
|
|
1154
1165
|
const allCookies = runtime.getCookies();
|
|
@@ -1226,6 +1237,10 @@ const cookieStorage = (cookieDomain, key2cookie) => {
|
|
|
1226
1237
|
getItem(key) {
|
|
1227
1238
|
const cookieName = key2cookie[key] || key;
|
|
1228
1239
|
const result = getCookie(cookieName);
|
|
1240
|
+
if (key === "__anon_id") {
|
|
1241
|
+
//anonymous id must always be a string, so we don't parse it to preserve its exact value
|
|
1242
|
+
return result;
|
|
1243
|
+
}
|
|
1229
1244
|
if (typeof result === "undefined" && key === "__user_id") {
|
|
1230
1245
|
//backward compatibility with old jitsu cookie. get user id if from traits
|
|
1231
1246
|
const traits = parse(getCookie("__eventn_id_usr")) || {};
|
|
@@ -1401,18 +1416,19 @@ function urlPath(url) {
|
|
|
1401
1416
|
return "/" + pathMatch;
|
|
1402
1417
|
}
|
|
1403
1418
|
function adjustPayload(payload, config, storage, s2s) {
|
|
1404
|
-
var _a, _b, _c, _d, _e;
|
|
1419
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1405
1420
|
const runtime = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
|
|
1406
1421
|
const url = runtime.pageUrl();
|
|
1407
1422
|
const parsedUrl = safeCall(() => new URL(url), undefined);
|
|
1408
1423
|
const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
|
|
1409
1424
|
const properties = payload.properties || {};
|
|
1410
|
-
if (payload.type === "page" && url) {
|
|
1411
|
-
properties.url
|
|
1412
|
-
properties.
|
|
1425
|
+
if (payload.type === "page" && (properties.url || url)) {
|
|
1426
|
+
const targetUrl = properties.url || url;
|
|
1427
|
+
properties.url = targetUrl.replace(hashRegex, "");
|
|
1428
|
+
properties.path = fixPath(urlPath(targetUrl));
|
|
1413
1429
|
}
|
|
1414
|
-
const customContext = ((_a = payload.properties) === null || _a === void 0 ? void 0 : _a.context) || {};
|
|
1415
|
-
(
|
|
1430
|
+
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) || {});
|
|
1431
|
+
(_c = payload.properties) === null || _c === void 0 ? true : delete _c.context;
|
|
1416
1432
|
const referrer = runtime.referrer();
|
|
1417
1433
|
const context = {
|
|
1418
1434
|
library: {
|
|
@@ -1420,7 +1436,7 @@ function adjustPayload(payload, config, storage, s2s) {
|
|
|
1420
1436
|
version: jitsuVersion,
|
|
1421
1437
|
env: isInBrowser() ? "browser" : "node",
|
|
1422
1438
|
},
|
|
1423
|
-
consent: ((
|
|
1439
|
+
consent: ((_d = config.privacy) === null || _d === void 0 ? void 0 : _d.consentCategories)
|
|
1424
1440
|
? {
|
|
1425
1441
|
categoryPreferences: config.privacy.consentCategories,
|
|
1426
1442
|
}
|
|
@@ -1439,14 +1455,13 @@ function adjustPayload(payload, config, storage, s2s) {
|
|
|
1439
1455
|
url: properties.url || url,
|
|
1440
1456
|
encoding: properties.encoding || runtime.documentEncoding(),
|
|
1441
1457
|
},
|
|
1442
|
-
clientIds: !((
|
|
1443
|
-
? Object.assign({ fbc: runtime.getCookie("_fbc"), fbp: runtime.getCookie("_fbp") }, getGa4Ids(runtime)) : undefined,
|
|
1458
|
+
clientIds: !((_e = config.privacy) === null || _e === void 0 ? void 0 : _e.disableUserIds) ? getClientIds(runtime, config.cookieCapture) : undefined,
|
|
1444
1459
|
campaign: parseUtms(query),
|
|
1445
1460
|
};
|
|
1446
1461
|
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) });
|
|
1447
1462
|
delete withContext.meta;
|
|
1448
1463
|
delete withContext.options;
|
|
1449
|
-
if ((
|
|
1464
|
+
if ((_f = config.privacy) === null || _f === void 0 ? void 0 : _f.disableUserIds) {
|
|
1450
1465
|
delete withContext.userId;
|
|
1451
1466
|
delete withContext.anonymousId;
|
|
1452
1467
|
delete withContext.context.traits;
|
|
@@ -1555,6 +1570,23 @@ function maskWriteKey(writeKey) {
|
|
|
1555
1570
|
}
|
|
1556
1571
|
return writeKey;
|
|
1557
1572
|
}
|
|
1573
|
+
function getErrorHandler(opts) {
|
|
1574
|
+
const configuredHandler = opts.errorPolicy || "log";
|
|
1575
|
+
if (typeof configuredHandler === "function") {
|
|
1576
|
+
return configuredHandler;
|
|
1577
|
+
}
|
|
1578
|
+
else if (configuredHandler === "rethrow") {
|
|
1579
|
+
return (msg, ...args) => {
|
|
1580
|
+
//ignore args, not clear what to do with them
|
|
1581
|
+
throw new Error(msg);
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
else {
|
|
1585
|
+
return (msg, ...args) => {
|
|
1586
|
+
console.error(msg, ...args);
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1558
1590
|
function send(method, payload, jitsuConfig, instance, store) {
|
|
1559
1591
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
1560
1592
|
var _a, _b;
|
|
@@ -1566,6 +1598,7 @@ function send(method, payload, jitsuConfig, instance, store) {
|
|
|
1566
1598
|
const url = s2s ? `${jitsuConfig.host}/api/s/s2s/${method}` : `${jitsuConfig.host}/api/s/${method}`;
|
|
1567
1599
|
const fetch = jitsuConfig.fetch || globalThis.fetch;
|
|
1568
1600
|
if (!fetch) {
|
|
1601
|
+
//don't run it through error handler since error is critical and should be addressed
|
|
1569
1602
|
throw new Error("Please specify fetch function in jitsu plugin initialization, fetch isn't available in global scope");
|
|
1570
1603
|
}
|
|
1571
1604
|
const debugHeader = jitsuConfig.debug ? { "X-Enable-Debug": "true" } : {};
|
|
@@ -1596,7 +1629,8 @@ function send(method, payload, jitsuConfig, instance, store) {
|
|
|
1596
1629
|
}
|
|
1597
1630
|
}
|
|
1598
1631
|
catch (e) {
|
|
1599
|
-
|
|
1632
|
+
getErrorHandler(jitsuConfig)(`Call to ${url} failed with error ${e.message}`);
|
|
1633
|
+
return Promise.resolve();
|
|
1600
1634
|
}
|
|
1601
1635
|
let responseText;
|
|
1602
1636
|
try {
|
|
@@ -1609,14 +1643,16 @@ function send(method, payload, jitsuConfig, instance, store) {
|
|
|
1609
1643
|
console.log(`[JITSU DEBUG] ${url} replied ${fetchResult.status}: ${responseText}. Original payload:\n${JSON.stringify(adjustedPayload, null, 2)}`);
|
|
1610
1644
|
}
|
|
1611
1645
|
if (!fetchResult.ok) {
|
|
1612
|
-
|
|
1646
|
+
getErrorHandler(jitsuConfig)(`Call to ${url} failed with error: ${fetchResult.status} - ${fetchResult.statusText}: ${responseText}`);
|
|
1647
|
+
return Promise.resolve();
|
|
1613
1648
|
}
|
|
1614
1649
|
let responseJson;
|
|
1615
1650
|
try {
|
|
1616
1651
|
responseJson = JSON.parse(responseText);
|
|
1617
1652
|
}
|
|
1618
1653
|
catch (e) {
|
|
1619
|
-
|
|
1654
|
+
getErrorHandler(jitsuConfig)(`Can't parse JSON: ${responseText}: ${e === null || e === void 0 ? void 0 : e.message}`);
|
|
1655
|
+
return Promise.resolve();
|
|
1620
1656
|
}
|
|
1621
1657
|
if (responseJson.destinations && responseJson.destinations.length > 0) {
|
|
1622
1658
|
if (jitsuConfig.s2s) {
|
|
@@ -1635,6 +1671,22 @@ function send(method, payload, jitsuConfig, instance, store) {
|
|
|
1635
1671
|
return adjustedPayload;
|
|
1636
1672
|
});
|
|
1637
1673
|
}
|
|
1674
|
+
const controllingTraits = ["$doNotSend"];
|
|
1675
|
+
/**
|
|
1676
|
+
* Remove all members of traits that controls identify/group behavior (see analytics.d.ts), and should not be recorded. Returns
|
|
1677
|
+
* copy of the object with these members removed.
|
|
1678
|
+
*
|
|
1679
|
+
* Do not modify traits object, but creates one
|
|
1680
|
+
* @param traits
|
|
1681
|
+
*/
|
|
1682
|
+
function stripControllingTraits(traits) {
|
|
1683
|
+
const res = Object.assign({}, traits);
|
|
1684
|
+
// see Traits definition in analytics.d.ts. We cannot define const here, so here's a little code duplication
|
|
1685
|
+
for (const key of controllingTraits) {
|
|
1686
|
+
delete res[key];
|
|
1687
|
+
}
|
|
1688
|
+
return res;
|
|
1689
|
+
}
|
|
1638
1690
|
const jitsuAnalyticsPlugin = (jitsuOptions = {}, storage) => {
|
|
1639
1691
|
// just to make sure that all undefined values are replaced with defaultConfig values
|
|
1640
1692
|
mergeConfig(jitsuOptions, jitsuOptions);
|
|
@@ -1693,16 +1745,21 @@ const jitsuAnalyticsPlugin = (jitsuOptions = {}, storage) => {
|
|
|
1693
1745
|
return send("track", payload, config, instance, storage);
|
|
1694
1746
|
},
|
|
1695
1747
|
identify: args => {
|
|
1696
|
-
var _a, _b;
|
|
1748
|
+
var _a, _b, _c;
|
|
1697
1749
|
const { payload, config, instance } = args;
|
|
1698
1750
|
if (((_a = config.privacy) === null || _a === void 0 ? void 0 : _a.dontSend) || ((_b = config.privacy) === null || _b === void 0 ? void 0 : _b.disableUserIds)) {
|
|
1699
1751
|
return;
|
|
1700
1752
|
}
|
|
1701
1753
|
// Store traits in cache to be able to use them in page and track events that run asynchronously with current identify.
|
|
1702
1754
|
storage.setItem("__user_id", payload.userId);
|
|
1755
|
+
const doNotSend = (_c = payload.traits) === null || _c === void 0 ? void 0 : _c.$doNotSend;
|
|
1703
1756
|
if (payload.traits && typeof payload.traits === "object") {
|
|
1757
|
+
payload.traits = stripControllingTraits(payload.traits);
|
|
1704
1758
|
storage.setItem("__user_traits", payload.traits);
|
|
1705
1759
|
}
|
|
1760
|
+
if (doNotSend) {
|
|
1761
|
+
return Promise.resolve();
|
|
1762
|
+
}
|
|
1706
1763
|
return send("identify", payload, config, instance, storage);
|
|
1707
1764
|
},
|
|
1708
1765
|
reset: args => {
|
|
@@ -1747,9 +1804,14 @@ const jitsuAnalyticsPlugin = (jitsuOptions = {}, storage) => {
|
|
|
1747
1804
|
const userId = (options === null || options === void 0 ? void 0 : options.userId) || (user === null || user === void 0 ? void 0 : user.userId);
|
|
1748
1805
|
const anonymousId = (options === null || options === void 0 ? void 0 : options.anonymousId) || (user === null || user === void 0 ? void 0 : user.anonymousId) || storage.getItem("__anon_id");
|
|
1749
1806
|
storage.setItem("__group_id", groupId);
|
|
1807
|
+
const doNotSend = traits === null || traits === void 0 ? void 0 : traits.$doNotSend;
|
|
1750
1808
|
if (traits && typeof traits === "object") {
|
|
1809
|
+
traits = stripControllingTraits(traits);
|
|
1751
1810
|
storage.setItem("__group_traits", traits);
|
|
1752
1811
|
}
|
|
1812
|
+
if (doNotSend) {
|
|
1813
|
+
return Promise.resolve();
|
|
1814
|
+
}
|
|
1753
1815
|
return send("group", Object.assign(Object.assign({ type: "group", groupId, traits }, (anonymousId ? { anonymousId } : {})), (userId ? { userId } : {})), jitsuOptions, instance, storage);
|
|
1754
1816
|
},
|
|
1755
1817
|
},
|
package/dist/jitsu.es.js
CHANGED
|
@@ -1059,10 +1059,13 @@ const defaultConfig = {
|
|
|
1059
1059
|
fetch: null,
|
|
1060
1060
|
echoEvents: false,
|
|
1061
1061
|
cookieDomain: undefined,
|
|
1062
|
+
cookieCapture: {},
|
|
1062
1063
|
runtime: undefined,
|
|
1063
1064
|
fetchTimeoutMs: undefined,
|
|
1064
1065
|
s2s: undefined,
|
|
1065
1066
|
idEndpoint: undefined,
|
|
1067
|
+
errorPolicy: "log",
|
|
1068
|
+
defaultPayloadContext: {},
|
|
1066
1069
|
privacy: {
|
|
1067
1070
|
dontSend: false,
|
|
1068
1071
|
disableUserIds: false,
|
|
@@ -1147,6 +1150,14 @@ function getCookie(name) {
|
|
|
1147
1150
|
const parts = value.split(`; ${name}=`);
|
|
1148
1151
|
return parts.length === 2 ? parts.pop().split(";").shift() : undefined;
|
|
1149
1152
|
}
|
|
1153
|
+
function getClientIds(runtime, customCookieCapture) {
|
|
1154
|
+
const cookieCapture = Object.assign({ fbc: "_fbc", fbp: "_fbp" }, customCookieCapture);
|
|
1155
|
+
const clientIds = Object.entries(cookieCapture).reduce((acc, [key, cookieName]) => {
|
|
1156
|
+
acc[key] = runtime.getCookie(cookieName);
|
|
1157
|
+
return acc;
|
|
1158
|
+
}, {});
|
|
1159
|
+
return Object.assign(Object.assign({}, clientIds), getGa4Ids(runtime));
|
|
1160
|
+
}
|
|
1150
1161
|
function getGa4Ids(runtime) {
|
|
1151
1162
|
var _a;
|
|
1152
1163
|
const allCookies = runtime.getCookies();
|
|
@@ -1224,6 +1235,10 @@ const cookieStorage = (cookieDomain, key2cookie) => {
|
|
|
1224
1235
|
getItem(key) {
|
|
1225
1236
|
const cookieName = key2cookie[key] || key;
|
|
1226
1237
|
const result = getCookie(cookieName);
|
|
1238
|
+
if (key === "__anon_id") {
|
|
1239
|
+
//anonymous id must always be a string, so we don't parse it to preserve its exact value
|
|
1240
|
+
return result;
|
|
1241
|
+
}
|
|
1227
1242
|
if (typeof result === "undefined" && key === "__user_id") {
|
|
1228
1243
|
//backward compatibility with old jitsu cookie. get user id if from traits
|
|
1229
1244
|
const traits = parse(getCookie("__eventn_id_usr")) || {};
|
|
@@ -1399,18 +1414,19 @@ function urlPath(url) {
|
|
|
1399
1414
|
return "/" + pathMatch;
|
|
1400
1415
|
}
|
|
1401
1416
|
function adjustPayload(payload, config, storage, s2s) {
|
|
1402
|
-
var _a, _b, _c, _d, _e;
|
|
1417
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1403
1418
|
const runtime = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
|
|
1404
1419
|
const url = runtime.pageUrl();
|
|
1405
1420
|
const parsedUrl = safeCall(() => new URL(url), undefined);
|
|
1406
1421
|
const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
|
|
1407
1422
|
const properties = payload.properties || {};
|
|
1408
|
-
if (payload.type === "page" && url) {
|
|
1409
|
-
properties.url
|
|
1410
|
-
properties.
|
|
1423
|
+
if (payload.type === "page" && (properties.url || url)) {
|
|
1424
|
+
const targetUrl = properties.url || url;
|
|
1425
|
+
properties.url = targetUrl.replace(hashRegex, "");
|
|
1426
|
+
properties.path = fixPath(urlPath(targetUrl));
|
|
1411
1427
|
}
|
|
1412
|
-
const customContext = ((_a = payload.properties) === null || _a === void 0 ? void 0 : _a.context) || {};
|
|
1413
|
-
(
|
|
1428
|
+
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) || {});
|
|
1429
|
+
(_c = payload.properties) === null || _c === void 0 ? true : delete _c.context;
|
|
1414
1430
|
const referrer = runtime.referrer();
|
|
1415
1431
|
const context = {
|
|
1416
1432
|
library: {
|
|
@@ -1418,7 +1434,7 @@ function adjustPayload(payload, config, storage, s2s) {
|
|
|
1418
1434
|
version: jitsuVersion,
|
|
1419
1435
|
env: isInBrowser() ? "browser" : "node",
|
|
1420
1436
|
},
|
|
1421
|
-
consent: ((
|
|
1437
|
+
consent: ((_d = config.privacy) === null || _d === void 0 ? void 0 : _d.consentCategories)
|
|
1422
1438
|
? {
|
|
1423
1439
|
categoryPreferences: config.privacy.consentCategories,
|
|
1424
1440
|
}
|
|
@@ -1437,14 +1453,13 @@ function adjustPayload(payload, config, storage, s2s) {
|
|
|
1437
1453
|
url: properties.url || url,
|
|
1438
1454
|
encoding: properties.encoding || runtime.documentEncoding(),
|
|
1439
1455
|
},
|
|
1440
|
-
clientIds: !((
|
|
1441
|
-
? Object.assign({ fbc: runtime.getCookie("_fbc"), fbp: runtime.getCookie("_fbp") }, getGa4Ids(runtime)) : undefined,
|
|
1456
|
+
clientIds: !((_e = config.privacy) === null || _e === void 0 ? void 0 : _e.disableUserIds) ? getClientIds(runtime, config.cookieCapture) : undefined,
|
|
1442
1457
|
campaign: parseUtms(query),
|
|
1443
1458
|
};
|
|
1444
1459
|
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) });
|
|
1445
1460
|
delete withContext.meta;
|
|
1446
1461
|
delete withContext.options;
|
|
1447
|
-
if ((
|
|
1462
|
+
if ((_f = config.privacy) === null || _f === void 0 ? void 0 : _f.disableUserIds) {
|
|
1448
1463
|
delete withContext.userId;
|
|
1449
1464
|
delete withContext.anonymousId;
|
|
1450
1465
|
delete withContext.context.traits;
|
|
@@ -1553,6 +1568,23 @@ function maskWriteKey(writeKey) {
|
|
|
1553
1568
|
}
|
|
1554
1569
|
return writeKey;
|
|
1555
1570
|
}
|
|
1571
|
+
function getErrorHandler(opts) {
|
|
1572
|
+
const configuredHandler = opts.errorPolicy || "log";
|
|
1573
|
+
if (typeof configuredHandler === "function") {
|
|
1574
|
+
return configuredHandler;
|
|
1575
|
+
}
|
|
1576
|
+
else if (configuredHandler === "rethrow") {
|
|
1577
|
+
return (msg, ...args) => {
|
|
1578
|
+
//ignore args, not clear what to do with them
|
|
1579
|
+
throw new Error(msg);
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
else {
|
|
1583
|
+
return (msg, ...args) => {
|
|
1584
|
+
console.error(msg, ...args);
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1556
1588
|
function send(method, payload, jitsuConfig, instance, store) {
|
|
1557
1589
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
1558
1590
|
var _a, _b;
|
|
@@ -1564,6 +1596,7 @@ function send(method, payload, jitsuConfig, instance, store) {
|
|
|
1564
1596
|
const url = s2s ? `${jitsuConfig.host}/api/s/s2s/${method}` : `${jitsuConfig.host}/api/s/${method}`;
|
|
1565
1597
|
const fetch = jitsuConfig.fetch || globalThis.fetch;
|
|
1566
1598
|
if (!fetch) {
|
|
1599
|
+
//don't run it through error handler since error is critical and should be addressed
|
|
1567
1600
|
throw new Error("Please specify fetch function in jitsu plugin initialization, fetch isn't available in global scope");
|
|
1568
1601
|
}
|
|
1569
1602
|
const debugHeader = jitsuConfig.debug ? { "X-Enable-Debug": "true" } : {};
|
|
@@ -1594,7 +1627,8 @@ function send(method, payload, jitsuConfig, instance, store) {
|
|
|
1594
1627
|
}
|
|
1595
1628
|
}
|
|
1596
1629
|
catch (e) {
|
|
1597
|
-
|
|
1630
|
+
getErrorHandler(jitsuConfig)(`Call to ${url} failed with error ${e.message}`);
|
|
1631
|
+
return Promise.resolve();
|
|
1598
1632
|
}
|
|
1599
1633
|
let responseText;
|
|
1600
1634
|
try {
|
|
@@ -1607,14 +1641,16 @@ function send(method, payload, jitsuConfig, instance, store) {
|
|
|
1607
1641
|
console.log(`[JITSU DEBUG] ${url} replied ${fetchResult.status}: ${responseText}. Original payload:\n${JSON.stringify(adjustedPayload, null, 2)}`);
|
|
1608
1642
|
}
|
|
1609
1643
|
if (!fetchResult.ok) {
|
|
1610
|
-
|
|
1644
|
+
getErrorHandler(jitsuConfig)(`Call to ${url} failed with error: ${fetchResult.status} - ${fetchResult.statusText}: ${responseText}`);
|
|
1645
|
+
return Promise.resolve();
|
|
1611
1646
|
}
|
|
1612
1647
|
let responseJson;
|
|
1613
1648
|
try {
|
|
1614
1649
|
responseJson = JSON.parse(responseText);
|
|
1615
1650
|
}
|
|
1616
1651
|
catch (e) {
|
|
1617
|
-
|
|
1652
|
+
getErrorHandler(jitsuConfig)(`Can't parse JSON: ${responseText}: ${e === null || e === void 0 ? void 0 : e.message}`);
|
|
1653
|
+
return Promise.resolve();
|
|
1618
1654
|
}
|
|
1619
1655
|
if (responseJson.destinations && responseJson.destinations.length > 0) {
|
|
1620
1656
|
if (jitsuConfig.s2s) {
|
|
@@ -1633,6 +1669,22 @@ function send(method, payload, jitsuConfig, instance, store) {
|
|
|
1633
1669
|
return adjustedPayload;
|
|
1634
1670
|
});
|
|
1635
1671
|
}
|
|
1672
|
+
const controllingTraits = ["$doNotSend"];
|
|
1673
|
+
/**
|
|
1674
|
+
* Remove all members of traits that controls identify/group behavior (see analytics.d.ts), and should not be recorded. Returns
|
|
1675
|
+
* copy of the object with these members removed.
|
|
1676
|
+
*
|
|
1677
|
+
* Do not modify traits object, but creates one
|
|
1678
|
+
* @param traits
|
|
1679
|
+
*/
|
|
1680
|
+
function stripControllingTraits(traits) {
|
|
1681
|
+
const res = Object.assign({}, traits);
|
|
1682
|
+
// see Traits definition in analytics.d.ts. We cannot define const here, so here's a little code duplication
|
|
1683
|
+
for (const key of controllingTraits) {
|
|
1684
|
+
delete res[key];
|
|
1685
|
+
}
|
|
1686
|
+
return res;
|
|
1687
|
+
}
|
|
1636
1688
|
const jitsuAnalyticsPlugin = (jitsuOptions = {}, storage) => {
|
|
1637
1689
|
// just to make sure that all undefined values are replaced with defaultConfig values
|
|
1638
1690
|
mergeConfig(jitsuOptions, jitsuOptions);
|
|
@@ -1691,16 +1743,21 @@ const jitsuAnalyticsPlugin = (jitsuOptions = {}, storage) => {
|
|
|
1691
1743
|
return send("track", payload, config, instance, storage);
|
|
1692
1744
|
},
|
|
1693
1745
|
identify: args => {
|
|
1694
|
-
var _a, _b;
|
|
1746
|
+
var _a, _b, _c;
|
|
1695
1747
|
const { payload, config, instance } = args;
|
|
1696
1748
|
if (((_a = config.privacy) === null || _a === void 0 ? void 0 : _a.dontSend) || ((_b = config.privacy) === null || _b === void 0 ? void 0 : _b.disableUserIds)) {
|
|
1697
1749
|
return;
|
|
1698
1750
|
}
|
|
1699
1751
|
// Store traits in cache to be able to use them in page and track events that run asynchronously with current identify.
|
|
1700
1752
|
storage.setItem("__user_id", payload.userId);
|
|
1753
|
+
const doNotSend = (_c = payload.traits) === null || _c === void 0 ? void 0 : _c.$doNotSend;
|
|
1701
1754
|
if (payload.traits && typeof payload.traits === "object") {
|
|
1755
|
+
payload.traits = stripControllingTraits(payload.traits);
|
|
1702
1756
|
storage.setItem("__user_traits", payload.traits);
|
|
1703
1757
|
}
|
|
1758
|
+
if (doNotSend) {
|
|
1759
|
+
return Promise.resolve();
|
|
1760
|
+
}
|
|
1704
1761
|
return send("identify", payload, config, instance, storage);
|
|
1705
1762
|
},
|
|
1706
1763
|
reset: args => {
|
|
@@ -1745,9 +1802,14 @@ const jitsuAnalyticsPlugin = (jitsuOptions = {}, storage) => {
|
|
|
1745
1802
|
const userId = (options === null || options === void 0 ? void 0 : options.userId) || (user === null || user === void 0 ? void 0 : user.userId);
|
|
1746
1803
|
const anonymousId = (options === null || options === void 0 ? void 0 : options.anonymousId) || (user === null || user === void 0 ? void 0 : user.anonymousId) || storage.getItem("__anon_id");
|
|
1747
1804
|
storage.setItem("__group_id", groupId);
|
|
1805
|
+
const doNotSend = traits === null || traits === void 0 ? void 0 : traits.$doNotSend;
|
|
1748
1806
|
if (traits && typeof traits === "object") {
|
|
1807
|
+
traits = stripControllingTraits(traits);
|
|
1749
1808
|
storage.setItem("__group_traits", traits);
|
|
1750
1809
|
}
|
|
1810
|
+
if (doNotSend) {
|
|
1811
|
+
return Promise.resolve();
|
|
1812
|
+
}
|
|
1751
1813
|
return send("group", Object.assign(Object.assign({ type: "group", groupId, traits }, (anonymousId ? { anonymousId } : {})), (userId ? { userId } : {})), jitsuOptions, instance, storage);
|
|
1752
1814
|
},
|
|
1753
1815
|
},
|