@mparticle/web-sdk 2.58.0 → 2.60.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mparticle/web-sdk",
3
- "version": "2.58.0",
3
+ "version": "2.60.0",
4
4
  "description": "mParticle core SDK for web applications",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/src/apiClient.ts CHANGED
@@ -10,6 +10,7 @@ import { IMParticleUser, ISDKUserAttributes } from './identity-user-interfaces';
10
10
  import { AsyncUploader, FetchUploader, XHRUploader } from './uploaders';
11
11
  import { IMParticleWebSDKInstance } from './mp-instance';
12
12
  import { appendUserInfo } from './user-utils';
13
+ import { hasExplicitIdentifier } from './identity-utils';
13
14
 
14
15
  export interface IAPIClient {
15
16
  uploader: BatchUploader | null;
@@ -88,6 +89,35 @@ export default function APIClient(
88
89
  });
89
90
  };
90
91
 
92
+ // When noFunctional is set and there are no identities passed, the SDK will not fully initialize.
93
+ // In this case, there will be no MPID, but we still want kits to initialize and forward the event to kits.
94
+ // The original event is queued for the MP server upload path so it can be sent once an MPID is returned.
95
+ // Returns true if the event was handled by this path (caller should return early).
96
+ const handleNoFunctionalPreMpidEvent = (event: SDKEvent, mpid: string | undefined): boolean => {
97
+ const noFunctionalWithoutId =
98
+ mpInstance._CookieConsentManager?.getNoFunctional() &&
99
+ !hasExplicitIdentifier(mpInstance._Store);
100
+
101
+ if (!noFunctionalWithoutId || mpid || !mpInstance._Store.configurationLoaded || mpInstance._Store.requireDelay) {
102
+ return false;
103
+ }
104
+
105
+ let forwarderEvent = event;
106
+ if (kitBlocker?.kitBlockingEnabled) {
107
+ forwarderEvent = kitBlocker.createBlockedEvent(event);
108
+ }
109
+ if (forwarderEvent) {
110
+ mpInstance._Forwarders.sendEventToForwarders(forwarderEvent);
111
+ event.AlreadySentToForwarders = true;
112
+ }
113
+ mpInstance.Logger.verbose(
114
+ 'noFunctional event forwarded to kits and queued for MP server upload when MPID is available.'
115
+ );
116
+ mpInstance._Store.eventQueue.push(event);
117
+
118
+ return true;
119
+ };
120
+
91
121
  this.sendEventToServer = function(event, _options) {
92
122
  const defaultOptions = {
93
123
  shouldUploadEvent: true,
@@ -112,8 +142,14 @@ export default function APIClient(
112
142
  mpInstance._Store.integrationDelayTimeoutStart,
113
143
  Date.now()
114
144
  );
115
- // We queue events if there is no MPID (MPID is null, or === 0), or there are integrations that that require this to stall because integration attributes
116
- // need to be set, or if we are still fetching the config (self hosted only), and so require delaying events
145
+
146
+ if (handleNoFunctionalPreMpidEvent(event, mpid)) {
147
+ return;
148
+ }
149
+
150
+ // We queue events if there is no MPID (MPID is null, or === 0), or there are integrations that
151
+ // require this to stall because integration attributes need to be set, or if we are still
152
+ // fetching the config (self hosted only), and so require delaying events
117
153
  if (
118
154
  !mpid ||
119
155
  mpInstance._Store.requireDelay ||
@@ -146,8 +182,10 @@ export default function APIClient(
146
182
  }
147
183
 
148
184
  // We need to check event again, because kitblocking
149
- // can nullify the event
150
- if (event) {
185
+ // can nullify the event.
186
+ // Skip if forwarders were already called in the noFunctional pre-MPID path
187
+ // to prevent double-sending when the event queue is later flushed.
188
+ if (event && !event.AlreadySentToForwarders) {
151
189
  mpInstance._Forwarders.sendEventToForwarders(event);
152
190
  }
153
191
  }
@@ -1,9 +1,9 @@
1
1
  import { Batch } from '@mparticle/event-models';
2
2
  import Constants from './constants';
3
- import { SDKEvent, SDKEventCustomFlags, SDKLoggerApi } from './sdkRuntimeModels';
3
+ import { SDKEvent, SDKEventCustomFlags } from './sdkRuntimeModels';
4
4
  import { convertEvents } from './sdkToEventsApiConverter';
5
5
  import { MessageType, EventType } from './types';
6
- import { getRampNumber, isEmpty } from './utils';
6
+ import { getRampNumber, isEmpty, obfuscateDevData } from './utils';
7
7
  import { SessionStorageVault, LocalStorageVault } from './vault';
8
8
  import {
9
9
  AsyncUploader,
@@ -74,19 +74,16 @@ export class BatchUploader {
74
74
  // so that we don't have to check it every time
75
75
  this.offlineStorageEnabled = this.isOfflineStorageAvailable();
76
76
 
77
- if (this.offlineStorageEnabled) {
77
+ // When noFunctional is true, prevent events/batches storage
78
+ const noFunctional = mpInstance._CookieConsentManager?.getNoFunctional();
79
+
80
+ if (this.offlineStorageEnabled && !noFunctional) {
78
81
  this.eventVault = new SessionStorageVault<SDKEvent[]>(
79
- `${mpInstance._Store.storageName}-events`,
80
- {
81
- logger: mpInstance.Logger,
82
- }
82
+ `${mpInstance._Store.storageName}-events`
83
83
  );
84
84
 
85
85
  this.batchVault = new LocalStorageVault<Batch[]>(
86
- `${mpInstance._Store.storageName}-batches`,
87
- {
88
- logger: mpInstance.Logger,
89
- }
86
+ `${mpInstance._Store.storageName}-batches`
90
87
  );
91
88
 
92
89
  // Load Events from Session Storage in case we have any in storage
@@ -257,15 +254,16 @@ export class BatchUploader {
257
254
  return;
258
255
  }
259
256
 
260
- const { Logger } = this.mpInstance;
261
-
262
257
  this.eventsQueuedForProcessing.push(event);
263
258
  if (this.offlineStorageEnabled && this.eventVault) {
264
259
  this.eventVault.store(this.eventsQueuedForProcessing);
265
260
  }
266
261
 
267
- Logger.verbose(`Queuing event: ${JSON.stringify(event)}`);
268
- Logger.verbose(`Queued event count: ${this.eventsQueuedForProcessing.length}`);
262
+ if (this.mpInstance.Logger.isVerbose()) {
263
+ const eventToLog = obfuscateDevData(event, this.mpInstance._Store.SDKConfig.isDevelopmentMode);
264
+ this.mpInstance.Logger.verbose(`Queuing event: ${JSON.stringify(eventToLog)}`);
265
+ this.mpInstance.Logger.verbose(`Queued event count: ${this.eventsQueuedForProcessing.length}`);
266
+ }
269
267
 
270
268
  if (this.shouldTriggerImmediateUpload(event.EventDataType)) {
271
269
  this.prepareAndUpload(false, false);
@@ -409,7 +407,6 @@ export class BatchUploader {
409
407
  this.batchesQueuedForProcessing = [];
410
408
 
411
409
  const batchesThatDidNotUpload = await this.uploadBatches(
412
- this.mpInstance.Logger,
413
410
  batchesToUpload,
414
411
  useBeacon
415
412
  );
@@ -429,8 +426,8 @@ export class BatchUploader {
429
426
  // therefore NOT overwrite Offline Storage when beacon returns, so that we can retry
430
427
  // uploading saved batches at a later time. Batches should only be removed from
431
428
  // Local Storage once we can confirm they are successfully uploaded.
429
+
432
430
  this.batchVault.store(this.batchesQueuedForProcessing);
433
-
434
431
  // Clear batch queue since everything should be in Offline Storage
435
432
  this.batchesQueuedForProcessing = [];
436
433
  }
@@ -440,10 +437,7 @@ export class BatchUploader {
440
437
  }
441
438
  }
442
439
 
443
- // TODO: Refactor to use logger as a class method
444
- // https://go.mparticle.com/work/SQDSDKS-5167
445
440
  private async uploadBatches(
446
- logger: SDKLoggerApi,
447
441
  batches: Batch[],
448
442
  useBeacon: boolean
449
443
  ): Promise<Batch[] | null> {
@@ -454,8 +448,11 @@ export class BatchUploader {
454
448
  return null;
455
449
  }
456
450
 
457
- logger.verbose(`Uploading batches: ${JSON.stringify(uploads)}`);
458
- logger.verbose(`Batch count: ${uploads.length}`);
451
+ if (this.mpInstance.Logger.isVerbose()) {
452
+ const uploadsToLog = obfuscateDevData(uploads, this.mpInstance._Store.SDKConfig.isDevelopmentMode);
453
+ this.mpInstance.Logger.verbose(`Uploading batches: ${JSON.stringify(uploadsToLog)}`);
454
+ this.mpInstance.Logger.verbose(`Batch count: ${uploads.length}`);
455
+ }
459
456
 
460
457
  for (let i = 0; i < uploads.length; i++) {
461
458
  const fetchPayload: IFetchPayload = {
@@ -479,20 +476,20 @@ export class BatchUploader {
479
476
  const response = await this.uploader.upload(fetchPayload);
480
477
 
481
478
  if (response.status >= 200 && response.status < 300) {
482
- logger.verbose(
479
+ this.mpInstance.Logger.verbose(
483
480
  `Upload success for request ID: ${uploads[i].source_request_id}`
484
481
  );
485
482
  } else if (
486
483
  response.status >= 500 ||
487
484
  response.status === 429
488
485
  ) {
489
- logger.error(
486
+ this.mpInstance.Logger.error(
490
487
  `HTTP error status ${response.status} received`
491
488
  );
492
489
  // Server error, add back current batches and try again later
493
490
  return uploads.slice(i, uploads.length);
494
491
  } else if (response.status >= 401) {
495
- logger.error(
492
+ this.mpInstance.Logger.error(
496
493
  `HTTP error status ${response.status} while uploading - please verify your API key.`
497
494
  );
498
495
  //if we're getting a 401, assume we'll keep getting a 401 and clear the uploads.
@@ -509,7 +506,7 @@ export class BatchUploader {
509
506
  );
510
507
  }
511
508
  } catch (e) {
512
- logger.error(
509
+ this.mpInstance.Logger.error(
513
510
  `Error sending event to mParticle servers. ${e}`
514
511
  );
515
512
  return uploads.slice(i, uploads.length);
@@ -73,6 +73,12 @@ export default function CookieSyncManager(
73
73
  return;
74
74
  }
75
75
 
76
+ // When noFunctional is true, persistence is not saved, so we cannot track cookie sync
77
+ // dates. Skip cookie sync to avoid running it on every page load.
78
+ if (mpInstance._CookieConsentManager?.getNoFunctional()) {
79
+ return;
80
+ }
81
+
76
82
  const persistence = mpInstance._Persistence.getPersistence();
77
83
 
78
84
  if (isEmpty(persistence)) {
@@ -8,10 +8,12 @@ export default class ForegroundTimeTracker {
8
8
  public startTime: number = 0;
9
9
  public totalTime: number = 0;
10
10
 
11
- constructor(timerKey: string) {
11
+ constructor(timerKey: string, private noFunctional: boolean = false) {
12
12
  this.localStorageName = `mprtcl-tos-${timerKey}`;
13
13
  this.timerVault = new LocalStorageVault<number>(this.localStorageName);
14
- this.loadTimeFromStorage();
14
+ if (!this.noFunctional) {
15
+ this.loadTimeFromStorage();
16
+ }
15
17
  this.addHandlers();
16
18
  if (document.hidden === false) {
17
19
  this.startTracking();
@@ -63,7 +65,7 @@ export default class ForegroundTimeTracker {
63
65
  }
64
66
 
65
67
  public updateTimeInPersistence(): void {
66
- if (this.isTrackerActive) {
68
+ if (this.isTrackerActive && !this.noFunctional) {
67
69
  this.timerVault.store(Math.round(this.totalTime));
68
70
  }
69
71
  }
@@ -1,5 +1,5 @@
1
1
  import Constants, { ONE_DAY_IN_SECONDS, MILLIS_IN_ONE_SEC } from './constants';
2
- import { Dictionary, parseNumber, isObject, generateHash } from './utils';
2
+ import { Dictionary, parseNumber, isObject, generateHash, isEmpty } from './utils';
3
3
  import { BaseVault } from './vault';
4
4
  import Types from './types';
5
5
  import {
@@ -13,6 +13,7 @@ import {
13
13
  IIdentityResponse,
14
14
  IMParticleUser,
15
15
  } from './identity-user-interfaces';
16
+ import { IStore } from './store';
16
17
 
17
18
  const { Identify, Modify, Login, Logout } = Constants.IdentityMethods;
18
19
  export const CACHE_HEADER = 'x-mp-max-age' as const;
@@ -298,3 +299,25 @@ export const hasIdentityRequestChanged = (
298
299
  JSON.stringify(currentUserIdentities) !== JSON.stringify(newIdentities)
299
300
  );
300
301
  };
302
+
303
+ /**
304
+ * Checks if deviceId or other user identifiers (like email) were explicitly provided
305
+ * by the partner via config.deviceId or config.identifyRequest.userIdentities.
306
+ * When noFunctional is true, then cookies are blocked, so the partner must explicitly
307
+ * pass deviceId or other identifiers to prevent new users from being created on each page load.
308
+ *
309
+ * @param store - The SDK store (provides SDKConfig.deviceId and SDKConfig.identifyRequest.userIdentities)
310
+ * @returns true if deviceId or other identifiers were explicitly provided in config, false otherwise
311
+ */
312
+ export const hasExplicitIdentifier = (store: IStore | undefined | null): boolean => {
313
+ const userIdentities = store?.SDKConfig?.identifyRequest?.userIdentities;
314
+ if (
315
+ userIdentities &&
316
+ isObject(userIdentities) &&
317
+ !isEmpty(userIdentities) &&
318
+ Object.values(userIdentities).some(Boolean)
319
+ ) {
320
+ return true;
321
+ }
322
+ return !!store?.SDKConfig?.deviceId;
323
+ };
@@ -6,7 +6,7 @@ import {
6
6
  IFetchPayload,
7
7
  } from './uploaders';
8
8
  import { CACHE_HEADER } from './identity-utils';
9
- import { parseNumber, valueof } from './utils';
9
+ import { obfuscateData, parseNumber, valueof } from './utils';
10
10
  import {
11
11
  IAliasCallback,
12
12
  IAliasRequest,
@@ -277,9 +277,17 @@ export default function IdentityAPIClient(
277
277
  message += ' - ' + errorMessage;
278
278
  }
279
279
 
280
- } else {
281
- message = 'Received Identity Response from server: ';
282
- message += JSON.stringify(identityResponse.responseText);
280
+ } else if (Logger.isVerbose()) {
281
+ const responseText = identityResponse.responseText;
282
+ const { isDevelopmentMode } = mpInstance._Store.SDKConfig;
283
+ let responseToLog = responseText;
284
+ if (!isDevelopmentMode && responseText?.matched_identities) {
285
+ responseToLog = {
286
+ ...responseText,
287
+ matched_identities: obfuscateData(responseText.matched_identities)
288
+ };
289
+ }
290
+ message = 'Received Identity Response from server: ' + JSON.stringify(responseToLog);
283
291
  }
284
292
 
285
293
  break;
@@ -306,7 +314,9 @@ export default function IdentityAPIClient(
306
314
  mpInstance._Store.identityCallInFlight = false;
307
315
  mpInstance._Store.identityCallFailed = false;
308
316
 
309
- Logger.verbose(message);
317
+ if (message) {
318
+ Logger.verbose(message);
319
+ }
310
320
  if (mpInstance._RoktManager?.isInitialized) {
311
321
  const requestCount = mpInstance._Store.identifyRequestCount;
312
322
  mpInstance.captureTiming(`${requestCount}-identityRequestEnd`);
package/src/logger.ts CHANGED
@@ -49,6 +49,10 @@ export class Logger {
49
49
  }
50
50
  }
51
51
 
52
+ public isVerbose(): boolean {
53
+ return this.logLevel === LogLevelType.Verbose;
54
+ }
55
+
52
56
  public setLogLevel(newLogLevel: LogLevelType): void {
53
57
  this.logLevel = newLogLevel;
54
58
  }
@@ -37,8 +37,8 @@ import KitBlocker from './kitBlocking';
37
37
  import ConfigAPIClient, { IKitConfigs } from './configAPIClient';
38
38
  import IdentityAPIClient from './identityApiClient';
39
39
  import { isFunction, parseConfig, valueof, generateDeprecationMessage } from './utils';
40
- import { LocalStorageVault } from './vault';
41
- import { removeExpiredIdentityCacheDates } from './identity-utils';
40
+ import { DisabledVault, LocalStorageVault } from './vault';
41
+ import { removeExpiredIdentityCacheDates, hasExplicitIdentifier } from './identity-utils';
42
42
  import IntegrationCapture from './integrationCapture';
43
43
  import { IPreInit, processReadyQueue } from './pre-init-utils';
44
44
  import { BaseEvent, MParticleWebSDK, SDKHelpersApi } from './sdkRuntimeModels';
@@ -97,6 +97,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
97
97
  getLauncherInstanceGuid: () => string;
98
98
  captureTiming(metricName: string);
99
99
  processQueueOnIdentityFailure?: () => void;
100
+ processQueueOnNoFunctional?: () => void;
100
101
  }
101
102
 
102
103
  const { Messages, HTTPCodes, FeatureFlags, CaptureIntegrationSpecificIdsV2Modes } = Constants;
@@ -150,13 +151,30 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
150
151
  if (self._Store?.isInitialized) {
151
152
  return;
152
153
  }
153
-
154
+
154
155
  if (self._Store?.identityCallFailed && self._RoktManager?.isReady()) {
155
156
  self._RoktManager.processMessageQueue();
156
157
  self._preInit.readyQueue = processReadyQueue(self._preInit.readyQueue);
157
158
  }
158
159
  };
159
160
 
161
+ /**
162
+ * Drains the ready queue for noFunctional sessions with no explicit identity.
163
+ */
164
+ this.processQueueOnNoFunctional = function() {
165
+ if (self._Store?.isInitialized) {
166
+ return;
167
+ }
168
+
169
+ const noFunctionalWithoutId =
170
+ self._CookieConsentManager?.getNoFunctional() &&
171
+ !hasExplicitIdentifier(self._Store);
172
+
173
+ if (noFunctionalWithoutId) {
174
+ self._preInit.readyQueue = processReadyQueue(self._preInit.readyQueue);
175
+ }
176
+ };
177
+
160
178
  // required for forwarders once they reference the mparticle instance
161
179
  this.IdentityType = IdentityType;
162
180
  this.EventType = EventType;
@@ -280,11 +298,16 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
280
298
  * @param {Function} f Callback to execute
281
299
  */
282
300
  this.ready = function(f) {
301
+ const noFunctionalWithoutId =
302
+ self._CookieConsentManager?.getNoFunctional() &&
303
+ !hasExplicitIdentifier(self._Store);
304
+
283
305
  const shouldExecute = isFunction(f) && (
284
- self._Store?.isInitialized ||
285
- (self._Store?.identityCallFailed && self._RoktManager.isReady())
306
+ self._Store?.isInitialized ||
307
+ (self._Store?.identityCallFailed && self._RoktManager.isReady()) ||
308
+ noFunctionalWithoutId
286
309
  );
287
-
310
+
288
311
  if (shouldExecute) {
289
312
  f();
290
313
  } else {
@@ -1135,11 +1158,16 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
1135
1158
  * @param {String or Number} value value for session attribute
1136
1159
  */
1137
1160
  this.setSessionAttribute = function(key, value) {
1138
- const queued = queueIfNotInitialized(function() {
1139
- self.setSessionAttribute(key, value);
1140
- }, self);
1161
+ const skipQueue =
1162
+ self._CookieConsentManager?.getNoFunctional() &&
1163
+ !hasExplicitIdentifier(self._Store);
1141
1164
 
1142
- if (queued) return;
1165
+ if (!skipQueue) {
1166
+ const queued = queueIfNotInitialized(function() {
1167
+ self.setSessionAttribute(key, value);
1168
+ }, self);
1169
+ if (queued) return;
1170
+ }
1143
1171
 
1144
1172
  // Logs to cookie
1145
1173
  // And logs to in-memory object
@@ -1185,7 +1213,7 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
1185
1213
  * @method setOptOut
1186
1214
  * @param {Boolean} isOptingOut boolean to opt out or not. When set to true, opt out of logging.
1187
1215
  */
1188
- this.setOptOut = function(isOptingOut) {
1216
+ this.setOptOut = function(isOptingOut: boolean) {
1189
1217
  const queued = queueIfNotInitialized(function() {
1190
1218
  self.setOptOut(isOptingOut);
1191
1219
  }, self);
@@ -1512,6 +1540,10 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
1512
1540
  );
1513
1541
  }
1514
1542
 
1543
+ // For noFunctional sessions with no identity, drain any pre-init ready callbacks
1544
+ // that were queued before _CookieConsentManager was available to evaluate the condition.
1545
+ mpInstance.processQueueOnNoFunctional?.();
1546
+
1515
1547
  // https://go.mparticle.com/work/SQDSDKS-6040
1516
1548
  if (mpInstance._Store.isFirstRun) {
1517
1549
  mpInstance._Store.isFirstRun = false;
@@ -1584,9 +1616,13 @@ function createKitBlocker(config, mpInstance) {
1584
1616
  }
1585
1617
 
1586
1618
  function createIdentityCache(mpInstance) {
1587
- return new LocalStorageVault(`${mpInstance._Store.storageName}-id-cache`, {
1588
- logger: mpInstance.Logger,
1589
- });
1619
+ // Identity expects mpInstance._Identity.idCache to always exist. DisabledVault
1620
+ // ensures no identity response data is written to localStorage when noFunctional is true
1621
+ if (mpInstance._CookieConsentManager?.getNoFunctional()) {
1622
+ return new DisabledVault(`${mpInstance._Store.storageName}-id-cache`);
1623
+ }
1624
+
1625
+ return new LocalStorageVault(`${mpInstance._Store.storageName}-id-cache`);
1590
1626
  }
1591
1627
 
1592
1628
  function runPreConfigFetchInitialization(mpInstance, apiKey, config) {
@@ -1664,15 +1700,21 @@ function processIdentityCallback(
1664
1700
  }
1665
1701
 
1666
1702
  function queueIfNotInitialized(func, self) {
1667
- // Core SDK methods must wait for Store initialization
1668
- if (!self._Store?.isInitialized) {
1669
- self._preInit.readyQueue.push(function() {
1670
- if (self._Store?.isInitialized) {
1671
- func();
1672
- }
1673
- });
1674
- return true;
1703
+ // When noFunctional is true with no explicit identifier, the SDK will never
1704
+ // receive an MPID. Let these calls through so events can still reach forwarders immediately.
1705
+ // sendEventToServer handles queuing for the MP server upload path separately.
1706
+ const noFunctionalWithoutId =
1707
+ self._CookieConsentManager?.getNoFunctional() &&
1708
+ !hasExplicitIdentifier(self._Store);
1709
+
1710
+ if (self._Store?.isInitialized || noFunctionalWithoutId) {
1711
+ return false;
1675
1712
  }
1676
- return false;
1713
+ self._preInit.readyQueue.push(function() {
1714
+ if (self._Store?.isInitialized) {
1715
+ func();
1716
+ }
1717
+ });
1718
+ return true;
1677
1719
  }
1678
1720
 
@@ -230,6 +230,11 @@ export default function _Persistence(mpInstance) {
230
230
  return;
231
231
  }
232
232
 
233
+ // Block mprtcl-v4 localStorage when noFunctional is true
234
+ if (mpInstance._CookieConsentManager?.getNoFunctional()) {
235
+ return;
236
+ }
237
+
233
238
  var key = mpInstance._Store.storageName,
234
239
  localStorageData = self.getLocalStorage() || {},
235
240
  currentUser = mpInstance.Identity.getCurrentUser(),
@@ -398,6 +403,11 @@ export default function _Persistence(mpInstance) {
398
403
  // https://go.mparticle.com/work/SQDSDKS-5022
399
404
  // https://go.mparticle.com/work/SQDSDKS-6021
400
405
  this.setCookie = function() {
406
+ // Block mprtcl-v4 cookies when noFunctional is true
407
+ if (mpInstance._CookieConsentManager?.getNoFunctional()) {
408
+ return;
409
+ }
410
+
401
411
  var mpid,
402
412
  currentUser = mpInstance.Identity.getCurrentUser();
403
413
  if (currentUser) {
@@ -803,6 +813,11 @@ export default function _Persistence(mpInstance) {
803
813
 
804
814
  // https://go.mparticle.com/work/SQDSDKS-6021
805
815
  this.savePersistence = function(persistence) {
816
+ // Block mprtcl-v4 persistence when noFunctional is true
817
+ if (mpInstance._CookieConsentManager?.getNoFunctional()) {
818
+ return;
819
+ }
820
+
806
821
  var encodedPersistence = self.encodePersistence(
807
822
  JSON.stringify(persistence)
808
823
  ),
@@ -9,6 +9,7 @@ import {
9
9
  isFunction,
10
10
  AttributeValue,
11
11
  isEmpty,
12
+ obfuscateDevData,
12
13
  } from "./utils";
13
14
  import { SDKIdentityApi } from "./identity.interfaces";
14
15
  import { SDKLoggerApi } from "./sdkRuntimeModels";
@@ -234,7 +235,10 @@ export default class RoktManager {
234
235
  const { attributes } = options;
235
236
  const sandboxValue = attributes?.sandbox || null;
236
237
  const mappedAttributes = this.mapPlacementAttributes(attributes, this.placementAttributesMapping);
237
- this.logger?.verbose(`mParticle.Rokt selectPlacements called with attributes:\n${JSON.stringify(attributes, null, 2)}`);
238
+ if (this.logger?.isVerbose()) {
239
+ const attributesToLog = obfuscateDevData(attributes, this.store?.SDKConfig?.isDevelopmentMode);
240
+ this.logger.verbose(`mParticle.Rokt selectPlacements called with attributes:\n${JSON.stringify(attributesToLog, null, 2)}`);
241
+ }
238
242
 
239
243
  this.currentUser = this.identityService.getCurrentUser();
240
244
  const currentUserIdentities = this.currentUser?.getUserIdentities()?.userIdentities || {};
@@ -273,7 +277,7 @@ export default class RoktManager {
273
277
  newIdentities[this.mappedEmailShaIdentityType] = newHashedEmail;
274
278
  this.logger.warning(`emailsha256 mismatch detected. Current mParticle hashedEmail differs from hashedEmail passed to selectPlacements call. Proceeding to call identify with hashedEmail from selectPlacements call. Please verify your implementation.`);
275
279
  }
276
-
280
+
277
281
  if (!isEmpty(newIdentities)) {
278
282
  // Call identify with the new user identities
279
283
  try {
@@ -288,7 +292,8 @@ export default class RoktManager {
288
292
  });
289
293
  });
290
294
  } catch (error) {
291
- this.logger.error('Failed to identify user with new email: ' + JSON.stringify(error));
295
+ const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
296
+ this.logger.error('Failed to identify user with updated identities: ' + errorMessage);
292
297
  }
293
298
  }
294
299
 
@@ -504,7 +509,10 @@ export default class RoktManager {
504
509
  return;
505
510
  }
506
511
 
507
- this.logger?.verbose(`RoktManager: Processing queued message: ${message.methodName} with payload: ${JSON.stringify(message.payload)}`);
512
+ if (this.logger?.isVerbose()) {
513
+ const payloadToLog = obfuscateDevData(message.payload, this.store?.SDKConfig?.isDevelopmentMode);
514
+ this.logger.verbose(`RoktManager: Processing queued message: ${message.methodName} with payload: ${JSON.stringify(payloadToLog)}`);
515
+ }
508
516
 
509
517
  // Capture resolve/reject functions before async processing
510
518
  const resolve = message.resolve;
@@ -94,6 +94,7 @@ export interface SDKEvent {
94
94
  ExpandedEventCount: number;
95
95
  ActiveTimeOnSite: number;
96
96
  IsBackgroundAST?: boolean;
97
+ AlreadySentToForwarders?: boolean;
97
98
  }
98
99
 
99
100
  export interface SDKGeoLocation {
@@ -393,6 +394,7 @@ export interface SDKLoggerApi {
393
394
  error(msg: string, code?: ErrorCodes): void;
394
395
  verbose(msg: string): void;
395
396
  warning(msg: string): void;
397
+ isVerbose(): boolean;
396
398
  setLogLevel(logLevel: LogLevelType): void;
397
399
  }
398
400
 
@@ -5,7 +5,7 @@ import Types from './types';
5
5
  import { generateDeprecationMessage } from './utils';
6
6
  import { IMParticleUser } from './identity-user-interfaces';
7
7
  import { IMParticleWebSDKInstance } from './mp-instance';
8
- import { hasIdentityRequestChanged } from './identity-utils';
8
+ import { hasIdentityRequestChanged, hasExplicitIdentifier } from './identity-utils';
9
9
 
10
10
  const { Messages } = Constants;
11
11
 
@@ -45,7 +45,12 @@ export default function SessionManager(
45
45
  const currentUser = mpInstance.Identity.getCurrentUser();
46
46
  const sdkIdentityRequest = SDKConfig.identifyRequest;
47
47
 
48
+ const shouldSuppressIdentify =
49
+ mpInstance._CookieConsentManager?.getNoFunctional() &&
50
+ !hasExplicitIdentifier(mpInstance._Store);
51
+
48
52
  if (
53
+ !shouldSuppressIdentify &&
49
54
  hasIdentityRequestChanged(currentUser, sdkIdentityRequest)
50
55
  ) {
51
56
  mpInstance.Identity.identify(
@@ -102,7 +107,11 @@ export default function SessionManager(
102
107
 
103
108
  self.setSessionTimer();
104
109
 
105
- if (!mpInstance._Store.identifyCalled) {
110
+ const shouldSuppressIdentify =
111
+ mpInstance._CookieConsentManager?.getNoFunctional() &&
112
+ !hasExplicitIdentifier(mpInstance._Store);
113
+
114
+ if (!mpInstance._Store.identifyCalled && !shouldSuppressIdentify) {
106
115
  mpInstance.Identity.identify(
107
116
  mpInstance._Store.SDKConfig.identifyRequest,
108
117
  mpInstance._Store.SDKConfig.identityCallback
package/src/store.ts CHANGED
@@ -732,7 +732,8 @@ export default function Store(
732
732
 
733
733
  if (workspaceToken) {
734
734
  this.SDKConfig.workspaceToken = workspaceToken;
735
- mpInstance._timeOnSiteTimer = new ForegroundTimer(workspaceToken);
735
+ const noFunctional = config?.launcherOptions?.noFunctional === true;
736
+ mpInstance._timeOnSiteTimer = new ForegroundTimer(workspaceToken, noFunctional);
736
737
  } else {
737
738
  mpInstance.Logger.warning(
738
739
  'You should have a workspaceToken on your config object for security purposes.'