@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.
- package/dist/mparticle.common.js +152 -133
- package/dist/mparticle.esm.js +152 -133
- package/dist/mparticle.js +4220 -4086
- package/dist/types/src/pageUrl.d.ts +10 -0
- package/dist/types/src/pageViewTracker.d.ts +2 -0
- package/dist/types/src/roktManager.d.ts +5 -1
- package/dist/types/src/sdkRuntimeModels.d.ts +2 -0
- package/package.json +1 -1
- package/src/batchUploader.ts +3 -2
- package/src/mp-instance.ts +5 -8
- package/src/mparticle-instance-manager.ts +3 -0
- package/src/pageUrl.ts +150 -0
- package/src/pageViewTracker.ts +53 -7
- package/src/roktManager.ts +34 -16
- package/src/sdkRuntimeModels.ts +2 -0
- package/src/serverModel.ts +3 -3
- package/src/utils.ts +1 -1
|
@@ -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
package/src/batchUploader.ts
CHANGED
|
@@ -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,
|
|
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:
|
|
208
|
+
PageUrl: cleanPageHref() || null,
|
|
208
209
|
IsBackgroundAST: true
|
|
209
210
|
} as SDKEvent;
|
|
210
211
|
|
package/src/mp-instance.ts
CHANGED
|
@@ -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
|
|
1428
|
-
|
|
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
|
-
|
|
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);
|
package/src/pageViewTracker.ts
CHANGED
|
@@ -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
|
|
169
|
-
//
|
|
170
|
-
//
|
|
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
|
package/src/roktManager.ts
CHANGED
|
@@ -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
|
-
//
|
|
508
|
-
//
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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 (
|
|
525
|
-
|
|
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 (
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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 =
|
|
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)
|
package/src/sdkRuntimeModels.ts
CHANGED
|
@@ -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;
|
package/src/serverModel.ts
CHANGED
|
@@ -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:
|
|
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 =
|
|
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 => {
|