Skip to content

Commit 3c49330

Browse files
alanjcharlesbsneed
andauthored
Multiple instance write key (#294)
* add check for same write key in multiple instances * Active writekey fixes --------- Co-authored-by: Brandon Sneed <[email protected]>
1 parent 51f56b9 commit 3c49330

File tree

6 files changed

+48
-11
lines changed

6 files changed

+48
-11
lines changed

Sources/Segment/Analytics.swift

+27-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class Analytics {
2828

2929
static internal let deadInstance = "DEADINSTANCE"
3030
static internal weak var firstInstance: Analytics? = nil
31+
@Atomic static internal var activeWriteKeys = [String]()
3132

3233
/**
3334
This method isn't a traditional singleton implementation. It's provided here
@@ -58,6 +59,12 @@ public class Analytics {
5859
/// - Parameters:
5960
/// - configuration: The configuration to use
6061
public init(configuration: Configuration) {
62+
if Self.isActiveWriteKey(configuration.values.writeKey) {
63+
fatalError("Cannot initialize multiple instances of Analytics with the same write key")
64+
} else {
65+
Self.addActiveWriteKey(configuration.values.writeKey)
66+
}
67+
6168
store = Store()
6269
storage = Storage(store: self.store, writeKey: configuration.values.writeKey)
6370
timeline = Timeline()
@@ -74,6 +81,10 @@ public class Analytics {
7481
platformStartup()
7582
}
7683

84+
deinit {
85+
Self.removeActiveWriteKey(configuration.values.writeKey)
86+
}
87+
7788
internal func process<E: RawEvent>(incomingEvent: E) {
7889
guard enabled == true else { return }
7990
let event = incomingEvent.applyRawEventData(store: store)
@@ -428,11 +439,26 @@ extension Analytics {
428439
Self.firstInstance = self
429440
}
430441
}
431-
442+
432443
/// Determines if an instance is dead.
433444
internal var isDead: Bool {
434445
return configuration.values.writeKey == Self.deadInstance
435446
}
447+
448+
/// Manage active writekeys. It's wrapped in @atomic
449+
internal static func isActiveWriteKey(_ writeKey: String) -> Bool {
450+
Self.activeWriteKeys.contains(writeKey)
451+
}
452+
453+
internal static func addActiveWriteKey(_ writeKey: String) {
454+
Self.activeWriteKeys.append(writeKey)
455+
}
456+
457+
internal static func removeActiveWriteKey(_ writeKey: String) {
458+
Self.activeWriteKeys.removeAll { key in
459+
writeKey == key
460+
}
461+
}
436462
}
437463

438464
// MARK: Operating mode based scheduling

Sources/Segment/Configuration.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class Configuration {
4747
internal var values: Values
4848

4949
/// Initialize a configuration object to pass along to an Analytics instance.
50-
///
50+
///
5151
/// - Parameter writeKey: Your Segment write key value
5252
public init(writeKey: String) {
5353
self.values = Values(writeKey: writeKey)
@@ -127,7 +127,7 @@ public extension Configuration {
127127
/// let config = Configuration(writeKey: "1234").defaultSettings(defaults)
128128
/// ```
129129
///
130-
/// - Parameter settings:
130+
/// - Parameter settings:
131131
/// - Returns: The current Configuration.
132132
@discardableResult
133133
func defaultSettings(_ settings: Settings?) -> Configuration {

Sources/Segment/ObjC/ObjCPlugin.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class ObjCSegmentMixpanel: NSObject, ObjCPlugin, ObjCPluginShim {
3030
@objc(SEGEventPlugin)
3131
public class ObjCEventPlugin: NSObject, EventPlugin, ObjCPlugin {
3232
public var type: PluginType = .enrichment
33-
public var analytics: Analytics? = nil
33+
public weak var analytics: Analytics? = nil
3434

3535
@objc(executeEvent:)
3636
public func execute(event: ObjCRawEvent?) -> ObjCRawEvent? {

Tests/Segment-Tests/Analytics_Tests.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ final class Analytics_Tests: XCTestCase {
1313

1414
let traits = MyTraits(email: "[email protected]")
1515
analytics.identify(userId: "brandon", traits: traits)
16+
17+
waitUntilStarted(analytics: analytics)
18+
checkIfLeaked(analytics)
1619
}
1720

1821
func testPluginConfigure() {
@@ -28,6 +31,8 @@ final class Analytics_Tests: XCTestCase {
2831
XCTAssertNotNil(ziggy.analytics)
2932
XCTAssertNotNil(myDestination.analytics)
3033
XCTAssertNotNil(goober.analytics)
34+
35+
waitUntilStarted(analytics: analytics)
3136
}
3237

3338
func testPluginRemove() {
@@ -91,6 +96,7 @@ final class Analytics_Tests: XCTestCase {
9196
XCTAssertEqual(ziggy1.receivedInitialUpdate, 1)
9297
XCTAssertEqual(ziggy2.receivedInitialUpdate, 1)
9398

99+
checkIfLeaked(analytics)
94100
}
95101

96102

@@ -160,6 +166,7 @@ final class Analytics_Tests: XCTestCase {
160166

161167
XCTAssertTrue(anonId != "")
162168
XCTAssertTrue(anonId.count == 36) // it's a UUID y0.
169+
waitUntilStarted(analytics: analytics)
163170
}
164171

165172
func testContext() {
@@ -560,7 +567,7 @@ final class Analytics_Tests: XCTestCase {
560567
var timeline: Timeline
561568
let type: PluginType
562569
let key: String
563-
var analytics: Analytics?
570+
weak var analytics: Analytics?
564571

565572
init(key: String) {
566573
self.key = key

Tests/Segment-Tests/Storage_Tests.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class StorageTests: XCTestCase {
5656

5757
let analytics = Analytics(configuration: Configuration(writeKey: "test"))
5858
analytics.storage.hardReset(doYouKnowHowToUseThis: true)
59-
59+
analytics.waitUntilStarted()
6060
// this will crash if it fails.
6161
let j = try! JSON(jsonSettings)
6262
analytics.storage.write(.settings, value: j)
@@ -70,7 +70,7 @@ class StorageTests: XCTestCase {
7070

7171
func testBasicWriting() throws {
7272
let analytics = Analytics(configuration: Configuration(writeKey: "test"))
73-
73+
analytics.waitUntilStarted()
7474
analytics.identify(userId: "brandon", traits: MyTraits(email: "[email protected]"))
7575

7676
let userInfo: UserInfo? = analytics.store.currentState()
@@ -91,6 +91,8 @@ class StorageTests: XCTestCase {
9191
let analytics = Analytics(configuration: Configuration(writeKey: "test"))
9292
analytics.storage.hardReset(doYouKnowHowToUseThis: true)
9393

94+
analytics.waitUntilStarted()
95+
9496
var event = IdentifyEvent(userId: "brandon1", traits: try! JSON(with: MyTraits(email: "[email protected]")))
9597
analytics.storage.write(.events, value: event)
9698

@@ -134,6 +136,8 @@ class StorageTests: XCTestCase {
134136
let analytics = Analytics(configuration: Configuration(writeKey: "test"))
135137
analytics.storage.hardReset(doYouKnowHowToUseThis: true)
136138

139+
analytics.waitUntilStarted()
140+
137141
var event = IdentifyEvent(userId: "brandon1", traits: try! JSON(with: MyTraits(email: "[email protected]")))
138142
analytics.storage.write(.events, value: event)
139143

Tests/Segment-Tests/Support/TestUtilities.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ struct MyTraits: Codable {
2626

2727
class GooberPlugin: EventPlugin {
2828
let type: PluginType
29-
var analytics: Analytics?
29+
weak var analytics: Analytics?
3030

3131
init() {
3232
self.type = .enrichment
@@ -41,7 +41,7 @@ class GooberPlugin: EventPlugin {
4141

4242
class ZiggyPlugin: EventPlugin {
4343
let type: PluginType
44-
var analytics: Analytics?
44+
weak var analytics: Analytics?
4545
var receivedInitialUpdate: Int = 0
4646

4747
var completion: (() -> Void)?
@@ -79,7 +79,7 @@ class MyDestination: DestinationPlugin {
7979
var timeline: Timeline
8080
let type: PluginType
8181
let key: String
82-
var analytics: Analytics?
82+
weak var analytics: Analytics?
8383
let trackCompletion: (() -> Bool)?
8484

8585
let disabled: Bool
@@ -114,7 +114,7 @@ class MyDestination: DestinationPlugin {
114114

115115
class OutputReaderPlugin: Plugin {
116116
let type: PluginType
117-
var analytics: Analytics?
117+
weak var analytics: Analytics?
118118

119119
var events = [RawEvent]()
120120
var lastEvent: RawEvent? = nil

0 commit comments

Comments
 (0)