@jitsu/js 1.10.3 → 1.10.4

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.
@@ -742,6 +742,19 @@ function getClientIds(runtime, customCookieCapture) {
742
742
  }, {});
743
743
  return Object.assign(Object.assign({}, clientIds), getGa4Ids(runtime));
744
744
  }
745
+ function parseGa4SessionId(cookieValue) {
746
+ if (typeof cookieValue !== "string") {
747
+ return undefined;
748
+ }
749
+ if (cookieValue.startsWith("GA1") || cookieValue.startsWith("GS1")) {
750
+ return cookieValue.split(".")[2];
751
+ }
752
+ else {
753
+ // parse new GA4 cookie format, e.g.: GS2.1.s1747323152$o28$g0$t1747323152$j60$l0$h69286059
754
+ const match = cookieValue.match(/^GS\d+\.\d+\.(?:[\w_-]+[$])*s(\d+)(?:$|[$])/);
755
+ return match ? match[1] : undefined;
756
+ }
757
+ }
745
758
  function getGa4Ids(runtime) {
746
759
  var _a;
747
760
  const allCookies = runtime.getCookies();
@@ -750,14 +763,11 @@ function getGa4Ids(runtime) {
750
763
  const sessionIds = gaSessionCookies.length > 0
751
764
  ? Object.fromEntries(gaSessionCookies
752
765
  .map(([key, value]) => {
753
- if (typeof value !== "string") {
766
+ const sessionId = parseGa4SessionId(value);
767
+ if (!sessionId) {
754
768
  return null;
755
769
  }
756
- const parts = value.split(".");
757
- if (parts.length < 3) {
758
- return null;
759
- }
760
- return [key.substring("_ga_".length), parts[2]];
770
+ return [key.substring("_ga_".length), sessionId];
761
771
  })
762
772
  .filter(v => v !== null))
763
773
  : undefined;
@@ -981,6 +991,15 @@ const emptyRuntime = (config) => ({
981
991
  return undefined;
982
992
  },
983
993
  });
994
+ function deepCopy(o) {
995
+ if (typeof o !== "object") {
996
+ return o;
997
+ }
998
+ if (!o) {
999
+ return o;
1000
+ }
1001
+ return JSON.parse(JSON.stringify(o));
1002
+ }
984
1003
  function deepMerge(target, source) {
985
1004
  if (typeof source !== "object" || source === null) {
986
1005
  return source;
@@ -1017,6 +1036,28 @@ function urlPath(url) {
1017
1036
  const pathMatch = matches && matches[3] ? matches[3].split("?")[0].replace(hashRegex, "") : "";
1018
1037
  return "/" + pathMatch;
1019
1038
  }
1039
+ function canonicalUrl() {
1040
+ if (!isInBrowser())
1041
+ return;
1042
+ const tags = document.getElementsByTagName("link");
1043
+ for (var i = 0, tag; (tag = tags[i]); i++) {
1044
+ if (tag.getAttribute("rel") === "canonical") {
1045
+ return tag.getAttribute("href");
1046
+ }
1047
+ }
1048
+ }
1049
+ /**
1050
+ * bugged analytics.js logic that produces 'url' parameter by concating canonical URL with current search part
1051
+ * I produces broken results in some cases like SPA where path is changed but canonical URL is not updated
1052
+ */
1053
+ function analyticsJsUrl() {
1054
+ if (!isInBrowser())
1055
+ return;
1056
+ const canonical = canonicalUrl();
1057
+ if (!canonical)
1058
+ return window.location.href.replace(hashRegex, "");
1059
+ return canonical.match(/\?/) ? canonical : canonical + window.location.search;
1060
+ }
1020
1061
  function adjustPayload(payload, config, storage, s2s) {
1021
1062
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1022
1063
  const runtime = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
@@ -1025,9 +1066,16 @@ function adjustPayload(payload, config, storage, s2s) {
1025
1066
  const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
1026
1067
  const properties = payload.properties || {};
1027
1068
  if (payload.type === "page" && (properties.url || url)) {
1028
- const targetUrl = properties.url || url;
1069
+ // we don't trust analytics.js URL logic since it's sticks with canonical URL on SPA pages
1070
+ let targetUrl = url || properties.url;
1071
+ if (properties.url && properties.url !== analyticsJsUrl()) {
1072
+ // properties.url is not the same as provided by analytics.js
1073
+ // it means that it was not overridden by user and we should use it
1074
+ targetUrl = properties.url;
1075
+ }
1029
1076
  properties.url = targetUrl.replace(hashRegex, "");
1030
1077
  properties.path = fixPath(urlPath(targetUrl));
1078
+ // other properties are correctly based on window.location in analytics.js
1031
1079
  }
1032
1080
  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) || {});
1033
1081
  (_c = payload.properties) === null || _c === void 0 ? true : delete _c.context;
@@ -1081,15 +1129,18 @@ function processDestinations(destinations, method, originalEvent, debug, analyti
1081
1129
  return __awaiter$1(this, void 0, void 0, function* () {
1082
1130
  const promises = [];
1083
1131
  for (const destination of destinations) {
1084
- let newEvents = [originalEvent];
1132
+ let newEvents = [];
1085
1133
  if (destination.newEvents) {
1086
1134
  try {
1087
- newEvents = destination.newEvents.map(e => e === "same" ? originalEvent : isDiff(e) ? diff.patch(originalEvent, e.__diff) : e);
1135
+ newEvents = destination.newEvents.map(e => e === "same" ? deepCopy(originalEvent) : isDiff(e) ? diff.patch(deepCopy(originalEvent), e.__diff) : e);
1088
1136
  }
1089
1137
  catch (e) {
1090
1138
  console.error(`[JITSU] Error applying '${destination.id}' changes to event: ${e === null || e === void 0 ? void 0 : e.message}`, e);
1091
1139
  }
1092
1140
  }
1141
+ else {
1142
+ newEvents = [deepCopy(originalEvent)];
1143
+ }
1093
1144
  const credentials = Object.assign(Object.assign({}, destination.credentials), destination.options);
1094
1145
  if (destination.deviceOptions.type === "internal-plugin") {
1095
1146
  const plugin = internalDestinationPlugins[destination.deviceOptions.name];
@@ -740,6 +740,19 @@ function getClientIds(runtime, customCookieCapture) {
740
740
  }, {});
741
741
  return Object.assign(Object.assign({}, clientIds), getGa4Ids(runtime));
742
742
  }
743
+ function parseGa4SessionId(cookieValue) {
744
+ if (typeof cookieValue !== "string") {
745
+ return undefined;
746
+ }
747
+ if (cookieValue.startsWith("GA1") || cookieValue.startsWith("GS1")) {
748
+ return cookieValue.split(".")[2];
749
+ }
750
+ else {
751
+ // parse new GA4 cookie format, e.g.: GS2.1.s1747323152$o28$g0$t1747323152$j60$l0$h69286059
752
+ const match = cookieValue.match(/^GS\d+\.\d+\.(?:[\w_-]+[$])*s(\d+)(?:$|[$])/);
753
+ return match ? match[1] : undefined;
754
+ }
755
+ }
743
756
  function getGa4Ids(runtime) {
744
757
  var _a;
745
758
  const allCookies = runtime.getCookies();
@@ -748,14 +761,11 @@ function getGa4Ids(runtime) {
748
761
  const sessionIds = gaSessionCookies.length > 0
749
762
  ? Object.fromEntries(gaSessionCookies
750
763
  .map(([key, value]) => {
751
- if (typeof value !== "string") {
764
+ const sessionId = parseGa4SessionId(value);
765
+ if (!sessionId) {
752
766
  return null;
753
767
  }
754
- const parts = value.split(".");
755
- if (parts.length < 3) {
756
- return null;
757
- }
758
- return [key.substring("_ga_".length), parts[2]];
768
+ return [key.substring("_ga_".length), sessionId];
759
769
  })
760
770
  .filter(v => v !== null))
761
771
  : undefined;
@@ -979,6 +989,15 @@ const emptyRuntime = (config) => ({
979
989
  return undefined;
980
990
  },
981
991
  });
992
+ function deepCopy(o) {
993
+ if (typeof o !== "object") {
994
+ return o;
995
+ }
996
+ if (!o) {
997
+ return o;
998
+ }
999
+ return JSON.parse(JSON.stringify(o));
1000
+ }
982
1001
  function deepMerge(target, source) {
983
1002
  if (typeof source !== "object" || source === null) {
984
1003
  return source;
@@ -1015,6 +1034,28 @@ function urlPath(url) {
1015
1034
  const pathMatch = matches && matches[3] ? matches[3].split("?")[0].replace(hashRegex, "") : "";
1016
1035
  return "/" + pathMatch;
1017
1036
  }
1037
+ function canonicalUrl() {
1038
+ if (!isInBrowser())
1039
+ return;
1040
+ const tags = document.getElementsByTagName("link");
1041
+ for (var i = 0, tag; (tag = tags[i]); i++) {
1042
+ if (tag.getAttribute("rel") === "canonical") {
1043
+ return tag.getAttribute("href");
1044
+ }
1045
+ }
1046
+ }
1047
+ /**
1048
+ * bugged analytics.js logic that produces 'url' parameter by concating canonical URL with current search part
1049
+ * I produces broken results in some cases like SPA where path is changed but canonical URL is not updated
1050
+ */
1051
+ function analyticsJsUrl() {
1052
+ if (!isInBrowser())
1053
+ return;
1054
+ const canonical = canonicalUrl();
1055
+ if (!canonical)
1056
+ return window.location.href.replace(hashRegex, "");
1057
+ return canonical.match(/\?/) ? canonical : canonical + window.location.search;
1058
+ }
1018
1059
  function adjustPayload(payload, config, storage, s2s) {
1019
1060
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1020
1061
  const runtime = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
@@ -1023,9 +1064,16 @@ function adjustPayload(payload, config, storage, s2s) {
1023
1064
  const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
1024
1065
  const properties = payload.properties || {};
1025
1066
  if (payload.type === "page" && (properties.url || url)) {
1026
- const targetUrl = properties.url || url;
1067
+ // we don't trust analytics.js URL logic since it's sticks with canonical URL on SPA pages
1068
+ let targetUrl = url || properties.url;
1069
+ if (properties.url && properties.url !== analyticsJsUrl()) {
1070
+ // properties.url is not the same as provided by analytics.js
1071
+ // it means that it was not overridden by user and we should use it
1072
+ targetUrl = properties.url;
1073
+ }
1027
1074
  properties.url = targetUrl.replace(hashRegex, "");
1028
1075
  properties.path = fixPath(urlPath(targetUrl));
1076
+ // other properties are correctly based on window.location in analytics.js
1029
1077
  }
1030
1078
  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) || {});
1031
1079
  (_c = payload.properties) === null || _c === void 0 ? true : delete _c.context;
@@ -1079,15 +1127,18 @@ function processDestinations(destinations, method, originalEvent, debug, analyti
1079
1127
  return __awaiter$1(this, void 0, void 0, function* () {
1080
1128
  const promises = [];
1081
1129
  for (const destination of destinations) {
1082
- let newEvents = [originalEvent];
1130
+ let newEvents = [];
1083
1131
  if (destination.newEvents) {
1084
1132
  try {
1085
- newEvents = destination.newEvents.map(e => e === "same" ? originalEvent : isDiff(e) ? diff.patch(originalEvent, e.__diff) : e);
1133
+ newEvents = destination.newEvents.map(e => e === "same" ? deepCopy(originalEvent) : isDiff(e) ? diff.patch(deepCopy(originalEvent), e.__diff) : e);
1086
1134
  }
1087
1135
  catch (e) {
1088
1136
  console.error(`[JITSU] Error applying '${destination.id}' changes to event: ${e === null || e === void 0 ? void 0 : e.message}`, e);
1089
1137
  }
1090
1138
  }
1139
+ else {
1140
+ newEvents = [deepCopy(originalEvent)];
1141
+ }
1091
1142
  const credentials = Object.assign(Object.assign({}, destination.credentials), destination.options);
1092
1143
  if (destination.deviceOptions.type === "internal-plugin") {
1093
1144
  const plugin = internalDestinationPlugins[destination.deviceOptions.name];
package/dist/jitsu.cjs.js CHANGED
@@ -1164,6 +1164,19 @@ function getClientIds(runtime, customCookieCapture) {
1164
1164
  }, {});
1165
1165
  return Object.assign(Object.assign({}, clientIds), getGa4Ids(runtime));
1166
1166
  }
1167
+ function parseGa4SessionId(cookieValue) {
1168
+ if (typeof cookieValue !== "string") {
1169
+ return undefined;
1170
+ }
1171
+ if (cookieValue.startsWith("GA1") || cookieValue.startsWith("GS1")) {
1172
+ return cookieValue.split(".")[2];
1173
+ }
1174
+ else {
1175
+ // parse new GA4 cookie format, e.g.: GS2.1.s1747323152$o28$g0$t1747323152$j60$l0$h69286059
1176
+ const match = cookieValue.match(/^GS\d+\.\d+\.(?:[\w_-]+[$])*s(\d+)(?:$|[$])/);
1177
+ return match ? match[1] : undefined;
1178
+ }
1179
+ }
1167
1180
  function getGa4Ids(runtime) {
1168
1181
  var _a;
1169
1182
  const allCookies = runtime.getCookies();
@@ -1172,14 +1185,11 @@ function getGa4Ids(runtime) {
1172
1185
  const sessionIds = gaSessionCookies.length > 0
1173
1186
  ? Object.fromEntries(gaSessionCookies
1174
1187
  .map(([key, value]) => {
1175
- if (typeof value !== "string") {
1188
+ const sessionId = parseGa4SessionId(value);
1189
+ if (!sessionId) {
1176
1190
  return null;
1177
1191
  }
1178
- const parts = value.split(".");
1179
- if (parts.length < 3) {
1180
- return null;
1181
- }
1182
- return [key.substring("_ga_".length), parts[2]];
1192
+ return [key.substring("_ga_".length), sessionId];
1183
1193
  })
1184
1194
  .filter(v => v !== null))
1185
1195
  : undefined;
@@ -1403,6 +1413,15 @@ const emptyRuntime = (config) => ({
1403
1413
  return undefined;
1404
1414
  },
1405
1415
  });
1416
+ function deepCopy(o) {
1417
+ if (typeof o !== "object") {
1418
+ return o;
1419
+ }
1420
+ if (!o) {
1421
+ return o;
1422
+ }
1423
+ return JSON.parse(JSON.stringify(o));
1424
+ }
1406
1425
  function deepMerge(target, source) {
1407
1426
  if (typeof source !== "object" || source === null) {
1408
1427
  return source;
@@ -1439,6 +1458,28 @@ function urlPath(url) {
1439
1458
  const pathMatch = matches && matches[3] ? matches[3].split("?")[0].replace(hashRegex, "") : "";
1440
1459
  return "/" + pathMatch;
1441
1460
  }
1461
+ function canonicalUrl() {
1462
+ if (!isInBrowser())
1463
+ return;
1464
+ const tags = document.getElementsByTagName("link");
1465
+ for (var i = 0, tag; (tag = tags[i]); i++) {
1466
+ if (tag.getAttribute("rel") === "canonical") {
1467
+ return tag.getAttribute("href");
1468
+ }
1469
+ }
1470
+ }
1471
+ /**
1472
+ * bugged analytics.js logic that produces 'url' parameter by concating canonical URL with current search part
1473
+ * I produces broken results in some cases like SPA where path is changed but canonical URL is not updated
1474
+ */
1475
+ function analyticsJsUrl() {
1476
+ if (!isInBrowser())
1477
+ return;
1478
+ const canonical = canonicalUrl();
1479
+ if (!canonical)
1480
+ return window.location.href.replace(hashRegex, "");
1481
+ return canonical.match(/\?/) ? canonical : canonical + window.location.search;
1482
+ }
1442
1483
  function adjustPayload(payload, config, storage, s2s) {
1443
1484
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1444
1485
  const runtime = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
@@ -1447,9 +1488,16 @@ function adjustPayload(payload, config, storage, s2s) {
1447
1488
  const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
1448
1489
  const properties = payload.properties || {};
1449
1490
  if (payload.type === "page" && (properties.url || url)) {
1450
- const targetUrl = properties.url || url;
1491
+ // we don't trust analytics.js URL logic since it's sticks with canonical URL on SPA pages
1492
+ let targetUrl = url || properties.url;
1493
+ if (properties.url && properties.url !== analyticsJsUrl()) {
1494
+ // properties.url is not the same as provided by analytics.js
1495
+ // it means that it was not overridden by user and we should use it
1496
+ targetUrl = properties.url;
1497
+ }
1451
1498
  properties.url = targetUrl.replace(hashRegex, "");
1452
1499
  properties.path = fixPath(urlPath(targetUrl));
1500
+ // other properties are correctly based on window.location in analytics.js
1453
1501
  }
1454
1502
  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) || {});
1455
1503
  (_c = payload.properties) === null || _c === void 0 ? true : delete _c.context;
@@ -1503,15 +1551,18 @@ function processDestinations(destinations, method, originalEvent, debug, analyti
1503
1551
  return __awaiter$1(this, void 0, void 0, function* () {
1504
1552
  const promises = [];
1505
1553
  for (const destination of destinations) {
1506
- let newEvents = [originalEvent];
1554
+ let newEvents = [];
1507
1555
  if (destination.newEvents) {
1508
1556
  try {
1509
- newEvents = destination.newEvents.map(e => e === "same" ? originalEvent : isDiff(e) ? diff.patch(originalEvent, e.__diff) : e);
1557
+ newEvents = destination.newEvents.map(e => e === "same" ? deepCopy(originalEvent) : isDiff(e) ? diff.patch(deepCopy(originalEvent), e.__diff) : e);
1510
1558
  }
1511
1559
  catch (e) {
1512
1560
  console.error(`[JITSU] Error applying '${destination.id}' changes to event: ${e === null || e === void 0 ? void 0 : e.message}`, e);
1513
1561
  }
1514
1562
  }
1563
+ else {
1564
+ newEvents = [deepCopy(originalEvent)];
1565
+ }
1515
1566
  const credentials = Object.assign(Object.assign({}, destination.credentials), destination.options);
1516
1567
  if (destination.deviceOptions.type === "internal-plugin") {
1517
1568
  const plugin = internalDestinationPlugins[destination.deviceOptions.name];
package/dist/jitsu.es.js CHANGED
@@ -1162,6 +1162,19 @@ function getClientIds(runtime, customCookieCapture) {
1162
1162
  }, {});
1163
1163
  return Object.assign(Object.assign({}, clientIds), getGa4Ids(runtime));
1164
1164
  }
1165
+ function parseGa4SessionId(cookieValue) {
1166
+ if (typeof cookieValue !== "string") {
1167
+ return undefined;
1168
+ }
1169
+ if (cookieValue.startsWith("GA1") || cookieValue.startsWith("GS1")) {
1170
+ return cookieValue.split(".")[2];
1171
+ }
1172
+ else {
1173
+ // parse new GA4 cookie format, e.g.: GS2.1.s1747323152$o28$g0$t1747323152$j60$l0$h69286059
1174
+ const match = cookieValue.match(/^GS\d+\.\d+\.(?:[\w_-]+[$])*s(\d+)(?:$|[$])/);
1175
+ return match ? match[1] : undefined;
1176
+ }
1177
+ }
1165
1178
  function getGa4Ids(runtime) {
1166
1179
  var _a;
1167
1180
  const allCookies = runtime.getCookies();
@@ -1170,14 +1183,11 @@ function getGa4Ids(runtime) {
1170
1183
  const sessionIds = gaSessionCookies.length > 0
1171
1184
  ? Object.fromEntries(gaSessionCookies
1172
1185
  .map(([key, value]) => {
1173
- if (typeof value !== "string") {
1186
+ const sessionId = parseGa4SessionId(value);
1187
+ if (!sessionId) {
1174
1188
  return null;
1175
1189
  }
1176
- const parts = value.split(".");
1177
- if (parts.length < 3) {
1178
- return null;
1179
- }
1180
- return [key.substring("_ga_".length), parts[2]];
1190
+ return [key.substring("_ga_".length), sessionId];
1181
1191
  })
1182
1192
  .filter(v => v !== null))
1183
1193
  : undefined;
@@ -1401,6 +1411,15 @@ const emptyRuntime = (config) => ({
1401
1411
  return undefined;
1402
1412
  },
1403
1413
  });
1414
+ function deepCopy(o) {
1415
+ if (typeof o !== "object") {
1416
+ return o;
1417
+ }
1418
+ if (!o) {
1419
+ return o;
1420
+ }
1421
+ return JSON.parse(JSON.stringify(o));
1422
+ }
1404
1423
  function deepMerge(target, source) {
1405
1424
  if (typeof source !== "object" || source === null) {
1406
1425
  return source;
@@ -1437,6 +1456,28 @@ function urlPath(url) {
1437
1456
  const pathMatch = matches && matches[3] ? matches[3].split("?")[0].replace(hashRegex, "") : "";
1438
1457
  return "/" + pathMatch;
1439
1458
  }
1459
+ function canonicalUrl() {
1460
+ if (!isInBrowser())
1461
+ return;
1462
+ const tags = document.getElementsByTagName("link");
1463
+ for (var i = 0, tag; (tag = tags[i]); i++) {
1464
+ if (tag.getAttribute("rel") === "canonical") {
1465
+ return tag.getAttribute("href");
1466
+ }
1467
+ }
1468
+ }
1469
+ /**
1470
+ * bugged analytics.js logic that produces 'url' parameter by concating canonical URL with current search part
1471
+ * I produces broken results in some cases like SPA where path is changed but canonical URL is not updated
1472
+ */
1473
+ function analyticsJsUrl() {
1474
+ if (!isInBrowser())
1475
+ return;
1476
+ const canonical = canonicalUrl();
1477
+ if (!canonical)
1478
+ return window.location.href.replace(hashRegex, "");
1479
+ return canonical.match(/\?/) ? canonical : canonical + window.location.search;
1480
+ }
1440
1481
  function adjustPayload(payload, config, storage, s2s) {
1441
1482
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1442
1483
  const runtime = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
@@ -1445,9 +1486,16 @@ function adjustPayload(payload, config, storage, s2s) {
1445
1486
  const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
1446
1487
  const properties = payload.properties || {};
1447
1488
  if (payload.type === "page" && (properties.url || url)) {
1448
- const targetUrl = properties.url || url;
1489
+ // we don't trust analytics.js URL logic since it's sticks with canonical URL on SPA pages
1490
+ let targetUrl = url || properties.url;
1491
+ if (properties.url && properties.url !== analyticsJsUrl()) {
1492
+ // properties.url is not the same as provided by analytics.js
1493
+ // it means that it was not overridden by user and we should use it
1494
+ targetUrl = properties.url;
1495
+ }
1449
1496
  properties.url = targetUrl.replace(hashRegex, "");
1450
1497
  properties.path = fixPath(urlPath(targetUrl));
1498
+ // other properties are correctly based on window.location in analytics.js
1451
1499
  }
1452
1500
  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) || {});
1453
1501
  (_c = payload.properties) === null || _c === void 0 ? true : delete _c.context;
@@ -1501,15 +1549,18 @@ function processDestinations(destinations, method, originalEvent, debug, analyti
1501
1549
  return __awaiter$1(this, void 0, void 0, function* () {
1502
1550
  const promises = [];
1503
1551
  for (const destination of destinations) {
1504
- let newEvents = [originalEvent];
1552
+ let newEvents = [];
1505
1553
  if (destination.newEvents) {
1506
1554
  try {
1507
- newEvents = destination.newEvents.map(e => e === "same" ? originalEvent : isDiff(e) ? diff.patch(originalEvent, e.__diff) : e);
1555
+ newEvents = destination.newEvents.map(e => e === "same" ? deepCopy(originalEvent) : isDiff(e) ? diff.patch(deepCopy(originalEvent), e.__diff) : e);
1508
1556
  }
1509
1557
  catch (e) {
1510
1558
  console.error(`[JITSU] Error applying '${destination.id}' changes to event: ${e === null || e === void 0 ? void 0 : e.message}`, e);
1511
1559
  }
1512
1560
  }
1561
+ else {
1562
+ newEvents = [deepCopy(originalEvent)];
1563
+ }
1513
1564
  const credentials = Object.assign(Object.assign({}, destination.credentials), destination.options);
1514
1565
  if (destination.deviceOptions.type === "internal-plugin") {
1515
1566
  const plugin = internalDestinationPlugins[destination.deviceOptions.name];
package/dist/web/p.js.txt CHANGED
@@ -1165,6 +1165,19 @@
1165
1165
  }, {});
1166
1166
  return Object.assign(Object.assign({}, clientIds), getGa4Ids(runtime));
1167
1167
  }
1168
+ function parseGa4SessionId(cookieValue) {
1169
+ if (typeof cookieValue !== "string") {
1170
+ return undefined;
1171
+ }
1172
+ if (cookieValue.startsWith("GA1") || cookieValue.startsWith("GS1")) {
1173
+ return cookieValue.split(".")[2];
1174
+ }
1175
+ else {
1176
+ // parse new GA4 cookie format, e.g.: GS2.1.s1747323152$o28$g0$t1747323152$j60$l0$h69286059
1177
+ const match = cookieValue.match(/^GS\d+\.\d+\.(?:[\w_-]+[$])*s(\d+)(?:$|[$])/);
1178
+ return match ? match[1] : undefined;
1179
+ }
1180
+ }
1168
1181
  function getGa4Ids(runtime) {
1169
1182
  var _a;
1170
1183
  const allCookies = runtime.getCookies();
@@ -1173,14 +1186,11 @@
1173
1186
  const sessionIds = gaSessionCookies.length > 0
1174
1187
  ? Object.fromEntries(gaSessionCookies
1175
1188
  .map(([key, value]) => {
1176
- if (typeof value !== "string") {
1189
+ const sessionId = parseGa4SessionId(value);
1190
+ if (!sessionId) {
1177
1191
  return null;
1178
1192
  }
1179
- const parts = value.split(".");
1180
- if (parts.length < 3) {
1181
- return null;
1182
- }
1183
- return [key.substring("_ga_".length), parts[2]];
1193
+ return [key.substring("_ga_".length), sessionId];
1184
1194
  })
1185
1195
  .filter(v => v !== null))
1186
1196
  : undefined;
@@ -1404,6 +1414,15 @@
1404
1414
  return undefined;
1405
1415
  },
1406
1416
  });
1417
+ function deepCopy(o) {
1418
+ if (typeof o !== "object") {
1419
+ return o;
1420
+ }
1421
+ if (!o) {
1422
+ return o;
1423
+ }
1424
+ return JSON.parse(JSON.stringify(o));
1425
+ }
1407
1426
  function deepMerge(target, source) {
1408
1427
  if (typeof source !== "object" || source === null) {
1409
1428
  return source;
@@ -1440,6 +1459,28 @@
1440
1459
  const pathMatch = matches && matches[3] ? matches[3].split("?")[0].replace(hashRegex, "") : "";
1441
1460
  return "/" + pathMatch;
1442
1461
  }
1462
+ function canonicalUrl() {
1463
+ if (!isInBrowser())
1464
+ return;
1465
+ const tags = document.getElementsByTagName("link");
1466
+ for (var i = 0, tag; (tag = tags[i]); i++) {
1467
+ if (tag.getAttribute("rel") === "canonical") {
1468
+ return tag.getAttribute("href");
1469
+ }
1470
+ }
1471
+ }
1472
+ /**
1473
+ * bugged analytics.js logic that produces 'url' parameter by concating canonical URL with current search part
1474
+ * I produces broken results in some cases like SPA where path is changed but canonical URL is not updated
1475
+ */
1476
+ function analyticsJsUrl() {
1477
+ if (!isInBrowser())
1478
+ return;
1479
+ const canonical = canonicalUrl();
1480
+ if (!canonical)
1481
+ return window.location.href.replace(hashRegex, "");
1482
+ return canonical.match(/\?/) ? canonical : canonical + window.location.search;
1483
+ }
1443
1484
  function adjustPayload(payload, config, storage, s2s) {
1444
1485
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1445
1486
  const runtime = config.runtime || (isInBrowser() ? windowRuntime(config) : emptyRuntime(config));
@@ -1448,9 +1489,16 @@
1448
1489
  const query = parsedUrl ? parseQuery(parsedUrl.search) : {};
1449
1490
  const properties = payload.properties || {};
1450
1491
  if (payload.type === "page" && (properties.url || url)) {
1451
- const targetUrl = properties.url || url;
1492
+ // we don't trust analytics.js URL logic since it's sticks with canonical URL on SPA pages
1493
+ let targetUrl = url || properties.url;
1494
+ if (properties.url && properties.url !== analyticsJsUrl()) {
1495
+ // properties.url is not the same as provided by analytics.js
1496
+ // it means that it was not overridden by user and we should use it
1497
+ targetUrl = properties.url;
1498
+ }
1452
1499
  properties.url = targetUrl.replace(hashRegex, "");
1453
1500
  properties.path = fixPath(urlPath(targetUrl));
1501
+ // other properties are correctly based on window.location in analytics.js
1454
1502
  }
1455
1503
  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) || {});
1456
1504
  (_c = payload.properties) === null || _c === void 0 ? true : delete _c.context;
@@ -1504,15 +1552,18 @@
1504
1552
  return __awaiter$1(this, void 0, void 0, function* () {
1505
1553
  const promises = [];
1506
1554
  for (const destination of destinations) {
1507
- let newEvents = [originalEvent];
1555
+ let newEvents = [];
1508
1556
  if (destination.newEvents) {
1509
1557
  try {
1510
- newEvents = destination.newEvents.map(e => e === "same" ? originalEvent : isDiff(e) ? diff.patch(originalEvent, e.__diff) : e);
1558
+ newEvents = destination.newEvents.map(e => e === "same" ? deepCopy(originalEvent) : isDiff(e) ? diff.patch(deepCopy(originalEvent), e.__diff) : e);
1511
1559
  }
1512
1560
  catch (e) {
1513
1561
  console.error(`[JITSU] Error applying '${destination.id}' changes to event: ${e === null || e === void 0 ? void 0 : e.message}`, e);
1514
1562
  }
1515
1563
  }
1564
+ else {
1565
+ newEvents = [deepCopy(originalEvent)];
1566
+ }
1516
1567
  const credentials = Object.assign(Object.assign({}, destination.credentials), destination.options);
1517
1568
  if (destination.deviceOptions.type === "internal-plugin") {
1518
1569
  const plugin = internalDestinationPlugins[destination.deviceOptions.name];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jitsu/js",
3
- "version": "1.10.3",
3
+ "version": "1.10.4",
4
4
  "description": "",
5
5
  "author": "Jitsu Dev Team <dev@jitsu.com>",
6
6
  "main": "dist/jitsu.cjs.js",
@@ -39,10 +39,10 @@
39
39
  "rollup": "^3.29.5",
40
40
  "ts-jest": "29.0.5",
41
41
  "typescript": "^5.6.3",
42
- "jsondiffpatch": "1.10.3"
42
+ "jsondiffpatch": "1.10.4"
43
43
  },
44
44
  "peerDependencies": {
45
- "@jitsu/protocols": "1.10.3"
45
+ "@jitsu/protocols": "1.10.4"
46
46
  },
47
47
  "dependencies": {
48
48
  "analytics": "0.8.9"