@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/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 = url.replace(hashRegex, "");
1413
- properties.path = fixPath(urlPath(url));
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
- (_b = payload.properties) === null || _b === void 0 ? true : delete _b.context;
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: ((_c = config.privacy) === null || _c === void 0 ? void 0 : _c.consentCategories)
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: !((_d = config.privacy) === null || _d === void 0 ? void 0 : _d.disableUserIds)
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 ((_e = config.privacy) === null || _e === void 0 ? void 0 : _e.disableUserIds) {
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
- throw new Error(`Calling ${url} failed: ${e.message}`);
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
- throw new Error(`Jitsu ${url} replied ${fetchResult.status} - ${fetchResult.statusText}: ${responseText}`);
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
- return Promise.reject(`Can't parse JSON: ${responseText}: ${e === null || e === void 0 ? void 0 : e.message}`);
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.10",
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.1",
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
- "@jitsu/protocols": "1.9.10",
39
- "jsondiffpatch": "1.9.10"
38
+ "jsondiffpatch": "1.9.12",
39
+ "@jitsu/protocols": "1.9.12"
40
40
  },
41
41
  "dependencies": {
42
42
  "analytics": "0.8.9"
@@ -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 = url.replace(hashRegex, "");
420
- properties.path = fixPath(urlPath(url));
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 = payload.properties?.context || {};
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
- throw new Error(`Calling ${url} failed: ${e.message}`);
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
- throw new Error(`Jitsu ${url} replied ${fetchResult.status} - ${fetchResult.statusText}: ${responseText}`);
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
- return Promise.reject(`Can't parse JSON: ${responseText}: ${e?.message}`);
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 } : {}) },