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
2 changes: 1 addition & 1 deletion Examples/destination_plugins/AmplitudeSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class AmplitudeSession: EventPlugin, iOSLifecycle {
private let fireTime = TimeInterval(300)

func update(settings: Settings, type: UpdateType) {
if settings.isDestinationEnabled(key: key) {
if settings.hasIntegrationSettings(key: key) {
active = true
} else {
active = false
Expand Down
2 changes: 1 addition & 1 deletion Segment.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@
attributes = {
LastSwiftMigration = 9999;
LastSwiftUpdateCheck = 1220;
LastUpgradeCheck = 1250;
LastUpgradeCheck = 1310;
};
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Segment" */;
compatibilityVersion = "Xcode 3.2";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1250"
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
42 changes: 36 additions & 6 deletions Sources/Segment/Analytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public class Analytics {

public var timeline: Timeline

/// Initialize this instance of Analytics with a given configuration setup.
/// - Parameters:
/// - configuration: The configuration to use
public init(configuration: Configuration) {
self.configuration = configuration

Expand All @@ -44,6 +47,9 @@ public class Analytics {
_ = timeline.process(incomingEvent: event)
}

/// Process a raw event through the system. Useful when one needs to queue and replay events at a later time.
/// - Parameters:
/// - event: An event conforming to RawEvent that will be processed.
public func process(event: RawEvent) {
switch event {
case let e as TrackEvent:
Expand All @@ -65,27 +71,32 @@ public class Analytics {
// MARK: - System Modifiers

extension Analytics {
/// Returns the anonymousId currently in use.
public var anonymousId: String {
if let userInfo: UserInfo = store.currentState() {
return userInfo.anonymousId
}
return ""
}

/// Returns the userId that was specified in the last identify call.
public var userId: String? {
if let userInfo: UserInfo = store.currentState() {
return userInfo.userId
}
return nil
}

/// Returns the traits that were specified in the last identify call.
public func traits<T: Codable>() -> T? {
if let userInfo: UserInfo = store.currentState() {
return userInfo.traits?.codableValue()
}
return nil
}

/// Tells this instance of Analytics to flush any queued events up to Segment.com. This command will also
/// be sent to each plugin present in the system.
public func flush() {
apply { plugin in
if let p = plugin as? EventPlugin {
Expand All @@ -94,6 +105,8 @@ extension Analytics {
}
}

/// Resets this instance of Analytics to a clean slate. Traits, UserID's, anonymousId, etc are all cleared or reset. This
/// command will also be sent to each plugin present in the system.
public func reset() {
store.dispatch(action: UserInfo.ResetAction())
apply { plugin in
Expand All @@ -103,6 +116,22 @@ extension Analytics {
}
}

/// Retrieve the version of this library in use.
/// - Returns: A string representing the version in "BREAKING.FEATURE.FIX" format.
public func version() -> String {
return Analytics.version()
}

/// Retrieve the version of this library in use.
/// - Returns: A string representing the version in "BREAKING.FEATURE.FIX" format.
public static func version() -> String {
return __segment_version
}
}

extension Analytics {
/// Manually retrieve the settings that were supplied from Segment.com.
/// - Returns: A Settings object containing integration settings, tracking plan, etc.
public func settings() -> Settings? {
var settings: Settings?
if let system: System = store.currentState() {
Expand All @@ -111,11 +140,12 @@ extension Analytics {
return settings
}

public func version() -> String {
return Analytics.version()
}

public static func version() -> String {
return __segment_version
/// Manually enable a destination plugin. This is useful when a given DestinationPlugin doesn't have any Segment tie-ins at all.
/// This will allow the destination to be processed in the same way within this library.
/// - Parameters:
/// - plugin: The destination plugin to enable.
public func manuallyEnableDestination(plugin: DestinationPlugin) {
self.store.dispatch(action: System.AddDestinationToSettingsAction(key: plugin.key))
}

}
8 changes: 0 additions & 8 deletions Sources/Segment/Plugins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,6 @@ extension Analytics {
public func add(plugin: Plugin) -> Plugin {
plugin.configure(analytics: self)
timeline.add(plugin: plugin)
if !(plugin is SegmentDestination), let destPlugin = plugin as? DestinationPlugin {
// need to maintain the list of integrations to inject into payload
store.dispatch(action: System.AddIntegrationAction(key: destPlugin.key))
}

return plugin
}

Expand All @@ -155,9 +150,6 @@ extension Analytics {
*/
public func remove(plugin: Plugin) {
timeline.remove(plugin: plugin)
if !(plugin is SegmentDestination), let destPlugin = plugin as? DestinationPlugin {
store.dispatch(action: System.RemoveIntegrationAction(key: destPlugin.key))
}
}

public func find<T: Plugin>(pluginType: T.Type) -> T? {
Expand Down
61 changes: 38 additions & 23 deletions Sources/Segment/Plugins/SegmentDestination.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,29 +70,13 @@ public class SegmentDestination: DestinationPlugin {
}

// MARK: - Event Handling Methods
public func identify(event: IdentifyEvent) -> IdentifyEvent? {
queueEvent(event: event)
return event
}

public func track(event: TrackEvent) -> TrackEvent? {
queueEvent(event: event)
return event
}

public func screen(event: ScreenEvent) -> ScreenEvent? {
queueEvent(event: event)
return event
}

public func alias(event: AliasEvent) -> AliasEvent? {
queueEvent(event: event)
return event
}

public func group(event: GroupEvent) -> GroupEvent? {
queueEvent(event: event)
return event
public func execute<T: RawEvent>(event: T?) -> T? {
let result: T? = event
if let r = result {
let modified = configureCloudDestinations(event: r)
queueEvent(event: modified)
}
return result
}

// MARK: - Abstracted Lifecycle Methods
Expand Down Expand Up @@ -156,6 +140,37 @@ public class SegmentDestination: DestinationPlugin {
}
}

// MARK: - Utility methods
extension SegmentDestination {
internal func configureCloudDestinations<T: RawEvent>(event: T) -> T {
guard let integrationSettings = analytics?.settings() else { return event }
guard let plugins = analytics?.timeline.plugins[.destination]?.plugins as? [DestinationPlugin] else { return event }
guard let customerValues = event.integrations?.dictionaryValue else { return event }

var merged = [String: Any]()

// compare settings to loaded plugins
for plugin in plugins {
let hasSettings = integrationSettings.hasIntegrationSettings(forPlugin: plugin)
if hasSettings {
// we have a device mode plugin installed.
// tell segment not to send it via cloud mode.
merged[plugin.key] = false
}
}

// apply customer values; the customer is always right!
for (key, value) in customerValues {
merged[key] = value
}

var modified = event
modified.integrations = try? JSON(merged)

return modified
}
}

// MARK: - Upload management

extension SegmentDestination {
Expand Down
21 changes: 7 additions & 14 deletions Sources/Segment/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ public struct Settings: Codable {
return integrationSettings(forKey: plugin.key)
}

public func isDestinationEnabled(key: String) -> Bool {
public func hasIntegrationSettings(forPlugin plugin: DestinationPlugin) -> Bool {
return hasIntegrationSettings(key: plugin.key)
}

public func hasIntegrationSettings(key: String) -> Bool {
guard let settings = integrations?.dictionaryValue else { return false }
if settings.keys.contains(key) {
return true
}
return false
return (settings[key] != nil)
}
}

Expand All @@ -85,15 +86,7 @@ extension Settings: Equatable {
}
}

extension Analytics {
/// Manually enable a destination plugin. This is useful when a given DestinationPlugin doesn't have any Segment tie-ins at all.
/// This will allow the destination to be processed in the same way within this library.
/// - Parameters:
/// - plugin: The destination plugin to enable.
public func manuallyEnableDestination(plugin: DestinationPlugin) {
self.store.dispatch(action: System.AddIntegrationAction(key: plugin.key))
}

extension Analytics {
internal func update(settings: Settings, type: UpdateType) {
apply { (plugin) in
// tell all top level plugins to update.
Expand Down
56 changes: 14 additions & 42 deletions Sources/Segment/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import Sovran

struct System: State {
let configuration: Configuration
let integrations: JSON?
let settings: Settings?
let running: Bool

Expand All @@ -21,60 +20,34 @@ struct System: State {

func reduce(state: System) -> System {
let result = System(configuration: state.configuration,
integrations: state.integrations,
settings: settings,
running: state.running)
return result
}
}

struct AddIntegrationAction: Action {
let key: String
struct ToggleRunningAction: Action {
let running: Bool

func reduce(state: System) -> System {
// we need to set any destination plugins to false in the
// integrations payload. this prevents them from being sent
// by segment.com once an event reaches segment.
if var integrations = state.integrations?.dictionaryValue {
integrations[key] = false
if let jsonIntegrations = try? JSON(integrations) {
let result = System(configuration: state.configuration,
integrations: jsonIntegrations,
settings: state.settings,
running: state.running)
return result
}
}
return state
return System(configuration: state.configuration,
settings: state.settings,
running: running)
}
}

struct RemoveIntegrationAction: Action {
struct AddDestinationToSettingsAction: Action {
let key: String

func reduce(state: System) -> System {
if var integrations = state.integrations?.dictionaryValue {
integrations.removeValue(forKey: key)
if let jsonIntegrations = try? JSON(integrations) {
let result = System(configuration: state.configuration,
integrations: jsonIntegrations,
settings: state.settings,
running: state.running)
return result
}
var settings = state.settings
if var integrations = settings?.integrations?.dictionaryValue {
integrations[key] = true
settings?.integrations = try? JSON(integrations)
}
return state
}
}

struct ToggleRunningAction: Action {
let running: Bool

func reduce(state: System) -> System {
return System(configuration: state.configuration,
integrations: state.integrations,
settings: state.settings,
running: running)
settings: settings,
running: state.running)
}
}
}
Expand Down Expand Up @@ -139,8 +112,7 @@ extension System {
settings = Settings(writeKey: configuration.values.writeKey, apiHost: HTTPClient.getDefaultAPIHost())
}
}
let integrationDictionary = try! JSON([String: Any]())
return System(configuration: configuration, integrations: integrationDictionary, settings: settings, running: false)
return System(configuration: configuration, settings: settings, running: false)
}
}

Expand Down
20 changes: 15 additions & 5 deletions Sources/Segment/Timeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,18 +210,28 @@ extension DestinationPlugin {
}
return result
}

internal func isDestinationEnabled(event: RawEvent) -> Bool {
var customerDisabled = false
if let disabled: Bool = event.integrations?.value(forKeyPath: KeyPath(self.key)), disabled == false {
customerDisabled = true
}

var hasSettings = false
if let settings = analytics?.settings() {
hasSettings = settings.hasIntegrationSettings(forPlugin: self)
}

return (hasSettings == true && customerDisabled == false)
}

internal func process<E: RawEvent>(incomingEvent: E) -> E? {
// This will process plugins (think destination middleware) that are tied
// to this destination.

var result: E? = nil

// For destination plugins, we will always have some kind of `settings`,
// and if we don't, it means this destination hasn't been setup on app.segment.com,
// which in turn ALSO means that we shouldn't be sending events to it.

if let enabled = analytics?.settings()?.isDestinationEnabled(key: self.key), enabled == true {
if isDestinationEnabled(event: incomingEvent) {
// apply .before and .enrichment types first ...
let beforeResult = timeline.applyPlugins(type: .before, event: incomingEvent)
let enrichmentResult = timeline.applyPlugins(type: .enrichment, event: beforeResult)
Expand Down
Loading