@blotoutio/edgetag-sdk-browser 1.35.1 → 1.35.2

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.
Files changed (2) hide show
  1. package/index.js +383 -51
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -509,6 +509,306 @@
509
509
  return true;
510
510
  };
511
511
 
512
+ /**
513
+ * Get a nested value from an object using dot notation
514
+ */
515
+ const getNestedValue = (obj, path) => {
516
+ const parts = path.split('.');
517
+ let current = obj;
518
+ for (const part of parts) {
519
+ if (current === null || current === undefined) {
520
+ return undefined;
521
+ }
522
+ if (typeof current !== 'object') {
523
+ return undefined;
524
+ }
525
+ current = current[part];
526
+ }
527
+ return current;
528
+ };
529
+ /**
530
+ * Set a nested value in an object using dot notation
531
+ */
532
+ const setNestedValue = (obj, path, value) => {
533
+ const parts = path.split('.');
534
+ let current = obj;
535
+ for (let i = 0; i < parts.length - 1; i++) {
536
+ const part = parts[i];
537
+ const existing = current[part];
538
+ if (existing === undefined) {
539
+ current[part] = {};
540
+ }
541
+ else if (existing === null || typeof existing === 'object') {
542
+ // null can be overwritten with an object
543
+ // existing objects can be traversed
544
+ current[part] || (current[part] = {});
545
+ }
546
+ else {
547
+ // existing is a primitive type (string, number, boolean, etc.)
548
+ console.log(`Cannot set nested path "${path}": "${parts
549
+ .slice(0, i + 1)
550
+ .join('.')}" is already a ${typeof existing} and cannot be converted to an object`);
551
+ return;
552
+ }
553
+ current = current[part];
554
+ }
555
+ current[parts[parts.length - 1]] = value;
556
+ };
557
+ /**
558
+ * Remove a nested value from an object using dot notation
559
+ */
560
+ const removeNestedValue = (obj, path) => {
561
+ const parts = path.split('.');
562
+ let current = obj;
563
+ for (let i = 0; i < parts.length - 1; i++) {
564
+ const part = parts[i];
565
+ const existing = current[part];
566
+ if (existing !== null && typeof existing === 'object') {
567
+ current = current[part];
568
+ }
569
+ else {
570
+ return;
571
+ }
572
+ }
573
+ delete current[parts[parts.length - 1]];
574
+ };
575
+ /**
576
+ * Evaluate a single condition rule
577
+ */
578
+ const evaluateCondition = (condition, payload, userData = {}) => {
579
+ let fieldValue;
580
+ if (condition.field.startsWith('user.')) {
581
+ const userPath = condition.field.replace('user.', '');
582
+ fieldValue = getNestedValue(userData, userPath);
583
+ }
584
+ else if (condition.field.startsWith('payload.')) {
585
+ const payloadPath = condition.field.replace('payload.', '');
586
+ if (payloadPath.includes('[]')) {
587
+ const [arrayPath, arrayField] = payloadPath.split('[]');
588
+ const arrayValue = getNestedValue(payload, arrayPath);
589
+ if (Array.isArray(arrayValue)) {
590
+ const fieldName = arrayField.startsWith('.')
591
+ ? arrayField.slice(1)
592
+ : arrayField;
593
+ fieldValue = arrayValue.flatMap((item) => typeof item === 'object' && item !== null
594
+ ? item[fieldName]
595
+ : []);
596
+ }
597
+ else {
598
+ fieldValue = undefined;
599
+ }
600
+ }
601
+ else {
602
+ fieldValue = getNestedValue(payload, payloadPath);
603
+ }
604
+ }
605
+ else {
606
+ fieldValue = getNestedValue(payload.data, condition.field);
607
+ }
608
+ const operator = condition.operator;
609
+ const conditionValue = condition.value;
610
+ switch (operator) {
611
+ case 'equals':
612
+ return fieldValue === conditionValue;
613
+ case 'notEquals':
614
+ return fieldValue !== conditionValue;
615
+ case 'greaterThan':
616
+ return (typeof fieldValue === 'number' &&
617
+ typeof conditionValue === 'number' &&
618
+ fieldValue > conditionValue);
619
+ case 'greaterThanOrEqual':
620
+ return (typeof fieldValue === 'number' &&
621
+ typeof conditionValue === 'number' &&
622
+ fieldValue >= conditionValue);
623
+ case 'lessThan':
624
+ return (typeof fieldValue === 'number' &&
625
+ typeof conditionValue === 'number' &&
626
+ fieldValue < conditionValue);
627
+ case 'lessThanOrEqual':
628
+ return (typeof fieldValue === 'number' &&
629
+ typeof conditionValue === 'number' &&
630
+ fieldValue <= conditionValue);
631
+ case 'contains':
632
+ if (Array.isArray(fieldValue)) {
633
+ // For array fields, check if any item in the array contains the condition value
634
+ return (typeof conditionValue === 'string' &&
635
+ fieldValue.some((val) => typeof val === 'string' && val === conditionValue));
636
+ }
637
+ return (typeof fieldValue === 'string' &&
638
+ typeof conditionValue === 'string' &&
639
+ fieldValue.includes(conditionValue));
640
+ case 'notContains':
641
+ if (Array.isArray(fieldValue)) {
642
+ // For array fields, check if no item in the array contains the condition value
643
+ return (typeof conditionValue === 'string' &&
644
+ !fieldValue.some((val) => typeof val === 'string' && val === conditionValue));
645
+ }
646
+ return (typeof fieldValue === 'string' &&
647
+ typeof conditionValue === 'string' &&
648
+ !fieldValue.includes(conditionValue));
649
+ case 'in':
650
+ if (Array.isArray(fieldValue)) {
651
+ return (Array.isArray(conditionValue) &&
652
+ fieldValue.some((val) => conditionValue.includes(val)));
653
+ }
654
+ return (Array.isArray(conditionValue) &&
655
+ conditionValue.some((val) => val === fieldValue));
656
+ case 'notIn':
657
+ if (Array.isArray(fieldValue)) {
658
+ return (Array.isArray(conditionValue) &&
659
+ !fieldValue.some((val) => conditionValue.includes(val)));
660
+ }
661
+ return (Array.isArray(conditionValue) &&
662
+ !conditionValue.some((val) => val === fieldValue));
663
+ case 'startsWith':
664
+ return (typeof fieldValue === 'string' &&
665
+ typeof conditionValue === 'string' &&
666
+ fieldValue.startsWith(conditionValue));
667
+ case 'endsWith':
668
+ return (typeof fieldValue === 'string' &&
669
+ typeof conditionValue === 'string' &&
670
+ fieldValue.endsWith(conditionValue));
671
+ case 'isNull':
672
+ return fieldValue === null || fieldValue === undefined;
673
+ case 'isNotNull':
674
+ return fieldValue !== null && fieldValue !== undefined;
675
+ default:
676
+ return false;
677
+ }
678
+ };
679
+ /**
680
+ * Evaluate conditions for a simplified rule (no nested groups)
681
+ */
682
+ const evaluateConditions = (conditions, logicalOperator, payload, userData = {}) => {
683
+ if (conditions.length === 0) {
684
+ return true;
685
+ }
686
+ if (logicalOperator === 'AND') {
687
+ return conditions.every((condition) => evaluateCondition(condition, payload, userData));
688
+ }
689
+ else {
690
+ return conditions.some((condition) => evaluateCondition(condition, payload, userData));
691
+ }
692
+ };
693
+ /**
694
+ * Apply actions to payload and providers
695
+ * @param actions - Array of actions to apply
696
+ * @param payload - The payload to modify
697
+ * @param providers - Optional separate providers object (if not in payload)
698
+ * @returns Updated payload, providers, skipEvent flag, and additional event names
699
+ */
700
+ const applyActions = (actions, payload, providers) => {
701
+ var _a;
702
+ const updatedPayload = structuredClone(payload);
703
+ if (!updatedPayload.data) {
704
+ updatedPayload.data = {};
705
+ }
706
+ const updatedProviders = ((_a = providers !== null && providers !== void 0 ? providers : updatedPayload.providers) !== null && _a !== void 0 ? _a : { all: true });
707
+ let skipEvent = false;
708
+ let skipBrowserEvent = false;
709
+ const additionalEvents = [];
710
+ for (const action of actions) {
711
+ switch (action.type) {
712
+ case 'skipEvent':
713
+ skipEvent = true;
714
+ break;
715
+ case 'skipBrowserEvent':
716
+ skipBrowserEvent = true;
717
+ break;
718
+ case 'skipProviders':
719
+ if (action.providers && updatedProviders) {
720
+ for (const provider of action.providers) {
721
+ updatedProviders[provider] = false;
722
+ }
723
+ }
724
+ break;
725
+ case 'updatePayloadField':
726
+ if (action.fieldPath && action.fieldValue !== undefined) {
727
+ const path = action.fieldPath.startsWith('data.')
728
+ ? action.fieldPath
729
+ : `data.${action.fieldPath}`;
730
+ setNestedValue(updatedPayload.data, path.replace('data.', ''), action.fieldValue);
731
+ }
732
+ break;
733
+ case 'addPayloadField':
734
+ if (action.fieldPath && action.fieldValue !== undefined) {
735
+ const path = action.fieldPath.startsWith('data.')
736
+ ? action.fieldPath
737
+ : `data.${action.fieldPath}`;
738
+ setNestedValue(updatedPayload.data, path.replace('data.', ''), action.fieldValue);
739
+ }
740
+ break;
741
+ case 'removePayloadField':
742
+ if (action.fieldPath) {
743
+ const path = action.fieldPath.startsWith('data.')
744
+ ? action.fieldPath
745
+ : `data.${action.fieldPath}`;
746
+ removeNestedValue(updatedPayload.data, path.replace('data.', ''));
747
+ }
748
+ break;
749
+ case 'createAdditionalEvents':
750
+ if (action.additionalEvents) {
751
+ additionalEvents.push(...action.additionalEvents);
752
+ }
753
+ break;
754
+ }
755
+ }
756
+ if (updatedProviders !== undefined) {
757
+ updatedPayload.providers = updatedProviders;
758
+ }
759
+ return {
760
+ updatedPayload,
761
+ updatedProviders,
762
+ skipEvent,
763
+ skipBrowserEvent,
764
+ additionalEvents,
765
+ };
766
+ };
767
+ /**
768
+ * Evaluate all event rules and apply actions
769
+ * @param rules - Array of event rules to evaluate
770
+ * @param payload - The payload to evaluate and modify
771
+ * @param providers - Optional separate providers object (if not in payload)
772
+ * @param userData - Optional user data for user.* field access
773
+ * @returns Updated payload, providers, skipEvent flag, and additional event names
774
+ */
775
+ const evaluateEventRules = (rules, payload, providers, userData = {}) => {
776
+ var _a;
777
+ let updatedPayload = payload;
778
+ let updatedProviders = ((_a = providers !== null && providers !== void 0 ? providers : payload.providers) !== null && _a !== void 0 ? _a : { all: true });
779
+ let skipEvent = false;
780
+ let skipBrowserEvent = false;
781
+ const allAdditionalEvents = [];
782
+ try {
783
+ for (const rule of rules) {
784
+ const conditionMatches = evaluateConditions(rule.conditions || [], rule.logicalOperator || 'AND', updatedPayload, userData);
785
+ if (conditionMatches) {
786
+ const actions = rule.actions || [];
787
+ const result = applyActions(actions, updatedPayload, updatedProviders);
788
+ updatedPayload = result.updatedPayload;
789
+ updatedProviders = result.updatedProviders;
790
+ allAdditionalEvents.push(...result.additionalEvents);
791
+ if (result.skipEvent) {
792
+ skipEvent = true;
793
+ }
794
+ if (result.skipBrowserEvent) {
795
+ skipBrowserEvent = true;
796
+ }
797
+ }
798
+ }
799
+ }
800
+ catch (error) {
801
+ console.error('Error evaluating event rules:', error);
802
+ }
803
+ return {
804
+ updatedPayload,
805
+ updatedProviders,
806
+ skipEvent,
807
+ skipBrowserEvent,
808
+ additionalEvents: allAdditionalEvents,
809
+ };
810
+ };
811
+
512
812
  // eslint-disable-next-line @nx/enforce-module-boundaries
