@mparticle/web-sdk 2.32.5 → 2.33.1

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/mparticle.js CHANGED
@@ -203,7 +203,7 @@ var mParticle = (function () {
203
203
  Base64: Base64$1
204
204
  };
205
205
 
206
- var version = "2.32.5";
206
+ var version = "2.33.1";
207
207
 
208
208
  var Constants = {
209
209
  sdkVersion: version,
@@ -713,9 +713,14 @@ var mParticle = (function () {
713
713
  pairs.forEach(function (pair) {
714
714
  var _a = pair.split('='),
715
715
  key = _a[0],
716
- value = _a[1];
717
- if (key && value) {
718
- params[key] = decodeURIComponent(value || '');
716
+ valueParts = _a.slice(1);
717
+ var value = valueParts.join('=');
718
+ if (key && value !== undefined) {
719
+ try {
720
+ params[key] = decodeURIComponent(value || '');
721
+ } catch (e) {
722
+ console.error("Failed to decode value for key ".concat(key, ": ").concat(e));
723
+ }
719
724
  }
720
725
  });
721
726
  return {
@@ -1923,7 +1928,8 @@ var mParticle = (function () {
1923
1928
  session_start_unixtime_ms: sdkEvent.SessionStartDate,
1924
1929
  custom_attributes: sdkEvent.EventAttributes,
1925
1930
  location: convertSDKLocation(sdkEvent.Location),
1926
- source_message_id: sdkEvent.SourceMessageId
1931
+ source_message_id: sdkEvent.SourceMessageId,
1932
+ active_time_on_site_ms: sdkEvent.ActiveTimeOnSite
1927
1933
  };
1928
1934
  return commonEventData;
1929
1935
  }
@@ -3380,21 +3386,23 @@ var mParticle = (function () {
3380
3386
  }
3381
3387
  };
3382
3388
  this.endSession = function (override) {
3383
- var _a;
3389
+ var _a, _b, _c, _d, _e;
3384
3390
  mpInstance.Logger.verbose(Messages$6.InformationMessages.StartingEndSession);
3385
3391
  if (override) {
3386
3392
  mpInstance._Events.logEvent({
3387
3393
  messageType: Types.MessageType.SessionEnd
3388
3394
  });
3389
3395
  mpInstance._Store.nullifySession();
3396
+ (_a = mpInstance._timeOnSiteTimer) === null || _a === void 0 ? void 0 : _a.resetTimer();
3390
3397
  return;
3391
3398
  }
3392
3399
  if (!mpInstance._Helpers.canLog()) {
3393
- // At this moment, an AbandonedEndSession is defined when on of three things occurs:
3400
+ // At this moment, an AbandonedEndSession is defined when one of three things occurs:
3394
3401
  // - the SDK's store is not enabled because mParticle.setOptOut was called
3395
3402
  // - the devToken is undefined
3396
3403
  // - webviewBridgeEnabled is set to false
3397
3404
  mpInstance.Logger.verbose(Messages$6.InformationMessages.AbandonEndSession);
3405
+ (_b = mpInstance._timeOnSiteTimer) === null || _b === void 0 ? void 0 : _b.resetTimer();
3398
3406
  return;
3399
3407
  }
3400
3408
  var sessionTimeoutInMilliseconds;
@@ -3402,13 +3410,14 @@ var mParticle = (function () {
3402
3410
  var cookies = mpInstance._Persistence.getPersistence();
3403
3411
  if (!cookies || cookies.gs && !cookies.gs.sid) {
3404
3412
  mpInstance.Logger.verbose(Messages$6.InformationMessages.NoSessionToEnd);
3413
+ (_c = mpInstance._timeOnSiteTimer) === null || _c === void 0 ? void 0 : _c.resetTimer();
3405
3414
  return;
3406
3415
  }
3407
3416
  // sessionId is not equal to cookies.sid if cookies.sid is changed in another tab
3408
3417
  if (cookies.gs.sid && mpInstance._Store.sessionId !== cookies.gs.sid) {
3409
3418
  mpInstance._Store.sessionId = cookies.gs.sid;
3410
3419
  }
3411
- if ((_a = cookies === null || cookies === void 0 ? void 0 : cookies.gs) === null || _a === void 0 ? void 0 : _a.les) {
3420
+ if ((_d = cookies === null || cookies === void 0 ? void 0 : cookies.gs) === null || _d === void 0 ? void 0 : _d.les) {
3412
3421
  sessionTimeoutInMilliseconds = mpInstance._Store.SDKConfig.sessionTimeout * 60000;
3413
3422
  var newDate = new Date().getTime();
3414
3423
  timeSinceLastEventSent = newDate - cookies.gs.les;
@@ -3422,6 +3431,7 @@ var mParticle = (function () {
3422
3431
  mpInstance._Store.nullifySession();
3423
3432
  }
3424
3433
  }
3434
+ (_e = mpInstance._timeOnSiteTimer) === null || _e === void 0 ? void 0 : _e.resetTimer();
3425
3435
  };
3426
3436
  this.setSessionTimer = function () {
3427
3437
  var sessionTimeoutInMilliseconds = mpInstance._Store.SDKConfig.sessionTimeout * 60000;
@@ -3872,6 +3882,109 @@ var mParticle = (function () {
3872
3882
  };
3873
3883
  }
3874
3884
 
3885
+ var ForegroundTimeTracker = /** @class */function () {
3886
+ function ForegroundTimeTracker(timerKey) {
3887
+ this.isTrackerActive = false;
3888
+ this.localStorageName = '';
3889
+ this.startTime = 0;
3890
+ this.totalTime = 0;
3891
+ this.localStorageName = "mprtcl-tos-".concat(timerKey);
3892
+ this.timerVault = new LocalStorageVault(this.localStorageName);
3893
+ this.loadTimeFromStorage();
3894
+ this.addHandlers();
3895
+ if (document.hidden === false) {
3896
+ this.startTracking();
3897
+ }
3898
+ }
3899
+ ForegroundTimeTracker.prototype.addHandlers = function () {
3900
+ var _this = this;
3901
+ // when user switches tabs or minimizes the window
3902
+ document.addEventListener('visibilitychange', function () {
3903
+ return _this.handleVisibilityChange();
3904
+ });
3905
+ // when user switches to another application
3906
+ window.addEventListener('blur', function () {
3907
+ return _this.handleWindowBlur();
3908
+ });
3909
+ // when window gains focus
3910
+ window.addEventListener('focus', function () {
3911
+ return _this.handleWindowFocus();
3912
+ });
3913
+ // this ensures that timers between tabs are in sync
3914
+ window.addEventListener('storage', function (event) {
3915
+ return _this.syncAcrossTabs(event);
3916
+ });
3917
+ // when user closes tab, refreshes, or navigates to another page via link
3918
+ window.addEventListener('beforeunload', function () {
3919
+ return _this.updateTimeInPersistence();
3920
+ });
3921
+ };
3922
+ ForegroundTimeTracker.prototype.handleVisibilityChange = function () {
3923
+ if (document.hidden) {
3924
+ this.stopTracking();
3925
+ } else {
3926
+ this.startTracking();
3927
+ }
3928
+ };
3929
+ ForegroundTimeTracker.prototype.handleWindowBlur = function () {
3930
+ if (this.isTrackerActive) {
3931
+ this.stopTracking();
3932
+ }
3933
+ };
3934
+ ForegroundTimeTracker.prototype.handleWindowFocus = function () {
3935
+ if (!this.isTrackerActive) {
3936
+ this.startTracking();
3937
+ }
3938
+ };
3939
+ ForegroundTimeTracker.prototype.syncAcrossTabs = function (event) {
3940
+ if (event.key === this.localStorageName && event.newValue !== null) {
3941
+ var newTime = parseFloat(event.newValue) || 0;
3942
+ this.totalTime = newTime;
3943
+ }
3944
+ };
3945
+ ForegroundTimeTracker.prototype.updateTimeInPersistence = function () {
3946
+ if (this.isTrackerActive) {
3947
+ this.timerVault.store(Math.round(this.totalTime));
3948
+ }
3949
+ };
3950
+ ForegroundTimeTracker.prototype.loadTimeFromStorage = function () {
3951
+ var storedTime = this.timerVault.retrieve();
3952
+ if (isNumber(storedTime) && storedTime !== null) {
3953
+ this.totalTime = storedTime;
3954
+ }
3955
+ };
3956
+ ForegroundTimeTracker.prototype.startTracking = function () {
3957
+ if (!document.hidden) {
3958
+ this.startTime = Math.floor(performance.now());
3959
+ this.isTrackerActive = true;
3960
+ }
3961
+ };
3962
+ ForegroundTimeTracker.prototype.stopTracking = function () {
3963
+ if (this.isTrackerActive) {
3964
+ this.setTotalTime();
3965
+ this.updateTimeInPersistence();
3966
+ this.isTrackerActive = false;
3967
+ }
3968
+ };
3969
+ ForegroundTimeTracker.prototype.setTotalTime = function () {
3970
+ if (this.isTrackerActive) {
3971
+ var now = Math.floor(performance.now());
3972
+ this.totalTime += now - this.startTime;
3973
+ this.startTime = now;
3974
+ }
3975
+ };
3976
+ ForegroundTimeTracker.prototype.getTimeInForeground = function () {
3977
+ this.setTotalTime();
3978
+ this.updateTimeInPersistence();
3979
+ return this.totalTime;
3980
+ };
3981
+ ForegroundTimeTracker.prototype.resetTimer = function () {
3982
+ this.totalTime = 0;
3983
+ this.updateTimeInPersistence();
3984
+ };
3985
+ return ForegroundTimeTracker;
3986
+ }();
3987
+
3875
3988
  function createSDKConfig(config) {
3876
3989
  // TODO: Refactor to create a default config object
3877
3990
  var sdkConfig = {};
@@ -4220,6 +4333,7 @@ var mParticle = (function () {
4220
4333
  }
4221
4334
  if (workspaceToken) {
4222
4335
  _this.SDKConfig.workspaceToken = workspaceToken;
4336
+ mpInstance._timeOnSiteTimer = new ForegroundTimeTracker(workspaceToken);
4223
4337
  } else {
4224
4338
  mpInstance.Logger.warning('You should have a workspaceToken on your config object for security purposes.');
4225
4339
  }
@@ -6529,6 +6643,7 @@ var mParticle = (function () {
6529
6643
  return jsonObject;
6530
6644
  };
6531
6645
  this.createEventObject = function (event, user) {
6646
+ var _a;
6532
6647
  var uploadObject = {};
6533
6648
  var eventObject = {};
6534
6649
  // The `optOut` variable is passed later in this method to the `uploadObject`
@@ -6539,6 +6654,7 @@ var mParticle = (function () {
6539
6654
  // TODO: Why is Webview Bridge Enabled or Opt Out necessary here?
6540
6655
  if (mpInstance._Store.sessionId || event.messageType === Types.MessageType.OptOut || mpInstance._Store.webviewBridgeEnabled) {
6541
6656
  var customFlags = __assign({}, event.customFlags);
6657
+ var integrationAttributes = mpInstance._Store.integrationAttributes;
6542
6658
  // https://go.mparticle.com/work/SQDSDKS-5053
6543
6659
  if (mpInstance._Helpers.getFeatureFlag && mpInstance._Helpers.getFeatureFlag(Constants.FeatureFlags.CaptureIntegrationSpecificIds)) {
6544
6660
  // Attempt to recapture click IDs in case a third party integration
@@ -6546,6 +6662,8 @@ var mParticle = (function () {
6546
6662
  mpInstance._IntegrationCapture.capture();
6547
6663
  var transformedClickIDs = mpInstance._IntegrationCapture.getClickIdsAsCustomFlags();
6548
6664
  customFlags = __assign(__assign({}, transformedClickIDs), customFlags);
6665
+ var transformedIntegrationAttributes = mpInstance._IntegrationCapture.getClickIdsAsIntegrationAttributes();
6666
+ integrationAttributes = __assign(__assign({}, transformedIntegrationAttributes), integrationAttributes);
6549
6667
  }
6550
6668
  if (event.hasOwnProperty('toEventAPIObject')) {
6551
6669
  eventObject = event.toEventAPIObject();
@@ -6557,6 +6675,7 @@ var mParticle = (function () {
6557
6675
  EventName: event.name || event.messageType,
6558
6676
  EventCategory: event.eventType,
6559
6677
  EventAttributes: mpInstance._Helpers.sanitizeAttributes(event.data, event.name),
6678
+ ActiveTimeOnSite: (_a = mpInstance._timeOnSiteTimer) === null || _a === void 0 ? void 0 : _a.getTimeInForeground(),
6560
6679
  SourceMessageId: event.sourceMessageId || mpInstance._Helpers.generateUniqueId(),
6561
6680
  EventDataType: event.messageType,
6562
6681
  CustomFlags: customFlags,
@@ -6583,7 +6702,7 @@ var mParticle = (function () {
6583
6702
  Package: mpInstance._Store.SDKConfig["package"],
6584
6703
  ClientGeneratedId: mpInstance._Store.clientId,
6585
6704
  DeviceId: mpInstance._Store.deviceId,
6586
- IntegrationAttributes: mpInstance._Store.integrationAttributes,
6705
+ IntegrationAttributes: integrationAttributes,
6587
6706
  CurrencyCode: mpInstance._Store.currencyCode,
6588
6707
  DataPlan: mpInstance._Store.SDKConfig.dataPlan ? mpInstance._Store.SDKConfig.dataPlan : {}
6589
6708
  };
@@ -9201,9 +9320,11 @@ var mParticle = (function () {
9201
9320
  // Integration outputs are used to determine how click ids are used within the SDK
9202
9321
  // CUSTOM_FLAGS are sent out when an Event is created via ServerModel.createEventObject
9203
9322
  // PARTNER_IDENTITIES are sent out in a Batch when a group of events are converted to a Batch
9323
+ // INTEGRATION_ATTRIBUTES are stored initially on the SDKEvent level but then is added to the Batch when the batch is created
9204
9324
  var IntegrationOutputs = {
9205
9325
  CUSTOM_FLAGS: 'custom_flags',
9206
- PARTNER_IDENTITIES: 'partner_identities'
9326
+ PARTNER_IDENTITIES: 'partner_identities',
9327
+ INTEGRATION_ATTRIBUTES: 'integration_attributes'
9207
9328
  };
9208
9329
  var integrationMapping = {
9209
9330
  // Facebook / Meta
@@ -9233,6 +9354,18 @@ var mParticle = (function () {
9233
9354
  mappedKey: 'GoogleEnhancedConversions.Wbraid',
9234
9355
  output: IntegrationOutputs.CUSTOM_FLAGS
9235
9356
  },
9357
+ // Rokt
9358
+ // https://docs.rokt.com/developers/integration-guides/web/advanced/rokt-id-tag/
9359
+ rtid: {
9360
+ mappedKey: 'passbackconversiontrackingid',
9361
+ output: IntegrationOutputs.INTEGRATION_ATTRIBUTES,
9362
+ moduleId: 1277
9363
+ },
9364
+ RoktTransactionId: {
9365
+ mappedKey: 'passbackconversiontrackingid',
9366
+ output: IntegrationOutputs.INTEGRATION_ATTRIBUTES,
9367
+ moduleId: 1277
9368
+ },
9236
9369
  // TIKTOK
9237
9370
  ttclid: {
9238
9371
  mappedKey: 'TikTok.Callback',
@@ -9249,6 +9382,7 @@ var mParticle = (function () {
9249
9382
  // Cache filtered mappings for faster access
9250
9383
  this.filteredPartnerIdentityMappings = this.filterMappings(IntegrationOutputs.PARTNER_IDENTITIES);
9251
9384
  this.filteredCustomFlagMappings = this.filterMappings(IntegrationOutputs.CUSTOM_FLAGS);
9385
+ this.filteredIntegrationAttributeMappings = this.filterMappings(IntegrationOutputs.INTEGRATION_ATTRIBUTES);
9252
9386
  }
9253
9387
  /**
9254
9388
  * Captures Integration Ids from cookies and query params and stores them in clickIds object
@@ -9256,19 +9390,33 @@ var mParticle = (function () {
9256
9390
  IntegrationCapture.prototype.capture = function () {
9257
9391
  var queryParams = this.captureQueryParams() || {};
9258
9392
  var cookies = this.captureCookies() || {};
9393
+ var localStorage = this.captureLocalStorage() || {};
9259
9394
  // Exclude _fbc if fbclid is present
9260
9395
  // https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc#retrieve-from-fbclid-url-query-parameter
9261
9396
  if (queryParams['fbclid'] && cookies['_fbc']) {
9262
9397
  delete cookies['_fbc'];
9263
9398
  }
9264
- this.clickIds = __assign(__assign(__assign({}, this.clickIds), queryParams), cookies);
9399
+ // If both rtid and RoktTransactionId are present, prioritize rtid
9400
+ // If RoktTransactionId is present in both cookies and localStorage,
9401
+ // prioritize localStorage
9402
+ if (queryParams['rtid'] && localStorage['RoktTransactionId'] && cookies['RoktTransactionId']) {
9403
+ delete localStorage['RoktTransactionId'];
9404
+ delete cookies['RoktTransactionId'];
9405
+ } else if (queryParams['rtid'] && localStorage['RoktTransactionId']) {
9406
+ delete localStorage['RoktTransactionId'];
9407
+ } else if (queryParams['rtid'] && cookies['RoktTransactionId']) {
9408
+ delete cookies['RoktTransactionId'];
9409
+ } else if (localStorage['RoktTransactionId'] && cookies['RoktTransactionId']) {
9410
+ delete cookies['RoktTransactionId'];
9411
+ }
9412
+ this.clickIds = __assign(__assign(__assign(__assign({}, this.clickIds), queryParams), localStorage), cookies);
9265
9413
  };
9266
9414
  /**
9267
9415
  * Captures cookies based on the integration ID mapping.
9268
9416
  */
9269
9417
  IntegrationCapture.prototype.captureCookies = function () {
9270
9418
  var cookies = getCookies(Object.keys(integrationMapping));
9271
- return this.applyProcessors(cookies);
9419
+ return this.applyProcessors(cookies, getHref(), this.initialTimestamp);
9272
9420
  };
9273
9421
  /**
9274
9422
  * Captures query parameters based on the integration ID mapping.
@@ -9277,6 +9425,19 @@ var mParticle = (function () {
9277
9425
  var queryParams = this.getQueryParams();
9278
9426
  return this.applyProcessors(queryParams, getHref(), this.initialTimestamp);
9279
9427
  };
9428
+ /**
9429
+ * Captures local storage based on the integration ID mapping.
9430
+ */
9431
+ IntegrationCapture.prototype.captureLocalStorage = function () {
9432
+ var localStorageItems = {};
9433
+ for (var key in integrationMapping) {
9434
+ var localStorageItem = localStorage.getItem(key);
9435
+ if (localStorageItem) {
9436
+ localStorageItems[key] = localStorageItem;
9437
+ }
9438
+ }
9439
+ return this.applyProcessors(localStorageItems, getHref(), this.initialTimestamp);
9440
+ };
9280
9441
  /**
9281
9442
  * Gets the query parameters based on the integration ID mapping.
9282
9443
  * @returns {Dictionary<string>} The query parameters.
@@ -9298,6 +9459,36 @@ var mParticle = (function () {
9298
9459
  IntegrationCapture.prototype.getClickIdsAsPartnerIdentities = function () {
9299
9460
  return this.getClickIds(this.clickIds, this.filteredPartnerIdentityMappings);
9300
9461
  };
9462
+ /**
9463
+ * Returns only the `integration_attributes` mapped integration output.
9464
+ * @returns {IntegrationAttributes} The integration attributes.
9465
+ */
9466
+ IntegrationCapture.prototype.getClickIdsAsIntegrationAttributes = function () {
9467
+ var _a;
9468
+ var _b, _c;
9469
+ // Integration IDs are stored in the following format:
9470
+ // {
9471
+ // "integration_attributes": {
9472
+ // "<moduleId>": {
9473
+ // "mappedKey": "clickIdValue"
9474
+ // }
9475
+ // }
9476
+ // }
9477
+ var mappedClickIds = {};
9478
+ for (var key in this.clickIds) {
9479
+ if (this.clickIds.hasOwnProperty(key)) {
9480
+ var value = this.clickIds[key];
9481
+ var mappingKey = (_b = this.filteredIntegrationAttributeMappings[key]) === null || _b === void 0 ? void 0 : _b.mappedKey;
9482
+ if (!isEmpty(mappingKey)) {
9483
+ var moduleId = (_c = this.filteredIntegrationAttributeMappings[key]) === null || _c === void 0 ? void 0 : _c.moduleId;
9484
+ if (moduleId && !mappedClickIds[moduleId]) {
9485
+ mappedClickIds[moduleId] = (_a = {}, _a[mappingKey] = value, _a);
9486
+ }
9487
+ }
9488
+ }
9489
+ }
9490
+ return mappedClickIds;
9491
+ };
9301
9492
  IntegrationCapture.prototype.getClickIds = function (clickIds, mappingList) {
9302
9493
  var _a;
9303
9494
  var mappedClickIds = {};
@@ -10472,6 +10663,9 @@ var mParticle = (function () {
10472
10663
  },
10473
10664
  _resetForTests: mockFunction,
10474
10665
  _APIClient: null,
10666
+ _timeOnSiteTimer: {
10667
+ getTimeInForeground: mockFunction
10668
+ },
10475
10669
  MPSideloadedKit: null,
10476
10670
  _Consent: null,
10477
10671
  _Events: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mparticle/web-sdk",
3
- "version": "2.32.5",
3
+ "version": "2.33.1",
4
4
  "description": "mParticle core SDK for web applications",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -87,7 +87,7 @@
87
87
  "@babel/preset-env": "^7.6.0",
88
88
  "@babel/preset-typescript": "^7.6.0",
89
89
  "@mparticle/data-planning-models": "^0.1.0",
90
- "@mparticle/event-models": "^1.1.8",
90
+ "@mparticle/event-models": "^1.1.9",
91
91
  "@rollup/plugin-babel": "6.0.3",
92
92
  "@rollup/plugin-commonjs": "25.0.4",
93
93
  "@rollup/plugin-json": "^5.0.2",
@@ -0,0 +1,113 @@
1
+ import { isNumber } from './utils';
2
+ import { LocalStorageVault } from './vault';
3
+
4
+ export default class ForegroundTimeTracker {
5
+ private isTrackerActive: boolean = false;
6
+ private localStorageName: string = '';
7
+ private timerVault: LocalStorageVault<number>;
8
+ public startTime: number = 0;
9
+ public totalTime: number = 0;
10
+
11
+ constructor(timerKey: string) {
12
+ this.localStorageName = `mprtcl-tos-${timerKey}`;
13
+ this.timerVault = new LocalStorageVault<number>(this.localStorageName);
14
+ this.loadTimeFromStorage();
15
+ this.addHandlers();
16
+ if (document.hidden === false) {
17
+ this.startTracking();
18
+ }
19
+ }
20
+
21
+ private addHandlers(): void {
22
+ // when user switches tabs or minimizes the window
23
+ document.addEventListener('visibilitychange', () =>
24
+ this.handleVisibilityChange()
25
+ );
26
+ // when user switches to another application
27
+ window.addEventListener('blur', () => this.handleWindowBlur());
28
+ // when window gains focus
29
+ window.addEventListener('focus', () => this.handleWindowFocus());
30
+ // this ensures that timers between tabs are in sync
31
+ window.addEventListener('storage', event => this.syncAcrossTabs(event));
32
+ // when user closes tab, refreshes, or navigates to another page via link
33
+ window.addEventListener('beforeunload', () =>
34
+ this.updateTimeInPersistence()
35
+ );
36
+ }
37
+
38
+ private handleVisibilityChange(): void {
39
+ if (document.hidden) {
40
+ this.stopTracking();
41
+ } else {
42
+ this.startTracking();
43
+ }
44
+ }
45
+
46
+ private handleWindowBlur(): void {
47
+ if (this.isTrackerActive) {
48
+ this.stopTracking();
49
+ }
50
+ }
51
+
52
+ private handleWindowFocus(): void {
53
+ if (!this.isTrackerActive) {
54
+ this.startTracking();
55
+ }
56
+ }
57
+
58
+ private syncAcrossTabs(event: StorageEvent): void {
59
+ if (event.key === this.localStorageName && event.newValue !== null) {
60
+ const newTime = parseFloat(event.newValue) || 0;
61
+ this.totalTime = newTime;
62
+ }
63
+ }
64
+
65
+ public updateTimeInPersistence(): void {
66
+ if (this.isTrackerActive) {
67
+ this.timerVault.store(Math.round(this.totalTime));
68
+ }
69
+ }
70
+
71
+ private loadTimeFromStorage(): void {
72
+ const storedTime = this.timerVault.retrieve();
73
+ if (isNumber(storedTime) && storedTime !== null) {
74
+ this.totalTime = storedTime;
75
+ }
76
+ }
77
+
78
+
79
+ private startTracking(): void {
80
+ if (!document.hidden) {
81
+ this.startTime = Math.floor(performance.now());
82
+ this.isTrackerActive = true;
83
+ }
84
+ }
85
+
86
+ private stopTracking(): void {
87
+ if (this.isTrackerActive) {
88
+ this.setTotalTime();
89
+ this.updateTimeInPersistence();
90
+ this.isTrackerActive = false;
91
+ }
92
+ }
93
+
94
+ private setTotalTime(): void {
95
+ if (this.isTrackerActive) {
96
+ const now = Math.floor(performance.now());
97
+ this.totalTime += now - this.startTime;
98
+ this.startTime = now;
99
+
100
+ }
101
+ }
102
+
103
+ public getTimeInForeground(): number {
104
+ this.setTotalTime();
105
+ this.updateTimeInPersistence();
106
+ return this.totalTime;
107
+ }
108
+
109
+ public resetTimer(): void {
110
+ this.totalTime = 0;
111
+ this.updateTimeInPersistence();
112
+ }
113
+ }
@@ -1,4 +1,5 @@
1
1
  import { SDKEventCustomFlags } from './sdkRuntimeModels';
2
+ import { IntegrationAttributes } from './store';
2
3
  import {
3
4
  Dictionary,
4
5
  queryStringParser,
@@ -53,16 +54,19 @@ export const facebookClickIdProcessor: IntegrationCaptureProcessorFunction = (
53
54
  // Integration outputs are used to determine how click ids are used within the SDK
54
55
  // CUSTOM_FLAGS are sent out when an Event is created via ServerModel.createEventObject
55
56
  // PARTNER_IDENTITIES are sent out in a Batch when a group of events are converted to a Batch
57
+ // INTEGRATION_ATTRIBUTES are stored initially on the SDKEvent level but then is added to the Batch when the batch is created
56
58
 
57
59
  const IntegrationOutputs = {
58
60
  CUSTOM_FLAGS: 'custom_flags',
59
61
  PARTNER_IDENTITIES: 'partner_identities',
62
+ INTEGRATION_ATTRIBUTES: 'integration_attributes',
60
63
  } as const;
61
64
 
62
65
  interface IntegrationMappingItem {
63
66
  mappedKey: string;
64
67
  output: valueof<typeof IntegrationOutputs>;
65
68
  processor?: IntegrationCaptureProcessorFunction;
69
+ moduleId?: number;
66
70
  }
67
71
 
68
72
  interface IntegrationIdMapping {
@@ -99,6 +103,19 @@ const integrationMapping: IntegrationIdMapping = {
99
103
  output: IntegrationOutputs.CUSTOM_FLAGS,
100
104
  },
101
105
 
106
+ // Rokt
107
+ // https://docs.rokt.com/developers/integration-guides/web/advanced/rokt-id-tag/
108
+ rtid: {
109
+ mappedKey: 'passbackconversiontrackingid',
110
+ output: IntegrationOutputs.INTEGRATION_ATTRIBUTES,
111
+ moduleId: 1277,
112
+ },
113
+ RoktTransactionId: {
114
+ mappedKey: 'passbackconversiontrackingid',
115
+ output: IntegrationOutputs.INTEGRATION_ATTRIBUTES,
116
+ moduleId: 1277,
117
+ },
118
+
102
119
  // TIKTOK
103
120
  ttclid: {
104
121
  mappedKey: 'TikTok.Callback',
@@ -115,6 +132,7 @@ export default class IntegrationCapture {
115
132
  public readonly initialTimestamp: number;
116
133
  public readonly filteredPartnerIdentityMappings: IntegrationIdMapping;
117
134
  public readonly filteredCustomFlagMappings: IntegrationIdMapping;
135
+ public readonly filteredIntegrationAttributeMappings: IntegrationIdMapping;
118
136
 
119
137
  constructor() {
120
138
  this.initialTimestamp = Date.now();
@@ -122,6 +140,7 @@ export default class IntegrationCapture {
122
140
  // Cache filtered mappings for faster access
123
141
  this.filteredPartnerIdentityMappings = this.filterMappings(IntegrationOutputs.PARTNER_IDENTITIES);
124
142
  this.filteredCustomFlagMappings = this.filterMappings(IntegrationOutputs.CUSTOM_FLAGS);
143
+ this.filteredIntegrationAttributeMappings = this.filterMappings(IntegrationOutputs.INTEGRATION_ATTRIBUTES);
125
144
  }
126
145
 
127
146
  /**
@@ -130,6 +149,7 @@ export default class IntegrationCapture {
130
149
  public capture(): void {
131
150
  const queryParams = this.captureQueryParams() || {};
132
151
  const cookies = this.captureCookies() || {};
152
+ const localStorage = this.captureLocalStorage() || {};
133
153
 
134
154
  // Exclude _fbc if fbclid is present
135
155
  // https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc#retrieve-from-fbclid-url-query-parameter
@@ -137,7 +157,21 @@ export default class IntegrationCapture {
137
157
  delete cookies['_fbc'];
138
158
  }
139
159
 
140
- this.clickIds = { ...this.clickIds, ...queryParams, ...cookies };
160
+ // If both rtid and RoktTransactionId are present, prioritize rtid
161
+ // If RoktTransactionId is present in both cookies and localStorage,
162
+ // prioritize localStorage
163
+ if (queryParams['rtid'] && localStorage['RoktTransactionId'] && cookies['RoktTransactionId']) {
164
+ delete localStorage['RoktTransactionId'];
165
+ delete cookies['RoktTransactionId'];
166
+ } else if (queryParams['rtid'] && localStorage['RoktTransactionId']) {
167
+ delete localStorage['RoktTransactionId'];
168
+ } else if (queryParams['rtid'] && cookies['RoktTransactionId']) {
169
+ delete cookies['RoktTransactionId'];
170
+ } else if (localStorage['RoktTransactionId'] && cookies['RoktTransactionId']) {
171
+ delete cookies['RoktTransactionId'];
172
+ }
173
+
174
+ this.clickIds = { ...this.clickIds, ...queryParams, ...localStorage, ...cookies };
141
175
  }
142
176
 
143
177
  /**
@@ -145,7 +179,7 @@ export default class IntegrationCapture {
145
179
  */
146
180
  public captureCookies(): Dictionary<string> {
147
181
  const cookies = getCookies(Object.keys(integrationMapping));
148
- return this.applyProcessors(cookies);
182
+ return this.applyProcessors(cookies, getHref(), this.initialTimestamp);
149
183
  }
150
184
 
151
185
  /**
@@ -156,6 +190,21 @@ export default class IntegrationCapture {
156
190
  return this.applyProcessors(queryParams, getHref(), this.initialTimestamp);
157
191
  }
158
192
 
193
+ /**
194
+ * Captures local storage based on the integration ID mapping.
195
+ */
196
+ public captureLocalStorage(): Dictionary<string> {
197
+ let localStorageItems: Dictionary<string> = {};
198
+ for (const key in integrationMapping) {
199
+ const localStorageItem = localStorage.getItem(key);
200
+ if (localStorageItem) {
201
+ localStorageItems[key] = localStorageItem;
202
+ }
203
+ }
204
+
205
+ return this.applyProcessors(localStorageItems, getHref(), this.initialTimestamp);
206
+ }
207
+
159
208
  /**
160
209
  * Gets the query parameters based on the integration ID mapping.
161
210
  * @returns {Dictionary<string>} The query parameters.
@@ -180,6 +229,37 @@ export default class IntegrationCapture {
180
229
  return this.getClickIds(this.clickIds, this.filteredPartnerIdentityMappings);
181
230
  }
182
231
 
232
+ /**
233
+ * Returns only the `integration_attributes` mapped integration output.
234
+ * @returns {IntegrationAttributes} The integration attributes.
235
+ */
236
+ public getClickIdsAsIntegrationAttributes(): IntegrationAttributes {
237
+ // Integration IDs are stored in the following format:
238
+ // {
239
+ // "integration_attributes": {
240
+ // "<moduleId>": {
241
+ // "mappedKey": "clickIdValue"
242
+ // }
243
+ // }
244
+ // }
245
+ const mappedClickIds: IntegrationAttributes = {};
246
+
247
+ for (const key in this.clickIds) {
248
+ if (this.clickIds.hasOwnProperty(key)) {
249
+ const value = this.clickIds[key];
250
+ const mappingKey = this.filteredIntegrationAttributeMappings[key]?.mappedKey;
251
+ if (!isEmpty(mappingKey)) {
252
+ const moduleId = this.filteredIntegrationAttributeMappings[key]?.moduleId;
253
+ if (moduleId && !mappedClickIds[moduleId]) {
254
+ mappedClickIds[moduleId] = { [mappingKey]: value };
255
+ }
256
+ }
257
+ }
258
+ }
259
+ return mappedClickIds;
260
+ }
261
+
262
+
183
263
  private getClickIds(
184
264
  clickIds: Dictionary<string>,
185
265
  mappingList: IntegrationIdMapping
@@ -30,6 +30,9 @@ export default class _BatchValidator {
30
30
  },
31
31
  _resetForTests: mockFunction,
32
32
  _APIClient: null,
33
+ _timeOnSiteTimer: {
34
+ getTimeInForeground: mockFunction
35
+ },
33
36
  MPSideloadedKit: null,
34
37
  _Consent: null,
35
38
  _Events: null,