Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
18 changes: 11 additions & 7 deletions Segment.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
465879BA2686560C00180335 /* watchOSDelegation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B82686560C00180335 /* watchOSDelegation.swift */; };
465879BB2686560C00180335 /* watchOSLifecycleMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B92686560C00180335 /* watchOSLifecycleMonitor.swift */; };
4663C729267A799100ADDD1A /* QueueTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4663C728267A799100ADDD1A /* QueueTimer.swift */; };
466EC2CE28FB7D5D001B384E /* OutputFileStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466EC2CD28FB7D5D001B384E /* OutputFileStream.swift */; };
4697A238290341EF00FAE14F /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4697A237290341EF00FAE14F /* Errors.swift */; };
46A018C225E5857D00F9CCD8 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018C125E5857D00F9CCD8 /* Context.swift */; };
46A018D425E6C9C200F9CCD8 /* LinuxUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018D325E6C9C200F9CCD8 /* LinuxUtils.swift */; };
46A018DA25E97FDF00F9CCD8 /* AppleUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018D925E97FDF00F9CCD8 /* AppleUtils.swift */; };
Expand All @@ -55,15 +57,13 @@
96259F8626CF1D45008AE301 /* ConsoleTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96259F8526CF1D45008AE301 /* ConsoleTarget.swift */; };
96469AF82706225900AC5772 /* SystemTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96469AF72706225900AC5772 /* SystemTarget.swift */; };
966945D7259BDCDD00271339 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C40ED259A7311008EB0B6 /* HTTPClient.swift */; };
967C40DA258D472C008EB0B6 /* SegmentLog_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C40D9258D472C008EB0B6 /* SegmentLog_Tests.swift */; };
967C40E3258D4DAF008EB0B6 /* Metrics_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C40E2258D4DAF008EB0B6 /* Metrics_Tests.swift */; };
9692724E25A4E5B7009B5298 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9692724D25A4E5B7009B5298 /* Startup.swift */; };
9692726825A583A6009B5298 /* SegmentDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9692726725A583A6009B5298 /* SegmentDestination.swift */; };
96A9624E2810C6B80011DE54 /* macOSLifecycleEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A9624D2810C6B80011DE54 /* macOSLifecycleEvents.swift */; };
96A9668927BC137F00078F8B /* iOSLifecycle_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A9668827BC137F00078F8B /* iOSLifecycle_Tests.swift */; };
96C33A9C25880A5E00F3D538 /* SegmentLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33A9B25880A5E00F3D538 /* SegmentLog.swift */; };
96C33AB1258961F500F3D538 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33AB0258961F500F3D538 /* Settings.swift */; };
96C95B16271DE22700C3EB9A /* LogTarget_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C95B15271DE22600C3EB9A /* LogTarget_Tests.swift */; };
96DBF37B26F39B5500724B0B /* Timeline_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96DBF37A26F39B5500724B0B /* Timeline_Tests.swift */; };
A31A16262576B6F200C9CDDF /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = A31A16252576B6F200C9CDDF /* Timeline.swift */; };
A31A162F2576B73F00C9CDDF /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = A31A162E2576B73F00C9CDDF /* State.swift */; };
Expand Down Expand Up @@ -125,6 +125,8 @@
465879B82686560C00180335 /* watchOSDelegation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = watchOSDelegation.swift; sourceTree = "<group>"; };
465879B92686560C00180335 /* watchOSLifecycleMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = watchOSLifecycleMonitor.swift; sourceTree = "<group>"; };
4663C728267A799100ADDD1A /* QueueTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTimer.swift; sourceTree = "<group>"; };
466EC2CD28FB7D5D001B384E /* OutputFileStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutputFileStream.swift; sourceTree = "<group>"; };
4697A237290341EF00FAE14F /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
46A018C125E5857D00F9CCD8 /* Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = "<group>"; };
46A018D325E6C9C200F9CCD8 /* LinuxUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinuxUtils.swift; sourceTree = "<group>"; };
46A018D925E97FDF00F9CCD8 /* AppleUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleUtils.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -356,6 +358,7 @@
A3AEE1492580208E002386EB /* iso8601.swift */,
46FE4CDF25A53FAD003A7362 /* Storage.swift */,
4621080B2605332D00EBC4A8 /* KeyPath.swift */,
466EC2CD28FB7D5D001B384E /* OutputFileStream.swift */,
46022770261F7A4800A9E913 /* Atomic.swift */,
46E382E62654429A00BA2502 /* Utils.swift */,
4663C728267A799100ADDD1A /* QueueTimer.swift */,
Expand Down Expand Up @@ -435,6 +438,7 @@
9620862B2575C0C800314F8D /* Events.swift */,
A31A16C225794BEF00C9CDDF /* Plugins.swift */,
96C33AB0258961F500F3D538 /* Settings.swift */,
4697A237290341EF00FAE14F /* Errors.swift */,
9692724D25A4E5B7009B5298 /* Startup.swift */,
A31A162E2576B73F00C9CDDF /* State.swift */,
A31A16252576B6F200C9CDDF /* Timeline.swift */,
Expand Down Expand Up @@ -507,7 +511,7 @@
attributes = {
LastSwiftMigration = 9999;
LastSwiftUpdateCheck = 1220;
LastUpgradeCheck = 1310;
LastUpgradeCheck = 1340;
};
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Segment" */;
compatibilityVersion = "Xcode 3.2";
Expand Down Expand Up @@ -570,13 +574,15 @@
9692726825A583A6009B5298 /* SegmentDestination.swift in Sources */,
4602276C261E7BF900A9E913 /* iOSDelegation.swift in Sources */,
46A018D425E6C9C200F9CCD8 /* LinuxUtils.swift in Sources */,
466EC2CE28FB7D5D001B384E /* OutputFileStream.swift in Sources */,
96C33A9C25880A5E00F3D538 /* SegmentLog.swift in Sources */,
46FE4C9725A3F35E003A7362 /* macOSLifecycleMonitor.swift in Sources */,
9620862C2575C0C800314F8D /* Events.swift in Sources */,
A3AEE1882581A8F1002386EB /* Deprecations.swift in Sources */,
966945D7259BDCDD00271339 /* HTTPClient.swift in Sources */,
A31A16CA25794D9700C9CDDF /* Plugins.swift in Sources */,
46A018C225E5857D00F9CCD8 /* Context.swift in Sources */,
4697A238290341EF00FAE14F /* Errors.swift in Sources */,
96208650257AA83E00314F8D /* iOSLifecycleMonitor.swift in Sources */,
46031D65266E7C10009BA540 /* StartupQueue.swift in Sources */,
465879BB2686560C00180335 /* watchOSLifecycleMonitor.swift in Sources */,
Expand All @@ -596,11 +602,9 @@
46210811260538BE00EBC4A8 /* KeyPath_Tests.swift in Sources */,
967C40E3258D4DAF008EB0B6 /* Metrics_Tests.swift in Sources */,
96A9668927BC137F00078F8B /* iOSLifecycle_Tests.swift in Sources */,
96C95B16271DE22700C3EB9A /* LogTarget_Tests.swift in Sources */,
A31A16512576C47400C9CDDF /* JSON_Tests.swift in Sources */,
46FE4D1D25A7A850003A7362 /* Storage_Tests.swift in Sources */,
46FE4CFB25A6C671003A7362 /* TestUtilities.swift in Sources */,
967C40DA258D472C008EB0B6 /* SegmentLog_Tests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -792,7 +796,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 11.0;
TVOS_DEPLOYMENT_TARGET = 12.0;
USE_HEADERMAP = NO;
WATCHOS_DEPLOYMENT_TARGET = 7.1;
};
Expand Down Expand Up @@ -865,7 +869,7 @@
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TVOS_DEPLOYMENT_TARGET = 11.0;
TVOS_DEPLOYMENT_TARGET = 12.0;
USE_HEADERMAP = NO;
WATCHOS_DEPLOYMENT_TARGET = 7.1;
};
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
LastUpgradeVersion = "1340"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
68 changes: 65 additions & 3 deletions Sources/Segment/Analytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import Sovran
// MARK: - Base Setup

public class Analytics {
internal var configuration: Configuration
internal var configuration: Configuration {
get {
// we're absolutely certain we will have a config
let system: System = store.currentState()!
return system.configuration
}
}
internal var store: Store
internal var storage: Storage

Expand All @@ -28,8 +34,6 @@ public class Analytics {
/// - Parameters:
/// - configuration: The configuration to use
public init(configuration: Configuration) {
self.configuration = configuration

store = Store()
storage = Storage(store: self.store, writeKey: configuration.values.writeKey)
timeline = Timeline()
Expand All @@ -38,11 +42,14 @@ public class Analytics {
store.provide(state: System.defaultState(configuration: configuration, from: storage))
store.provide(state: UserInfo.defaultState(from: storage))

storage.analytics = self

// Get everything running
platformStartup()
}

internal func process<E: RawEvent>(incomingEvent: E) {
guard enabled == true else { return }
let event = incomingEvent.applyRawEventData(store: store)
_ = timeline.process(incomingEvent: event)
}
Expand All @@ -51,6 +58,7 @@ public class Analytics {
/// - Parameters:
/// - event: An event conforming to RawEvent that will be processed.
public func process(event: RawEvent) {
guard enabled == true else { return }
switch event {
case let e as TrackEvent:
timeline.process(incomingEvent: e)
Expand All @@ -71,6 +79,20 @@ public class Analytics {
// MARK: - System Modifiers

extension Analytics {
/// Enable/Disable analytics capture
public var enabled: Bool {
get {
if let system: System = store.currentState() {
return system.enabled
}
// we don't have state if we get here, so assume we're not enabled.
return false
}
set(value) {
store.dispatch(action: System.ToggleEnabledAction(enabled: value))
}
}

/// Returns the anonymousId currently in use.
public var anonymousId: String {
if let userInfo: UserInfo = store.currentState() {
Expand All @@ -87,6 +109,32 @@ extension Analytics {
return nil
}

/// Adjusts the flush interval post configuration.
public var flushInterval: TimeInterval {
get {
configuration.values.flushInterval
}
set(value) {
if let state: System = store.currentState() {
let config = state.configuration.flushInterval(value)
store.dispatch(action: System.UpdateConfigurationAction(configuration: config))
}
}
}

/// Adjusts the flush-at count post configuration.
public var flushAt: Int {
get {
configuration.values.flushAt
}
set(value) {
if let state: System = store.currentState() {
let config = state.configuration.flushAt(value)
store.dispatch(action: System.UpdateConfigurationAction(configuration: config))
}
}
}

/// Returns the traits that were specified in the last identify call.
public func traits<T: Codable>() -> T? {
if let userInfo: UserInfo = store.currentState() {
Expand Down Expand Up @@ -184,6 +232,20 @@ extension Analytics {
return storage.read(Storage.Constants.events)
}

/// Purge all pending event upload files.
public func purgeStorage() {
if let files = pendingUploads {
for file in files {
purgeStorage(fileURL: file)
}
}
}

/// Purge a single event upload file.
public func purgeStorage(fileURL: URL) {
try? FileManager.default.removeItem(at: fileURL)
}

/// Wait until the Analytics object has completed startup.
/// This method is primarily useful for command line utilities where
/// it's desirable to wait until the system is up and running
Expand Down
12 changes: 12 additions & 0 deletions Sources/Segment/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class Configuration {
var autoAddSegmentDestination: Bool = true
var apiHost: String = HTTPClient.getDefaultAPIHost()
var cdnHost: String = HTTPClient.getDefaultCDNHost()
var errorHandler: ((Error) -> Void)?
}
internal var values: Values

Expand Down Expand Up @@ -95,5 +96,16 @@ public extension Configuration {
values.cdnHost = value
return self
}

@discardableResult
func errorHandler(_ value: @escaping (Error) -> Void) -> Configuration {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious as to why it returns the Configuration ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config methods are all chainable and this continues that. Makes is so you can do ...

let analytics = Configuration(writeKey: "1234")
                         .trackLifecycleEvents(true)
                         .apiHost("eu1.segmentapi.com")
                         .whateverElse()

.. instead of having to create an instance and then set them all afterwards.

values.errorHandler = value
return self
}
}

extension Analytics {
func configuration<T>(valueFor: () -> T) -> T {
return valueFor()
}
}
79 changes: 79 additions & 0 deletions Sources/Segment/Errors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// Errors.swift
//
//
// Created by Brandon Sneed on 10/20/22.
//

import Foundation

public enum AnalyticsError: Error {
case storageUnableToCreate(String)
case storageUnableToWrite(String)
case storageUnableToRename(String)
case storageUnableToOpen(String)
case storageInvalid(String)
case storageUnknown(Error)

case networkUnexpectedHTTPCode(Int)
case networkServerLimited(Int)
case networkServerRejected(Int)
case networkUnknown(Error)
case networkInvalidData

case jsonUnableToSerialize(Error)
case jsonUnableToDeserialize(Error)
case jsonUnknown(Error)

case pluginError(Error)
}

extension Analytics {
/// Tries to convert known error types to AnalyticsError.
static internal func translate(error: Error) -> Error {
if let e = error as? OutputFileStream.OutputStreamError {
switch e {
case .invalidPath(let path):
return AnalyticsError.storageInvalid(path)
case .unableToCreate(let path):
return AnalyticsError.storageUnableToCreate(path)
case .unableToOpen(let path):
return AnalyticsError.storageUnableToOpen(path)
case .unableToWrite(let path):
return AnalyticsError.storageUnableToWrite(path)
}
}

if let e = error as? JSON.JSONError {
switch e {
case .incorrectType:
return AnalyticsError.jsonUnableToDeserialize(e)
case .nonJSONType:
return AnalyticsError.jsonUnableToDeserialize(e)
case .unknown:
return AnalyticsError.jsonUnknown(e)
}
}
return error
}

/// Reports an internal error to the user-defined error handler.
public func reportInternalError(_ error: Error, fatal: Bool = false) {
let translatedError = Self.translate(error: error)
configuration.values.errorHandler?(translatedError)
Self.segmentLog(message: "An internal error occurred: \(translatedError)", kind: .error)
if fatal {
exceptionFailure("A critical error occurred: \(translatedError)")
}
}

static public func reportInternalError(_ error: Error, fatal: Bool = false) {
// we don't have an instance of analytics to call to get our error handler,
// but we can at least report the message to the console.
let translatedError = Self.translate(error: error)
Self.segmentLog(message: "An internal error occurred: \(translatedError)", kind: .error)
if fatal {
exceptionFailure("A critical error occurred: \(translatedError)")
}
}
}
Loading