@mparticle/web-sdk 2.25.2 → 2.26.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.
@@ -10,6 +10,12 @@ import { convertEvents } from './sdkToEventsApiConverter';
10
10
  import Types from './types';
11
11
  import { getRampNumber, isEmpty } from './utils';
12
12
  import { SessionStorageVault, LocalStorageVault } from './vault';
13
+ import {
14
+ AsyncUploader,
15
+ FetchUploader,
16
+ XHRUploader,
17
+ fetchPayload
18
+ } from './uploaders';
13
19
 
14
20
  /**
15
21
  * BatchUploader contains all the logic to store/retrieve events and batches
@@ -108,11 +114,12 @@ export class BatchUploader {
108
114
  _Store: { deviceId },
109
115
  } = this.mpInstance;
110
116
 
111
- const offlineStorageFeatureFlagValue = getFeatureFlag(
117
+ // https://go.mparticle.com/work/SQDSDKS-6317
118
+ const offlineStorageFeatureFlagValue: string = getFeatureFlag(
112
119
  Constants.FeatureFlags.OfflineStorage
113
- );
120
+ ) as string;
114
121
 
115
- const offlineStoragePercentage = parseInt(
122
+ const offlineStoragePercentage: number = parseInt(
116
123
  offlineStorageFeatureFlagValue,
117
124
  10
118
125
  );
@@ -422,66 +429,4 @@ export class BatchUploader {
422
429
  }
423
430
  return null;
424
431
  }
425
- }
426
-
427
- abstract class AsyncUploader {
428
- url: string;
429
- public abstract upload(fetchPayload: fetchPayload): Promise<XHRResponse>;
430
-
431
- constructor(url: string) {
432
- this.url = url;
433
- }
434
- }
435
-
436
- class FetchUploader extends AsyncUploader {
437
- public async upload(fetchPayload: fetchPayload): Promise<XHRResponse> {
438
- const response: XHRResponse = await fetch(this.url, fetchPayload);
439
- return response;
440
- }
441
- }
442
-
443
- class XHRUploader extends AsyncUploader {
444
- public async upload(fetchPayload: fetchPayload): Promise<XHRResponse> {
445
- const response: XHRResponse = await this.makeRequest(
446
- this.url,
447
- fetchPayload.body
448
- );
449
- return response;
450
- }
451
-
452
- private async makeRequest(
453
- url: string,
454
- data: string
455
- ): Promise<XMLHttpRequest> {
456
- const xhr: XMLHttpRequest = new XMLHttpRequest();
457
- return new Promise((resolve, reject) => {
458
- xhr.onreadystatechange = () => {
459
- if (xhr.readyState !== 4) return;
460
-
461
- // Process the response
462
- if (xhr.status >= 200 && xhr.status < 300) {
463
- resolve(xhr);
464
- } else {
465
- reject(xhr);
466
- }
467
- };
468
-
469
- xhr.open('post', url);
470
- xhr.send(data);
471
- });
472
- }
473
- }
474
-
475
- interface XHRResponse {
476
- status: number;
477
- statusText?: string;
478
- }
479
-
480
- interface fetchPayload {
481
- method: string;
482
- headers: {
483
- Accept: string;
484
- 'Content-Type': string;
485
- };
486
- body: string;
487
- }
432
+ }
package/src/constants.ts CHANGED
@@ -26,6 +26,8 @@ const Constants = {
26
26
  BadKey: 'Key value cannot be object or array',
27
27
  BadLogPurchase:
28
28
  'Transaction attributes and a product are both required to log a purchase, https://docs.mparticle.com/?javascript#measuring-transactions',
29
+ AudienceAPINotEnabled:
30
+ 'Your workspace is not enabled to retrieve user audiences.',
29
31
  },
30
32
  InformationMessages: {
31
33
  CookieSearch: 'Searching for cookie',
@@ -135,6 +137,7 @@ const Constants = {
135
137
  configUrl: 'jssdkcdns.mparticle.com/JS/v2/',
136
138
  identityUrl: 'identity.mparticle.com/v1/',
137
139
  aliasUrl: 'jssdks.mparticle.com/v1/identity/',
140
+ userAudienceUrl: 'nativesdks.mparticle.com/v1/',
138
141
  },
139
142
  Base64CookieKeys: {
140
143
  csm: 1,
@@ -170,6 +173,7 @@ const Constants = {
170
173
  OfflineStorage: 'offlineStorage',
171
174
  DirectUrlRouting: 'directURLRouting',
172
175
  CacheIdentity: 'cacheIdentity',
176
+ AudienceAPI: 'audienceAPI',
173
177
  },
174
178
  DefaultInstance: 'default_instance',
175
179
  CCPAPurpose: 'data_sale_opt_out',
package/src/identity.js CHANGED
@@ -5,15 +5,17 @@ import {
5
5
  createKnownIdentities,
6
6
  tryCacheIdentity,
7
7
  } from './identity-utils';
8
+ import AudienceManager from './audienceManager';
8
9
 
9
- var Messages = Constants.Messages,
10
- HTTPCodes = Constants.HTTPCodes;
10
+ const { Messages, HTTPCodes, FeatureFlags } = Constants;
11
+ const { ErrorMessages } = Messages;
11
12
 
12
13
  const { Identify, Modify, Login, Logout } = Constants.IdentityMethods;
13
14
 
14
15
  export default function Identity(mpInstance) {
15
16
  var self = this;
16
17
  this.idCache = null;
18
+ this.audienceManager = null;
17
19
 
18
20
  this.checkIdentitySwap = function(
19
21
  previousMPID,
@@ -1265,6 +1267,34 @@ export default function Identity(mpInstance) {
1265
1267
  getFirstSeenTime: function() {
1266
1268
  return mpInstance._Persistence.getFirstSeenTime(mpid);
1267
1269
  },
1270
+ /**
1271
+ * Get user audiences
1272
+ * @method getuserAudiences
1273
+ * @param {Function} [callback] A callback function that is invoked when the user audience request completes
1274
+ */
1275
+ getUserAudiences: function(callback) {
1276
+ // user audience API is feature flagged
1277
+ if (
1278
+ !mpInstance._Helpers.getFeatureFlag(
1279
+ FeatureFlags.AudienceAPI
1280
+ )
1281
+ ) {
1282
+ mpInstance.Logger.error(
1283
+ ErrorMessages.AudienceAPINotEnabled
1284
+ );
1285
+ return;
1286
+ }
1287
+ if (self.audienceManager === null) {
1288
+ self.audienceManager = new AudienceManager(
1289
+ mpInstance._Store.SDKConfig.userAudienceUrl,
1290
+ mpInstance._Store.devToken,
1291
+ mpInstance.Logger,
1292
+ mpid
1293
+ );
1294
+ }
1295
+
1296
+ self.audienceManager.sendGetUserAudienceRequest(mpid, callback);
1297
+ },
1268
1298
  };