513
813
  const getGlobalSettings = () => {
514
814
  var _a;
@@ -965,7 +1265,7 @@
965
1265
  referrer: getReferrer(destination),
966
1266
  search: getSearch(destination),
967
1267
  locale: getLocale(),
968
- sdkVersion: "1.35.1" ,
1268
+ sdkVersion: "1.35.2" ,
969
1269
  ...(payload || {}),
970
1270
  };
971
1271
  let storage = {};
@@ -1139,13 +1439,14 @@
1139
1439
  processGetData(instances[0], keys, callback);
1140
1440
  };
1141
1441
 
1142
- const sendTag = (destination, { eventName, eventId, data, providerData, providers, options }) => {
1442
+ const sendTag = (destination, { eventName, eventId, data, providerData, providers, options, configuratorProcessed, }) => {
1143
1443
  const payload = {
1144
1444
  eventName,
1145
1445
  eventId,
1146
1446
  timestamp: Date.now(),
1147
1447
  data,
1148
1448
  providerData,
1449
+ configuratorProcessed,
1149
1450
  };
1150
1451
  if (providers) {
1151
1452
  payload.providers = providers;
@@ -1178,64 +1479,93 @@
1178
1479
  const consentCategory = getConsentCategories(destination);
1179
1480
  const consentSettings = getSetting(destination, 'consentSetting');
1180
1481
  const userConsent = { consentChannel, consentCategory, consentSettings };
1482
+ const userProperties = getSetting(destination, 'userProperties');
1181
1483
  if (skipZeroPurchaseEvent && isZeroPurchaseEvent({ eventName, data })) {
1182
1484
  return;
1183
1485
  }
1184
- const conversion = preparePayloadWithConversion(JSON.parse(JSON.stringify(data)), getSetting(destination, 'currency'));
1185
- for (const pkg of providerPackages) {
1186
- if (!pkg || !pkg.name || !pkg.tag) {
1187
- continue;
1486
+ const originalData = structuredClone(data);
1487
+ const originalProviders = structuredClone(providers);
1488
+ const rules = (getSetting(destination, 'configuratorSetting') ||
1489
+ []);
1490
+ const rulesResult = evaluateEventRules(rules, structuredClone({ eventName, data }), providers !== null && providers !== void 0 ? providers : undefined, userProperties || {});
1491
+ if (!rulesResult.skipEvent) {
1492
+ if (rulesResult.updatedPayload) {
1493
+ // eslint-disable-next-line no-param-reassign
1494
+ data = rulesResult.updatedPayload.data;
1188
1495
  }
1189
- if (!configuredTags.has(pkg.name)) {
1190
- logger.log(`Provider ${pkg.name} is not in allow list`);
1191
- continue;
1496
+ if (rulesResult.updatedProviders) {
1497
+ // eslint-disable-next-line no-param-reassign
1498
+ providers = rulesResult.updatedProviders;
1192
1499
  }
1193
- const variables = getProviderVariables(destination, pkg.name);
1194
- const result = {};
1195
- const executionContext = new Map();
1196
- for (const variable of variables) {
1197
- if (!isProviderInstanceAllowed(providers, pkg.name, variable.tagName)) {
1198
- logger.log(`Provider instance is not allowed (${pkg.name}: ${variable.tagName})`);
1199
- continue;
1200
- }
1201
- if (!hasUserConsent(userConsent, pkg.name, variable.tagName)) {
1202
- logger.log(`Consent is missing (${pkg.name}: ${variable.tagName})`);
1203
- continue;
1204
- }
1205
- if (!doesGeoRequestMatchList(requestCountry, requestRegion, isEURequest, variable.geoRegions)) {
1206
- logger.log('GEO request region does not match the filter, skiping');
1207
- continue;
1500
+ if (!rulesResult.skipBrowserEvent) {
1501
+ const conversion = preparePayloadWithConversion(JSON.parse(JSON.stringify(data)), getSetting(destination, 'currency'));
1502
+ for (const pkg of providerPackages) {
1503
+ if (!pkg || !pkg.name || !pkg.tag) {
1504
+ continue;
1505
+ }
1506
+ if (!configuredTags.has(pkg.name)) {
1507
+ logger.log(`Provider ${pkg.name} is not in allow list`);
1508
+ continue;
1509
+ }
1510
+ const variables = getProviderVariables(destination, pkg.name);
1511
+ const result = {};
1512
+ const executionContext = new Map();
1513
+ for (const variable of variables) {
1514
+ if (!isProviderInstanceAllowed(providers, pkg.name, variable.tagName)) {
1515
+ logger.log(`Provider instance is not allowed (${pkg.name}: ${variable.tagName})`);
1516
+ continue;
1517
+ }
1518
+ if (!hasUserConsent(userConsent, pkg.name, variable.tagName)) {
1519
+ logger.log(`Consent is missing (${pkg.name}: ${variable.tagName})`);
1520
+ continue;
1521
+ }
1522
+ if (!doesGeoRequestMatchList(requestCountry, requestRegion, isEURequest, variable.geoRegions)) {
1523
+ logger.log('GEO request region does not match the filter, skiping');
1524
+ continue;
1525
+ }
1526
+ const payload = ((_a = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _a === void 0 ? void 0 : _a.length) === 0 ||
1527
+ ((_b = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _b === void 0 ? void 0 : _b.includes(pkg.name))
1528
+ ? conversion.payload
1529
+ : data;
1530
+ result[variable.tagName] = pkg.tag({
1531
+ userId,
1532
+ sessionId,
1533
+ eventName,
1534
+ eventId,
1535
+ data: JSON.parse(JSON.stringify(payload)),
1536
+ sendTag: sendTag.bind(null, destination),
1537
+ getEdgeData: processGetData.bind(null, destination),
1538
+ manifestVariables: variable.variableSet,
1539
+ executionContext,
1540
+ destination,
1541
+ });
1542
+ }
1543
+ providerData[pkg.name] = result;
1208
1544
  }
1209
- const payload = ((_a = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _a === void 0 ? void 0 : _a.length) === 0 ||
1210
- ((_b = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _b === void 0 ? void 0 : _b.includes(pkg.name))
1211
- ? conversion.payload
1212
- : data;
1213
- result[variable.tagName] = pkg.tag({
1214
- userId,
1215
- sessionId,
1216
- eventName,
1217
- eventId,
1218
- data: JSON.parse(JSON.stringify(payload)),
1219
- sendTag: sendTag.bind(null, destination),
1220
- getEdgeData: processGetData.bind(null, destination),
1221
- manifestVariables: variable.variableSet,
1222
- executionContext,
1223
- destination,
1224
- });
1225
1545
  }
1226
- providerData[pkg.name] = result;
1546
+ else {
1547
+ logger.log('Browser events skipped by event rules, sending tag to server only');
1548
+ }
1549
+ if (!hasAllowedManifestTags(configuredTags, userConsent, providers)) {
1550
+ return;
1551
+ }
1552
+ sendTag(destination, {
1553
+ configuratorProcessed: true,
1554
+ eventName,
1555
+ eventId,
1556
+ data,
1557
+ providerData,
1558
+ providers,
1559
+ options,
1560
+ });
1227
1561
  }
1228
- if (!hasAllowedManifestTags(configuredTags, userConsent, providers)) {
1229
- return;
1562
+ else {
1563
+ logger.log('Event skipped by event rules');
1564
+ }
1565
+ const additionalEvents = rulesResult.additionalEvents || [];
1566
+ for (const additionalEventName of additionalEvents) {
1567
+ handleTag(additionalEventName, structuredClone(originalData || {}), originalProviders, options);
1230
1568
  }
1231
- sendTag(destination, {
1232
- eventName,
1233
- eventId,
1234
- data,
1235
- providerData,
1236
- providers,
1237
- options,
1238
- });
1239
1569
  };
1240
1570
  const handleTag = (eventName, data = {}, providers, options) => {
1241
1571
  if (options === null || options === void 0 ? void 0 : options.destination) {
@@ -1767,6 +2097,8 @@
1767
2097
  geoRegion: result.geoRegion,
1768
2098
  isEURequest: result.isEURequest,
1769
2099
  consentSetting: result.consentSetting,
2100
+ configuratorSetting: result.configuratorSetting,
2101
+ userProperties: result.userProperties,
1770
2102
  });
1771
2103
  if (result.storageId != null) {
1772
2104
  savePerKey(preferences.edgeURL, 'local', tagStorage, result.storageId, storageIdKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotoutio/edgetag-sdk-browser",
3
- "version": "1.35.1",
3
+ "version": "1.35.2",
4
4
  "description": "Browser SDK for EdgeTag",
5
5
  "author": "Blotout",
6
6
  "license": "MIT",