@mparticle/web-sdk 2.79.2 → 2.80.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.
@@ -0,0 +1,1210 @@
1
+ import Constants from './constants';
2
+ import Polyfill from './polyfill';
3
+ import * as Utils from './utils';
4
+ import { IMParticleWebSDKInstance } from './mp-instance';
5
+ import { IPersistence, IPersistenceMinified } from './persistence.interfaces';
6
+ import { IdentityApiData, MPID } from '@mparticle/web-sdk';
7
+ import { CookieSyncDates } from './cookieSyncManager';
8
+ import { Dictionary } from './utils';
9
+ import { IMParticleInstanceManager } from './sdkRuntimeModels';
10
+
11
+ const Base64 = Polyfill.Base64,
12
+ Messages = Constants.Messages,
13
+ Base64CookieKeys = Constants.Base64CookieKeys,
14
+ SDKv2NonMPIDCookieKeys = Constants.SDKv2NonMPIDCookieKeys,
15
+ StorageNames = Constants.StorageNames;
16
+
17
+ function getMParticleManager(): IMParticleInstanceManager | undefined {
18
+ return (window as { mParticle?: IMParticleInstanceManager }).mParticle;
19
+ }
20
+
21
+ export default function _Persistence(
22
+ this: IPersistence,
23
+ mpInstance: IMParticleWebSDKInstance
24
+ ) {
25
+ const self = this;
26
+
27
+ // https://go.mparticle.com/work/SQDSDKS-5022
28
+ this.useLocalStorage = function(): boolean {
29
+ return (
30
+ !mpInstance._Store.SDKConfig.useCookieStorage &&
31
+ mpInstance._Store.isLocalStorageAvailable
32
+ );
33
+ };
34
+
35
+ function setFirstRunFromExistingData(
36
+ localStorageData: IPersistenceMinified | null,
37
+ cookies: IPersistenceMinified | null
38
+ ): void {
39
+ if (!localStorageData && !cookies) {
40
+ mpInstance._Store.isFirstRun = true;
41
+ (mpInstance._Store as Dictionary).mpid = 0;
42
+ return;
43
+ }
44
+ mpInstance._Store.isFirstRun = false;
45
+ }
46
+
47
+ function mergeStorageSources(
48
+ localStorageData: IPersistenceMinified | null,
49
+ cookies: IPersistenceMinified | null
50
+ ) {
51
+ if (localStorageData && cookies) {
52
+ // https://go.mparticle.com/work/SQDSDKS-6047
53
+ return Utils.extend(false, localStorageData, cookies);
54
+ }
55
+ return localStorageData || cookies;
56
+ }
57
+
58
+ // For migrating from localStorage to cookies -- If an instance switches from
59
+ // localStorage to cookies, then no mParticle cookie exists yet and there is
60
+ // localStorage. Get the localStorage, set them to cookies, then delete the
61
+ // localStorage item.
62
+ function migrateLocalStorageToCookies(
63
+ storage: Storage,
64
+ localStorageData: IPersistenceMinified | null,
65
+ cookies: IPersistenceMinified | null
66
+ ) {
67
+ let allData;
68
+ if (localStorageData) {
69
+ allData = mergeStorageSources(localStorageData, cookies);
70
+ storage.removeItem(mpInstance._Store.storageName);
71
+ } else if (cookies) {
72
+ allData = cookies;
73
+ }
74
+ self.storeDataInMemory(allData);
75
+ return allData;
76
+ }
77
+
78
+ // For migrating from cookie to localStorage -- If an instance is newly
79
+ // switching from cookies to localStorage, then no mParticle localStorage
80
+ // exists yet and there are cookies. Get the cookies, set them to
81
+ // localStorage, then delete the cookies.
82
+ function migrateCookiesToLocalStorage(
83
+ localStorageData: IPersistenceMinified | null,
84
+ cookies: IPersistenceMinified | null
85
+ ) {
86
+ if (!cookies) {
87
+ self.storeDataInMemory(localStorageData);
88
+ return;
89
+ }
90
+ const allData = mergeStorageSources(localStorageData, cookies);
91
+ self.storeDataInMemory(allData);
92
+ self.expireCookies(mpInstance._Store.storageName);
93
+ return allData;
94
+ }
95
+
96
+ function loadPersistenceIntoMemory(
97
+ localStorageData: IPersistenceMinified | null,
98
+ cookies: IPersistenceMinified | null
99
+ ) {
100
+ if (!mpInstance._Store.isLocalStorageAvailable) {
101
+ self.storeDataInMemory(cookies);
102
+ return;
103
+ }
104
+ if (mpInstance._Store.SDKConfig.useCookieStorage) {
105
+ return migrateLocalStorageToCookies(
106
+ window.localStorage,
107
+ localStorageData,
108
+ cookies
109
+ );
110
+ }
111
+ return migrateCookiesToLocalStorage(localStorageData, cookies);
112
+ }
113
+
114
+ function copyNonCurrentUserMpids(allData: IPersistenceMinified): void {
115
+ for (let key in allData) {
116
+ if (
117
+ allData.hasOwnProperty(key) &&
118
+ !SDKv2NonMPIDCookieKeys[key]
119
+ ) {
120
+ mpInstance._Store.nonCurrentUserMPIDs[key] = allData[key];
121
+ }
122
+ }
123
+ }
124
+
125
+ function clearCorruptStorage(): void {
126
+ if (
127
+ self.useLocalStorage() &&
128
+ mpInstance._Store.isLocalStorageAvailable
129
+ ) {
130
+ localStorage.removeItem(mpInstance._Store.storageName);
131
+ return;
132
+ }
133
+ self.expireCookies(mpInstance._Store.storageName);
134
+ }
135
+
136
+ this.initializeStorage = function(): void {
137
+ try {
138
+ const localStorageData = self.getLocalStorage();
139
+ const cookies = self.getCookie();
140
+
141
+ // https://go.mparticle.com/work/SQDSDKS-6045
142
+ setFirstRunFromExistingData(localStorageData, cookies);
143
+
144
+ // https://go.mparticle.com/work/SQDSDKS-6045
145
+ if (!mpInstance._Store.isLocalStorageAvailable) {
146
+ mpInstance._Store.SDKConfig.useCookieStorage = true;
147
+ }
148
+
149
+ // https://go.mparticle.com/work/SQDSDKS-6046
150
+ const allData = loadPersistenceIntoMemory(
151
+ localStorageData,
152
+ cookies
153
+ );
154
+ copyNonCurrentUserMpids(allData);
155
+ self.update();
156
+ } catch (e) {
157
+ // If cookies or local storage is corrupt, we want to remove it
158
+ // so that in the future, initializeStorage will work
159
+ clearCorruptStorage();
160
+ mpInstance.Logger.error('Error initializing storage: ' + e);
161
+ }
162
+ };
163
+
164
+ this.update = function(): void {
165
+ if (!mpInstance._Store.webviewBridgeEnabled) {
166
+ if (mpInstance._Store.SDKConfig.useCookieStorage) {
167
+ self.setCookie();
168
+ }
169
+
170
+ self.setLocalStorage();
171
+ }
172
+ };
173
+
174
+ function applyEmptyPersistenceDefaults(): void {
175
+ mpInstance.Logger.verbose(
176
+ Messages.InformationMessages.CookieNotFound
177
+ );
178
+ mpInstance._Store.clientId =
179
+ mpInstance._Store.clientId ||
180
+ mpInstance._Helpers.generateUniqueId();
181
+ mpInstance._Store.deviceId =
182
+ mpInstance._Store.deviceId ||
183
+ mpInstance._Helpers.generateUniqueId();
184
+ }
185
+
186
+ function hydrateStoreFromPersistence(
187
+ obj: IPersistenceMinified,
188
+ currentMPID?: MPID
189
+ ): void {
190
+ // Set MPID first, then change object to match MPID data
191
+ if (currentMPID) {
192
+ (mpInstance._Store as Dictionary).mpid = currentMPID;
193
+ } else {
194
+ (mpInstance._Store as Dictionary).mpid = obj.cu || 0;
195
+ }
196
+
197
+ obj.gs = obj.gs || ({} as IPersistenceMinified['gs']);
198
+
199
+ mpInstance._Store.sessionId =
200
+ obj.gs.sid || mpInstance._Store.sessionId;
201
+ mpInstance._Store.isEnabled =
202
+ typeof obj.gs.ie !== 'undefined'
203
+ ? obj.gs.ie
204
+ : mpInstance._Store.isEnabled;
205
+ mpInstance._Store.sessionAttributes =
206
+ obj.gs.sa || mpInstance._Store.sessionAttributes;
207
+ mpInstance._Store.localSessionAttributes =
208
+ obj.gs.lsa || mpInstance._Store.localSessionAttributes;
209
+ mpInstance._Store.serverSettings =
210
+ obj.gs.ss || mpInstance._Store.serverSettings;
211
+ mpInstance._Store.devToken =
212
+ mpInstance._Store.devToken || obj.gs.dt;
213
+ mpInstance._Store.SDKConfig.appVersion =
214
+ mpInstance._Store.SDKConfig.appVersion || obj.gs.av;
215
+ mpInstance._Store.clientId =
216
+ obj.gs.cgid ||
217
+ mpInstance._Store.clientId ||
218
+ mpInstance._Helpers.generateUniqueId();
219
+
220
+ // For most persistence values, we prioritize localstorage/cookie values over
221
+ // Store. However, we allow device ID to be overriden via a config value and
222
+ // thus the priority of the deviceId value is
223
+ // 1. value passed via config.deviceId
224
+ // 2. previous value in persistence
225
+ // 3. generate new guid
226
+ mpInstance._Store.deviceId =
227
+ mpInstance._Store.deviceId ||
228
+ obj.gs.das ||
229
+ mpInstance._Helpers.generateUniqueId();
230
+
231
+ mpInstance._Store.integrationAttributes = obj.gs.ia || {};
232
+ mpInstance._Store.context = obj.gs.c || mpInstance._Store.context;
233
+ mpInstance._Store.currentSessionMPIDs =
234
+ obj.gs.csm || mpInstance._Store.currentSessionMPIDs;
235
+
236
+ mpInstance._Store.isLoggedIn = obj.l === true;
237
+
238
+ if (obj.gs.les) {
239
+ mpInstance._Store.dateLastEventSent = new Date(obj.gs.les);
240
+ }
241
+
242
+ mpInstance._Store.sessionStartDate = obj.gs.ssd
243
+ ? new Date(obj.gs.ssd)
244
+ : new Date();
245
+
246
+ if (currentMPID) {
247
+ obj = obj[currentMPID];
248
+ } else {
249
+ obj = obj[obj.cu];
250
+ }
251
+ }
252
+
253
+ // https://go.mparticle.com/work/SQDSDKS-6045
254
+ this.storeDataInMemory = function(obj: IPersistenceMinified, currentMPID?: MPID): void {
255
+ try {
256
+ if (!obj) {
257
+ applyEmptyPersistenceDefaults();
258
+ return;
259
+ }
260
+ hydrateStoreFromPersistence(obj, currentMPID);
261
+ } catch (e) {
262
+ mpInstance.Logger.error(Messages.ErrorMessages.CookieParseError);
263
+ }
264
+ };
265
+
266
+ // https://go.mparticle.com/work/SQDSDKS-5022
267
+ this.determineLocalStorageAvailability = function(
268
+ storage?: Storage
269
+ ): boolean {
270
+ const mParticleManager = getMParticleManager();
271
+ if (mParticleManager?._forceNoLocalStorage) {
272
+ return false;
273
+ }
274
+
275
+ try {
276
+ storage.setItem('mparticle', 'test');
277
+ const result = storage.getItem('mparticle') === 'test';
278
+ storage.removeItem('mparticle');
279
+ return Boolean(result && storage);
280
+ } catch (e) {
281
+ return false;
282
+ }
283
+ };
284
+
285
+ // https://go.mparticle.com/work/SQDSDKS-6021
286
+ this.setLocalStorage = function(): void {
287
+ if (!mpInstance._Store.isLocalStorageAvailable) {
288
+ return;
289
+ }
290
+
291
+ // Block mprtcl-v4 localStorage when noFunctional is true
292
+ if (mpInstance._CookieConsentManager?.getNoFunctional()) {
293
+ return;
294
+ }
295
+
296
+ let key = mpInstance._Store.storageName,
297
+ localStorageData = self.getLocalStorage() || ({} as IPersistenceMinified),
298
+ currentUser = mpInstance.Identity.getCurrentUser(),
299
+ mpid = currentUser ? currentUser.getMPID() : null;
300
+ if (!mpInstance._Store.SDKConfig.useCookieStorage) {
301
+ localStorageData.gs = localStorageData.gs || ({} as IPersistenceMinified['gs']);
302
+
303
+ localStorageData.l = mpInstance._Store.isLoggedIn ? 1 : 0;
304
+
305
+ if (mpInstance._Store.sessionId) {
306
+ localStorageData.gs.csm = mpInstance._Store.currentSessionMPIDs;
307
+ }
308
+
309
+ localStorageData.gs.ie = mpInstance._Store.isEnabled;
310
+
311
+ if (mpid) {
312
+ localStorageData.cu = mpid;
313
+ }
314
+
315
+ if (Object.keys(mpInstance._Store.nonCurrentUserMPIDs).length) {
316
+ localStorageData = Utils.extend(
317
+ {},
318
+ localStorageData,
319
+ mpInstance._Store.nonCurrentUserMPIDs
320
+ );
321
+ mpInstance._Store.nonCurrentUserMPIDs = {};
322
+ }
323
+
324
+ localStorageData = setGlobalStorageAttributes(localStorageData);
325
+
326
+ try {
327
+ window.localStorage.setItem(
328
+ encodeURIComponent(key),
329
+ self.encodePersistence(JSON.stringify(localStorageData))
330
+ );
331
+ } catch (e) {
332
+ mpInstance.Logger.error(
333
+ 'Error with setting localStorage item.'
334
+ );
335
+ }
336
+ }
337
+ };
338
+
339
+ function setGlobalStorageAttributes(data: IPersistenceMinified): IPersistenceMinified {
340
+ let store = mpInstance._Store;
341
+ data.gs.sid = store.sessionId;
342
+ data.gs.ie = store.isEnabled;
343
+ data.gs.sa = store.sessionAttributes;
344
+ data.gs.lsa = store.localSessionAttributes;
345
+ data.gs.ss = store.serverSettings;
346
+ data.gs.dt = store.devToken;
347
+ data.gs.les = store.dateLastEventSent
348
+ ? store.dateLastEventSent.getTime()
349
+ : null;
350
+ data.gs.av = store.SDKConfig.appVersion;
351
+ data.gs.cgid = store.clientId;
352
+ data.gs.das = store.deviceId;
353
+ data.gs.c = store.context;
354
+ data.gs.ssd = store.sessionStartDate
355
+ ? store.sessionStartDate.getTime()
356
+ : 0;
357
+ data.gs.ia = store.integrationAttributes;
358
+
359
+ return data;
360
+ }
361
+
362
+ this.getLocalStorage = function(): IPersistenceMinified | null {
363
+ if (!mpInstance._Store.isLocalStorageAvailable) {
364
+ return null;
365
+ }
366
+
367
+ const key = mpInstance._Store.storageName;
368
+ const decodedPersistence = self.decodePersistence(
369
+ window.localStorage.getItem(key)
370
+ );
371
+
372
+ if (!decodedPersistence) {
373
+ return null;
374
+ }
375
+
376
+ const parsedPersistence = JSON.parse(
377
+ decodedPersistence
378
+ ) as IPersistenceMinified;
379
+ const obj: IPersistenceMinified = {} as IPersistenceMinified;
380
+
381
+ for (const key in parsedPersistence) {
382
+ if (parsedPersistence.hasOwnProperty(key)) {
383
+ obj[key] = parsedPersistence[key];
384
+ }
385
+ }
386
+
387
+ if (Object.keys(obj).length) {
388
+ return obj;
389
+ }
390
+
391
+ return null;
392
+ };
393
+
394
+ this.expireCookies = function(cookieName: string): void {
395
+ let date = new Date(),
396
+ expires,
397
+ domain,
398
+ cookieDomain;
399
+
400
+ cookieDomain = self.getCookieDomain();
401
+
402
+ if (cookieDomain === '') {
403
+ domain = '';
404
+ } else {
405
+ domain = ';domain=' + cookieDomain;
406
+ }
407
+
408
+ date.setTime(date.getTime() - 24 * 60 * 60 * 1000);
409
+ expires = '; expires=' + date.toUTCString();
410
+ document.cookie = cookieName + '=' + '' + expires + '; path=/' + domain;
411
+ };
412
+
413
+ this.getCookie = function(): IPersistenceMinified | null {
414
+ let cookies,
415
+ key = mpInstance._Store.storageName,
416
+ i,
417
+ l,
418
+ parts,
419
+ name,
420
+ cookie,
421
+ result: string | Dictionary<string> | undefined = key
422
+ ? undefined
423
+ : {};
424
+
425
+ mpInstance.Logger.verbose(Messages.InformationMessages.CookieSearch);
426
+
427
+ try {
428
+ cookies = window.document.cookie.split('; ');
429
+ } catch (e) {
430
+ mpInstance.Logger.verbose('Unable to parse undefined cookie');
431
+ return null;
432
+ }
433
+
434
+ for (i = 0, l = cookies.length; i < l; i++) {
435
+ try {
436
+ parts = cookies[i].split('=');
437
+ name = parts.shift();
438
+ cookie = parts.join('=');
439
+ } catch (e) {
440
+ mpInstance.Logger.verbose(
441
+ 'Unable to parse cookie: ' + name + '. Skipping.'
442
+ );
443
+ }
444
+
445
+ if (key && key === name) {
446
+ result = (mpInstance._Helpers as Dictionary).converted(cookie);
447
+ break;
448
+ }
449
+
450
+ if (!key) {
451
+ (result as Dictionary<string>)[name as string] = (
452
+ mpInstance._Helpers as Dictionary
453
+ ).converted(cookie);
454
+ }
455
+ }
456
+
457
+ if (result) {
458
+ mpInstance.Logger.verbose(Messages.InformationMessages.CookieFound);
459
+ return JSON.parse(
460
+ self.decodePersistence(result as string) as string
461
+ );
462
+ } else {
463
+ return null;
464
+ }
465
+ };
466
+
467
+ // https://go.mparticle.com/work/SQDSDKS-5022
468
+ // https://go.mparticle.com/work/SQDSDKS-6021
469
+ this.setCookie = function(): void {
470
+ // Block mprtcl-v4 cookies when noFunctional is true
471
+ if (mpInstance._CookieConsentManager?.getNoFunctional()) {
472
+ return;
473
+ }
474
+
475
+ let mpid,
476
+ currentUser = mpInstance.Identity.getCurrentUser();
477
+ if (currentUser) {
478
+ mpid = currentUser.getMPID();
479
+ }
480
+ let date = new Date(),
481
+ key = mpInstance._Store.storageName,
482
+ cookies = self.getCookie() || ({} as IPersistenceMinified),
483
+ expires = new Date(
484
+ date.getTime() +
485
+ mpInstance._Store.SDKConfig.cookieExpiration *
486
+ 24 *
487
+ 60 *
488
+ 60 *
489
+ 1000
490
+ ).toUTCString(),
491
+ cookieDomain,
492
+ domain,
493
+ encodedCookiesWithExpirationAndPath;
494
+
495
+ cookieDomain = self.getCookieDomain();
496
+
497
+ if (cookieDomain === '') {
498
+ domain = '';
499
+ } else {
500
+ domain = ';domain=' + cookieDomain;
501
+ }
502
+
503
+ cookies.gs = cookies.gs || ({} as IPersistenceMinified['gs']);
504
+
505
+ if (mpInstance._Store.sessionId) {
506
+ cookies.gs.csm = mpInstance._Store.currentSessionMPIDs;
507
+ }
508
+
509
+ if (mpid) {
510
+ cookies.cu = mpid;
511
+ }
512
+
513
+ cookies.l = mpInstance._Store.isLoggedIn ? 1 : 0;
514
+
515
+ cookies = setGlobalStorageAttributes(cookies);
516
+
517
+ if (Object.keys(mpInstance._Store.nonCurrentUserMPIDs).length) {
518
+ cookies = Utils.extend(
519
+ {},
520
+ cookies,
521
+ mpInstance._Store.nonCurrentUserMPIDs
522
+ );
523
+ mpInstance._Store.nonCurrentUserMPIDs = {};
524
+ }
525
+
526
+ encodedCookiesWithExpirationAndPath = self.reduceAndEncodePersistence(
527
+ cookies,
528
+ expires,
529
+ domain,
530
+ mpInstance._Store.SDKConfig.maxCookieSize
531
+ );
532
+
533
+ mpInstance.Logger.verbose(Messages.InformationMessages.CookieSet);
534
+
535
+ window.document.cookie =
536
+ encodeURIComponent(key) + '=' + encodedCookiesWithExpirationAndPath;
537
+ };
538
+
539
+ /* This function determines if a cookie is greater than the configured maxCookieSize.
540
+ - If it is, we remove an MPID and its associated UI/UA/CSD from the cookie.
541
+ - Once removed, check size, and repeat.
542
+ - Never remove the currentUser's MPID from the cookie.
543
+
544
+ MPID removal priority:
545
+ 1. If there are no currentSessionMPIDs, remove a random MPID from the the cookie.
546
+ 2. If there are currentSessionMPIDs:
547
+ a. Remove at random MPIDs on the cookie that are not part of the currentSessionMPIDs
548
+ b. Then remove MPIDs based on order in currentSessionMPIDs array, which
549
+ stores MPIDs based on earliest login.
550
+ */
551
+ function isEncodedCookieTooLarge(
552
+ encoded: string,
553
+ maxCookieSize: number
554
+ ): boolean {
555
+ return encoded.length > maxCookieSize;
556
+ }
557
+
558
+ function removeUnsessionedMpidsWhenOversized(
559
+ persistence: IPersistenceMinified,
560
+ expires: string,
561
+ domain: string,
562
+ maxCookieSize: number
563
+ ): string {
564
+ let encodedCookiesWithExpirationAndPath;
565
+ for (let key in persistence) {
566
+ if (!persistence.hasOwnProperty(key)) {
567
+ continue;
568
+ }
569
+ encodedCookiesWithExpirationAndPath = createFullEncodedCookie(
570
+ persistence,
571
+ expires,
572
+ domain
573
+ );
574
+ if (
575
+ !isEncodedCookieTooLarge(
576
+ encodedCookiesWithExpirationAndPath,
577
+ maxCookieSize
578
+ )
579
+ ) {
580
+ continue;
581
+ }
582
+ if (SDKv2NonMPIDCookieKeys[key] || key === persistence.cu) {
583
+ continue;
584
+ }
585
+ delete persistence[key];
586
+ }
587
+ return encodedCookiesWithExpirationAndPath;
588
+ }
589
+
590
+ function collectRemovableMpids(
591
+ persistence: IPersistenceMinified
592
+ ): Dictionary {
593
+ const MPIDsOnCookie: Dictionary = {};
594
+ for (let potentialMPID in persistence) {
595
+ if (!persistence.hasOwnProperty(potentialMPID)) {
596
+ continue;
597
+ }
598
+ if (
599
+ SDKv2NonMPIDCookieKeys[potentialMPID] ||
600
+ potentialMPID === persistence.cu
601
+ ) {
602
+ continue;
603
+ }
604
+ MPIDsOnCookie[potentialMPID] = 1;
605
+ }
606
+ return MPIDsOnCookie;
607
+ }
608
+
609
+ function removeMpidsNotInCurrentSession(
610
+ persistence: IPersistenceMinified,
611
+ MPIDsOnCookie: Dictionary,
612
+ currentSessionMPIDs: MPID[],
613
+ expires: string,
614
+ domain: string,
615
+ maxCookieSize: number
616
+ ): string {
617
+ let encodedCookiesWithExpirationAndPath;
618
+ for (let mpid in MPIDsOnCookie) {
619
+ encodedCookiesWithExpirationAndPath = createFullEncodedCookie(
620
+ persistence,
621
+ expires,
622
+ domain
623
+ );
624
+ if (
625
+ !isEncodedCookieTooLarge(
626
+ encodedCookiesWithExpirationAndPath,
627
+ maxCookieSize
628
+ )
629
+ ) {
630
+ continue;
631
+ }
632
+ if (!MPIDsOnCookie.hasOwnProperty(mpid)) {
633
+ continue;
634
+ }
635
+ if (currentSessionMPIDs.indexOf(mpid) === -1) {
636
+ delete persistence[mpid];
637
+ }
638
+ }
639
+ return encodedCookiesWithExpirationAndPath;
640
+ }
641
+
642
+ function logAndRemoveOversizedMpid(
643
+ persistence: IPersistenceMinified,
644
+ MPIDtoRemove: MPID,
645
+ maxCookieSize: number
646
+ ): void {
647
+ if (persistence[MPIDtoRemove]) {
648
+ mpInstance.Logger.verbose(
649
+ 'Size of new encoded cookie is larger than maxCookieSize setting of ' +
650
+ maxCookieSize +
651
+ '. Removing from cookie the earliest logged in MPID containing: ' +
652
+ JSON.stringify(persistence[MPIDtoRemove], null, 2)
653
+ );
654
+ delete persistence[MPIDtoRemove];
655
+ return;
656
+ }
657
+ mpInstance.Logger.error(
658
+ 'Unable to save MPID data to cookies because the resulting encoded cookie is larger than the maxCookieSize setting of ' +
659
+ maxCookieSize +
660
+ '. We recommend using a maxCookieSize of 1500.'
661
+ );
662
+ }
663
+
664
+ function removeCurrentSessionMpidsByAge(
665
+ persistence: IPersistenceMinified,
666
+ currentSessionMPIDs: MPID[],
667
+ expires: string,
668
+ domain: string,
669
+ maxCookieSize: number
670
+ ): string {
671
+ let encodedCookiesWithExpirationAndPath;
672
+ for (let i = 0; i < currentSessionMPIDs.length; i++) {
673
+ encodedCookiesWithExpirationAndPath = createFullEncodedCookie(
674
+ persistence,
675
+ expires,
676
+ domain
677
+ );
678
+ if (
679
+ !isEncodedCookieTooLarge(
680
+ encodedCookiesWithExpirationAndPath,
681
+ maxCookieSize
682
+ )
683
+ ) {
684
+ break;
685
+ }
686
+ logAndRemoveOversizedMpid(
687
+ persistence,
688
+ currentSessionMPIDs[i],
689
+ maxCookieSize
690
+ );
691
+ }
692
+ return encodedCookiesWithExpirationAndPath;
693
+ }
694
+
695
+ this.reduceAndEncodePersistence = function(
696
+ persistence: IPersistenceMinified,
697
+ expires: string,
698
+ domain: string,
699
+ maxCookieSize: number
700
+ ): string {
701
+ const currentSessionMPIDs = persistence.gs.csm
702
+ ? persistence.gs.csm
703
+ : [];
704
+ if (!currentSessionMPIDs.length) {
705
+ return removeUnsessionedMpidsWhenOversized(
706
+ persistence,
707
+ expires,
708
+ domain,
709
+ maxCookieSize
710
+ );
711
+ }
712
+
713
+ const MPIDsOnCookie = collectRemovableMpids(persistence);
714
+ if (Object.keys(MPIDsOnCookie).length) {
715
+ removeMpidsNotInCurrentSession(
716
+ persistence,
717
+ MPIDsOnCookie,
718
+ currentSessionMPIDs,
719
+ expires,
720
+ domain,
721
+ maxCookieSize
722
+ );
723
+ }
724
+ return removeCurrentSessionMpidsByAge(
725
+ persistence,
726
+ currentSessionMPIDs,
727
+ expires,
728
+ domain,
729
+ maxCookieSize
730
+ );
731
+ };
732
+
733
+ function createFullEncodedCookie(persistence: IPersistenceMinified, expires: string, domain: string): string {
734
+ return (
735
+ self.encodePersistence(JSON.stringify(persistence)) +
736
+ ';expires=' +
737
+ expires +
738
+ ';path=/' +
739
+ domain
740
+ );
741
+ }
742
+
743
+ function cookieUiMatchesRequestedIdentity(
744
+ cookieUIs,
745
+ requestedIdentityType: string,
746
+ requestedValue
747
+ ): boolean {
748
+ for (let cookieUIType in cookieUIs) {
749
+ if (
750
+ requestedIdentityType === cookieUIType &&
751
+ requestedValue === cookieUIs[cookieUIType]
752
+ ) {
753
+ return true;
754
+ }
755
+ }
756
+ return false;
757
+ }
758
+
759
+ function findMpidForRequestedIdentity(
760
+ persistence: IPersistenceMinified,
761
+ requestedIdentityType: string,
762
+ requestedValue
763
+ ) {
764
+ let matchedUser;
765
+ for (let key in persistence) {
766
+ // any value in persistence that has an MPID key will be an MPID to search through
767
+ // other keys on the cookie are currentSessionMPIDs and currentMPID which should not be searched
768
+ if (!persistence[key].mpid) {
769
+ continue;
770
+ }
771
+ if (
772
+ cookieUiMatchesRequestedIdentity(
773
+ persistence[key].ui,
774
+ requestedIdentityType,
775
+ requestedValue
776
+ )
777
+ ) {
778
+ matchedUser = key;
779
+ }
780
+ }
781
+ return matchedUser;
782
+ }
783
+
784
+ function findMpidMatchingIdentities(
785
+ persistence: IPersistenceMinified | null,
786
+ identityApiData: IdentityApiData
787
+ ) {
788
+ let matchedUser;
789
+ for (let requestedIdentityType in identityApiData.userIdentities) {
790
+ if (!persistence || !Object.keys(persistence).length) {
791
+ continue;
792
+ }
793
+ const match = findMpidForRequestedIdentity(
794
+ persistence,
795
+ requestedIdentityType,
796
+ identityApiData.userIdentities[requestedIdentityType]
797
+ );
798
+ if (match) {
799
+ matchedUser = match;
800
+ }
801
+ }
802
+ return matchedUser;
803
+ }
804
+
805
+ this.findPrevCookiesBasedOnUI = function(identityApiData: IdentityApiData): void {
806
+ const persistence = mpInstance._Persistence.getPersistence();
807
+ if (!identityApiData) {
808
+ return;
809
+ }
810
+ const matchedUser = findMpidMatchingIdentities(
811
+ persistence,
812
+ identityApiData
813
+ );
814
+ if (matchedUser) {
815
+ self.storeDataInMemory(persistence, matchedUser);
816
+ }
817
+ };
818
+
819
+ function isNonEmptyArrayOrObject(value): boolean {
820
+ if (Array.isArray(value)) {
821
+ return value.length > 0;
822
+ }
823
+ if (!mpInstance._Helpers.isObject(value)) {
824
+ return false;
825
+ }
826
+ return Object.keys(value).length > 0;
827
+ }
828
+
829
+ function encodeGsBase64Field(gs, key: string): void {
830
+ if (!gs[key] || !isNonEmptyArrayOrObject(gs[key])) {
831
+ delete gs[key];
832
+ return;
833
+ }
834
+ gs[key] = Base64.encode(JSON.stringify(gs[key]));
835
+ }
836
+
837
+ function encodeGlobalSettings(gs): void {
838
+ for (let key in gs) {
839
+ if (!gs.hasOwnProperty(key)) {
840
+ continue;
841
+ }
842
+ if (Base64CookieKeys[key]) {
843
+ encodeGsBase64Field(gs, key);
844
+ continue;
845
+ }
846
+ if (key === 'ie') {
847
+ gs[key] = gs[key] ? 1 : 0;
848
+ continue;
849
+ }
850
+ if (!gs[key]) {
851
+ delete gs[key];
852
+ }
853
+ }
854
+ }
855
+
856
+ function encodeMpidBase64Field(container, key: string): void {
857
+ const value = container[key];
858
+ if (
859
+ mpInstance._Helpers.isObject(value) &&
860
+ Object.keys(value).length
861
+ ) {
862
+ container[key] = Base64.encode(JSON.stringify(value));
863
+ return;
864
+ }
865
+ delete container[key];
866
+ }
867
+
868
+ function encodeMpidRecord(record): void {
869
+ for (let key in record) {
870
+ if (!record.hasOwnProperty(key)) {
871
+ continue;
872
+ }
873
+ if (Base64CookieKeys[key]) {
874
+ encodeMpidBase64Field(record, key);
875
+ }
876
+ }
877
+ }
878
+
879
+ function encodeMpidRecords(persistence): void {
880
+ for (let mpid in persistence) {
881
+ if (!persistence.hasOwnProperty(mpid)) {
882
+ continue;
883
+ }
884
+ if (SDKv2NonMPIDCookieKeys[mpid]) {
885
+ continue;
886
+ }
887
+ encodeMpidRecord(persistence[mpid]);
888
+ }
889
+ }
890
+
891
+ this.encodePersistence = function(persistenceString: string): string {
892
+ const persistence = JSON.parse(
893
+ persistenceString
894
+ ) as IPersistenceMinified;
895
+ encodeGlobalSettings(persistence.gs);
896
+ encodeMpidRecords(persistence);
897
+ return Utils.createCookieString(JSON.stringify(persistence));
898
+ };
899
+
900
+ function decodeGlobalSettings(gs): void {
901
+ for (let key in gs) {
902
+ if (!gs.hasOwnProperty(key)) {
903
+ continue;
904
+ }
905
+ if (Base64CookieKeys[key]) {
906
+ gs[key] = JSON.parse(Base64.decode(gs[key]));
907
+ continue;
908
+ }
909
+ if (key === 'ie') {
910
+ gs[key] = Boolean(gs[key]);
911
+ }
912
+ }
913
+ }
914
+
915
+ function decodeMpidRecord(record): void {
916
+ for (let key in record) {
917
+ if (!record.hasOwnProperty(key)) {
918
+ continue;
919
+ }
920
+ if (!Base64CookieKeys[key]) {
921
+ continue;
922
+ }
923
+ if (record[key].length) {
924
+ record[key] = JSON.parse(Base64.decode(record[key]));
925
+ }
926
+ }
927
+ }
928
+
929
+ function decodeMpidRecords(persistence): void {
930
+ for (let mpid in persistence) {
931
+ if (!persistence.hasOwnProperty(mpid)) {
932
+ continue;
933
+ }
934
+ if (!SDKv2NonMPIDCookieKeys[mpid]) {
935
+ decodeMpidRecord(persistence[mpid]);
936
+ continue;
937
+ }
938
+ if (mpid === 'l') {
939
+ persistence[mpid] = Boolean(persistence[mpid]);
940
+ }
941
+ }
942
+ }
943
+
944
+ // TODO: This should actually be decodePersistenceString or
945
+ // we should refactor this to take a string and return an object
946
+ this.decodePersistence = function(
947
+ persistenceString: string | null
948
+ ): string | void {
949
+ try {
950
+ if (!persistenceString) {
951
+ return;
952
+ }
953
+ const persistence = JSON.parse(
954
+ Utils.revertCookieString(persistenceString)
955
+ ) as IPersistenceMinified;
956
+ if (
957
+ mpInstance._Helpers.isObject(persistence) &&
958
+ Object.keys(persistence).length
959
+ ) {
960
+ decodeGlobalSettings(persistence.gs);
961
+ decodeMpidRecords(persistence);
962
+ }
963
+ return JSON.stringify(persistence);
964
+ } catch (e) {
965
+ mpInstance.Logger.error('Problem with decoding cookie');
966
+ }
967
+ };
968
+
969
+ this.getCookieDomain = function(): string {
970
+ if (mpInstance._Store.SDKConfig.cookieDomain) {
971
+ return mpInstance._Store.SDKConfig.cookieDomain;
972
+ } else {
973
+ let rootDomain = self.getDomain(document, location.hostname);
974
+ if (rootDomain === '') {
975
+ return '';
976
+ } else {
977
+ return '.' + rootDomain;
978
+ }
979
+ }
980
+ };
981
+
982
+ // This function loops through the parts of a full hostname, attempting to set a cookie on that domain. It will set a cookie at the highest level possible.
983
+ // For example subdomain.domain.co.uk would try the following combinations:
984
+ // "co.uk" -> fail
985
+ // "domain.co.uk" -> success, return
986
+ // "subdomain.domain.co.uk" -> skipped, because already found
987
+ this.getDomain = function(doc: Document, locationHostname: string): string {
988
+ let i,
989
+ testParts,
990
+ mpTest = 'mptest=cookie',
991
+ hostname = locationHostname.split('.');
992
+ for (i = hostname.length - 1; i >= 0; i--) {
993
+ testParts = hostname.slice(i).join('.');
994
+ doc.cookie = mpTest + ';domain=.' + testParts + ';';
995
+ if (doc.cookie.indexOf(mpTest) > -1) {
996
+ doc.cookie =
997
+ mpTest.split('=')[0] +
998
+ '=;domain=.' +
999
+ testParts +
1000
+ ';expires=Thu, 01 Jan 1970 00:00:01 GMT;';
1001
+ return testParts;
1002
+ }
1003
+ }
1004
+ return '';
1005
+ };
1006
+
1007
+ this.saveUserCookieSyncDatesToPersistence = function(mpid: MPID, csd: CookieSyncDates): void {
1008
+ if (csd) {
1009
+ let persistence = self.getPersistence();
1010
+ if (persistence) {
1011
+ if (persistence[mpid]) {
1012
+ persistence[mpid].csd = csd;
1013
+ } else {
1014
+ persistence[mpid] = {
1015
+ csd: csd,
1016
+ };
1017
+ }
1018
+ }
1019
+ self.savePersistence(persistence);
1020
+ }
1021
+ };
1022
+
1023
+ this.swapCurrentUser = function(
1024
+ previousMPID: MPID,
1025
+ currentMPID: MPID,
1026
+ currentSessionMPIDs?: MPID[]
1027
+ ): void {
1028
+ if (previousMPID && currentMPID && previousMPID !== currentMPID) {
1029
+ let persistence = self.getPersistence();
1030
+ if (persistence) {
1031
+ persistence.cu = currentMPID;
1032
+ persistence.gs.csm = currentSessionMPIDs;
1033
+ self.savePersistence(persistence);
1034
+ }
1035
+ }
1036
+ };
1037
+
1038
+ // https://go.mparticle.com/work/SQDSDKS-6021
1039
+ this.savePersistence = function(persistence: IPersistenceMinified): void {
1040
+ // Block mprtcl-v4 persistence when noFunctional is true
1041
+ if (mpInstance._CookieConsentManager?.getNoFunctional()) {
1042
+ return;
1043
+ }
1044
+
1045
+ let encodedPersistence = self.encodePersistence(
1046
+ JSON.stringify(persistence)
1047
+ ),
1048
+ date = new Date(),
1049
+ key = mpInstance._Store.storageName,
1050
+ expires = new Date(
1051
+ date.getTime() +
1052
+ mpInstance._Store.SDKConfig.cookieExpiration *
1053
+ 24 *
1054
+ 60 *
1055
+ 60 *
1056
+ 1000
1057
+ ).toUTCString(),
1058
+ cookieDomain = self.getCookieDomain(),
1059
+ domain;
1060
+
1061
+ if (cookieDomain === '') {
1062
+ domain = '';
1063
+ } else {
1064
+ domain = ';domain=' + cookieDomain;
1065
+ }
1066
+
1067
+ if (mpInstance._Store.SDKConfig.useCookieStorage) {
1068
+ let encodedCookiesWithExpirationAndPath = self.reduceAndEncodePersistence(
1069
+ persistence,
1070
+ expires,
1071
+ domain,
1072
+ mpInstance._Store.SDKConfig.maxCookieSize
1073
+ );
1074
+ window.document.cookie =
1075
+ encodeURIComponent(key) +
1076
+ '=' +
1077
+ encodedCookiesWithExpirationAndPath;
1078
+ } else {
1079
+ if (mpInstance._Store.isLocalStorageAvailable) {
1080
+ try {
1081
+ localStorage.setItem(
1082
+ mpInstance._Store.storageName,
1083
+ encodedPersistence
1084
+ );
1085
+ } catch (e) {
1086
+ mpInstance.Logger.error(
1087
+ 'Error saving persistence to localStorage.'
1088
+ );
1089
+ }
1090
+ }
1091
+ }
1092
+ };
1093
+
1094
+ this.getPersistence = function(): IPersistenceMinified | null {
1095
+ let persistence = this.useLocalStorage()
1096
+ ? this.getLocalStorage()
1097
+ : this.getCookie();
1098
+
1099
+ return persistence;
1100
+ };
1101
+
1102
+ this.getFirstSeenTime = function(mpid: MPID): number | null {
1103
+ if (!mpid) {
1104
+ return null;
1105
+ }
1106
+ let persistence = self.getPersistence();
1107
+ if (persistence && persistence[mpid] && persistence[mpid].fst) {
1108
+ return persistence[mpid].fst;
1109
+ } else {
1110
+ return null;
1111
+ }
1112
+ };
1113
+
1114
+ /**
1115
+ * set the "first seen" time for a user. the time will only be set once for a given
1116
+ * mpid after which subsequent calls will be ignored
1117
+ */
1118
+ this.setFirstSeenTime = function(mpid: MPID, time?: number): void {
1119
+ if (!mpid) {
1120
+ return;
1121
+ }
1122
+ // https://go.mparticle.com/work/SQDSDKS-6329
1123
+ if (!time) {
1124
+ time = new Date().getTime();
1125
+ }
1126
+ let persistence = self.getPersistence();
1127
+ if (persistence) {
1128
+ if (!persistence[mpid]) {
1129
+ persistence[mpid] = {};
1130
+ }
1131
+ if (!persistence[mpid].fst) {
1132
+ persistence[mpid].fst = time;
1133
+ self.savePersistence(persistence);
1134
+ }
1135
+ }
1136
+ };
1137
+
1138
+ /**
1139
+ * returns the "last seen" time for a user. If the mpid represents the current user, the
1140
+ * return value will always be the current time, otherwise it will be to stored "last seen"
1141
+ * time
1142
+ */
1143
+ this.getLastSeenTime = function(mpid: MPID): number | null {
1144
+ if (!mpid) {
1145
+ return null;
1146
+ }
1147
+ if (mpid === mpInstance.Identity.getCurrentUser().getMPID()) {
1148
+ //if the mpid is the current user, its last seen time is the current time
1149
+ return new Date().getTime();
1150
+ } else {
1151
+ let persistence = self.getPersistence();
1152
+ if (persistence && persistence[mpid] && persistence[mpid].lst) {
1153
+ return persistence[mpid].lst;
1154
+ }
1155
+ return null;
1156
+ }
1157
+ };
1158
+
1159
+ this.setLastSeenTime = function(mpid: MPID, time?: number): void {
1160
+ if (!mpid) {
1161
+ return;
1162
+ }
1163
+ // https://go.mparticle.com/work/SQDSDKS-6329
1164
+ if (!time) {
1165
+ time = new Date().getTime();
1166
+ }
1167
+ let persistence = self.getPersistence();
1168
+ if (persistence && persistence[mpid]) {
1169
+ persistence[mpid].lst = time;
1170
+ self.savePersistence(persistence);
1171
+ }
1172
+ };
1173
+
1174
+ this.getDeviceId = function(): string {
1175
+ return mpInstance._Store.deviceId;
1176
+ };
1177
+
1178
+ this.setDeviceId = function(guid: string): void {
1179
+ mpInstance._Store.deviceId = guid;
1180
+ self.update();
1181
+ };
1182
+
1183
+ this.resetPersistence = function(): void {
1184
+ localStorage.clear();
1185
+
1186
+ self.expireCookies(StorageNames.cookieName);
1187
+ self.expireCookies(StorageNames.cookieNameV2);
1188
+ self.expireCookies(StorageNames.cookieNameV3);
1189
+ self.expireCookies(StorageNames.cookieNameV4);
1190
+ self.expireCookies(mpInstance._Store.storageName);
1191
+
1192
+ const mParticleManager = getMParticleManager();
1193
+ if (mParticleManager?._isTestEnv) {
1194
+ let testWorkspaceToken = 'abcdef';
1195
+ localStorage.removeItem(
1196
+ mpInstance._Helpers.createMainStorageName(testWorkspaceToken)
1197
+ );
1198
+ self.expireCookies(
1199
+ mpInstance._Helpers.createMainStorageName(testWorkspaceToken)
1200
+ );
1201
+ }
1202
+ };
1203
+
1204
+ // https://go.mparticle.com/work/SQDSDKS-6045
1205
+ // Forwarder Batching Code
1206
+ this.forwardingStatsBatches = {
1207
+ uploadsTable: {},
1208
+ forwardingStatsEventQueue: [],
1209
+ };
1210
+ }