Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions Sources/AnalyticsLive/Signals/Broadcasters/SegmentBroadcaster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,32 @@
public class SegmentBroadcaster: SignalBroadcaster {
public weak var analytics: Analytics? = nil {
didSet {
#if DEBUG
guard let analytics else { return }
self.mini = MiniAnalytics(analytics: analytics)
#endif
if sendToSegment {
guard let analytics else { return }
self.mini = MiniAnalytics(analytics: analytics)
}
}
}

internal let sendToSegment: Bool
internal let obfuscate: Bool
internal var mini: MiniAnalytics? = nil

public func added(signal: any RawSignal) {
#if DEBUG
mini?.track(signal: signal)
#endif
var s = signal
if sendToSegment {
mini?.track(signal: s, obfuscate: obfuscate)
}
}

public func relay() {
#if DEBUG
mini?.flush()
#endif
if sendToSegment {
mini?.flush()
}

Check warning on line 35 in Sources/AnalyticsLive/Signals/Broadcasters/SegmentBroadcaster.swift

View check run for this annotation

Codecov / codecov/patch

Sources/AnalyticsLive/Signals/Broadcasters/SegmentBroadcaster.swift#L33-L35

Added lines #L33 - L35 were not covered by tests
}

public init() { }
public init(sendToSegment: Bool = false, obfuscate: Bool = true) {
self.obfuscate = obfuscate
self.sendToSegment = sendToSegment
}
}
47 changes: 31 additions & 16 deletions Sources/AnalyticsLive/Signals/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,27 @@
"signals.segment.build",
]

public let writeKey: String
public let maximumBufferSize: Int
public let relayCount: Int
public let relayInterval: TimeInterval
public let broadcasters: [SignalBroadcaster]?
public let useUIKitAutoSignal: Bool
public let useSwiftUIAutoSignal: Bool
public let useNetworkAutoSignal: Bool
public let allowedNetworkHosts: [String]
public let blockedNetworkHosts: [String]
internal let writeKey: String
internal let maximumBufferSize: Int
internal let relayCount: Int
internal let relayInterval: TimeInterval
internal var broadcasters: [SignalBroadcaster]
internal let sendDebugSignalsToSegment: Bool
internal let obfuscateDebugSignals: Bool
internal let useUIKitAutoSignal: Bool
internal let useSwiftUIAutoSignal: Bool
internal let useNetworkAutoSignal: Bool
internal let allowedNetworkHosts: [String]
internal let blockedNetworkHosts: [String]

