@mparticle/web-sdk 2.51.0 → 2.53.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/README.md +1 -1
- package/dist/mparticle.common.js +51 -22
- package/dist/mparticle.esm.js +51 -22
- package/dist/mparticle.js +338 -114
- package/package.json +1 -1
- package/src/batchUploader.ts +3 -3
- package/src/cookieConsentManager.ts +64 -0
- package/src/cookieSyncManager.ts +22 -2
- package/src/identity.interfaces.ts +5 -2
- package/src/identity.js +3 -1
- package/src/identityApiClient.ts +11 -12
- package/src/logger.ts +66 -0
- package/src/mp-instance.ts +8 -1
- package/src/mparticle-instance-manager.ts +2 -2
- package/src/roktManager.ts +97 -7
- package/src/sdkRuntimeModels.ts +9 -1
- package/src/sessionManager.ts +45 -40
- package/src/store.ts +1 -0
- package/src/stub/mparticle.stub.js +1 -0
- package/src/utils.ts +13 -3
- package/src/logger.js +0 -61
package/package.json
CHANGED
package/src/batchUploader.ts
CHANGED
|
@@ -257,15 +257,15 @@ export class BatchUploader {
|
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
const {
|
|
260
|
+
const { Logger } = this.mpInstance;
|
|
261
261
|
|
|
262
262
|
this.eventsQueuedForProcessing.push(event);
|
|
263
263
|
if (this.offlineStorageEnabled && this.eventVault) {
|
|
264
264
|
this.eventVault.store(this.eventsQueuedForProcessing);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
verbose(`Queuing event: ${JSON.stringify(event)}`);
|
|
268
|
-
verbose(`Queued event count: ${this.eventsQueuedForProcessing.length}`);
|
|
267
|
+
Logger.verbose(`Queuing event: ${JSON.stringify(event)}`);
|
|
268
|
+
Logger.verbose(`Queued event count: ${this.eventsQueuedForProcessing.length}`);
|
|
269
269
|
|
|
270
270
|
if (this.shouldTriggerImmediateUpload(event.EventDataType)) {
|
|
271
271
|
this.prepareAndUpload(false, false);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie consent flags control SDK behavior based on user consent preferences for Rokt integration.
|
|
3
|
+
* @see https://docs.rokt.com/developers/integration-guides/web/cookie-consent-flags/
|
|
4
|
+
*/
|
|
5
|
+
export interface ICookieConsentFlags {
|
|
6
|
+
/**
|
|
7
|
+
* When true, indicates the user has opted out of functional cookies/tracking.
|
|
8
|
+
* Default: false (functional tracking is allowed)
|
|
9
|
+
*/
|
|
10
|
+
noFunctional: boolean;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* When true, indicates the user has opted out of targeting cookies/tracking.
|
|
14
|
+
* This flag is used to block cookie syncs and other targeting-related features.
|
|
15
|
+
* Default: false (targeting is allowed)
|
|
16
|
+
*/
|
|
17
|
+
noTargeting: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ICookieConsentManager {
|
|
21
|
+
/**
|
|
22
|
+
* Targeting is allowed when noTargeting is false (default)
|
|
23
|
+
*/
|
|
24
|
+
getNoTargeting: () => boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Functional tracking is allowed when noFunctional is false (default)
|
|
28
|
+
*/
|
|
29
|
+
getNoFunctional: () => boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* CookieConsentManager handles storage and access of consent flags (noFunctional, noTargeting)
|
|
34
|
+
* that are passed via launcherOptions during SDK initialization.
|
|
35
|
+
*
|
|
36
|
+
* These flags allow Rokt integration to respect user privacy choices.
|
|
37
|
+
*
|
|
38
|
+
* Default behavior: Both flags default to false, meaning all features are allowed
|
|
39
|
+
* unless explicitly opted out by the user.
|
|
40
|
+
*/
|
|
41
|
+
export default class CookieConsentManager implements ICookieConsentManager {
|
|
42
|
+
constructor(private flags: ICookieConsentFlags) {
|
|
43
|
+
this.flags = {
|
|
44
|
+
noFunctional: flags.noFunctional === true,
|
|
45
|
+
noTargeting: flags.noTargeting === true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns true if third-party targeting is disabled.
|
|
51
|
+
* Targeting is allowed when noTargeting is false (default).
|
|
52
|
+
*/
|
|
53
|
+
getNoTargeting(): boolean {
|
|
54
|
+
return this.flags.noTargeting;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns true if functional tracking is disabled.
|
|
59
|
+
* Functional tracking is allowed when noFunctional is false (default).
|
|
60
|
+
*/
|
|
61
|
+
getNoFunctional(): boolean {
|
|
62
|
+
return this.flags.noFunctional;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/cookieSyncManager.ts
CHANGED
|
@@ -13,6 +13,17 @@ const { InformationMessages } = Messages;
|
|
|
13
13
|
|
|
14
14
|
export const DAYS_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
|
|
15
15
|
|
|
16
|
+
// Partner module IDs for cookie sync configurations
|
|
17
|
+
export const PARTNER_MODULE_IDS = {
|
|
18
|
+
AdobeEventForwarder: 11,
|
|
19
|
+
DoubleclickDFP: 41,
|
|
20
|
+
AppNexus: 50,
|
|
21
|
+
Lotame: 58,
|
|
22
|
+
TradeDesk: 103,
|
|
23
|
+
VerizonMedia: 155,
|
|
24
|
+
Rokt: 1277,
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
16
27
|
export type CookieSyncDates = Dictionary<number>;
|
|
17
28
|
|
|
18
29
|
export interface IPixelConfiguration {
|
|
@@ -98,6 +109,11 @@ export default function CookieSyncManager(
|
|
|
98
109
|
return;
|
|
99
110
|
}
|
|
100
111
|
|
|
112
|
+
// For Rokt, block cookie sync when noTargeting privacy flag is true
|
|
113
|
+
if (moduleId === PARTNER_MODULE_IDS.Rokt && mpInstance._CookieConsentManager.getNoTargeting()) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
101
117
|
const { isEnabledForUserConsent } = mpInstance._Consent;
|
|
102
118
|
|
|
103
119
|
if (!isEnabledForUserConsent(filteringConsentRuleValues, mpInstance.Identity.getCurrentUser())) {
|
|
@@ -111,8 +127,12 @@ export default function CookieSyncManager(
|
|
|
111
127
|
return;
|
|
112
128
|
}
|
|
113
129
|
|
|
114
|
-
//
|
|
115
|
-
|
|
130
|
+
// The Trade Desk requires a URL parameter for GDPR enabled users.
|
|
131
|
+
// It is optional but to simplify the code, we add it for all Trade
|
|
132
|
+
// // Desk cookie syncs.
|
|
133
|
+
const domain = moduleId === PARTNER_MODULE_IDS.TradeDesk ? window.location.hostname : undefined;
|
|
134
|
+
// Add domain parameter for Trade Desk
|
|
135
|
+
const fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain);
|
|
116
136
|
|
|
117
137
|
self.performCookieSync(
|
|
118
138
|
fullUrl,
|
|
@@ -107,12 +107,14 @@ export interface IIdentityRequest {
|
|
|
107
107
|
): IdentityPreProcessResult;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
export type AliasRequestScope = 'device' | 'mpid';
|
|
111
|
+
|
|
110
112
|
export interface IAliasRequest {
|
|
111
113
|
destinationMpid: MPID;
|
|
112
114
|
sourceMpid: MPID;
|
|
113
115
|
startTime: number;
|
|
114
116
|
endTime: number;
|
|
115
|
-
scope?:
|
|
117
|
+
scope?: AliasRequestScope;
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
export interface IAliasCallback {
|
|
@@ -151,7 +153,8 @@ export interface SDKIdentityApi {
|
|
|
151
153
|
): void;
|
|
152
154
|
createAliasRequest?(
|
|
153
155
|
sourceUser: IMParticleUser,
|
|
154
|
-
destinationUser: IMParticleUser
|
|
156
|
+
destinationUser: IMParticleUser,
|
|
157
|
+
scope?: AliasRequestScope
|
|
155
158
|
): IAliasRequest;
|
|
156
159
|
}
|
|
157
160
|
|
package/src/identity.js
CHANGED
|
@@ -195,6 +195,7 @@ export default function Identity(mpInstance) {
|
|
|
195
195
|
SourceMpid: aliasRequest.sourceMpid,
|
|
196
196
|
StartUnixtimeMs: aliasRequest.startTime,
|
|
197
197
|
EndUnixtimeMs: aliasRequest.endTime,
|
|
198
|
+
Scope: aliasRequest.scope,
|
|
198
199
|
};
|
|
199
200
|
},
|
|
200
201
|
|
|
@@ -744,7 +745,7 @@ export default function Identity(mpInstance) {
|
|
|
744
745
|
after applying this adjustment it will be impossible to create an aliasRequest passes the aliasUsers()
|
|
745
746
|
validation that the startTime must be less than the endTime
|
|
746
747
|
*/
|
|
747
|
-
createAliasRequest: function(sourceUser, destinationUser) {
|
|
748
|
+
createAliasRequest: function(sourceUser, destinationUser, scope) {
|
|
748
749
|
try {
|
|
749
750
|
if (!destinationUser || !sourceUser) {
|
|
750
751
|
mpInstance.Logger.error(
|
|
@@ -788,6 +789,7 @@ export default function Identity(mpInstance) {
|
|
|
788
789
|
sourceMpid: sourceUser.getMPID(),
|
|
789
790
|
startTime: startTime,
|
|
790
791
|
endTime: endTime,
|
|
792
|
+
scope: scope || 'device',
|
|
791
793
|
};
|
|
792
794
|
} catch (e) {
|
|
793
795
|
mpInstance.Logger.error(
|
package/src/identityApiClient.ts
CHANGED
|
@@ -88,12 +88,12 @@ export default function IdentityAPIClient(
|
|
|
88
88
|
aliasRequest: IAliasRequest,
|
|
89
89
|
aliasCallback: IAliasCallback
|
|
90
90
|
) {
|
|
91
|
-
const {
|
|
91
|
+
const { Logger } = mpInstance;
|
|
92
92
|
const { invokeAliasCallback } = mpInstance._Helpers;
|
|
93
93
|
const { aliasUrl } = mpInstance._Store.SDKConfig;
|
|
94
94
|
const { devToken: apiKey } = mpInstance._Store;
|
|
95
95
|
|
|
96
|
-
verbose(Messages.InformationMessages.SendAliasHttp);
|
|
96
|
+
Logger.verbose(Messages.InformationMessages.SendAliasHttp);
|
|
97
97
|
|
|
98
98
|
// https://go.mparticle.com/work/SQDSDKS-6750
|
|
99
99
|
const uploadUrl = `https://${aliasUrl}${apiKey}/Alias`;
|
|
@@ -136,7 +136,7 @@ export default function IdentityAPIClient(
|
|
|
136
136
|
try {
|
|
137
137
|
aliasResponseBody = await response.json();
|
|
138
138
|
} catch (e) {
|
|
139
|
-
verbose('The request has no response body');
|
|
139
|
+
Logger.verbose('The request has no response body');
|
|
140
140
|
}
|
|
141
141
|
} else {
|
|
142
142
|
// https://go.mparticle.com/work/SQDSDKS-6568
|
|
@@ -171,11 +171,11 @@ export default function IdentityAPIClient(
|
|
|
171
171
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
verbose(message);
|
|
174
|
+
Logger.verbose(message);
|
|
175
175
|
invokeAliasCallback(aliasCallback, response.status, errorMessage);
|
|
176
176
|
} catch (e) {
|
|
177
177
|
const errorMessage = (e as Error).message || e.toString();
|
|
178
|
-
error('Error sending alias request to mParticle servers. ' + errorMessage);
|
|
178
|
+
Logger.error('Error sending alias request to mParticle servers. ' + errorMessage);
|
|
179
179
|
invokeAliasCallback(
|
|
180
180
|
aliasCallback,
|
|
181
181
|
HTTPCodes.noHttpCoverage,
|
|
@@ -193,15 +193,14 @@ export default function IdentityAPIClient(
|
|
|
193
193
|
mpid: MPID,
|
|
194
194
|
knownIdentities: UserIdentities
|
|
195
195
|
) {
|
|
196
|
-
const { verbose, error } = mpInstance.Logger;
|
|
197
196
|
const { invokeCallback } = mpInstance._Helpers;
|
|
198
|
-
|
|
199
|
-
verbose(Messages.InformationMessages.SendIdentityBegin);
|
|
197
|
+
const { Logger } = mpInstance;
|
|
198
|
+
Logger.verbose(Messages.InformationMessages.SendIdentityBegin);
|
|
200
199
|
if (!identityApiRequest) {
|
|
201
|
-
error(Messages.ErrorMessages.APIRequestEmpty);
|
|
200
|
+
Logger.error(Messages.ErrorMessages.APIRequestEmpty);
|
|
202
201
|
return;
|
|
203
202
|
}
|
|
204
|
-
verbose(Messages.InformationMessages.SendIdentityHttp);
|
|
203
|
+
Logger.verbose(Messages.InformationMessages.SendIdentityHttp);
|
|
205
204
|
|
|
206
205
|
if (mpInstance._Store.identityCallInFlight) {
|
|
207
206
|
invokeCallback(
|
|
@@ -289,7 +288,7 @@ export default function IdentityAPIClient(
|
|
|
289
288
|
|
|
290
289
|
mpInstance._Store.identityCallInFlight = false;
|
|
291
290
|
|
|
292
|
-
verbose(message);
|
|
291
|
+
Logger.verbose(message);
|
|
293
292
|
parseIdentityResponse(
|
|
294
293
|
identityResponse,
|
|
295
294
|
previousMPID,
|
|
@@ -304,7 +303,7 @@ export default function IdentityAPIClient(
|
|
|
304
303
|
|
|
305
304
|
const errorMessage = (err as Error).message || err.toString();
|
|
306
305
|
|
|
307
|
-
error('Error sending identity request to servers' + ' - ' + errorMessage);
|
|
306
|
+
Logger.error('Error sending identity request to servers' + ' - ' + errorMessage);
|
|
308
307
|
invokeCallback(
|
|
309
308
|
callback,
|
|
310
309
|
HTTPCodes.noHttpCoverage,
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { LogLevelType, SDKInitConfig, SDKLoggerApi } from './sdkRuntimeModels';
|
|
2
|
+
|
|
3
|
+
export type ILoggerConfig = Pick<SDKInitConfig, 'logLevel' | 'logger'>;
|
|
4
|
+
export type IConsoleLogger = Partial<Pick<SDKLoggerApi, 'error' | 'warning' | 'verbose'>>;
|
|
5
|
+
|
|
6
|
+
export class Logger {
|
|
7
|
+
private logLevel: LogLevelType;
|
|
8
|
+
private logger: IConsoleLogger;
|
|
9
|
+
|
|
10
|
+
constructor(config: ILoggerConfig) {
|
|
11
|
+
this.logLevel = config.logLevel ?? LogLevelType.Warning;
|
|
12
|
+
this.logger = config.logger ?? new ConsoleLogger();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public verbose(msg: string): void {
|
|
16
|
+
if(this.logLevel === LogLevelType.None)
|
|
17
|
+
return;
|
|
18
|
+
|
|
19
|
+
if (this.logger.verbose && this.logLevel === LogLevelType.Verbose) {
|
|
20
|
+
this.logger.verbose(msg);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public warning(msg: string): void {
|
|
25
|
+
if(this.logLevel === LogLevelType.None)
|
|
26
|
+
return;
|
|
27
|
+
|
|
28
|
+
if (this.logger.warning &&
|
|
29
|
+
(this.logLevel === LogLevelType.Verbose || this.logLevel === LogLevelType.Warning)) {
|
|
30
|
+
this.logger.warning(msg);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public error(msg: string): void {
|
|
35
|
+
if(this.logLevel === LogLevelType.None)
|
|
36
|
+
return;
|
|
37
|
+
|
|
38
|
+
if (this.logger.error) {
|
|
39
|
+
this.logger.error(msg);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public setLogLevel(newLogLevel: LogLevelType): void {
|
|
44
|
+
this.logLevel = newLogLevel;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class ConsoleLogger implements IConsoleLogger {
|
|
49
|
+
public verbose(msg: string): void {
|
|
50
|
+
if (console && console.info) {
|
|
51
|
+
console.info(msg);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public error(msg: string): void {
|
|
56
|
+
if (console && console.error) {
|
|
57
|
+
console.error(msg);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public warning(msg: string): void {
|
|
62
|
+
if (console && console.warn) {
|
|
63
|
+
console.warn(msg);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/mp-instance.ts
CHANGED
|
@@ -25,7 +25,7 @@ import CookieSyncManager, { ICookieSyncManager } from './cookieSyncManager';
|
|
|
25
25
|
import SessionManager, { ISessionManager } from './sessionManager';
|
|
26
26
|
import Ecommerce from './ecommerce';
|
|
27
27
|
import Store, { IStore } from './store';
|
|
28
|
-
import Logger from './logger';
|
|
28
|
+
import { Logger } from './logger';
|
|
29
29
|
import Persistence from './persistence';
|
|
30
30
|
import Events from './events';
|
|
31
31
|
import Forwarders from './forwarders';
|
|
@@ -51,6 +51,7 @@ import { IPersistence } from './persistence.interfaces';
|
|
|
51
51
|
import ForegroundTimer from './foregroundTimeTracker';
|
|
52
52
|
import RoktManager, { IRoktOptions } from './roktManager';
|
|
53
53
|
import filteredMparticleUser from './filteredMparticleUser';
|
|
54
|
+
import CookieConsentManager, { ICookieConsentManager } from './cookieConsentManager';
|
|
54
55
|
|
|
55
56
|
export interface IErrorLogMessage {
|
|
56
57
|
message?: string;
|
|
@@ -82,6 +83,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
|
|
|
82
83
|
_IntegrationCapture: IntegrationCapture;
|
|
83
84
|
_NativeSdkHelpers: INativeSdkHelpers;
|
|
84
85
|
_Persistence: IPersistence;
|
|
86
|
+
_CookieConsentManager: ICookieConsentManager;
|
|
85
87
|
_RoktManager: RoktManager;
|
|
86
88
|
_SessionManager: ISessionManager;
|
|
87
89
|
_ServerModel: IServerModel;
|
|
@@ -227,6 +229,7 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
|
|
|
227
229
|
if (instance._Store) {
|
|
228
230
|
delete instance._Store;
|
|
229
231
|
}
|
|
232
|
+
instance.Logger = new Logger(config);
|
|
230
233
|
instance._Store = new Store(config, instance);
|
|
231
234
|
instance._Store.isLocalStorageAvailable = instance._Persistence.determineLocalStorageAvailability(
|
|
232
235
|
window.localStorage
|
|
@@ -1554,6 +1557,10 @@ function runPreConfigFetchInitialization(mpInstance, apiKey, config) {
|
|
|
1554
1557
|
window.mParticle.Store = mpInstance._Store;
|
|
1555
1558
|
mpInstance.Logger.verbose(StartingInitialization);
|
|
1556
1559
|
|
|
1560
|
+
// Initialize CookieConsentManager with privacy flags from launcherOptions
|
|
1561
|
+
const { noFunctional, noTargeting } = config?.launcherOptions ?? {};
|
|
1562
|
+
mpInstance._CookieConsentManager = new CookieConsentManager({ noFunctional, noTargeting });
|
|
1563
|
+
|
|
1557
1564
|
// Check to see if localStorage is available before main configuration runs
|
|
1558
1565
|
// since we will need this for the current implementation of user persistence
|
|
1559
1566
|
// TODO: Refactor this when we refactor User Identity Persistence
|
|
@@ -368,10 +368,10 @@ function mParticleInstanceManager(this: IMParticleInstanceManager) {
|
|
|
368
368
|
aliasUsers: function(aliasRequest, callback) {
|
|
369
369
|
self.getInstance().Identity.aliasUsers(aliasRequest, callback);
|
|
370
370
|
},
|
|
371
|
-
createAliasRequest: function(sourceUser, destinationUser) {
|
|
371
|
+
createAliasRequest: function(sourceUser, destinationUser, scope) {
|
|
372
372
|
return self
|
|
373
373
|
.getInstance()
|
|
374
|
-
.Identity.createAliasRequest(sourceUser, destinationUser);
|
|
374
|
+
.Identity.createAliasRequest(sourceUser, destinationUser, scope);
|
|
375
375
|
},
|
|
376
376
|
getCurrentUser: function() {
|
|
377
377
|
return self.getInstance().Identity.getCurrentUser();
|
package/src/roktManager.ts
CHANGED
|
@@ -252,15 +252,51 @@ export default class RoktManager {
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Hashes attributes and returns both original and hashed versions
|
|
257
|
+
* with Rokt-compatible key names (like emailsha256, mobilesha256)
|
|
258
|
+
*
|
|
259
|
+
*
|
|
260
|
+
* @param {IRoktPartnerAttributes} attributes - Attributes to hash
|
|
261
|
+
* @returns {Promise<IRoktPartnerAttributes>} Object with both original and hashed attributes
|
|
262
|
+
*
|
|
263
|
+
*/
|
|
264
|
+
public async hashAttributes(attributes: IRoktPartnerAttributes): Promise<IRoktPartnerAttributes> {
|
|
260
265
|
try {
|
|
261
|
-
|
|
266
|
+
if (!attributes || typeof attributes !== 'object') {
|
|
267
|
+
return {};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Get own property keys only
|
|
271
|
+
const keys = Object.keys(attributes);
|
|
272
|
+
|
|
273
|
+
if (keys.length === 0) {
|
|
274
|
+
return {};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Hash all attributes in parallel
|
|
278
|
+
const hashPromises = keys.map(async (key) => {
|
|
279
|
+
const attributeValue = attributes[key];
|
|
280
|
+
const hashedValue = await this.hashSha256(attributeValue);
|
|
281
|
+
return { key, attributeValue, hashedValue };
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const results = await Promise.all(hashPromises);
|
|
285
|
+
|
|
286
|
+
// Build the result object
|
|
287
|
+
const hashedAttributes: IRoktPartnerAttributes = {};
|
|
288
|
+
for (const { key, attributeValue, hashedValue } of results) {
|
|
289
|
+
hashedAttributes[key] = attributeValue;
|
|
290
|
+
if (hashedValue) {
|
|
291
|
+
hashedAttributes[`${key}sha256`] = hashedValue;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return hashedAttributes;
|
|
295
|
+
|
|
262
296
|
} catch (error) {
|
|
263
|
-
|
|
297
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
298
|
+
this.logger.error(`Failed to hashAttributes, returning an empty object: ${errorMessage}`);
|
|
299
|
+
return {};
|
|
264
300
|
}
|
|
265
301
|
}
|
|
266
302
|
|
|
@@ -290,6 +326,29 @@ export default class RoktManager {
|
|
|
290
326
|
}
|
|
291
327
|
}
|
|
292
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Hashes an attribute using SHA-256
|
|
331
|
+
*
|
|
332
|
+
* @param {string | number | boolean | undefined | null} attribute - The value to hash
|
|
333
|
+
* @returns {Promise<string | undefined | null>} SHA-256 hashed value or undefined/null
|
|
334
|
+
*
|
|
335
|
+
*/
|
|
336
|
+
public async hashSha256(attribute: string | number | boolean | undefined | null): Promise<string | undefined | null> {
|
|
337
|
+
if (attribute === null || attribute === undefined) {
|
|
338
|
+
this.logger.warning(`hashSha256 received null/undefined as input`);
|
|
339
|
+
return attribute as null | undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const normalizedValue = String(attribute).trim().toLocaleLowerCase();
|
|
344
|
+
return await this.sha256Hex(normalizedValue);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
347
|
+
this.logger.error(`Failed to hashSha256, returning undefined: ${errorMessage}`);
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
293
352
|
public getLocalSessionAttributes(): LocalSessionAttributes {
|
|
294
353
|
return this.store.getLocalSessionAttributes();
|
|
295
354
|
}
|
|
@@ -400,6 +459,37 @@ export default class RoktManager {
|
|
|
400
459
|
this.messageQueue.delete(messageId);
|
|
401
460
|
}
|
|
402
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Hashes a string input using SHA-256 and returns the hex digest
|
|
464
|
+
* Uses the Web Crypto API for secure hashing
|
|
465
|
+
*
|
|
466
|
+
* @param {string} input - The string to hash
|
|
467
|
+
* @returns {Promise<string>} The SHA-256 hash as a hexadecimal string
|
|
468
|
+
*/
|
|
469
|
+
private async sha256Hex(input: string): Promise<string> {
|
|
470
|
+
const encoder = new TextEncoder();
|
|
471
|
+
const encodedInput = encoder.encode(input);
|
|
472
|
+
const digest = await crypto.subtle.digest('SHA-256', encodedInput);
|
|
473
|
+
return this.arrayBufferToHex(digest);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Converts an ArrayBuffer to a hexadecimal string representation
|
|
478
|
+
* Each byte is converted to a 2-character hex string with leading zeros
|
|
479
|
+
*
|
|
480
|
+
* @param {ArrayBuffer} buffer - The buffer to convert
|
|
481
|
+
* @returns {string} The hexadecimal string representation
|
|
482
|
+
*/
|
|
483
|
+
private arrayBufferToHex(buffer: ArrayBuffer): string {
|
|
484
|
+
const bytes = new Uint8Array(buffer);
|
|
485
|
+
let hexString = '';
|
|
486
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
487
|
+
const hexByte = bytes[i].toString(16).padStart(2, '0');
|
|
488
|
+
hexString += hexByte;
|
|
489
|
+
}
|
|
490
|
+
return hexString;
|
|
491
|
+
}
|
|
492
|
+
|
|
403
493
|
/**
|
|
404
494
|
* Checks if an identity value has changed by comparing current and new values
|
|
405
495
|
*
|
package/src/sdkRuntimeModels.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { SDKECommerceAPI } from './ecommerce.interfaces';
|
|
|
40
40
|
import { IErrorLogMessage, IMParticleWebSDKInstance, IntegrationDelays } from './mp-instance';
|
|
41
41
|
import Constants from './constants';
|
|
42
42
|
import RoktManager, { IRoktLauncherOptions } from './roktManager';
|
|
43
|
+
import { IConsoleLogger } from './logger';
|
|
43
44
|
|
|
44
45
|
// TODO: Resolve this with version in @mparticle/web-sdk
|
|
45
46
|
export type SDKEventCustomFlags = Dictionary<any>;
|
|
@@ -259,7 +260,13 @@ export interface IMParticleInstanceManager extends MParticleWebSDK {
|
|
|
259
260
|
export type BooleanStringLowerCase = 'false' | 'true';
|
|
260
261
|
export type BooleanStringTitleCase = 'False' | 'True';
|
|
261
262
|
|
|
262
|
-
export type LogLevelType =
|
|
263
|
+
export type LogLevelType = (typeof LogLevelType)[keyof typeof LogLevelType];
|
|
264
|
+
export const LogLevelType = {
|
|
265
|
+
None: 'none',
|
|
266
|
+
Verbose: 'verbose',
|
|
267
|
+
Warning: 'warning',
|
|
268
|
+
Error: 'error',
|
|
269
|
+
} as const;
|
|
263
270
|
|
|
264
271
|
// TODO: This should eventually be moved into wherever init logic lives
|
|
265
272
|
// TODO: Replace/Merge this with MPConfiguration in @types/mparticle__web-sdk
|
|
@@ -306,6 +313,7 @@ export interface SDKInitConfig
|
|
|
306
313
|
launcherOptions?: IRoktLauncherOptions;
|
|
307
314
|
|
|
308
315
|
rq?: Function[] | any[];
|
|
316
|
+
logger?: IConsoleLogger;
|
|
309
317
|
}
|
|
310
318
|
|
|
311
319
|
export interface DataPlanConfig {
|