mixpanel-react-native 3.2.0-beta.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -17
- package/README.md +10 -31
- package/android/src/main/java/com/mixpanel/reactnative/MixpanelReactNativeModule.java +2 -272
- package/index.d.ts +1 -64
- package/index.js +8 -103
- package/ios/MixpanelReactNative.m +1 -19
- package/ios/MixpanelReactNative.swift +5 -183
- package/javascript/mixpanel-main.js +1 -21
- package/javascript/mixpanel-network.js +41 -86
- package/javascript/mixpanel-persistent.js +4 -36
- package/javascript/mixpanel-storage.js +2 -2
- package/package.json +38 -18
- package/.github/dependabot.yml +0 -7
- package/FEATURE_FLAGS_JS_MODE_FINDINGS.md +0 -119
- package/FEATURE_FLAGS_QUICKSTART.md +0 -399
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/android/gradlew +0 -172
- package/android/gradlew.bat +0 -84
- package/javascript/mixpanel-flags-js.js +0 -465
- package/javascript/mixpanel-flags.js +0 -710
|
@@ -18,39 +18,16 @@ open class MixpanelReactNative: NSObject {
|
|
|
18
18
|
properties: [String: Any],
|
|
19
19
|
serverURL: String,
|
|
20
20
|
useGzipCompression: Bool = false,
|
|
21
|
-
featureFlagsOptions: [String: Any]?,
|
|
22
21
|
resolver resolve: RCTPromiseResolveBlock,
|
|
23
22
|
rejecter reject: RCTPromiseRejectBlock) -> Void {
|
|
24
23
|
let autoProps = properties // copy
|
|
25
24
|
AutomaticProperties.setAutomaticProperties(autoProps)
|
|
26
25
|
let propsProcessed = MixpanelTypeHandler.processProperties(properties: autoProps)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if let flagsOptions = featureFlagsOptions {
|
|
33
|
-
featureFlagsEnabled = flagsOptions["enabled"] as? Bool ?? false
|
|
34
|
-
featureFlagsContext = flagsOptions["context"] as? [String: Any]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Create MixpanelOptions with all configuration including feature flags
|
|
38
|
-
let options = MixpanelOptions(
|
|
39
|
-
token: token,
|
|
40
|
-
flushInterval: Constants.DEFAULT_FLUSH_INTERVAL,
|
|
41
|
-
instanceName: token,
|
|
42
|
-
trackAutomaticEvents: trackAutomaticEvents,
|
|
43
|
-
optOutTrackingByDefault: optOutTrackingByDefault,
|
|
44
|
-
useUniqueDistinctId: false,
|
|
45
|
-
superProperties: propsProcessed,
|
|
46
|
-
serverURL: serverURL,
|
|
47
|
-
proxyServerConfig: nil,
|
|
48
|
-
useGzipCompression: useGzipCompression,
|
|
49
|
-
featureFlagsEnabled: featureFlagsEnabled,
|
|
50
|
-
featureFlagsContext: featureFlagsContext ?? [:]
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
Mixpanel.initialize(options: options)
|
|
26
|
+
Mixpanel.initialize(token: token, trackAutomaticEvents: trackAutomaticEvents, flushInterval: Constants.DEFAULT_FLUSH_INTERVAL,
|
|
27
|
+
instanceName: token, optOutTrackingByDefault: optOutTrackingByDefault,
|
|
28
|
+
superProperties: propsProcessed,
|
|
29
|
+
serverURL: serverURL,
|
|
30
|
+
useGzipCompression: useGzipCompression)
|
|
54
31
|
resolve(true)
|
|
55
32
|
}
|
|
56
33
|
|
|
@@ -483,159 +460,4 @@ open class MixpanelReactNative: NSObject {
|
|
|
483
460
|
return Mixpanel.getInstance(name: token)
|
|
484
461
|
}
|
|
485
462
|
|
|
486
|
-
// MARK: - Feature Flags
|
|
487
|
-
|
|
488
|
-
@objc
|
|
489
|
-
func loadFlags(_ token: String,
|
|
490
|
-
resolver resolve: RCTPromiseResolveBlock,
|
|
491
|
-
rejecter reject: RCTPromiseRejectBlock) -> Void {
|
|
492
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
493
|
-
let flags = instance.flags else {
|
|
494
|
-
resolve(nil)
|
|
495
|
-
return
|
|
496
|
-
}
|
|
497
|
-
flags.loadFlags()
|
|
498
|
-
resolve(nil)
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
@objc
|
|
502
|
-
func areFlagsReadySync(_ token: String) -> NSNumber {
|
|
503
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token) else {
|
|
504
|
-
NSLog("[Mixpanel - areFlagsReadySync: instance is nil for token: \(token)]")
|
|
505
|
-
return NSNumber(value: false)
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
guard let flags = instance.flags else {
|
|
509
|
-
NSLog("[Mixpanel - areFlagsReadySync: flags is nil")
|
|
510
|
-
return NSNumber(value: false)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
let ready = flags.areFlagsReady()
|
|
514
|
-
NSLog("[Mixpanel - areFlagsReadySync: flags ready = \(ready)")
|
|
515
|
-
return NSNumber(value: ready)
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
@objc
|
|
519
|
-
func getVariantSync(_ token: String,
|
|
520
|
-
featureName: String,
|
|
521
|
-
fallback: [String: Any]) -> [String: Any] {
|
|
522
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
523
|
-
let flags = instance.flags else {
|
|
524
|
-
return fallback
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
let fallbackVariant = convertDictToVariant(fallback)
|
|
528
|
-
let variant = flags.getVariantSync(featureName, fallback: fallbackVariant)
|
|
529
|
-
return convertVariantToDict(variant)
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
@objc
|
|
533
|
-
func getVariantValueSync(_ token: String,
|
|
534
|
-
featureName: String,
|
|
535
|
-
fallbackValue: Any) -> Any {
|
|
536
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
537
|
-
let flags = instance.flags else {
|
|
538
|
-
return fallbackValue
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return flags.getVariantValueSync(featureName, fallbackValue: fallbackValue) ?? fallbackValue
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
@objc
|
|
545
|
-
func isEnabledSync(_ token: String,
|
|
546
|
-
featureName: String,
|
|
547
|
-
fallbackValue: Bool) -> NSNumber {
|
|
548
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
549
|
-
let flags = instance.flags else {
|
|
550
|
-
return NSNumber(value: fallbackValue)
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
let enabled = flags.isEnabledSync(featureName, fallbackValue: fallbackValue)
|
|
554
|
-
return NSNumber(value: enabled)
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
@objc
|
|
558
|
-
func getVariant(_ token: String,
|
|
559
|
-
featureName: String,
|
|
560
|
-
fallback: [String: Any],
|
|
561
|
-
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
562
|
-
rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
563
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
564
|
-
let flags = instance.flags else {
|
|
565
|
-
resolve(fallback)
|
|
566
|
-
return
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
let fallbackVariant = convertDictToVariant(fallback)
|
|
570
|
-
flags.getVariant(featureName, fallback: fallbackVariant) { variant in
|
|
571
|
-
resolve(self.convertVariantToDict(variant))
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
@objc
|
|
576
|
-
func getVariantValue(_ token: String,
|
|
577
|
-
featureName: String,
|
|
578
|
-
fallbackValue: Any,
|
|
579
|
-
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
580
|
-
rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
581
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
582
|
-
let flags = instance.flags else {
|
|
583
|
-
resolve(fallbackValue)
|
|
584
|
-
return
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
flags.getVariantValue(featureName, fallbackValue: fallbackValue) { value in
|
|
588
|
-
resolve(value)
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
@objc
|
|
593
|
-
func isEnabled(_ token: String,
|
|
594
|
-
featureName: String,
|
|
595
|
-
fallbackValue: Bool,
|
|
596
|
-
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
597
|
-
rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
598
|
-
guard let instance = MixpanelReactNative.getMixpanelInstance(token),
|
|
599
|
-
let flags = instance.flags else {
|
|
600
|
-
resolve(fallbackValue)
|
|
601
|
-
return
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
flags.isEnabled(featureName, fallbackValue: fallbackValue) { isEnabled in
|
|
605
|
-
resolve(isEnabled)
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Helper methods for variant conversion
|
|
610
|
-
private func convertDictToVariant(_ dict: [String: Any]) -> MixpanelFlagVariant {
|
|
611
|
-
let key = dict["key"] as? String ?? ""
|
|
612
|
-
let value = dict["value"] ?? NSNull()
|
|
613
|
-
let experimentID = dict["experimentID"] as? String
|
|
614
|
-
let isExperimentActive = dict["isExperimentActive"] as? Bool
|
|
615
|
-
let isQATester = dict["isQATester"] as? Bool
|
|
616
|
-
|
|
617
|
-
return MixpanelFlagVariant(
|
|
618
|
-
key: key,
|
|
619
|
-
value: value,
|
|
620
|
-
isExperimentActive: isExperimentActive,
|
|
621
|
-
isQATester: isQATester,
|
|
622
|
-
experimentID: experimentID
|
|
623
|
-
)
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
private func convertVariantToDict(_ variant: MixpanelFlagVariant) -> [String: Any] {
|
|
627
|
-
var dict: [String: Any] = [
|
|
628
|
-
"key": variant.key,
|
|
629
|
-
"value": variant.value ?? NSNull()
|
|
630
|
-
]
|
|
631
|
-
|
|
632
|
-
if let experimentID = variant.experimentID {
|
|
633
|
-
dict["experimentID"] = experimentID
|
|
634
|
-
}
|
|
635
|
-
dict["isExperimentActive"] = variant.isExperimentActive
|
|
636
|
-
dict["isQATester"] = variant.isQATester
|
|
637
|
-
|
|
638
|
-
return dict
|
|
639
|
-
}
|
|
640
|
-
|
|
641
463
|
}
|
|
@@ -21,17 +21,10 @@ export default class MixpanelMain {
|
|
|
21
21
|
trackAutomaticEvents = false,
|
|
22
22
|
optOutTrackingDefault = false,
|
|
23
23
|
superProperties = null,
|
|
24
|
-
serverURL = "https://api.mixpanel.com"
|
|
25
|
-
useGzipCompression = false,
|
|
26
|
-
featureFlagsOptions = {}
|
|
24
|
+
serverURL = "https://api.mixpanel.com"
|
|
27
25
|
) {
|
|
28
26
|
MixpanelLogger.log(token, `Initializing Mixpanel`);
|
|
29
27
|
|
|
30
|
-
// Store feature flags options for later use
|
|
31
|
-
this.featureFlagsOptions = featureFlagsOptions;
|
|
32
|
-
this.featureFlagsEnabled = featureFlagsOptions.enabled || false;
|
|
33
|
-
this.featureFlagsContext = featureFlagsOptions.context || {};
|
|
34
|
-
|
|
35
28
|
await this.mixpanelPersistent.initializationCompletePromise(token);
|
|
36
29
|
if (optOutTrackingDefault) {
|
|
37
30
|
await this.optOutTracking(token);
|
|
@@ -44,11 +37,6 @@ export default class MixpanelMain {
|
|
|
44
37
|
await this.registerSuperProperties(token, {
|
|
45
38
|
...superProperties,
|
|
46
39
|
});
|
|
47
|
-
|
|
48
|
-
// Initialize feature flags if enabled
|
|
49
|
-
if (this.featureFlagsEnabled) {
|
|
50
|
-
MixpanelLogger.log(token, "Feature flags enabled during initialization");
|
|
51
|
-
}
|
|
52
40
|
}
|
|
53
41
|
|
|
54
42
|
getMetaData() {
|
|
@@ -82,14 +70,6 @@ export default class MixpanelMain {
|
|
|
82
70
|
await this.mixpanelPersistent.reset(token);
|
|
83
71
|
}
|
|
84
72
|
|
|
85
|
-
/**
|
|
86
|
-
* Get the feature flags context that was provided during initialization
|
|
87
|
-
* @returns {object} The feature flags context object
|
|
88
|
-
*/
|
|
89
|
-
getFeatureFlagsContext() {
|
|
90
|
-
return this.featureFlagsContext || {};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
73
|
async track(token, eventName, properties) {
|
|
94
74
|
if (this.mixpanelPersistent.getOptedOut(token)) {
|
|
95
75
|
MixpanelLogger.log(
|
|
@@ -15,77 +15,37 @@ export const MixpanelNetwork = (() => {
|
|
|
15
15
|
serverURL,
|
|
16
16
|
useIPAddressForGeoLocation,
|
|
17
17
|
retryCount = 0,
|
|
18
|
-
headers = {},
|
|
19
18
|
}) => {
|
|
20
19
|
retryCount = retryCount || 0;
|
|
21
|
-
|
|
22
|
-
const separator = endpoint.includes('?') ? '&' : '?';
|
|
23
|
-
const url = `${serverURL}${endpoint}${separator}ip=${+useIPAddressForGeoLocation}`;
|
|
20
|
+
const url = `${serverURL}${endpoint}?ip=${+useIPAddressForGeoLocation}`;
|
|
24
21
|
MixpanelLogger.log(token, `Sending request to: ${url}`);
|
|
25
22
|
|
|
26
23
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
const response = await fetch(url, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: {
|
|
27
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
28
|
+
},
|
|
29
|
+
body: `data=${encodeURIComponent(JSON.stringify(data))}`,
|
|
30
|
+
});
|
|
29
31
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
method: "POST",
|
|
37
|
-
headers: {
|
|
38
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
39
|
-
...headers,
|
|
40
|
-
},
|
|
41
|
-
body: `data=${encodeURIComponent(JSON.stringify(data))}`,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const response = await fetch(url, fetchOptions);
|
|
45
|
-
|
|
46
|
-
// Handle GET requests differently - they return the data directly
|
|
47
|
-
if (isGetRequest) {
|
|
48
|
-
if (response.status === 200) {
|
|
49
|
-
const responseData = await response.json();
|
|
50
|
-
MixpanelLogger.log(token, `GET request successful: ${endpoint}`);
|
|
51
|
-
return responseData;
|
|
52
|
-
} else {
|
|
53
|
-
throw new MixpanelHttpError(
|
|
54
|
-
`HTTP error! status: ${response.status}`,
|
|
55
|
-
response.status
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
// Handle POST requests (existing logic)
|
|
60
|
-
const responseBody = await response.json();
|
|
61
|
-
if (response.status !== 200) {
|
|
62
|
-
throw new MixpanelHttpError(
|
|
63
|
-
`HTTP error! status: ${response.status}`,
|
|
64
|
-
response.status
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const message =
|
|
69
|
-
responseBody === 0
|
|
70
|
-
? `${url} api rejected some items`
|
|
71
|
-
: `Mixpanel batch sent successfully, endpoint: ${endpoint}, data: ${JSON.stringify(
|
|
72
|
-
data
|
|
73
|
-
)}`;
|
|
74
|
-
|
|
75
|
-
MixpanelLogger.log(token, message);
|
|
76
|
-
return responseBody;
|
|
32
|
+
const responseBody = await response.json();
|
|
33
|
+
if (response.status !== 200) {
|
|
34
|
+
throw new MixpanelHttpError(
|
|
35
|
+
`HTTP error! status: ${response.status}`,
|
|
36
|
+
response.status
|
|
37
|
+
);
|
|
77
38
|
}
|
|
78
|
-
} catch (error) {
|
|
79
|
-
// Determine if this is a GET or POST request
|
|
80
|
-
const isGetRequest = data === null || data === undefined;
|
|
81
39
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
40
|
+
const message =
|
|
41
|
+
responseBody === 0
|
|
42
|
+
? `${url} api rejected some items`
|
|
43
|
+
: `Mixpanel batch sent successfully, endpoint: ${endpoint}, data: ${JSON.stringify(
|
|
44
|
+
data
|
|
45
|
+
)}`;
|
|
87
46
|
|
|
88
|
-
|
|
47
|
+
MixpanelLogger.log(token, message);
|
|
48
|
+
} catch (error) {
|
|
89
49
|
if (error.code === 400) {
|
|
90
50
|
// This indicates that the data was invalid and we should not retry
|
|
91
51
|
throw new MixpanelHttpError(
|
|
@@ -93,35 +53,30 @@ export const MixpanelNetwork = (() => {
|
|
|
93
53
|
error.code
|
|
94
54
|
);
|
|
95
55
|
}
|
|
96
|
-
|
|
97
56
|
MixpanelLogger.warn(
|
|
98
57
|
token,
|
|
99
58
|
`API request to ${url} has failed with reason: ${error.message}`
|
|
100
59
|
);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
60
|
+
const maxRetries = 5;
|
|
61
|
+
const backoff = Math.min(2 ** retryCount * 2000, 60000); // Exponential backoff
|
|
62
|
+
if (retryCount < maxRetries) {
|
|
63
|
+
MixpanelLogger.log(token, `Retrying in ${backoff / 1000} seconds...`);
|
|
64
|
+
await new Promise((resolve) => setTimeout(resolve, backoff));
|
|
65
|
+
return sendRequest({
|
|
66
|
+
token,
|
|
67
|
+
endpoint,
|
|
68
|
+
data,
|
|
69
|
+
serverURL,
|
|
70
|
+
useIPAddressForGeoLocation,
|
|
71
|
+
retryCount: retryCount + 1,
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
MixpanelLogger.warn(token, `Max retries reached. Giving up.`);
|
|
75
|
+
throw new MixpanelHttpError(
|
|
76
|
+
`HTTP error! status: ${error.code}`,
|
|
77
|
+
error.code
|
|
78
|
+
);
|
|
118
79
|
}
|
|
119
|
-
|
|
120
|
-
MixpanelLogger.warn(token, `Request failed. Not retrying.`);
|
|
121
|
-
throw new MixpanelHttpError(
|
|
122
|
-
`HTTP error! status: ${error.code || 'unknown'}`,
|
|
123
|
-
error.code
|
|
124
|
-
);
|
|
125
80
|
}
|
|
126
81
|
};
|
|
127
82
|
|
|
@@ -11,41 +11,9 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import "react-native-get-random-values"; // Polyfill for crypto.getRandomValues
|
|
13
13
|
import { AsyncStorageAdapter } from "./mixpanel-storage";
|
|
14
|
-
import
|
|
14
|
+
import { v4 as uuidv4 } from "uuid";
|
|
15
15
|
import { MixpanelLogger } from "mixpanel-react-native/javascript/mixpanel-logger";
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* Generate a UUID v4, with cross-platform fallbacks
|
|
19
|
-
* Tries: uuid package → Web Crypto API → manual generation
|
|
20
|
-
*/
|
|
21
|
-
function generateUUID() {
|
|
22
|
-
// Try uuid package first (works in React Native with polyfill)
|
|
23
|
-
try {
|
|
24
|
-
const result = uuid.v4();
|
|
25
|
-
if (result) return result;
|
|
26
|
-
} catch (e) {
|
|
27
|
-
// Fall through to alternatives
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Try Web Crypto API (modern browsers)
|
|
31
|
-
const cryptoObj =
|
|
32
|
-
(typeof globalThis !== "undefined" && globalThis.crypto) ||
|
|
33
|
-
(typeof window !== "undefined" && window.crypto) ||
|
|
34
|
-
(typeof crypto !== "undefined" && crypto);
|
|
35
|
-
|
|
36
|
-
if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
|
|
37
|
-
return cryptoObj.randomUUID();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Last resort: manual UUID v4 generation using Math.random
|
|
41
|
-
// Less secure but functional for device IDs
|
|
42
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
|
43
|
-
const r = (Math.random() * 16) | 0;
|
|
44
|
-
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
45
|
-
return v.toString(16);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
17
|
export class MixpanelPersistent {
|
|
50
18
|
static instance;
|
|
51
19
|
|
|
@@ -74,7 +42,7 @@ export class MixpanelPersistent {
|
|
|
74
42
|
}
|
|
75
43
|
|
|
76
44
|
async initializationCompletePromise(token) {
|
|
77
|
-
|
|
45
|
+
await Promise.all([
|
|
78
46
|
this.loadIdentity(token),
|
|
79
47
|
this.loadSuperProperties(token),
|
|
80
48
|
this.loadTimeEvents(token),
|
|
@@ -99,8 +67,8 @@ export class MixpanelPersistent {
|
|
|
99
67
|
this._identity[token].deviceId = storageToken;
|
|
100
68
|
|
|
101
69
|
if (!this._identity[token].deviceId) {
|
|
102
|
-
// Generate device ID with
|
|
103
|
-
this._identity[token].deviceId =
|
|
70
|
+
// Generate device ID using uuidv4() with polyfilled crypto.getRandomValues
|
|
71
|
+
this._identity[token].deviceId = uuidv4();
|
|
104
72
|
await this.storageAdapter.setItem(
|
|
105
73
|
getDeviceIdKey(token),
|
|
106
74
|
this._identity[token].deviceId
|
|
@@ -12,9 +12,9 @@ export class AsyncStorageAdapter {
|
|
|
12
12
|
}
|
|
13
13
|
} catch {
|
|
14
14
|
console.error(
|
|
15
|
-
"[
|
|
15
|
+
"[Mixpanel] AsyncStorage not available. Install @react-native-async-storage/async-storage (^1.15.0 or ^2.0.0), or provide a custom storage implementation. See: https://github.com/mixpanel/mixpanel-react-native#readme"
|
|
16
16
|
);
|
|
17
|
-
console.error("[Mixpanel] Falling back to in-memory storage");
|
|
17
|
+
console.error("[Mixpanel] Falling back to in-memory storage. Data will not persist across app restarts.");
|
|
18
18
|
this.storage = new InMemoryStorage();
|
|
19
19
|
}
|
|
20
20
|
} else {
|
package/package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixpanel-react-native",
|
|
3
|
-
"version": "3.2.0
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Official React Native Tracking Library for Mixpanel Analytics",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"index.js",
|
|
8
|
+
"index.d.ts",
|
|
9
|
+
"javascript/",
|
|
10
|
+
"ios/",
|
|
11
|
+
"android/src/",
|
|
12
|
+
"android/build.gradle",
|
|
13
|
+
"MixpanelReactNative.podspec",
|
|
14
|
+
"react-native.config.js",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE.md",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
6
19
|
"scripts": {
|
|
7
20
|
"test": "jest"
|
|
8
21
|
},
|
|
@@ -31,18 +44,17 @@
|
|
|
31
44
|
"mp_lib": "react-native"
|
|
32
45
|
},
|
|
33
46
|
"devDependencies": {
|
|
34
|
-
"@
|
|
35
|
-
"@babel/
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
47
|
+
"@react-native-async-storage/async-storage": "^1.24.0",
|
|
48
|
+
"@babel/core": "^7.26.0",
|
|
49
|
+
"@babel/runtime": "^7.26.0",
|
|
50
|
+
"@react-native-community/eslint-config": "^3.2.0",
|
|
51
|
+
"babel-jest": "^29.7.0",
|
|
52
|
+
"eslint": "^8.57.0",
|
|
53
|
+
"jest": "^29.7.0",
|
|
40
54
|
"jest-fetch-mock": "^3.0.3",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"react-
|
|
44
|
-
"react-native-dotenv": "^3.4.11",
|
|
45
|
-
"react-test-renderer": "16.13.1"
|
|
55
|
+
"metro-react-native-babel-preset": "^0.77.0",
|
|
56
|
+
"react-native": "^0.76.0",
|
|
57
|
+
"react-test-renderer": "^18.3.1"
|
|
46
58
|
},
|
|
47
59
|
"jest": {
|
|
48
60
|
"modulePathIgnorePatterns": [
|
|
@@ -56,13 +68,21 @@
|
|
|
56
68
|
],
|
|
57
69
|
"verbose": true,
|
|
58
70
|
"preset": "react-native",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
71
|
+
"transformIgnorePatterns": [
|
|
72
|
+
"node_modules/(?!(@react-native|react-native|react-native-get-random-values|uuid)/)"
|
|
73
|
+
],
|
|
74
|
+
"testEnvironment": "node"
|
|
62
75
|
},
|
|
63
76
|
"dependencies": {
|
|
64
|
-
"@react-native-async-storage/async-storage": "^1.24.0",
|
|
65
77
|
"react-native-get-random-values": "^1.9.0",
|
|
66
|
-
"uuid": "
|
|
78
|
+
"uuid": "^9.0.1"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"@react-native-async-storage/async-storage": "^1.15.0 || ^2.0.0"
|
|
82
|
+
},
|
|
83
|
+
"peerDependenciesMeta": {
|
|
84
|
+
"@react-native-async-storage/async-storage": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
67
87
|
}
|
|
68
|
-
}
|
|
88
|
+
}
|