Skip to content

Commit 17d5986

Browse files
committed
Add control ability server side.
1 parent 39d684e commit 17d5986

File tree

9 files changed

+95
-47
lines changed

9 files changed

+95
-47
lines changed

Package.resolved

100755100644
Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ let package = Package(
1818
targets: ["AnalyticsLive"])
1919
],
2020
dependencies: [
21-
.package(url: "https://github.com/segmentio/analytics-swift.git", from: "1.8.0"),
21+
.package(url: "https://github.com/segmentio/analytics-swift.git", from: "1.9.1"),
22+
//.package(path: "../analytics-swift"),
2223
.package(url: "https://github.com/segmentio/substrata-swift.git", from: "2.0.11"),
2324
//.package(path: "../substrata-swift")
2425
],

Sources/AnalyticsLive/LivePlugins/LivePlugins.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ public class LivePlugins: UtilityPlugin, WaitingPlugin {
8181
DispatchQueue.main.async { [weak self] in
8282
guard let self else { return }
8383
let useFallback = self.forceFallback || !success
84-
self.loadEdgeFn(url: Bundler.getLocalBundleURL(bundleName: Constants.edgeFunctionFilename), useFallback: useFallback)
84+
let url = Bundler.getLocalBundleURL(bundleName: Constants.edgeFunctionFilename)
85+
self.loadEdgeFn(url: url, useFallback: useFallback)
8586
analytics.resumeEventProcessing(plugin: self)
8687
}
8788
}

Sources/AnalyticsLive/Signals/Broadcasters/SegmentBroadcaster.swift

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,8 @@ import Foundation
99
import Segment
1010

