Skip to content

Commit f7463b8

Browse files
authored
Merge pull request #549 from mixpanel/multi-instances-fixes
add support for multiple instances under the same token
2 parents 3e18f03 + aa7c38e commit f7463b8

File tree

7 files changed

+162
-86
lines changed

7 files changed

+162
-86
lines changed

MixpanelDemo/MixpanelDemoTests/MixpanelBaseTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,19 @@ class MixpanelBaseTests: XCTestCase, MixpanelDelegate {
100100
}
101101

102102
func eventQueue(token: String) -> Queue {
103-
return MixpanelPersistence.init(token: token).loadEntitiesInBatch(type: .events)
103+
return MixpanelPersistence.init(instanceName: token).loadEntitiesInBatch(type: .events)
104104
}
105105

106106
func peopleQueue(token: String) -> Queue {
107-
return MixpanelPersistence.init(token: token).loadEntitiesInBatch(type: .people)
107+
return MixpanelPersistence.init(instanceName: token).loadEntitiesInBatch(type: .people)
108108
}
109109

110110
func unIdentifiedPeopleQueue(token: String) -> Queue {
111-
return MixpanelPersistence.init(token: token).loadEntitiesInBatch(type: .people, flag: PersistenceConstant.unIdentifiedFlag)
111+
return MixpanelPersistence.init(instanceName: token).loadEntitiesInBatch(type: .people, flag: PersistenceConstant.unIdentifiedFlag)
112112
}
113113

114114
func groupQueue(token: String) -> Queue {
115-
return MixpanelPersistence.init(token: token).loadEntitiesInBatch(type: .groups)
115+
return MixpanelPersistence.init(instanceName: token).loadEntitiesInBatch(type: .groups)
116116
}
117117