1269
1299
  };
1270
1300
 
@@ -86,6 +86,9 @@ export default function mParticleInstance(instanceName) {
86
86
  this._Identity = new Identity(this);
87
87
  this.Identity = this._Identity.IdentityAPI;
88
88
  this.generateHash = this._Helpers.generateHash;
89
+
90
+ // https://go.mparticle.com/work/SQDSDKS-6289
91
+ // TODO: Replace this with Store once Store is moved earlier in the init process
89
92
  this.getDeviceId = this._Persistence.getDeviceId;
90
93
 
91
94
  if (typeof window !== 'undefined') {
@@ -231,7 +234,7 @@ export default function mParticleInstance(instanceName) {
231
234
  self.setDeviceId(guid);
232
235
  }, self);
233
236
  if (queued) return;
234
- this._Persistence.setDeviceId(guid);
237
+ this._Store.setDeviceId(guid);
235
238
  };
236
239
  /**
237
240
  * Returns a boolean for whether or not the SDKhas been fully initialized
@@ -1,6 +1,11 @@
1
1
  import * as EventsApi from '@mparticle/event-models';
2
2
  import { DataPlanVersion } from '@mparticle/data-planning-models';
3
- import { MPConfiguration, IdentityApiData } from '@mparticle/web-sdk';
3
+ import {
4
+ MPConfiguration,
5
+ IdentityApiData,
6
+ MPID,
7
+ Callback,
8
+ } from '@mparticle/web-sdk';
4
9
  import { IStore } from './store';
5
10
  import Validators from './validators';
6
11
  import { Dictionary, valueof } from './utils';
@@ -246,19 +251,27 @@ export interface SDKIdentityApi {
246
251
 
247
252
  export interface SDKHelpersApi {
248
253
  canLog?(): boolean;
249
- createServiceUrl(arg0: string, arg1: string): void;
254
+ createServiceUrl(url: string, devToken?: string): void;
250
255
  createXHR?(cb: () => void): XMLHttpRequest;
251
256
  extend?(...args: any[]);
252
257
  parseNumber?(value: string | number): number;
253
258
  generateUniqueId();
254
259
  generateHash?(value: string): string;
255
- getFeatureFlag?(feature: string); // TODO: Feature Constants should be converted to enum
260
+ // https://go.mparticle.com/work/SQDSDKS-6317
261
+ getFeatureFlag?(feature: string): boolean | string; // TODO: Feature Constants should be converted to enum
256
262
  isDelayedByIntegration?(
257
263
  delayedIntegrations: Dictionary<boolean>,
258
264
  timeoutStart: number,
259
265
  now: number
260
266
  ): boolean;
261
267
  isObject?(item: any);
268
+ invokeCallback?(
269
+ callback: Callback,
270
+ code: string,
271
+ body: string,
272
+ mParticleUser?: MParticleUser,
273
+ previousMpid?: MPID
274
+ ): void;
262
275
  returnConvertedBoolean(data: string | boolean | number): boolean;
263
276
  sanitizeAttributes?(
264
277
  attrs: Dictionary<string>,
@@ -129,7 +129,7 @@ export default function SessionManager(
129
129
  messageType: Types.MessageType.SessionEnd,
130
130
  });
131
131
 
132
- nullifySession();
132
+ mpInstance._Store.nullifySession();
133
133
  return;
134
134
  }
135
135
 
@@ -175,7 +175,7 @@ export default function SessionManager(
175
175
  });
176
176
 
177
177
  mpInstance._Store.sessionStartDate = null;
178
- nullifySession();
178
+ mpInstance._Store.nullifySession();
179
179
  }
180
180
  }
181
181
  };
@@ -204,7 +204,7 @@ export default function SessionManager(
204
204
  clearTimeout(mpInstance._Store.globalTimer);
205
205
  };
206
206
 
207
- this.startNewSessionIfNeeded = function(): void {
207
+ this.startNewSessionIfNeeded = function (): void {
208
208
  if (!mpInstance._Store.webviewBridgeEnabled) {
209
209
  const persistence = mpInstance._Persistence.getPersistence();
210
210
 
@@ -217,11 +217,4 @@ export default function SessionManager(
217
217
  }
218
218
  }
219
219
  };
220
-
221
- function nullifySession(): void {
222
- mpInstance._Store.sessionId = null;
223
- mpInstance._Store.dateLastEventSent = null;
224
- mpInstance._Store.sessionAttributes = {};
225
- mpInstance._Persistence.update();
226
- }
227
220
  }
package/src/store.ts CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  import { isNumber, isDataPlanSlug, Dictionary, parseNumber } from './utils';
24
24
  import { SDKConsentState } from './consent';
25
25
  import { Kit, MPForwarder } from './forwarders.interfaces';
26
+ import { IPersistenceMinified } from './persistence.interfaces';
26
27
 
27
28
  // This represents the runtime configuration of the SDK AFTER
28
29
  // initialization has been complete and all settings and
@@ -60,6 +61,7 @@ export interface SDKConfig {
60
61
  aliasUrl?: string;
61
62
  configUrl?: string;
62
63
  identityUrl?: string;
64
+ userAudienceUrl?: string;
63
65
  isIOS?: boolean;
64
66
  maxProducts: number;
65
67
  requestConfig?: boolean;
@@ -162,6 +164,18 @@ export interface IStore {
162
164
  integrationDelayTimeoutStart: number; // UNIX Timestamp
163
165
  webviewBridgeEnabled?: boolean;
164
166
  wrapperSDKInfo: WrapperSDKInfo;
167
+
168
+ persistenceData?: IPersistenceMinified;
169
+
170
+ getDeviceId?(): string;
171
+ setDeviceId?(deviceId: string): void;
172
+
173
+ getFirstSeenTime?(mpid: MPID): number;
174
+ setFirstSeenTime?(mpid: MPID, time?: number): void;
175
+ getLastSeenTime?(mpid: MPID): number;
176
+ setLastSeenTime?(mpid: MPID, time?: number): void;
177
+
178
+ nullifySession?: () => void;
165
179
  }
166
180
 
167
181
  // TODO: Merge this with SDKStoreApi in sdkRuntimeModels
@@ -214,6 +228,27 @@ export default function Store(
214
228
  version: null,
215
229
  isInfoSet: false,
216
230
  },
231
+
232
+ // Placeholder for in-memory persistence model
233
+ persistenceData: {
234
+ cu: null,
235
+ gs: {
236
+ sid: null,
237
+ ie: null,
238
+ sa: null,
239
+ ss: null,
240
+ dt: null,
241
+ av: null,
242
+ cgid: null,
243
+ das: null,
244
+ ia: null,
245
+ c: null,
246
+ csm: null,
247
+ les: null,
248
+ ssd: null,
249
+ },
250
+ l: null,
251
+ },
217
252
  };
218
253
 
219
254
  for (var key in defaultStore) {
@@ -418,6 +453,92 @@ export default function Store(
418
453
  }
419
454
  }
420
455
  }
456
+
457
+ this.getDeviceId = () => this.deviceId;
458
+ this.setDeviceId = (deviceId: string) => {
459
+ this.deviceId = deviceId;
460
+ this.persistenceData.gs.das = deviceId;
461
+ mpInstance._Persistence.update();
462
+ };
463
+
464
+ this.nullifySession = (): void => {
465
+ this.sessionId = null;
466
+ this.dateLastEventSent = null;
467
+ this.sessionAttributes = {};
468
+ mpInstance._Persistence.update();
469
+ };
470
+
471
+ this.getFirstSeenTime = (mpid: MPID) => {
472
+ if (!mpid) {
473
+ return null;
474
+ }
475
+
476
+ if (
477
+ this.persistenceData &&
478
+ this.persistenceData[mpid] &&
479
+ this.persistenceData[mpid].fst
480
+ ) {
481
+ return this.persistenceData[mpid].fst;
482
+ } else {
483
+ return null;
484
+ }
485
+ };
486
+
487
+ this.setFirstSeenTime = (mpid: MPID, _time?: number) => {
488
+ if (!mpid) {
489
+ return;
490
+ }
491
+
492
+ const time = _time || new Date().getTime();
493
+
494
+ if (this.persistenceData) {
495
+ if (!this.persistenceData[mpid]) {
496
+ this.persistenceData[mpid] = {};
497
+ }
498
+ if (!this.persistenceData[mpid].fst) {
499
+ this.persistenceData[mpid].fst = time;
500
+ mpInstance._Persistence.savePersistence(this.persistenceData);
501
+ }
502
+ }
503
+ };
504
+
505
+ this.getLastSeenTime = (mpid: MPID) => {
506
+ if (!mpid) {
507
+ return null;
508
+ }
509
+ // // https://go.mparticle.com/work/SQDSDKS-6315
510
+ const currentUser = mpInstance.Identity.getCurrentUser();
511
+ if (mpid === currentUser?.getMPID()) {
512
+ // if the mpid is the current user, its last seen time is the current time
513
+ return new Date().getTime();
514
+ } else if (
515
+ this.persistenceData &&
516
+ this.persistenceData[mpid] &&
517
+ this.persistenceData[mpid].lst
518
+ ) {
519
+ return this.persistenceData[mpid].lst;
520
+ } else {
521
+ return null;
522
+ }
523
+ };
524
+
525
+ this.setLastSeenTime = (mpid: MPID, _time?: number) => {
526
+ if (!mpid) {
527
+ return;
528
+ }
529
+
530
+ const time = _time || new Date().getTime();
531
+
532
+ if (this.persistenceData) {
533
+ if (!this.persistenceData[mpid]) {
534
+ this.persistenceData[mpid] = {};
535
+ }
536
+ if (!this.persistenceData[mpid].lst) {
537
+ this.persistenceData[mpid].lst = time;
538
+ mpInstance._Persistence.savePersistence(this.persistenceData);
539
+ }
540
+ }
541
+ };
421
542
  }
422
543
 
423
544
  export function processFlags(
@@ -431,6 +552,7 @@ export function processFlags(
431
552
  OfflineStorage,
432
553
  DirectUrlRouting,
433
554
  CacheIdentity,
555
+ AudienceAPI,
434
556
  } = Constants.FeatureFlags;
435
557
 
436
558
  if (!config.flags) {
@@ -446,6 +568,7 @@ export function processFlags(
446
568
  flags[OfflineStorage] = config.flags[OfflineStorage] || '0';
447
569
  flags[DirectUrlRouting] = config.flags[DirectUrlRouting] === 'True';
448
570
  flags[CacheIdentity] = config.flags[CacheIdentity] === 'True';
571
+ flags[AudienceAPI] = config.flags[AudienceAPI] === 'True';
449
572
 
450
573
  return flags;
451
574
  }
@@ -527,4 +650,4 @@ function processDirectBaseUrls(
527
650
  }
528
651
 
529
652
  return directBaseUrls;
530
- }
653
+ }
@@ -0,0 +1,59 @@
1
+ export interface fetchPayload {
2
+ method: string;
3
+ headers: {
4
+ Accept: string;
5
+ 'Content-Type'?: string;
6
+ };
7
+ body?: string;
8
+ }
9
+
10
+ export abstract class AsyncUploader {
11
+ url: string;
12
+ public abstract upload(
13
+ fetchPayload: fetchPayload,
14
+ url?: string
15
+ ): Promise<Response>;
16
+
17
+ constructor(url: string) {
18
+ this.url = url;
19
+ }
20
+ }
21
+
22
+ export class FetchUploader extends AsyncUploader {
23
+ public async upload(
24
+ fetchPayload: fetchPayload,
25
+ _url?: string
26
+ ): Promise<Response> {
27
+ const url = _url || this.url;
28
+ return await fetch(url, fetchPayload);
29
+ }
30
+ }
31
+
32
+ export class XHRUploader extends AsyncUploader {
33
+ public async upload(fetchPayload: fetchPayload): Promise<Response> {
34
+ const response: Response = await this.makeRequest(
35
+ this.url,
36
+ fetchPayload.body
37
+ );
38
+ return response;
39
+ }
40
+
41
+ private async makeRequest(url: string, data: string): Promise<Response> {
42
+ const xhr: XMLHttpRequest = new XMLHttpRequest();
43
+ return new Promise((resolve, reject) => {
44
+ xhr.onreadystatechange = () => {
45
+ if (xhr.readyState !== 4) return;
46
+
47
+ // Process the response
48
+ if (xhr.status >= 200 && xhr.status < 300) {
49
+ resolve((xhr as unknown) as Response);
50
+ } else {
51
+ reject(xhr);
52
+ }
53
+ };
54
+
55
+ xhr.open('post', url);
56
+ xhr.send(data);
57
+ });
58
+ }
59
+ }