@mparticle/web-sdk 3.22.0 → 3.23.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.
@@ -0,0 +1,10 @@
1
+ import { valueof } from './utils';
2
+ export declare const UrlMode: {
3
+ readonly Path: "path";
4
+ readonly Full: "full";
5
+ };
6
+ export type UrlMode = valueof<typeof UrlMode>;
7
+ export declare const EXCLUDED_URL_QUERY_PARAMS: string[];
8
+ export declare const EXCLUDED_URL_FRAGMENT_PARAMS: string[];
9
+ export declare const cleanUrl: (href: string, mode: UrlMode) => string;
10
+ export declare const cleanPageHref: () => string;
@@ -3,6 +3,8 @@ import { BaseEvent } from './sdkRuntimeModels';
3
3
  import { Dictionary } from './utils';
4
4
  export declare const WIN_APV_KEY = "__mpApv__";
5
5
  export declare const ALLOWED_QUERY_PARAMS: string[];
6
+ export declare const ALLOWED_QUERY_PARAM_PREFIXES: string[];
7
+ export declare const isAllowedQueryParam: (name: string) => boolean;
6
8
  interface IPageSnapshot {
7
9
  path: string;
8
10
  params: Dictionary<string>;
@@ -5,6 +5,7 @@ import { Dictionary, AttributeValue } from "./utils";
5
5
  import { SDKIdentityApi } from "./identity.interfaces";
6
6
  import { SDKLoggerApi } from "./sdkRuntimeModels";
7
7
  import { IStore, LocalSessionAttributes } from "./store";
8
+ import { MPID } from "@mparticle/web-sdk";
8
9
  import { IErrorReportingService, ILoggingService } from "./reporting/types";
9
10
  import { IRoktLauncherOptions } from "./roktLauncherOptions";
10
11
  import IntegrationCapture from "./integrationCapture";
@@ -82,6 +83,7 @@ export default class RoktManager {
82
83
  kit: IRoktKit;
83
84
  filters: RoktKitFilterSettings;
84
85
  private currentUser;
86
+ private createFilteredUser?;
85
87
  private messageQueue;
86
88
  private sandbox;
87
89
  private placementAttributesMapping;
@@ -116,10 +118,11 @@ export default class RoktManager {
116
118
  * @param {Function} captureTiming - Function to capture performance timing marks
117
119
  * @param {IErrorReportingService} errorReporter - Dispatcher for error/warning reporting
118
120
  * @param {ILoggingService} loggingService - Dispatcher for informational logging
121
+ * @param {Function} createFilteredUser - Builds a user as the Rokt connection's filters and the data plan allow
119
122
  *
120
123
  * @throws Logs error to console if placementAttributesMapping parsing fails
121
124
  */
122
- init(roktConfig: IKitConfigs, filteredUser: IMParticleUser, identityService: SDKIdentityApi, store: IStore, logger?: SDKLoggerApi, options?: IRoktOptions, captureTiming?: (metricsName: string) => void, errorReporter?: IErrorReportingService, loggingService?: ILoggingService, integrationCapture?: IntegrationCapture): void;
125
+ init(roktConfig: IKitConfigs, filteredUser: IMParticleUser, identityService: SDKIdentityApi, store: IStore, logger?: SDKLoggerApi, options?: IRoktOptions, captureTiming?: (metricsName: string) => void, errorReporter?: IErrorReportingService, loggingService?: ILoggingService, integrationCapture?: IntegrationCapture, createFilteredUser?: (mpid: MPID) => IMParticleUser): void;
123
126
  get isInitialized(): boolean;
124
127
  attachKit(kit: IRoktKit): void;
125
128
  private watchRouteChanges;
@@ -146,6 +149,7 @@ export default class RoktManager {
146
149
  * });
147
150
  */
148
151
  selectPlacements(options: IRoktSelectPlacementsOptions): Promise<IRoktSelection>;
152
+ private logFilteredIdentity;
149
153
  /**
150
154
  * Hashes attributes and returns both original and hashed versions
151
155
  * with Rokt-compatible key names (like emailsha256, mobilesha256)
@@ -19,6 +19,7 @@ import { IErrorLogMessage, IMParticleWebSDKInstance, IntegrationDelays } from '.
19
19
  import Constants from './constants';
20
20
  import RoktManager, { IRoktLauncherOptions } from './roktManager';
21
21
  import { IConsoleLogger } from './logger';
22
+ import { UrlMode } from './pageUrl';
22
23
  import { ErrorCodes, IErrorReportingService, ILoggingService } from './reporting/types';
23
24
  export type SDKEventCustomFlags = Dictionary<any>;
24
25
  export interface SDKEvent {
@@ -194,6 +195,7 @@ export interface IMParticleInstanceManager extends MParticleWebSDK {
194
195
  _instances: Dictionary<IMParticleWebSDKInstance>;
195
196
  _isTestEnv?: boolean;
196
197
  _forceNoLocalStorage?: boolean;
198
+ _cleanUrl(href: string, mode: UrlMode): string;
197
199
  config: SDKInitConfig;
198
200
  isIOS?: boolean;
199
201
  MPSideloadedKit: typeof MPSideloadedKit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mparticle/web-sdk",
3
- "version": "3.22.0",
3
+ "version": "3.23.0",
4
4
  "description": "mParticle core SDK for web applications",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -3,7 +3,8 @@ import Constants from './constants';
3
3
  import { SDKEvent, SDKEventCustomFlags } from './sdkRuntimeModels';
4
4
  import { convertEvents } from './sdkToEventsApiConverter';
5
5
  import { MessageType, EventType } from './types';
6
- import { getRampNumber, getHref, isEmpty, obfuscateDevData } from './utils';
6
+ import { getRampNumber, isEmpty, obfuscateDevData } from './utils';
7
+ import { cleanPageHref } from './pageUrl';
7
8
  import { SessionStorageVault, LocalStorageVault, StorageResult } from './vault';
8
9
  import {
9
10
  AsyncUploader,
@@ -204,7 +205,7 @@ export class BatchUploader {
204
205
  Debug: SDKConfig.isDevelopmentMode,
205
206
  ActiveTimeOnSite: _timeOnSiteTimer?.getTimeInForeground() || 0,
206
207
  TotalTimeOnSite: _Store.getTotalTimeOnSite?.() || 0,
207
- PageUrl: getHref() || null,
208
+ PageUrl: cleanPageHref() || null,
208
209
  IsBackgroundAST: true
209
210
  } as SDKEvent;
210
211
 
@@ -45,7 +45,7 @@ import { removeExpiredIdentityCacheDates, hasExplicitIdentifier } from './identi
45
45
  import IntegrationCapture, { isIntegrationCaptureEnabled } from './integrationCapture';
46
46
  import { IPreInit, processReadyQueue } from './pre-init-utils';
47
47
  import { BaseEvent, MParticleWebSDK, SDKHelpersApi } from './sdkRuntimeModels';
48
- import { Dictionary, SDKEventAttrs } from '@mparticle/web-sdk';
48
+ import { Dictionary, MPID, SDKEventAttrs } from '@mparticle/web-sdk';
49
49
  import { IIdentity } from './identity.interfaces';
50
50
  import { IEvents } from './events.interfaces';
51
51
  import { IECommerce } from './ecommerce.interfaces';
@@ -1424,12 +1424,8 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
1424
1424
  if (roktConfig) {
1425
1425
  const accountId = roktConfig.settings?.accountId;
1426
1426
  mpInstance._Store.setRoktAccountId(accountId);
1427
- const { userAttributeFilters } = roktConfig;
1428
- const roktFilteredUser = filteredMparticleUser(
1429
- currentUserMPID,
1430
- { userAttributeFilters },
1431
- mpInstance
1432
- );
1427
+ const createRoktFilteredUser = (mpid: MPID) =>
1428
+ filteredMparticleUser(mpid, roktConfig, mpInstance, kitBlocker);
1433
1429
  const roktOptions: IRoktOptions = {
1434
1430
  sandbox: config?.isDevelopmentMode,
1435
1431
  launcherOptions: config?.launcherOptions,
@@ -1438,7 +1434,7 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
1438
1434
  // https://go.mparticle.com/work/SQDSDKS-7339
1439
1435
  mpInstance._RoktManager.init(
1440
1436
  roktConfig,
1441
- roktFilteredUser,
1437
+ createRoktFilteredUser(currentUserMPID),
1442
1438
  mpInstance.Identity,
1443
1439
  mpInstance._Store,
1444
1440
  mpInstance.Logger,
@@ -1447,6 +1443,7 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
1447
1443
  mpInstance._ErrorReportingDispatcher,
1448
1444
  mpInstance._LoggingDispatcher,
1449
1445
  mpInstance._IntegrationCapture,
1446
+ createRoktFilteredUser,
1450
1447
  );
1451
1448
  }
1452
1449
 
@@ -7,6 +7,7 @@ import MPSideloadedKit from './sideloadedKit';
7
7
  import { IMParticleInstanceManager } from './sdkRuntimeModels';
8
8
  import { IStore } from './store';
9
9
  import { Dictionary } from '@mparticle/web-sdk';
10
+ import { cleanUrl } from './pageUrl';
10
11
 
11
12
  if (!Array.prototype.forEach) {
12
13
  Array.prototype.forEach = Polyfill.forEach;
@@ -449,6 +450,8 @@ function mParticleInstanceManager(this: IMParticleInstanceManager) {
449
450
  this.generateHash = function(string) {
450
451
  return self.getInstance().generateHash(string);
451
452
  };
453
+ // Kits clean every URL they send with the same rule as core events.
454
+ this._cleanUrl = cleanUrl;
452
455
  this.addForwarder = function(forwarder) {
453
456
  self.getInstance().addForwarder(forwarder);
454
457
  };
package/src/pageUrl.ts ADDED
@@ -0,0 +1,150 @@
1
+ import { isAllowedQueryParam } from './pageViewTracker';
2
+ import { getHref, valueof } from './utils';
3
+
4
+ // The one rule for a URL that leaves the SDK, shared with kits through
5
+ // `mParticle._cleanUrl`. `path` keeps scheme, host and path. `full` adds the
6
+ // query params an auto page view may capture, then the fragment.
7
+ export const UrlMode = {
8
+ Path: 'path',
9
+ Full: 'full',
10
+ } as const;
11
+
12
+ export type UrlMode = valueof<typeof UrlMode>;
13
+
14
+ // OAuth 2.0 / OIDC authorization-flow params. They stay off URLs even where the
15
+ // page view allowlist admits them, because a URL travels on every event.
16
+ export const EXCLUDED_URL_QUERY_PARAMS: string[] = [
17
+ 'client_id',
18
+ 'redirect_uri',
19
+ 'response_type',
20
+ 'scope',
21
+ 'state',
22
+ 'code',
23
+ 'nonce',
24
+ ];
25
+
26
+ // An OAuth implicit or OIDC fragment response puts its tokens in `#k=v&...`.
27
+ export const EXCLUDED_URL_FRAGMENT_PARAMS: string[] = EXCLUDED_URL_QUERY_PARAMS.concat(
28
+ [
29
+ 'access_token',
30
+ 'id_token',
31
+ 'refresh_token',
32
+ 'provider_token',
33
+ 'provider_refresh_token',
34
+ ]
35
+ );
36
+
37
+ // Allowlisted params whose value is itself a URL, so it can carry a query.
38
+ const URL_VALUED_QUERY_PARAMS: string[] = ['ref', 'referrer'];
39
+
40
+ const USERINFO = /^((?:[a-z][a-z0-9+.-]*:)?\/\/)[^/]*@/i;
41
+
42
+ const decodeQueryComponent = (component: string): string =>
43
+ decodeURIComponent(component.replace(/\+/g, ' '));
44
+
45
+ const cleanQueryPair = (pair: string): string | null => {
46
+ const separator = pair.indexOf('=');
47
+ const rawName = separator === -1 ? pair : pair.slice(0, separator);
48
+ let name: string;
49
+ try {
50
+ name = decodeQueryComponent(rawName);
51
+ } catch (e) {
52
+ return null;
53
+ }
54
+
55
+ const lowerName = name.toLowerCase();
56
+ if (
57
+ !name ||
58
+ !isAllowedQueryParam(name) ||
59
+ EXCLUDED_URL_QUERY_PARAMS.indexOf(lowerName) !== -1
60
+ ) {
61
+ return null;
62
+ }
63
+
64
+ if (separator === -1 || URL_VALUED_QUERY_PARAMS.indexOf(lowerName) === -1) {
65
+ return pair;
66
+ }
67
+
68
+ let value: string;
69
+ try {
70
+ value = decodeQueryComponent(pair.slice(separator + 1));
71
+ } catch (e) {
72
+ return null;
73
+ }
74
+ const cleanedValue = cleanUrl(value, UrlMode.Path);
75
+ return cleanedValue === value
76
+ ? pair
77
+ : `${rawName}=${encodeURIComponent(cleanedValue)}`;
78
+ };
79
+
80
+ const cleanQuery = (query: string): string =>
81
+ query
82
+ .split('&')
83
+ .map(cleanQueryPair)
84
+ .filter((pair): pair is string => pair !== null)
85
+ .join('&');
86
+
87
+ const keepsFragmentPair = (pair: string): boolean => {
88
+ try {
89
+ const name = decodeQueryComponent(pair.split('=')[0]);
90
+ return EXCLUDED_URL_FRAGMENT_PARAMS.indexOf(name.toLowerCase()) === -1;
91
+ } catch (e) {
92
+ return false;
93
+ }
94
+ };
95
+
96
+ // A hash route (`#/...`, `#!...`, or a path with no `=` before its `?`) keeps
97
+ // its path; a query inside it gets the same allowlist as the URL's own query.
98
+ // A fragment of bare pairs loses the OAuth/OIDC ones.
99
+ const cleanFragment = (hash: string): string => {
100
+ const queryStart = hash.indexOf('?');
101
+ const route = queryStart === -1 ? hash : hash.slice(0, queryStart);
102
+ const isRoute =
103
+ /^#[/!]/.test(hash) || (queryStart !== -1 && route.indexOf('=') === -1);
104
+
105
+ if (isRoute && queryStart !== -1) {
106
+ const query = cleanQuery(hash.slice(queryStart + 1));
107
+ return route + (query ? `?${query}` : '');
108
+ }
109
+
110
+ if (hash.indexOf('=') === -1) {
111
+ return hash;
112
+ }
113
+
114
+ const pairs = hash
115
+ .slice(1)
116
+ .split('&')
117
+ .filter(keepsFragmentPair)
118
+ .join('&');
119
+ return pairs ? `#${pairs}` : '';
120
+ };
121
+
122
+ // String-based rather than `new URL`, so kept pairs and the fragment go out
123
+ // exactly as the browser reported them and no environment can parse differently.
124
+ // An unknown mode is treated as `path`.
125
+ export const cleanUrl = (href: string, mode: UrlMode): string => {
126
+ if (typeof href !== 'string' || !href) {
127
+ return '';
128
+ }
129
+
130
+ const hashStart = href.indexOf('#');
131
+ const hash = hashStart === -1 ? '' : href.slice(hashStart);
132
+ const beforeHash = hashStart === -1 ? href : href.slice(0, hashStart);
133
+ const queryStart = beforeHash.indexOf('?');
134
+ const path = (queryStart === -1
135
+ ? beforeHash
136
+ : beforeHash.slice(0, queryStart)
137
+ ).replace(USERINFO, '$1');
138
+
139
+ if (mode !== UrlMode.Full) {
140
+ return path;
141
+ }
142
+
143
+ const query =
144
+ queryStart === -1 ? '' : cleanQuery(beforeHash.slice(queryStart + 1));
145
+
146
+ return path + (query ? `?${query}` : '') + cleanFragment(hash);
147
+ };
148
+
149
+ // The current page as it may appear on an event.
150
+ export const cleanPageHref = (): string => cleanUrl(getHref(), UrlMode.Full);
@@ -55,6 +55,12 @@ export const ALLOWED_QUERY_PARAMS: string[] = [
55
55
  'utm_term',
56
56
  'utm_content',
57
57
  'utm_id',
58
+ 'utm_source_platform',
59
+ 'utm_creative_format',
60
+ 'utm_marketing_tactic',
61
+ 'gad_source',
62
+ 'gad_campaignid',
63
+ 'mc_cid',
58
64
 
59
65
  // Ad-network click ids
60
66
  'gclid',
@@ -66,6 +72,12 @@ export const ALLOWED_QUERY_PARAMS: string[] = [
66
72
  'twclid',
67
73
  'li_fat_id',
68
74
  'dclid',
75
+ 'srsltid',
76
+ 'epik',
77
+ 'ScCid',
78
+ 'irclickid',
79
+ 'rtid',
80
+ 'rclid',
69
81
 
70
82
  // OAuth / OIDC — see the SECURITY note above
71
83
  'client_id',
@@ -120,6 +132,27 @@ export const ALLOWED_QUERY_PARAMS: string[] = [
120
132
  'referrer',
121
133
  ];
122
134
 
135
+ // Params matched by name prefix, for platforms that put a per-product id in the
136
+ // name. Salesforce Commerce Cloud spells a variant choice
137
+ // `dwvar_<productId>_<attribute>`, e.g. `dwvar_12345_color=navy`.
138
+ export const ALLOWED_QUERY_PARAM_PREFIXES: string[] = ['dwvar_'];
139
+
140
+ const hasAllowedPrefix = (name: string): boolean =>
141
+ ALLOWED_QUERY_PARAM_PREFIXES.some(prefix => name.indexOf(prefix) === 0);
142
+
143
+ const ALLOWED_QUERY_PARAMS_LOWER: string[] = ALLOWED_QUERY_PARAMS.map(name =>
144
+ name.toLowerCase()
145
+ );
146
+
147
+ // Whether a query param name, in any case, is one an auto page view captures.
148
+ export const isAllowedQueryParam = (name: string): boolean => {
149
+ const lower = name.toLowerCase();
150
+ return (
151
+ ALLOWED_QUERY_PARAMS_LOWER.indexOf(lower) !== -1 ||
152
+ hasAllowedPrefix(lower)
153
+ );
154
+ };
155
+
123
156
  interface IPageSnapshot {
124
157
  path: string;
125
158
  params: Dictionary<string>;
@@ -152,8 +185,18 @@ interface IPendingNavigation {
152
185
  // attribute), drops empty values, and carries the fallback for browsers without
153
186
  // URLSearchParams. Tolerates an empty href, so SSR yields no params rather than
154
187
  // throwing.
155
- export const allowedQueryParams = (href: string): Dictionary<string> =>
156
- queryStringParser(href, ALLOWED_QUERY_PARAMS);
188
+ export const allowedQueryParams = (href: string): Dictionary<string> => {
189
+ const params = queryStringParser(href, ALLOWED_QUERY_PARAMS);
190
+ const allParams = queryStringParser(href);
191
+
192
+ Object.keys(allParams)
193
+ .filter(name => hasAllowedPrefix(name) && allParams[name])
194
+ .forEach(name => {
195
+ params[name] = allParams[name];
196
+ });
197
+
198
+ return params;
199
+ };
157
200
 
158
201
  export const AUTO_PAGE_VIEW_ATTRIBUTE = 'is_auto_page_view';
159
202
 
@@ -165,10 +208,9 @@ export const autoPageViewAttribute = (
165
208
  ): Dictionary<boolean> =>
166
209
  isAutoPageView ? { [AUTO_PAGE_VIEW_ATTRIBUTE]: true } : {};
167
210
 
168
- // The captured params, in allowlist order. Ordering comes from the constant
169
- // rather than a sort: it is deterministic without needing a comparator, and it
170
- // does not depend on the object's insertion order, so reordering the query string
171
- // cannot produce a different key.
211
+ // The captured params, in allowlist order, then prefix-matched names sorted.
212
+ // Neither depends on the object's insertion order, so reordering the query
213
+ // string cannot produce a different key.
172
214
  //
173
215
  // Membership is an own-property check, not `in`. `in` walks the prototype
174
216
  // chain, so a name matching an Object.prototype member reports as present on
@@ -176,7 +218,11 @@ export const autoPageViewAttribute = (
176
218
  // only thing making `in` safe here — and it stops being a safe assumption the
177
219
  // moment this list can be extended from configuration.
178
220
  const capturedNames = (params: Dictionary<string>): string[] =>
179
- ALLOWED_QUERY_PARAMS.filter(name => hasOwnProp(params, name));
221
+ ALLOWED_QUERY_PARAMS.filter(name => hasOwnProp(params, name)).concat(
222
+ Object.keys(params)
223
+ .filter(hasAllowedPrefix)
224
+ .sort()
225
+ );
180
226
 
181
227
  // The dedup key: pathname plus the allowlisted params in a fixed order, so that
182
228
  // reordering the query string is not a new page. Params outside the allowlist
@@ -15,7 +15,7 @@ import {
15
15
  import { SDKIdentityApi } from "./identity.interfaces";
16
16
  import { SDKLoggerApi } from "./sdkRuntimeModels";
17
17
  import { IStore, LocalSessionAttributes } from "./store";
18
- import { UserIdentities } from "@mparticle/web-sdk";
18
+ import { MPID, UserIdentities } from "@mparticle/web-sdk";
19
19
  import { IdentityType, PerformanceMarkType } from "./types";
20
20
  import { ErrorCodes, IErrorReportingService, ILoggingService, WSDKErrorSeverity } from "./reporting/types";
21
21
  import { IRoktLauncherOptions, normalizeRoktLauncherOptions } from "./roktLauncherOptions";
@@ -136,6 +136,7 @@ export default class RoktManager {
136
136
  public kit: IRoktKit = null;
137
137
  public filters: RoktKitFilterSettings = {};
138
138
  private currentUser: IMParticleUser | null = null;
139
+ private createFilteredUser?: (mpid: MPID) => IMParticleUser;
139
140
  private messageQueue: Map<string, IRoktMessage> = new Map();
140
141
  private sandbox: boolean | null = null;
141
142
  private placementAttributesMapping: Dictionary<string>[] = [];
@@ -178,6 +179,7 @@ export default class RoktManager {
178
179
  * @param {Function} captureTiming - Function to capture performance timing marks
179
180
  * @param {IErrorReportingService} errorReporter - Dispatcher for error/warning reporting
180
181
  * @param {ILoggingService} loggingService - Dispatcher for informational logging
182
+ * @param {Function} createFilteredUser - Builds a user as the Rokt connection's filters and the data plan allow
181
183
  *
182
184
  * @throws Logs error to console if placementAttributesMapping parsing fails
183
185
  */
@@ -192,6 +194,7 @@ export default class RoktManager {
192
194
  errorReporter?: IErrorReportingService,
193
195
  loggingService?: ILoggingService,
194
196
  integrationCapture?: IntegrationCapture,
197
+ createFilteredUser?: (mpid: MPID) => IMParticleUser,
195
198
  ): void {
196
199
  const { userAttributeFilters, settings } = roktConfig || {};
197
200
  const { placementAttributesMapping, hashedEmailUserIdentityType } = settings || {};
@@ -204,6 +207,7 @@ export default class RoktManager {
204
207
  this.loggingService = loggingService;
205
208
  this.captureTiming = captureTiming;
206
209
  this.integrationCapture = integrationCapture;
210
+ this.createFilteredUser = createFilteredUser;
207
211
 
208
212
  this.captureTiming?.(PerformanceMarkType.JointSdkRoktKitInit);
209
213
 
@@ -504,11 +508,13 @@ export default class RoktManager {
504
508
  }
505
509
  }
506
510
 
507
- // Optimistic merge: apply the pending identity changes locally so
508
- // enrichedAttributes are correct without waiting for the identify round-trip.
509
- // The next call to selectPlacements re-fetches currentUser (see line above),
510
- // so stale identity state does not persist beyond this invocation.
511
- const finalUserIdentities = { ...currentUserIdentities, ...newIdentities };
511
+ // The identify above needs every identity, so currentUserIdentities stays unfiltered.
512
+ // Without createFilteredUser the connection's filters cannot be applied, so no identity is added.
513
+ const roktUser =
514
+ this.currentUser && this.createFilteredUser
515
+ ? this.createFilteredUser(this.currentUser.getMPID())
516
+ : null;
517
+ const roktUserIdentities = roktUser?.getUserIdentities()?.userIdentities || {};
512
518
 
513
519
  const timeOnSite = this.store?.getTimeOnSite?.();
514
520
  const totalTimeOnSite = this.store?.getTotalTimeOnSite?.();
@@ -521,19 +527,25 @@ export default class RoktManager {
521
527
  };
522
528
 
523
529
  // Propagate email from current user identities if not already in attributes
524
- if (finalUserIdentities.email && !enrichedAttributes.email) {
525
- enrichedAttributes.email = finalUserIdentities.email;
530
+ if (!enrichedAttributes.email) {
531
+ if (roktUserIdentities.email) {
532
+ enrichedAttributes.email = roktUserIdentities.email;
533
+ } else if (currentUserIdentities.email) {
534
+ this.logFilteredIdentity('email');
535
+ }
526
536
  }
527
537
 
528
538
  // Propagate emailsha256 from current user identities if not already in attributes
529
- if (isValidHashedEmailIdentityType) {
530
- const hashedEmail = finalUserIdentities[this.mappedEmailShaIdentityType];
531
- if (
532
- hashedEmail &&
533
- !enrichedAttributes.emailsha256 &&
534
- !enrichedAttributes[this.mappedEmailShaIdentityType]
535
- ) {
539
+ if (
540
+ isValidHashedEmailIdentityType &&
541
+ !enrichedAttributes.emailsha256 &&
542
+ !enrichedAttributes[this.mappedEmailShaIdentityType]
543
+ ) {
544
+ const hashedEmail = roktUserIdentities[this.mappedEmailShaIdentityType];
545
+ if (hashedEmail) {
536
546
  enrichedAttributes.emailsha256 = hashedEmail;
547
+ } else if (currentUserIdentities[this.mappedEmailShaIdentityType]) {
548
+ this.logFilteredIdentity(this.mappedEmailShaIdentityType);
537
549
  }
538
550
  }
539
551
 
@@ -546,7 +558,7 @@ export default class RoktManager {
546
558
  }
547
559
  }
548
560
 
549
- this.filters.filteredUser = this.currentUser || this.filters.filteredUser || null;
561
+ this.filters.filteredUser = roktUser || this.filters.filteredUser || null;
550
562
 
551
563
  const enrichedOptions = {
552
564
  ...options,
@@ -559,6 +571,12 @@ export default class RoktManager {
559
571
  }
560
572
  }
561
573
 
574
+ private logFilteredIdentity(identityType: string): void {
575
+ this.logger?.verbose(
576
+ `mParticle.Rokt selectPlacements did not add the ${identityType} user identity to the attributes because the Rokt connection does not allow it`
577
+ );
578
+ }
579
+
562
580
  /**
563
581
  * Hashes attributes and returns both original and hashed versions
564
582
  * with Rokt-compatible key names (like emailsha256, mobilesha256)
@@ -53,6 +53,7 @@ import {
53
53
  import Constants from './constants';
54
54
  import RoktManager, { IRoktLauncherOptions } from './roktManager';
55
55
  import { IConsoleLogger } from './logger';
56
+ import { UrlMode } from './pageUrl';
56
57
  import { ErrorCodes, IErrorReportingService, ILoggingService } from './reporting/types';
57
58
 
58
59
  // TODO: Resolve this with version in @mparticle/web-sdk
@@ -282,6 +283,7 @@ export interface IMParticleInstanceManager extends MParticleWebSDK {
282
283
  _instances: Dictionary<IMParticleWebSDKInstance>;
283
284
  _isTestEnv?: boolean;
284
285
  _forceNoLocalStorage?: boolean;
286
+ _cleanUrl(href: string, mode: UrlMode): string;
285
287
 
286
288
  // Public Properties
287
289
  config: SDKInitConfig;
@@ -17,8 +17,8 @@ import {
17
17
  Dictionary,
18
18
  isValidCustomFlagProperty,
19
19
  extend,
20
- getHref,
21
20
  } from './utils';
21
+ import { cleanPageHref } from './pageUrl';
22
22
  import { IntegrationAttributes, ServerSettings } from './store';
23
23
  import { MPID } from '@mparticle/web-sdk';
24
24
  import {
@@ -310,7 +310,7 @@ export default function ServerModel(
310
310
  ),
311
311
  ActiveTimeOnSite: mpInstance._timeOnSiteTimer?.getTimeInForeground(),
312
312
  TotalTimeOnSite: mpInstance._Store.getTotalTimeOnSite?.(),
313
- PageUrl: getHref() || null,
313
+ PageUrl: cleanPageHref() || null,
314
314
  SourceMessageId:
315
315
  event.sourceMessageId ||
316
316
  mpInstance._Helpers.generateUniqueId(),
@@ -352,7 +352,7 @@ export default function ServerModel(
352
352
 
353
353
  if (eventObject.EventDataType === MessageType.AppStateTransition) {
354
354
  eventObject.IsFirstRun = mpInstance._Store.isFirstRun;
355
- eventObject.LaunchReferral = window.location.href || null;
355
+ eventObject.LaunchReferral = cleanPageHref() || null;
356
356
  }
357
357
 
358
358
  // FIXME: Remove duplicate occurence
package/src/utils.ts CHANGED
@@ -385,7 +385,7 @@ interface URLSearchParamsFallback {
385
385
 
386
386
  const queryStringParserFallback = (url: string): URLSearchParamsFallback => {
387
387
  const params: Dictionary<string> = {};
388
- const queryString = url.split('?')[1] || '';
388
+ const queryString = url.split('#')[0].split('?')[1] || '';
389
389
  const pairs = queryString.split('&');
390
390
 
391
391
  pairs.forEach(pair => {