118118
func flushAndWaitForTrackingQueue(_ mixpanel: MixpanelInstance) {

MixpanelDemo/MixpanelDemoTests/MixpanelDemoTests.swift

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
312312
testMixpanel.createAlias(alias, distinctId: testMixpanel.distinctId)
313313
waitForTrackingQueue(testMixpanel)
314314

315-
let mixpanelIdentity = MixpanelPersistence.loadIdentity(apiToken: testMixpanel.apiToken)
315+
let mixpanelIdentity = MixpanelPersistence.loadIdentity(instanceName: testMixpanel.apiToken)
316316
XCTAssertTrue(distinctId == mixpanelIdentity.distinctID && distinctId == mixpanelIdentity.peopleDistinctID && distinctId == mixpanelIdentity.userId && alias == mixpanelIdentity.alias)
317317
removeDBfile(testMixpanel.apiToken)
318318
unidentifiedQueue = unIdentifiedPeopleQueue(token: testMixpanel.apiToken)
@@ -330,7 +330,7 @@ class MixpanelDemoTests: MixpanelBaseTests {
330330
let unidentifiedQueue2 = unIdentifiedPeopleQueue(token: testMixpanel2.apiToken)
331331
// The user profile updates should still be held in unidentifiedQueue cause no identify is called
332332
XCTAssertTrue(!unidentifiedQueue2.isEmpty)
333-
let mixpanelIdentity2 = MixpanelPersistence.loadIdentity(apiToken: testMixpanel2.apiToken)
333+
let mixpanelIdentity2 = MixpanelPersistence.loadIdentity(instanceName: testMixpanel2.apiToken)
334334
XCTAssertTrue(distinctId2 == mixpanelIdentity2.distinctID && nil == mixpanelIdentity2.peopleDistinctID && nil == mixpanelIdentity2.userId && alias == mixpanelIdentity2.alias)
335335
removeDBfile(testMixpanel2.apiToken)
336336
}
@@ -343,18 +343,18 @@ class MixpanelDemoTests: MixpanelBaseTests {
343343
waitForTrackingQueue(testMixpanel)
344344
testMixpanel.createAlias(alias, distinctId: testMixpanel.distinctId)
345345
waitForTrackingQueue(testMixpanel)
346-
var mixpanelIdentity = MixpanelPersistence.loadIdentity(apiToken: testMixpanel.apiToken)
346+
var mixpanelIdentity = MixpanelPersistence.loadIdentity(instanceName: testMixpanel.apiToken)
347347
XCTAssertTrue(distinctId == mixpanelIdentity.distinctID && distinctId == mixpanelIdentity.peopleDistinctID && distinctId == mixpanelIdentity.userId && alias == mixpanelIdentity.alias)
348348
testMixpanel.archive()
349349
waitForTrackingQueue(testMixpanel)
350350
testMixpanel.unarchive()
351351
waitForTrackingQueue(testMixpanel)
352-
mixpanelIdentity = MixpanelPersistence.loadIdentity(apiToken: testMixpanel.apiToken)
352+
mixpanelIdentity = MixpanelPersistence.loadIdentity(instanceName: testMixpanel.apiToken)
353353
XCTAssertTrue(testMixpanel.distinctId == mixpanelIdentity.distinctID && testMixpanel.people.distinctId == mixpanelIdentity.peopleDistinctID && testMixpanel.anonymousId == mixpanelIdentity.anonymousId &&
354354
testMixpanel.userId == mixpanelIdentity.userId && testMixpanel.alias == mixpanelIdentity.alias)
355-
MixpanelPersistence.deleteMPUserDefaultsData(apiToken: testMixpanel.apiToken)
355+
MixpanelPersistence.deleteMPUserDefaultsData(instanceName: testMixpanel.apiToken)
356356
waitForTrackingQueue(testMixpanel)
357-
mixpanelIdentity = MixpanelPersistence.loadIdentity(apiToken: testMixpanel.apiToken)
357+
mixpanelIdentity = MixpanelPersistence.loadIdentity(instanceName: testMixpanel.apiToken)
358358
XCTAssertTrue("" == mixpanelIdentity.distinctID && nil == mixpanelIdentity.peopleDistinctID && nil == mixpanelIdentity.anonymousId && nil == mixpanelIdentity.userId && nil == mixpanelIdentity.alias)
359359
removeDBfile(testMixpanel.apiToken)
360360
}
@@ -798,17 +798,17 @@ class MixpanelDemoTests: MixpanelBaseTests {
798798
testMixpanel.time(event: "Time Event B")
799799
testMixpanel.time(event: "Time Event C")
800800
waitForTrackingQueue(testMixpanel)
801-
var testTimedEvents = MixpanelPersistence.loadTimedEvents(apiToken: testMixpanel.apiToken)
801+
var testTimedEvents = MixpanelPersistence.loadTimedEvents(instanceName: testMixpanel.apiToken)
802802
XCTAssertTrue(testTimedEvents.count == 3, "Each call to time() should add an event to timedEvents")
803803
XCTAssertNotNil(testTimedEvents["Time Event A"], "Keys in timedEvents should be event names")
804804
testMixpanel.clearTimedEvent(event: "Time Event A")
805805
waitForTrackingQueue(testMixpanel)
806-
testTimedEvents = MixpanelPersistence.loadTimedEvents(apiToken: testMixpanel.apiToken)
806+
testTimedEvents = MixpanelPersistence.loadTimedEvents(instanceName: testMixpanel.apiToken)
807807
XCTAssertNil(testTimedEvents["Time Event A"], "clearTimedEvent should remove key/value pair")
808808
XCTAssertTrue(testTimedEvents.count == 2, "clearTimedEvent shoud remove only one key/value pair")
809809
testMixpanel.clearTimedEvents()
810810
waitForTrackingQueue(testMixpanel)
811-
XCTAssertTrue(MixpanelPersistence.loadTimedEvents(apiToken: testMixpanel.apiToken).count == 0, "clearTimedEvents should remove all key/value pairs")
811+
XCTAssertTrue(MixpanelPersistence.loadTimedEvents(instanceName: testMixpanel.apiToken).count == 0, "clearTimedEvents should remove all key/value pairs")
812812
removeDBfile(testMixpanel.apiToken)
813813
}
814814

@@ -933,6 +933,78 @@ class MixpanelDemoTests: MixpanelBaseTests {
933933
XCTAssertTrue(testMixpanel === testMixpanel2, "instance with same token should be reused and no sqlite db locked error should be populated")
934934
}
935935

936+
937+
func testMultipleInstancesWithSameTokenButDifferentInstanceNameShouldNotCrash() {
938+
let testToken = randomId()
939+
let concurentQueue = DispatchQueue(label: "multithread", attributes: .concurrent)
940+
941+
942+
for i in 1...10 {
943+
concurentQueue.async {
944+
let testMixpanel = Mixpanel.initialize(token: testToken, flushInterval: 60, instanceName: "instance\(i)")
945+
testMixpanel.loggingEnabled = true
946+
testMixpanel.track(event: "test")
947+
}
948+
}
949+
950+
sleep(5)
951+
XCTAssertTrue(true, "no sqlite db locked error should be populated")
952+
for j in 1...10 {
953+
removeDBfile("instance\(j)")
954+
}
955+
}
956+
957+
func testMultipleInstancesWithSameTokenButDifferentInstanceName() {
958+
let testToken = randomId()
959+
let instance1 = Mixpanel.initialize(token: testToken, flushInterval: 60, instanceName: "instance1")
960+
let instance2 = Mixpanel.initialize(token: testToken, flushInterval: 60, instanceName: "instance2")
961+
962+
XCTAssertNotEqual(instance1.distinctId, instance2.distinctId)
963+
instance1.identify(distinctId: "user1")
964+
instance1.track(event: "test")
965+
waitForTrackingQueue(instance1)
966+
XCTAssertEqual(instance1.distinctId, "user1")
967+
XCTAssertEqual(instance1.userId, "user1")
968+
let events = eventQueue(token: "instance1")
969+
let properties = events.last?["properties"] as? InternalProperties
970+
// event property should have the current distinct id
971+
XCTAssertEqual(properties?["distinct_id"] as? String, "user1")
972+
973+
instance1.people.set(property: "p1", to: "a")
974+
waitForTrackingQueue(instance1)
975+
976+
let peopleQueue_value = peopleQueue(token: "instance1")
977+
let setValue = peopleQueue_value.last!["$set"] as! InternalProperties
978+
XCTAssertEqual(setValue["p1"] as? String, "a", "custom people property not queued")
979+
980+
XCTAssertEqual(peopleQueue_value.last?["$distinct_id"] as? String,
981+
"user1", "distinct id not set properly on the people record")
982+
983+
instance2.identify(distinctId: "user2")
984+
instance2.track(event: "test2")
985+
waitForTrackingQueue(instance2)
986+
XCTAssertEqual(instance2.distinctId, "user2")
987+
XCTAssertEqual(instance2.userId, "user2")
988+
let events2 = eventQueue(token: "instance2")
989+
let properties2 = events2.last?["properties"] as? InternalProperties
990+
// event property should have the current distinct id
991+
XCTAssertEqual(properties2?["distinct_id"] as? String, "user2")
992+
993+
instance2.people.set(property: "p2", to: "b")
994+
waitForTrackingQueue(instance2)
995+
996+
let peopleQueue2_value = peopleQueue(token: "instance2")
997+
XCTAssertEqual(peopleQueue2_value.last?["$distinct_id"] as? String,
998+
"user2", "distinct id not set properly on the people record")
999+
1000+
let setValue2 = peopleQueue2_value.last!["$set"] as! InternalProperties
1001+
XCTAssertEqual(setValue2["p2"] as? String, "b", "custom people property not queued")
1002+
1003+
removeDBfile("instance1")
1004+
removeDBfile("instance2")
1005+
}
1006+
1007+
9361008
func testReadWriteMultiThreadShouldNotCrash() {
9371009
let concurentQueue = DispatchQueue(label: "multithread", attributes: .concurrent)
9381010
let testMixpanel = Mixpanel.initialize(token: randomId(), flushInterval: 60)
@@ -1061,24 +1133,24 @@ class MixpanelDemoTests: MixpanelBaseTests {
10611133
XCTAssertEqual(setProperties["g"] as? String, "yo")
10621134
let setProperties2 = group[1]["$set"] as! InternalProperties
10631135
XCTAssertEqual(setProperties2["a"] as? Int, 1)
1064-
XCTAssertTrue(MixpanelPersistence.loadOptOutStatusFlag(apiToken: token)!)
1065-
XCTAssertTrue(MixpanelPersistence.loadAutomaticEventsEnabledFlag(apiToken: token))
1136+
XCTAssertTrue(MixpanelPersistence.loadOptOutStatusFlag(instanceName: token)!)
1137+
XCTAssertTrue(MixpanelPersistence.loadAutomaticEventsEnabledFlag(instanceName: token))
10661138

10671139
//timedEvents
1068-
let testTimedEvents = MixpanelPersistence.loadTimedEvents(apiToken: token)
1140+
let testTimedEvents = MixpanelPersistence.loadTimedEvents(instanceName: token)
10691141
XCTAssertEqual(testTimedEvents.count, 3)
10701142
XCTAssertNotNil(testTimedEvents["Time Event A"])
10711143
XCTAssertNotNil(testTimedEvents["Time Event B"])
10721144
XCTAssertNotNil(testTimedEvents["Time Event C"])
1073-
let identity = MixpanelPersistence.loadIdentity(apiToken: token)
1145+
let identity = MixpanelPersistence.loadIdentity(instanceName: token)
10741146
XCTAssertEqual(identity.distinctID, "demo_user")
10751147
XCTAssertEqual(identity.peopleDistinctID, "demo_user")
10761148
XCTAssertNotNil(identity.anonymousId)
10771149
XCTAssertEqual(identity.userId, "demo_user")
10781150
XCTAssertEqual(identity.alias, "New Alias")
10791151
XCTAssertEqual(identity.hadPersistedDistinctId, false)
10801152

1081-
let superProperties = MixpanelPersistence.loadSuperProperties(apiToken: token)
1153+
let superProperties = MixpanelPersistence.loadSuperProperties(instanceName: token)
10821154
XCTAssertEqual(superProperties.count, 7)
10831155
XCTAssertEqual(superProperties["Super Property 1"] as? Int, 1)
10841156
XCTAssertEqual(superProperties["Super Property 7"] as? NSNull, NSNull())

Sources/AutomaticEvents.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ class AutomaticEvents: NSObject, SKPaymentTransactionObserver, SKProductsRequest
4747

4848
let awaitingTransactionsWriteLock = DispatchQueue(label: "com.mixpanel.awaiting_transactions_writeLock", qos: .utility)
4949

50-
func initializeEvents(apiToken: String) {
50+
func initializeEvents(instanceName: String) {
5151
let legacyFirstOpenKey = "MPFirstOpen"
52-
let firstOpenKey = "MPFirstOpen-\(apiToken)"
52+
let firstOpenKey = "MPFirstOpen-\(instanceName)"
5353
// do not track `$ae_first_open` again if the legacy key exist,
5454
// but we will start using the key with the mixpanel token in favour of multiple instances support
5555
if let defaults = defaults, !defaults.bool(forKey: legacyFirstOpenKey) {

Sources/Mixpanel.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ open class Mixpanel {
2424

2525
- parameter token: your project token
2626
- parameter flushInterval: Optional. Interval to run background flushing
27-
- parameter instanceName: Optional. The name you want to call this instance, must be unique 1:1 for each instance's project token. Defaults to project token.
27+
- parameter instanceName: Optional. The name you want to uniquely identify the Mixpanel Instance.
28+
It is useful when you want more than one Mixpanel instance under the same project token.
2829
- parameter optOutTrackingByDefault: Optional. Whether or not to be opted out from tracking by default
2930
- parameter trackAutomaticEvents: Optional. Whether or not to collect common mobile events, it takes precedence over Autotrack settings from the Mixpanel server.
3031
- parameter useUniqueDistinctId: Optional. whether or not to use the unique device identifier as the distinct_id

0 commit comments

Comments
 (0)