@mparticle/web-sdk 2.28.3 → 2.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mparticle.common.js +39 -23
- package/dist/mparticle.esm.js +39 -23
- package/dist/mparticle.js +333 -69
- package/package.json +1 -1
- package/src/aliasRequestApiClient.ts +1 -1
- package/src/constants.ts +1 -0
- package/src/identity-utils.ts +1 -28
- package/src/identity.interfaces.ts +1 -1
- package/src/identity.js +1 -5
- package/src/identityApiClient.js +100 -82
- package/src/integrationCapture.ts +153 -0
- package/src/mp-instance.js +9 -3
- package/src/sdkRuntimeModels.ts +2 -0
- package/src/sdkToEventsApiConverter.ts +2 -2
- package/src/serverModel.ts +14 -1
- package/src/store.ts +2 -0
- package/src/uploaders.ts +11 -3
- package/src/utils.ts +105 -1
package/src/identityApiClient.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import Constants from './constants';
|
|
2
|
-
import { xhrIdentityResponseAdapter } from './identity-utils';
|
|
3
2
|
import { sendAliasRequest } from './aliasRequestApiClient';
|
|
3
|
+
import { FetchUploader, XHRUploader } from './uploaders';
|
|
4
|
+
import { CACHE_HEADER } from './identity-utils';
|
|
5
|
+
import { parseNumber } from './utils';
|
|
4
6
|
|
|
5
7
|
var HTTPCodes = Constants.HTTPCodes,
|
|
6
8
|
Messages = Constants.Messages;
|
|
@@ -12,7 +14,7 @@ export default function IdentityAPIClient(mpInstance) {
|
|
|
12
14
|
await sendAliasRequest(mpInstance, aliasRequest, callback);
|
|
13
15
|
};
|
|
14
16
|
|
|
15
|
-
this.sendIdentityRequest = function(
|
|
17
|
+
this.sendIdentityRequest = async function(
|
|
16
18
|
identityApiRequest,
|
|
17
19
|
method,
|
|
18
20
|
callback,
|
|
@@ -21,93 +23,109 @@ export default function IdentityAPIClient(mpInstance) {
|
|
|
21
23
|
mpid,
|
|
22
24
|
knownIdentities
|
|
23
25
|
) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
xhrCallback = function() {
|
|
27
|
-
if (xhr.readyState === 4) {
|
|
28
|
-
// https://go.mparticle.com/work/SQDSDKS-6368
|
|
29
|
-
mpInstance.Logger.verbose(
|
|
30
|
-
'Received ' + xhr.statusText + ' from server'
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
// https://go.mparticle.com/work/SQDSDKS-6565
|
|
34
|
-
const identityResponse = xhrIdentityResponseAdapter(xhr);
|
|
35
|
-
parseIdentityResponse(
|
|
36
|
-
identityResponse,
|
|
37
|
-
previousMPID,
|
|
38
|
-
callback,
|
|
39
|
-
originalIdentityApiData,
|
|
40
|
-
method,
|
|
41
|
-
knownIdentities,
|
|
42
|
-
false
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
mpInstance.Logger.verbose(
|
|
48
|
-
Messages.InformationMessages.SendIdentityBegin
|
|
49
|
-
);
|
|
26
|
+
const { verbose, error } = mpInstance.Logger;
|
|
27
|
+
const { invokeCallback } = mpInstance._Helpers;
|
|
50
28
|
|
|
29
|
+
verbose(Messages.InformationMessages.SendIdentityBegin);
|
|
51
30
|
if (!identityApiRequest) {
|
|
52
|
-
|
|
31
|
+
error(Messages.ErrorMessages.APIRequestEmpty);
|
|
53
32
|
return;
|
|
54
33
|
}
|
|
34
|
+
verbose(Messages.InformationMessages.SendIdentityHttp);
|
|
55
35
|
|
|
56
|
-
mpInstance.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
xhr.send(JSON.stringify(identityApiRequest));
|
|
96
|
-
}
|
|
97
|
-
} catch (e) {
|
|
98
|
-
mpInstance._Store.identityCallInFlight = false;
|
|
99
|
-
mpInstance._Helpers.invokeCallback(
|
|
100
|
-
callback,
|
|
101
|
-
HTTPCodes.noHttpCoverage,
|
|
102
|
-
e
|
|
103
|
-
);
|
|
104
|
-
mpInstance.Logger.error(
|
|
105
|
-
'Error sending identity request to servers with status code ' +
|
|
106
|
-
xhr.status +
|
|
107
|
-
' - ' +
|
|
108
|
-
e
|
|
36
|
+
if (mpInstance._Store.identityCallInFlight) {
|
|
37
|
+
invokeCallback(
|
|
38
|
+
callback,
|
|
39
|
+
HTTPCodes.activeIdentityRequest,
|
|
40
|
+
'There is currently an Identity request processing. Please wait for this to return before requesting again'
|
|
41
|
+
);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const previousMPID = mpid || null;
|
|
46
|
+
const uploadUrl = this.getUploadUrl(method, mpid);
|
|
47
|
+
|
|
48
|
+
const uploader = window.fetch
|
|
49
|
+
? new FetchUploader(uploadUrl)
|
|
50
|
+
: new XHRUploader(uploadUrl);
|
|
51
|
+
|
|
52
|
+
const fetchPayload = {
|
|
53
|
+
method: 'post',
|
|
54
|
+
headers: {
|
|
55
|
+
Accept: 'text/plain;charset=UTF-8',
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
'x-mp-key': mpInstance._Store.devToken,
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify(identityApiRequest),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
mpInstance._Store.identityCallInFlight = true;
|
|
64
|
+
const response = await uploader.upload(fetchPayload);
|
|
65
|
+
|
|
66
|
+
let identityResponse;
|
|
67
|
+
|
|
68
|
+
if (response.json) {
|
|
69
|
+
// https://go.mparticle.com/work/SQDSDKS-6568
|
|
70
|
+
// FetchUploader returns the response as a JSON object that we have to await
|
|
71
|
+
const responseBody = await response.json();
|
|
72
|
+
identityResponse = this.getIdentityResponseFromFetch(
|
|
73
|
+
response,
|
|
74
|
+
responseBody
|
|
109
75
|
);
|
|
76
|
+
} else {
|
|
77
|
+
identityResponse = this.getIdentityResponseFromXHR(response);
|
|
110
78
|
}
|
|
79
|
+
|
|
80
|
+
verbose(
|
|
81
|
+
'Received Identity Response from server: ' +
|
|
82
|
+
JSON.stringify(identityResponse.responseText)
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
parseIdentityResponse(
|
|
86
|
+
identityResponse,
|
|
87
|
+
previousMPID,
|
|
88
|
+
callback,
|
|
89
|
+
originalIdentityApiData,
|
|
90
|
+
method,
|
|
91
|
+
knownIdentities,
|
|
92
|
+
false
|
|
93
|
+
);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
mpInstance._Store.identityCallInFlight = false;
|
|
96
|
+
invokeCallback(callback, HTTPCodes.noHttpCoverage, err);
|
|
97
|
+
error('Error sending identity request to servers' + ' - ' + err);
|
|
111
98
|
}
|
|
112
99
|
};
|
|
100
|
+
|
|
101
|
+
this.getUploadUrl = (method, mpid) => {
|
|
102
|
+
const uploadServiceUrl = mpInstance._Helpers.createServiceUrl(
|
|
103
|
+
mpInstance._Store.SDKConfig.identityUrl
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const uploadUrl =
|
|
107
|
+
method === Modify
|
|
108
|
+
? uploadServiceUrl + mpid + '/' + method
|
|
109
|
+
: uploadServiceUrl + method;
|
|
110
|
+
|
|
111
|
+
return uploadUrl;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
this.getIdentityResponseFromFetch = (response, responseBody) => ({
|
|
115
|
+
status: response.status,
|
|
116
|
+
responseText: responseBody,
|
|
117
|
+
cacheMaxAge: parseInt(response.headers.get(CACHE_HEADER)) || 0,
|
|
118
|
+
expireTimestamp: 0,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this.getIdentityResponseFromXHR = response => ({
|
|
122
|
+
status: response.status,
|
|
123
|
+
responseText: response.responseText
|
|
124
|
+
? JSON.parse(response.responseText)
|
|
125
|
+
: {},
|
|
126
|
+
cacheMaxAge: parseNumber(
|
|
127
|
+
response.getResponseHeader(CACHE_HEADER) || ''
|
|
128
|
+
),
|
|
129
|
+
expireTimestamp: 0,
|
|
130
|
+
});
|
|
113
131
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { SDKEventCustomFlags } from './sdkRuntimeModels';
|
|
2
|
+
import {
|
|
3
|
+
Dictionary,
|
|
4
|
+
queryStringParser,
|
|
5
|
+
getCookies,
|
|
6
|
+
getHref,
|
|
7
|
+
isEmpty,
|
|
8
|
+
} from './utils';
|
|
9
|
+
|
|
10
|
+
export interface IntegrationCaptureProcessorFunction {
|
|
11
|
+
(clickId: string, url: string, timestamp?: number): string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Facebook Click ID has specific formatting rules
|
|
15
|
+
// The formatted ClickID value must be of the form version.subdomainIndex.creationTime.<fbclid>, where:
|
|
16
|
+
// - version is always this prefix: fb
|
|
17
|
+
// - subdomainIndex is which domain the cookie is defined on ('com' = 0, 'example.com' = 1, 'www.example.com' = 2)
|
|
18
|
+
// - creationTime is the UNIX time since epoch in milliseconds when the _fbc was stored. If you don't save the _fbc cookie, use the timestamp when you first observed or received this fbclid value
|
|
19
|
+
// - <fbclid> is the value for the fbclid query parameter in the page URL.
|
|
20
|
+
// https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc
|
|
21
|
+
export const facebookClickIdProcessor: IntegrationCaptureProcessorFunction = (
|
|
22
|
+
clickId: string,
|
|
23
|
+
url: string,
|
|
24
|
+
timestamp?: number,
|
|
25
|
+
): string => {
|
|
26
|
+
if (!clickId || !url) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const urlSegments = url?.split('//')
|
|
31
|
+
if (!urlSegments) {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const urlParts = urlSegments[1].split('/');
|
|
36
|
+
const domainParts = urlParts[0].split('.');
|
|
37
|
+
let subdomainIndex: number = 1;
|
|
38
|
+
|
|
39
|
+
// The rules for subdomainIndex are for parsing the domain portion
|
|
40
|
+
// of the URL for cookies, but in this case we are parsing the URL
|
|
41
|
+
// itself, so we can ignore the use of 0 for 'com'
|
|
42
|
+
if (domainParts.length >= 3) {
|
|
43
|
+
subdomainIndex = 2;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If timestamp is not provided, use the current time
|
|
47
|
+
const _timestamp = timestamp || Date.now();
|
|
48
|
+
|
|
49
|
+
return `fb.${subdomainIndex}.${_timestamp}.${clickId}`;
|
|
50
|
+
};
|
|
51
|
+
interface IntegrationIdMapping {
|
|
52
|
+
[key: string]: {
|
|
53
|
+
mappedKey: string;
|
|
54
|
+
processor?: IntegrationCaptureProcessorFunction;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const integrationMapping: IntegrationIdMapping = {
|
|
59
|
+
fbclid: {
|
|
60
|
+
mappedKey: 'Facebook.ClickId',
|
|
61
|
+
processor: facebookClickIdProcessor,
|
|
62
|
+
},
|
|
63
|
+
_fbp: {
|
|
64
|
+
mappedKey: 'Facebook.BrowserId',
|
|
65
|
+
},
|
|
66
|
+
_fbc: {
|
|
67
|
+
mappedKey: 'Facebook.ClickId',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default class IntegrationCapture {
|
|
72
|
+
public clickIds: Dictionary<string>;
|
|
73
|
+
public readonly initialTimestamp: number;
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
this.initialTimestamp = Date.now();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Captures Integration Ids from cookies and query params and stores them in clickIds object
|
|
81
|
+
*/
|
|
82
|
+
public capture(): void {
|
|
83
|
+
const queryParams = this.captureQueryParams() || {};
|
|
84
|
+
const cookies = this.captureCookies() || {};
|
|
85
|
+
|
|
86
|
+
// Exclude _fbc if fbclid is present
|
|
87
|
+
// https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc#retrieve-from-fbclid-url-query-parameter
|
|
88
|
+
if (queryParams['fbclid'] && cookies['_fbc']) {
|
|
89
|
+
delete cookies['_fbc'];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.clickIds = { ...this.clickIds, ...queryParams, ...cookies };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Captures cookies based on the integration ID mapping.
|
|
97
|
+
*/
|
|
98
|
+
public captureCookies(): Dictionary<string> {
|
|
99
|
+
const cookies = getCookies(Object.keys(integrationMapping));
|
|
100
|
+
return this.applyProcessors(cookies);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Captures query parameters based on the integration ID mapping.
|
|
105
|
+
*/
|
|
106
|
+
public captureQueryParams(): Dictionary<string> {
|
|
107
|
+
const queryParams = this.getQueryParams();
|
|
108
|
+
return this.applyProcessors(queryParams, getHref(), this.initialTimestamp);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Gets the query parameters based on the integration ID mapping.
|
|
113
|
+
* @returns {Dictionary<string>} The query parameters.
|
|
114
|
+
*/
|
|
115
|
+
public getQueryParams(): Dictionary<string> {
|
|
116
|
+
return queryStringParser(getHref(), Object.keys(integrationMapping));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Converts captured click IDs to custom flags for SDK events.
|
|
121
|
+
* @returns {SDKEventCustomFlags} The custom flags.
|
|
122
|
+
*/
|
|
123
|
+
public getClickIdsAsCustomFlags(): SDKEventCustomFlags {
|
|
124
|
+
const customFlags: SDKEventCustomFlags = {};
|
|
125
|
+
|
|
126
|
+
if (!this.clickIds) {
|
|
127
|
+
return customFlags;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const [key, value] of Object.entries(this.clickIds)) {
|
|
131
|
+
const mappedKey = integrationMapping[key]?.mappedKey;
|
|
132
|
+
if (!isEmpty(mappedKey)) {
|
|
133
|
+
customFlags[mappedKey] = value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return customFlags;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private applyProcessors(clickIds: Dictionary<string>, url?: string, timestamp?: number): Dictionary<string> {
|
|
140
|
+
const processedClickIds: Dictionary<string> = {};
|
|
141
|
+
|
|
142
|
+
for (const [key, value] of Object.entries(clickIds)) {
|
|
143
|
+
const processor = integrationMapping[key]?.processor;
|
|
144
|
+
if (processor) {
|
|
145
|
+
processedClickIds[key] = processor(value, url, timestamp);
|
|
146
|
+
} else {
|
|
147
|
+
processedClickIds[key] = value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return processedClickIds;
|
|
152
|
+
}
|
|
153
|
+
}
|
package/src/mp-instance.js
CHANGED
|
@@ -39,9 +39,10 @@ import IdentityAPIClient from './identityApiClient';
|
|
|
39
39
|
import { isEmpty, isFunction } from './utils';
|
|
40
40
|
import { LocalStorageVault } from './vault';
|
|
41
41
|
import { removeExpiredIdentityCacheDates } from './identity-utils';
|
|
42
|
+
import IntegrationCapture from './integrationCapture';
|
|
42
43
|
|
|
43
44
|
const { Messages, HTTPCodes, FeatureFlags } = Constants;
|
|
44
|
-
const { ReportBatching } = FeatureFlags;
|
|
45
|
+
const { ReportBatching, CaptureIntegrationSpecificIds } = FeatureFlags;
|
|
45
46
|
const { StartingInitialization } = Messages.InformationMessages;
|
|
46
47
|
|
|
47
48
|
/**
|
|
@@ -77,6 +78,7 @@ export default function mParticleInstance(instanceName) {
|
|
|
77
78
|
integrationDelays: {},
|
|
78
79
|
forwarderConstructors: [],
|
|
79
80
|
};
|
|
81
|
+
this._IntegrationCapture = new IntegrationCapture();
|
|
80
82
|
|
|
81
83
|
// required for forwarders once they reference the mparticle instance
|
|
82
84
|
this.IdentityType = Types.IdentityType;
|
|
@@ -1336,6 +1338,10 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
|
|
|
1336
1338
|
mpInstance._ForwardingStatsUploader.startForwardingStatsTimer();
|
|
1337
1339
|
}
|
|
1338
1340
|
|
|
1341
|
+
if (mpInstance._Helpers.getFeatureFlag(CaptureIntegrationSpecificIds)) {
|
|
1342
|
+
mpInstance._IntegrationCapture.capture();
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1339
1345
|
mpInstance._Forwarders.processForwarders(
|
|
1340
1346
|
config,
|
|
1341
1347
|
mpInstance._APIClient.prepareForwardingStats
|
|
@@ -1533,9 +1539,9 @@ function processReadyQueue(readyQueue) {
|
|
|
1533
1539
|
processPreloadedItem(readyQueueItem);
|
|
1534
1540
|
}
|
|
1535
1541
|
});
|
|
1536
|
-
|
|
1537
|
-
return [];
|
|
1538
1542
|
}
|
|
1543
|
+
// https://go.mparticle.com/work/SQDSDKS-6835
|
|
1544
|
+
return [];
|
|
1539
1545
|
}
|
|
1540
1546
|
|
|
1541
1547
|
function queueIfNotInitialized(func, self) {
|
package/src/sdkRuntimeModels.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
ISDKUserAttributes,
|
|
31
31
|
} from './identity-user-interfaces';
|
|
32
32
|
import { IIdentityType } from './types.interfaces';
|
|
33
|
+
import IntegrationCapture from './integrationCapture';
|
|
33
34
|
|
|
34
35
|
// TODO: Resolve this with version in @mparticle/web-sdk
|
|
35
36
|
export type SDKEventCustomFlags = Dictionary<any>;
|
|
@@ -150,6 +151,7 @@ interface IEvents {
|
|
|
150
151
|
|
|
151
152
|
export interface MParticleWebSDK {
|
|
152
153
|
addForwarder(mockForwarder: MPForwarder): void;
|
|
154
|
+
_IntegrationCapture: IntegrationCapture;
|
|
153
155
|
IdentityType: IIdentityType;
|
|
154
156
|
_Identity: IIdentity;
|
|
155
157
|
Identity: SDKIdentityApi;
|
|
@@ -77,8 +77,8 @@ export function convertEvents(
|
|
|
77
77
|
|
|
78
78
|
device_info: {
|
|
79
79
|
platform: EventsApi.DeviceInformationPlatformEnum.web,
|
|
80
|
-
screen_width: window.screen.width,
|
|
81
|
-
screen_height: window.screen.height,
|
|
80
|
+
screen_width: typeof window !== 'undefined' ? window.screen.width : 0,
|
|
81
|
+
screen_height: typeof window !== 'undefined' ? window.screen.height : 0,
|
|
82
82
|
},
|
|
83
83
|
user_attributes: lastEvent.UserAttributes,
|
|
84
84
|
user_identities: convertUserIdentities(lastEvent.UserIdentities),
|
package/src/serverModel.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
BaseEvent,
|
|
9
9
|
MParticleWebSDK,
|
|
10
10
|
SDKEvent,
|
|
11
|
+
SDKEventCustomFlags,
|
|
11
12
|
SDKGeoLocation,
|
|
12
13
|
SDKProduct,
|
|
13
14
|
} from './sdkRuntimeModels';
|
|
@@ -317,6 +318,18 @@ export default function ServerModel(
|
|
|
317
318
|
event.messageType === Types.MessageType.OptOut ||
|
|
318
319
|
mpInstance._Store.webviewBridgeEnabled
|
|
319
320
|
) {
|
|
321
|
+
let customFlags: SDKEventCustomFlags = {...event.customFlags};
|
|
322
|
+
|
|
323
|
+
// https://go.mparticle.com/work/SQDSDKS-5053
|
|
324
|
+
if (mpInstance._Helpers.getFeatureFlag && mpInstance._Helpers.getFeatureFlag(Constants.FeatureFlags.CaptureIntegrationSpecificIds)) {
|
|
325
|
+
|
|
326
|
+
// Attempt to recapture click IDs in case a third party integration
|
|
327
|
+
// has added or updated new click IDs since the last event was sent.
|
|
328
|
+
mpInstance._IntegrationCapture.capture();
|
|
329
|
+
const transformedClickIDs = mpInstance._IntegrationCapture.getClickIdsAsCustomFlags();
|
|
330
|
+
customFlags = {...transformedClickIDs, ...customFlags};
|
|
331
|
+
}
|
|
332
|
+
|
|
320
333
|
if (event.hasOwnProperty('toEventAPIObject')) {
|
|
321
334
|
eventObject = event.toEventAPIObject();
|
|
322
335
|
} else {
|
|
@@ -336,7 +349,7 @@ export default function ServerModel(
|
|
|
336
349
|
event.sourceMessageId ||
|
|
337
350
|
mpInstance._Helpers.generateUniqueId(),
|
|
338
351
|
EventDataType: event.messageType,
|
|
339
|
-
CustomFlags:
|
|
352
|
+
CustomFlags: customFlags,
|
|
340
353
|
UserAttributeChanges: event.userAttributeChanges,
|
|
341
354
|
UserIdentityChanges: event.userIdentityChanges,
|
|
342
355
|
};
|
package/src/store.ts
CHANGED
|
@@ -710,6 +710,7 @@ export function processFlags(config: SDKInitConfig): IFeatureFlags {
|
|
|
710
710
|
DirectUrlRouting,
|
|
711
711
|
CacheIdentity,
|
|
712
712
|
AudienceAPI,
|
|
713
|
+
CaptureIntegrationSpecificIds,
|
|
713
714
|
} = Constants.FeatureFlags;
|
|
714
715
|
|
|
715
716
|
if (!config.flags) {
|
|
@@ -727,6 +728,7 @@ export function processFlags(config: SDKInitConfig): IFeatureFlags {
|
|
|
727
728
|
flags[DirectUrlRouting] = config.flags[DirectUrlRouting] === 'True';
|
|
728
729
|
flags[CacheIdentity] = config.flags[CacheIdentity] === 'True';
|
|
729
730
|
flags[AudienceAPI] = config.flags[AudienceAPI] === 'True';
|
|
731
|
+
flags[CaptureIntegrationSpecificIds] = config.flags[CaptureIntegrationSpecificIds] === 'True';
|
|
730
732
|
|
|
731
733
|
return flags;
|
|
732
734
|
}
|
package/src/uploaders.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
type HTTPMethod = 'get' | 'post';
|
|
2
|
+
|
|
1
3
|
export interface fetchPayload {
|
|
2
4
|
method: string;
|
|
3
5
|
headers: {
|
|
@@ -34,7 +36,8 @@ export class XHRUploader extends AsyncUploader {
|
|
|
34
36
|
const response: Response = await this.makeRequest(
|
|
35
37
|
this.url,
|
|
36
38
|
fetchPayload.body,
|
|
37
|
-
fetchPayload.method as
|
|
39
|
+
fetchPayload.method as HTTPMethod,
|
|
40
|
+
fetchPayload.headers
|
|
38
41
|
);
|
|
39
42
|
return response;
|
|
40
43
|
}
|
|
@@ -51,7 +54,8 @@ export class XHRUploader extends AsyncUploader {
|
|
|
51
54
|
private async makeRequest(
|
|
52
55
|
url: string,
|
|
53
56
|
data: string,
|
|
54
|
-
method:
|
|
57
|
+
method: HTTPMethod = 'post',
|
|
58
|
+
headers: Record<string, string> = {}
|
|
55
59
|
): Promise<Response> {
|
|
56
60
|
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
|
57
61
|
return new Promise((resolve, reject) => {
|
|
@@ -69,8 +73,12 @@ export class XHRUploader extends AsyncUploader {
|
|
|
69
73
|
reject((xhr as unknown) as Response);
|
|
70
74
|
};
|
|
71
75
|
|
|
72
|
-
|
|
73
76
|
xhr.open(method, url);
|
|
77
|
+
|
|
78
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
79
|
+
xhr.setRequestHeader(key, value);
|
|
80
|
+
});
|
|
81
|
+
|
|
74
82
|
xhr.send(data);
|
|
75
83
|
});
|
|
76
84
|
}
|
package/src/utils.ts
CHANGED
|
@@ -222,7 +222,7 @@ const isDataPlanSlug = (str: string): boolean => str === toDataPlanSlug(str);
|
|
|
222
222
|
const isStringOrNumber = (value: any): boolean =>
|
|
223
223
|
isString(value) || isNumber(value);
|
|
224
224
|
|
|
225
|
-
const isEmpty = (value: Dictionary<any> | null | undefined): boolean =>
|
|
225
|
+
const isEmpty = (value: Dictionary<any> | string | null | undefined): boolean =>
|
|
226
226
|
value == null || !(Object.keys(value) || value).length;
|
|
227
227
|
|
|
228
228
|
const mergeObjects = <T extends object>(...objects: T[]): T => {
|
|
@@ -232,6 +232,107 @@ const mergeObjects = <T extends object>(...objects: T[]): T => {
|
|
|
232
232
|
const moveElementToEnd = <T>(array: T[], index: number): T[] =>
|
|
233
233
|
array.slice(0, index).concat(array.slice(index + 1), array[index]);
|
|
234
234
|
|
|
235
|
+
const queryStringParser = (
|
|
236
|
+
url: string,
|
|
237
|
+
keys: string[] = []
|
|
238
|
+
): Dictionary<string> => {
|
|
239
|
+
let urlParams: URLSearchParams | URLSearchParamsFallback;
|
|
240
|
+
let results: Dictionary<string> = {};
|
|
241
|
+
|
|
242
|
+
if (!url) return results;
|
|
243
|
+
|
|
244
|
+
if (typeof URL !== 'undefined' && typeof URLSearchParams !== 'undefined') {
|
|
245
|
+
const urlObject = new URL(url);
|
|
246
|
+
urlParams = new URLSearchParams(urlObject.search);
|
|
247
|
+
} else {
|
|
248
|
+
urlParams = queryStringParserFallback(url);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (isEmpty(keys)) {
|
|
252
|
+
urlParams.forEach((value, key) => {
|
|
253
|
+
results[key] = value;
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
keys.forEach(key => {
|
|
257
|
+
const value = urlParams.get(key);
|
|
258
|
+
if (value) {
|
|
259
|
+
results[key] = value;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return results;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
interface URLSearchParamsFallback {
|
|
268
|
+
get: (key: string) => string | null;
|
|
269
|
+
forEach: (callback: (value: string, key: string) => void) => void;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const queryStringParserFallback = (url: string): URLSearchParamsFallback => {
|
|
273
|
+
let params: Dictionary<string> = {};
|
|
274
|
+
const queryString = url.split('?')[1] || '';
|
|
275
|
+
const pairs = queryString.split('&');
|
|
276
|
+
|
|
277
|
+
pairs.forEach(pair => {
|
|
278
|
+
var [key, value] = pair.split('=');
|
|
279
|
+
if (key && value) {
|
|
280
|
+
params[key] = decodeURIComponent(value || '');
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
get: function(key: string) {
|
|
286
|
+
return params[key];
|
|
287
|
+
},
|
|
288
|
+
forEach: function(callback: (value: string, key: string) => void) {
|
|
289
|
+
for (var key in params) {
|
|
290
|
+
if (params.hasOwnProperty(key)) {
|
|
291
|
+
callback(params[key], key);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// Get cookies as a dictionary
|
|
299
|
+
const getCookies = (keys?: string[]): Dictionary<string> => {
|
|
300
|
+
// Helper function to parse cookies from document.cookie
|
|
301
|
+
const parseCookies = (): string[] => {
|
|
302
|
+
if (typeof window === 'undefined') {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
return window.document.cookie.split(';').map(cookie => cookie.trim());
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Helper function to filter cookies by keys
|
|
309
|
+
const filterCookies = (
|
|
310
|
+
cookies: string[],
|
|
311
|
+
keys?: string[]
|
|
312
|
+
): Dictionary<string> => {
|
|
313
|
+
const results: Dictionary<string> = {};
|
|
314
|
+
for (const cookie of cookies) {
|
|
315
|
+
const [key, value] = cookie.split('=');
|
|
316
|
+
if (!keys || keys.includes(key)) {
|
|
317
|
+
results[key] = value;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return results;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Parse cookies from document.cookie
|
|
324
|
+
const parsedCookies: string[] = parseCookies();
|
|
325
|
+
|
|
326
|
+
// Filter cookies by keys if provided
|
|
327
|
+
return filterCookies(parsedCookies, keys);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const getHref = (): string => {
|
|
331
|
+
return typeof window !== 'undefined' && window.location
|
|
332
|
+
? window.location.href
|
|
333
|
+
: '';
|
|
334
|
+
};
|
|
335
|
+
|
|
235
336
|
export {
|
|
236
337
|
createCookieString,
|
|
237
338
|
revertCookieString,
|
|
@@ -262,4 +363,7 @@ export {
|
|
|
262
363
|
isValidCustomFlagProperty,
|
|
263
364
|
mergeObjects,
|
|
264
365
|
moveElementToEnd,
|
|
366
|
+
queryStringParser,
|
|
367
|
+
getCookies,
|
|
368
|
+
getHref,
|
|
265
369
|
};
|