1111
public class SegmentBroadcaster: SignalBroadcaster {
12-
public weak var analytics: Analytics? = nil {
13-
didSet {
14-
if sendToSegment {
15-
guard let analytics else { return }
16-
self.mini = MiniAnalytics(analytics: analytics)
17-
}
18-
}
19-
}
20-
21-
internal let sendToSegment: Bool
22-
internal let obfuscate: Bool
12+
internal var sendToSegment: Bool
13+
internal var obfuscate: Bool
2314
internal var mini: MiniAnalytics? = nil
2415

2516
public func added(signal: any RawSignal) {
@@ -35,8 +26,16 @@ public class SegmentBroadcaster: SignalBroadcaster {
3526
}
3627
}
3728

38-
public init(sendToSegment: Bool = false, obfuscate: Bool = true) {
29+
public init(sendToSegment: Bool = false, obfuscate: Bool = true, writeKey: String, apiHost: String) {
3930
self.obfuscate = obfuscate
4031
self.sendToSegment = sendToSegment
32+
if sendToSegment {
33+
self.mini = MiniAnalytics(writeKey: writeKey, apiHost: apiHost)
34+
}
35+
}
36+
37+
public func disable() {
38+
self.obfuscate = true
39+
self.sendToSegment = false
4140
}
4241
}

Sources/AnalyticsLive/Signals/Configuration.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public struct SignalsConfiguration {
2626
internal var broadcasters: [SignalBroadcaster]
2727
internal let sendDebugSignalsToSegment: Bool
2828
internal let obfuscateDebugSignals: Bool
29+
internal let apiHost: String
2930
internal let useUIKitAutoSignal: Bool
3031
internal let useSwiftUIAutoSignal: Bool
3132
internal let useNetworkAutoSignal: Bool
@@ -40,6 +41,7 @@ public struct SignalsConfiguration {
4041
broadcasters: [SignalBroadcaster] = [],
4142
sendDebugSignalsToSegment: Bool = false,
4243
obfuscateDebugSignals: Bool = true,
44+
apiHost: String = "signals.segment.io/v1",
4345
useUIKitAutoSignal: Bool = false,
4446
useSwiftUIAutoSignal: Bool = false,
4547
useNetworkAutoSignal: Bool = false,
@@ -53,20 +55,22 @@ public struct SignalsConfiguration {
5355
self.broadcasters = broadcasters
5456
self.sendDebugSignalsToSegment = sendDebugSignalsToSegment
5557
self.obfuscateDebugSignals = obfuscateDebugSignals
58+
self.apiHost = apiHost
5659
self.useUIKitAutoSignal = useUIKitAutoSignal
5760
self.useSwiftUIAutoSignal = useSwiftUIAutoSignal
5861
self.useNetworkAutoSignal = useNetworkAutoSignal
5962
self.allowedNetworkHosts = allowedNetworkHosts
6063

61-
if !self.broadcasters.contains(where: { $0 is SegmentBroadcaster }) {
64+
/*if !self.broadcasters.contains(where: { $0 is SegmentBroadcaster }) {
6265
if self.sendDebugSignalsToSegment {
6366
let seg = SegmentBroadcaster(
6467
sendToSegment: self.sendDebugSignalsToSegment,
65-
obfuscate: self.obfuscateDebugSignals
68+
obfuscate: self.obfuscateDebugSignals,
69+
apiHost: self.apiHost
6670
)
6771
self.broadcasters.append(seg)
6872
}
69-
}
73+
}*/
7074

7175
var blocked = blockedNetworkHosts + Self.autoBlockedHosts
7276
// block the webhook if it's in use

Sources/AnalyticsLive/Signals/Minilytics/MiniAnalytics.swift

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,30 +56,41 @@ internal struct MiniTrackEvent: RawEvent {
5656
}
5757

5858
internal class MiniAnalytics {
59-
let analytics: Analytics
6059
let session: URLSession
60+
let apiHost: String
6161
let storage: TransientDB
6262

6363
@Atomic var flushing: Bool = false
64+
65+
struct LocalUserInfo {
66+
var userId: String? = nil
67+
var anonymousId: String? = nil
68+
}
69+
@Atomic var userInfo = LocalUserInfo()
70+
6471
// used for testing only.
6572
internal static var observer: ((_ in: any RawSignal, _ out: MiniTrackEvent) -> Void)? = nil
6673

67-
init(analytics: Analytics) {
68-
self.analytics = analytics
74+
init(writeKey: String, apiHost: String) {
75+
self.apiHost = apiHost
6976
self.session = Self.configuredSession()
7077

7178
let fileStore = DirectoryStore(
7279
configuration:
7380
DirectoryStore.Configuration(
74-
writeKey: analytics.writeKey,
75-
storageLocation: Self.signalStorageDirectory(writeKey: analytics.writeKey),
81+
writeKey: writeKey,
82+
storageLocation: Self.signalStorageDirectory(writeKey: writeKey),
7683
baseFilename: "segment-signals",
7784
maxFileSize: 475000,
7885
indexKey: "signalFileIndex")
7986
)
8087
self.storage = TransientDB(store: fileStore, asyncAppend: true)
8188
}
8289

90+
func stateSubscriber(userInfo: UserInfo) {
91+
_userInfo.set(LocalUserInfo(userId: userInfo.userId, anonymousId: userInfo.anonymousId))
92+
}
93+
8394
func track(signal: any RawSignal, obfuscate: Bool) {
8495
let input = signal
8596
var signal = signal
@@ -90,9 +101,10 @@ internal class MiniAnalytics {
90101

91102
guard let props = try? JSON(with: signal) else { return }
92103

93-
let anonId = analytics.anonymousId
104+
let userInfo = self.userInfo
105+
let anonId = userInfo.anonymousId
106+
let userId = userInfo.userId
94107
let messageId = UUID().uuidString
95-
let userId = analytics.userId
96108
let timestamp = Date().iso8601()
97109

98110
let track = MiniTrackEvent(
@@ -137,7 +149,7 @@ internal class MiniAnalytics {
137149
break
138150
}
139151

140-
analytics.log(message: "Processed: \(url.lastPathComponent)")
152+
Analytics.segmentLog(message: "Processed: \(url.lastPathComponent)", kind: .debug)
141153
group.leave()
142154
}
143155
}
@@ -150,7 +162,7 @@ internal class MiniAnalytics {
150162

151163
extension MiniAnalytics {
152164
//private static let defaultAPIHost = "signals.segment.io/v1"
153-
private static let defaultAPIHost = "signals.segment.build/v1"
165+
//private static let defaultAPIHost = "signals.segment.build/v1"
154166

155167
func segmentURL(for host: String, path: String) -> URL? {
156168
let s = "https://\(host)\(path)"
@@ -160,8 +172,8 @@ extension MiniAnalytics {
160172

161173
@discardableResult
162174
func startBatchUpload(batch: URL, completion: @escaping (_ result: Result<Bool, Error>) -> Void) -> URLSessionDataTask? {
163-
guard let uploadURL = segmentURL(for: Self.defaultAPIHost, path: "/b") else {
164-
analytics.reportInternalError(HTTPClientErrors.failedToOpenBatch, fatal: false)
175+
guard let uploadURL = segmentURL(for: self.apiHost, path: "/b") else {
176+
Analytics.reportInternalError(HTTPClientErrors.failedToOpenBatch, fatal: false)
165177
completion(.failure(HTTPClientErrors.failedToOpenBatch))
166178
return nil
167179
}
@@ -181,7 +193,7 @@ extension MiniAnalytics {
181193
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60)
182194
request.httpMethod = method
183195
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
184-
request.addValue("analytics-ios/\(analytics.version())", forHTTPHeaderField: "User-Agent")
196+
request.addValue("analytics-ios/\(Analytics.version())", forHTTPHeaderField: "User-Agent")
185197
request.addValue("gzip", forHTTPHeaderField: "Accept-Encoding")
186198

187199
return request
@@ -196,22 +208,22 @@ extension MiniAnalytics {
196208

197209
private func handleResponse(data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Result<Bool, Error>) -> Void) {
198210
if let error = error {
199-
analytics.log(message: "Error uploading request \(error.localizedDescription).")
200-
analytics.reportInternalError(AnalyticsError.networkUnknown(error), fatal: false)
211+
Analytics.segmentLog(message: "Error uploading request \(error.localizedDescription).", kind: .error)
212+
Analytics.reportInternalError(AnalyticsError.networkUnknown(error), fatal: false)
201213
completion(.failure(HTTPClientErrors.unknown(error: error)))
202214
} else if let httpResponse = response as? HTTPURLResponse {
203215
switch (httpResponse.statusCode) {
204216
case 1..<300:
205217
completion(.success(true))
206218
return
207219
case 300..<400:
208-
analytics.reportInternalError(AnalyticsError.networkUnexpectedHTTPCode(httpResponse.statusCode), fatal: false)
220+
Analytics.reportInternalError(AnalyticsError.networkUnexpectedHTTPCode(httpResponse.statusCode), fatal: false)
209221
completion(.failure(HTTPClientErrors.statusCode(code: httpResponse.statusCode)))
210222
case 429:
211-
analytics.reportInternalError(AnalyticsError.networkServerLimited(httpResponse.statusCode), fatal: false)
223+
Analytics.reportInternalError(AnalyticsError.networkServerLimited(httpResponse.statusCode), fatal: false)
212224
completion(.failure(HTTPClientErrors.statusCode(code: httpResponse.statusCode)))
213225
default:
214-
analytics.reportInternalError(AnalyticsError.networkServerRejected(httpResponse.statusCode), fatal: false)
226+
Analytics.reportInternalError(AnalyticsError.networkServerRejected(httpResponse.statusCode), fatal: false)
215227
completion(.failure(HTTPClientErrors.statusCode(code: httpResponse.statusCode)))
216228
}
217229
}

Sources/AnalyticsLive/Signals/Signals.swift

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@ public class Signals: Plugin {
2222
@Atomic internal var counter: Int = 0
2323
@Atomic internal var configuration: SignalsConfiguration = SignalsConfiguration(writeKey: "NONE")
2424

25+
struct AutoInstrumentationSettings: Decodable {
26+
let disableTraffic: Bool?
27+
let sampleRate: Double?
28+
}
29+
2530
struct QueuedSignal {
2631
let signal: any RawSignal
2732
let source: SignalSource
2833
}
2934
@Atomic internal var queuedSignals = [QueuedSignal]()
3035
@Atomic internal var ready = false
36+
weak var segmentBroadcaster: SegmentBroadcaster? = nil
3137

3238
public var anonymousId: String {
3339
get {
@@ -57,9 +63,37 @@ public class Signals: Plugin {
5763
if let e = analytics.find(pluginType: LivePlugins.self) {
5864
e.addDependent(plugin: self)
5965
}
60-
61-
for var b in broadcasters {
62-
b.analytics = analytics
66+
67+
// only pull the lock once.
68+
_configuration.mutate { config in
69+
// if we don't have a segment broadcaster already ...
70+
if !config.broadcasters.contains(where: { $0 is SegmentBroadcaster }) {
71+
// and they turned this on, add it.
72+
if config.sendDebugSignalsToSegment {
73+
let seg = SegmentBroadcaster(
74+
sendToSegment: true,
75+
obfuscate: config.obfuscateDebugSignals,
76+
writeKey: config.writeKey,
77+
apiHost: config.apiHost
78+
)
79+
if let mini = seg.mini {
80+
// let the miniAnalytics instance get user info updates.
81+
analytics.subscribeToUserInfo(handler: mini.stateSubscriber)
82+
}
83+
config.broadcasters.append(seg)
84+
segmentBroadcaster = seg
85+
}
86+
}
87+
}
88+
}
89+
90+
public func update(settings: Settings, type: UpdateType) {
91+
guard type == .initial else { return }
92+
guard let aiSettings: AutoInstrumentationSettings = settings.autoInstrumentation?.codableValue() else { return }
93+
guard let disableTraffic = aiSettings.disableTraffic else { return }
94+
guard let segmentBroadcaster else { return }
95+
if disableTraffic {
96+
segmentBroadcaster.disable()
6397
}
6498
}
6599

@@ -92,10 +126,6 @@ public class Signals: Plugin {
92126

93127
// Start swizzlers with new config
94128
startConfiguredSwizzlers()
95-
96-
for var b in broadcasters {
97-
b.analytics = analytics
98-
}
99129
}
100130

101131
public func emit<T: RawSignal>(signal: T, source: SignalSource = .manual) {
@@ -300,7 +330,7 @@ extension Signals {
300330
}
301331

302332
internal func isRepeating(event: RawEvent?) -> Bool {
303-
let type: String? = event?.context?.value(forKeyPath: KeyPath("__eventOrigin.type"))
333+
let type: String? = event?.context?.value(forKeyPath: JSONKeyPath("__eventOrigin.type"))
304334
guard let type else { return false }
305335
if type == "signals" {
306336
return true

Sources/AnalyticsLive/Signals/Types.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import Substrata
1212
// MARK: -- Signal Broadcaster
1313

1414
public protocol SignalBroadcaster {
15-
var analytics: Analytics? { get set }
1615
func added(signal: any RawSignal)
1716
func relay()
1817
}

Sources/AnalyticsLive/Signals/Utilities/Extensions.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
//
77

88
import Foundation
9+
import Segment
910

1011
extension URL {
1112
func host() -> String? {
1213
return self.host
1314
}
1415
}
16+

0 commit comments

Comments
 (0)