@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.
@@ -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
- var xhr,
25
- previousMPID,
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
- mpInstance.Logger.error(Messages.ErrorMessages.APIRequestEmpty);
31
+ error(Messages.ErrorMessages.APIRequestEmpty);
53
32
  return;
54
33
  }
34
+ verbose(Messages.InformationMessages.SendIdentityHttp);
55
35
 
56
- mpInstance.Logger.verbose(
57
- Messages.InformationMessages.SendIdentityHttp
58
- );
59
- xhr = mpInstance._Helpers.createXHR(xhrCallback);
60
-
61
- if (xhr) {
62
- try {
63
- if (mpInstance._Store.identityCallInFlight) {
64
- mpInstance._Helpers.invokeCallback(
65
- callback,
66
- HTTPCodes.activeIdentityRequest,
67
- 'There is currently an Identity request processing. Please wait for this to return before requesting again'
68
- );
69
- } else {
70
- previousMPID = mpid || null;
71
- if (method === Modify) {
72
- xhr.open(
73
- 'post',
74
- mpInstance._Helpers.createServiceUrl(
75
- mpInstance._Store.SDKConfig.identityUrl
76
- ) +
77
- mpid +
78
- '/' +
79
- method
80
- );
81
- } else {
82
- xhr.open(
83
- 'post',
84
- mpInstance._Helpers.createServiceUrl(
85
- mpInstance._Store.SDKConfig.identityUrl
86
- ) + method
87
- );
88
- }
89
- xhr.setRequestHeader('Content-Type', 'application/json');
90
- xhr.setRequestHeader(
91
- 'x-mp-key',
92
- mpInstance._Store.devToken
93
- );
94
- mpInstance._Store.identityCallInFlight = true;
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
+ }
@@ -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) {
@@ -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),
@@ -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: event.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 'post' | 'get'
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: 'post' | 'get' = 'post'
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
  };