public init(
writeKey: String,
maximumBufferSize: Int = 1000,
relayCount: Int = 20,
relayInterval: TimeInterval = 60,
broadcasters: [SignalBroadcaster]? = [SegmentBroadcaster()],
broadcasters: [SignalBroadcaster] = [],
sendDebugSignalsToSegment: Bool = false,
obfuscateDebugSignals: Bool = true,
useUIKitAutoSignal: Bool = false,
useSwiftUIAutoSignal: Bool = false,
useNetworkAutoSignal: Bool = false,
Expand All @@ -47,20 +51,31 @@
self.relayCount = relayCount
self.relayInterval = relayInterval
self.broadcasters = broadcasters
self.sendDebugSignalsToSegment = sendDebugSignalsToSegment
self.obfuscateDebugSignals = obfuscateDebugSignals
self.useUIKitAutoSignal = useUIKitAutoSignal
self.useSwiftUIAutoSignal = useSwiftUIAutoSignal
self.useNetworkAutoSignal = useNetworkAutoSignal
self.allowedNetworkHosts = allowedNetworkHosts

if !self.broadcasters.contains(where: { $0 is SegmentBroadcaster }) {
if self.sendDebugSignalsToSegment {
let seg = SegmentBroadcaster(
sendToSegment: self.sendDebugSignalsToSegment,
obfuscate: self.obfuscateDebugSignals
)
self.broadcasters.append(seg)
}
}

var blocked = blockedNetworkHosts + Self.autoBlockedHosts
// block the webhook if it's in use
if let broadcasters = self.broadcasters {
for b in broadcasters {
if let webhook = b as? WebhookBroadcaster, let host = webhook.webhookURL.host() {
blocked.append(host)
}
for b in self.broadcasters {
if let webhook = b as? WebhookBroadcaster, let host = webhook.webhookURL.host() {
blocked.append(host)

Check warning on line 75 in Sources/AnalyticsLive/Signals/Configuration.swift

View check run for this annotation

Codecov / codecov/patch

Sources/AnalyticsLive/Signals/Configuration.swift#L75

Added line #L75 was not covered by tests
}
}

self.blockedNetworkHosts = blocked
}
}
Expand Down
13 changes: 9 additions & 4 deletions Sources/AnalyticsLive/Signals/Minilytics/MiniAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ internal class MiniAnalytics {
let storage: TransientDB

@Atomic var flushing: Bool = false
// used for testing only.
internal static var observer: ((_ in: any RawSignal, _ out: MiniTrackEvent) -> Void)? = nil

init(analytics: Analytics) {
self.analytics = analytics
Expand All @@ -78,14 +80,13 @@ internal class MiniAnalytics {
self.storage = TransientDB(store: fileStore, asyncAppend: true)
}

func track(signal: any RawSignal) {
func track(signal: any RawSignal, obfuscate: Bool) {
var input = signal
var signal = signal

#if !DEBUG
if let obf = signal as? JSONObfuscation {
if obfuscate, let obf = signal as? JSONObfuscation {
signal = obf.obfuscated()
}
#endif

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

Expand All @@ -102,6 +103,10 @@ internal class MiniAnalytics {
properties: props)

storage.append(data: track)

if let observer = Self.observer {
observer(input, track)
}
}

func flush() {
Expand Down
14 changes: 1 addition & 13 deletions Sources/AnalyticsLive/Signals/Signals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ public class Signals: Plugin, LivePluginsDependent {
public func useConfiguration(_ configuration: SignalsConfiguration) {
_configuration.set(configuration)

addDefaultBroadcasters()
updateJSConfiguration()
updateNativeConfiguration()

Expand Down Expand Up @@ -239,23 +238,12 @@ extension Signals {
}
}

internal func addDefaultBroadcasters() {
if let cb = configuration.broadcasters {
broadcasters = cb
}

if !broadcasters.contains(where: { broadcaster in
return broadcaster is SegmentBroadcaster
}) {
broadcasters.append(SegmentBroadcaster())
}
}

internal func updateJSConfiguration() {
signalObject?.setValue(configuration.maximumBufferSize, for: "maxBufferSize")
}

internal func updateNativeConfiguration() {
broadcasters = configuration.broadcasters
broadcastTimer = QueueTimer(interval: configuration.relayInterval, handler: { [weak self] in
guard let self else { return }
for b in self.broadcasters { b.relay() }
Expand Down
7 changes: 0 additions & 7 deletions Sources/AnalyticsLive/Signals/Utilities/Obfuscation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,6 @@ extension NetworkSignal: JSONObfuscation {

extension JSONObfuscation {
func obfuscate(_ data: JSON?) -> JSON? {
#if DEBUG
let debugging = true
#else
let debugging = false
#endif
if debugging { return data }

guard let data else { return data }

switch data {
Expand Down
86 changes: 86 additions & 0 deletions Tests/AnalyticsLiveTests/Signals/SignalsTests.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,89 @@
import XCTest
import Segment
@testable import AnalyticsLive

final class TestSignals: XCTestCase {

override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

func testSendToSegment() throws {
LivePlugins.clearCache()

let config = Configuration(writeKey: "signals_test")
.flushInterval(999999999)
.flushAt(99999999)
let analytics = Analytics(configuration: config)

let signalsConfig = SignalsConfiguration(
writeKey: "signals_test",
sendDebugSignalsToSegment: true
)

// set up an observer.
let expectation = self.expectation(description: "observer called")
MiniAnalytics.observer = { signal, event in
print("signal: \(signal.prettyPrint())")
print("event: \(event.prettyPrint())")

XCTAssertEqual(event.properties.value(forKeyPath: "data.data.customer_name"), "XXXX XXX")
XCTAssertEqual(event.properties.value(forKeyPath: "data.data.price"), "99.99")
expectation.fulfill()
}
Signals.shared.useConfiguration(signalsConfig)
analytics.add(plugin: LivePlugins(fallbackFileURL: bundleTestFile(file: "MyEdgeFunctions.js")))
analytics.add(plugin: Signals.shared)

analytics.waitUntilStarted()

let localData = LocalDataSignal(action: .loaded, identifier: "1234", data: ["price": "19.95", "customer_name": "John Doe"])
Signals.emit(signal: localData)

waitForExpectations(timeout: 5) { error in

}
}

func testSendToSegmentUnobfuscated() throws {
LivePlugins.clearCache()

let config = Configuration(writeKey: "signals_test2")
.flushInterval(999999999)
.flushAt(99999999)
let analytics = Analytics(configuration: config)

let signalsConfig = SignalsConfiguration(
writeKey: "signals_test2",
sendDebugSignalsToSegment: true,
obfuscateDebugSignals: false
)

// set up an observer.
let expectation = self.expectation(description: "observer called")
MiniAnalytics.observer = { signal, event in
print("signal: \(signal.prettyPrint())")
print("event: \(event.prettyPrint())")

XCTAssertEqual(event.properties.value(forKeyPath: "data.data.customer_name"), "John Doe")
XCTAssertEqual(event.properties.value(forKeyPath: "data.data.price"), "19.95")
expectation.fulfill()
}
Signals.shared.useConfiguration(signalsConfig)
analytics.add(plugin: LivePlugins(fallbackFileURL: bundleTestFile(file: "MyEdgeFunctions.js")))
analytics.add(plugin: Signals.shared)

analytics.waitUntilStarted()

let localData = LocalDataSignal(action: .loaded, identifier: "1234", data: ["price": "19.95", "customer_name": "John Doe"])
Signals.emit(signal: localData)

waitForExpectations(timeout: 5) { error in

}
}
}
Loading