Skip to content
Open
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
10 changes: 0 additions & 10 deletions Sources/Segment/Analytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,6 @@ public class Analytics {

_ = timeline.process(incomingEvent: event)

/*let flushPolicies = configuration.values.flushPolicies
for policy in flushPolicies {
policy.updateState(event: event)

if (policy.shouldFlush() == true) {
flush()
policy.reset()
}
}*/

let flushPolicies = configuration.values.flushPolicies

var shouldFlush = false
Expand Down
134 changes: 134 additions & 0 deletions Sources/Segment/Builtins.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//
// Builtins.swift
// Segment
//
// Created by Brandon Sneed on 10/31/25.
//

import Foundation

extension Analytics {
internal static let versionKey = "SEGVersionKey"
internal static let buildKey = "SEGBuildKeyV2"

internal static var appCurrentVersion: String {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
}

internal static var appCurrentBuild: String {
Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
}

public func checkAndTrackInstallOrUpdate() {
let previousVersion = UserDefaults.standard.string(forKey: Self.versionKey)
let previousBuild = UserDefaults.standard.string(forKey: Self.buildKey)

if previousBuild == nil {
// Fresh install
if configuration.values.trackedApplicationLifecycleEvents.contains(.applicationInstalled) {
trackApplicationInstalled(version: Self.appCurrentVersion, build: Self.appCurrentBuild)
}
} else if let previousBuild, Self.appCurrentBuild != previousBuild {
// App was updated
if configuration.values.trackedApplicationLifecycleEvents.contains(.applicationUpdated) {
trackApplicationUpdated(
previousVersion: previousVersion ?? "",
previousBuild: previousBuild,
version: Self.appCurrentVersion,
build: Self.appCurrentBuild
)
}
}

// Always update UserDefaults
UserDefaults.standard.setValue(Self.appCurrentVersion, forKey: Self.versionKey)
UserDefaults.standard.setValue(Self.appCurrentBuild, forKey: Self.buildKey)
}

/// Tracks an Application Installed event.
/// - Parameters:
/// - version: The app version (e.g., "1.0.0")
/// - build: The app build number (e.g., "42")
public func trackApplicationInstalled(version: String, build: String) {
track(name: "Application Installed", properties: [
"version": version,
"build": build
])
}

/// Tracks an Application Updated event.
/// - Parameters:
/// - previousVersion: The previous app version
/// - previousBuild: The previous build number
/// - version: The current app version
/// - build: The current build number
public func trackApplicationUpdated(previousVersion: String, previousBuild: String, version: String, build: String) {
track(name: "Application Updated", properties: [
"previous_version": previousVersion,
"previous_build": previousBuild,
"version": version,
"build": build
])
}

/// Tracks an Application Opened event.
/// - Parameters:
/// - fromBackground: Whether the app was opened from background (true) or cold start (false)
/// - url: The URL that opened the app, if any
/// - referringApp: The bundle ID of the app that referred this open, if any
public func trackApplicationOpened(fromBackground: Bool, url: String? = nil, referringApp: String? = nil) {
var properties: [String: Any] = [
"from_background": fromBackground,
"version": Self.appCurrentVersion,
"build": Self.appCurrentBuild
]

if let url = url {
properties["url"] = url
}

if let referringApp = referringApp {
properties["referring_application"] = referringApp
}

track(name: "Application Opened", properties: properties)
}

/// Tracks an Application Backgrounded event.
public func trackApplicationBackgrounded() {
track(name: "Application Backgrounded")
}

/// Tracks an Application Foregrounded event.
public func trackApplicationForegrounded() {
track(name: "Application Foregrounded")
}
}

#if os(macOS)

extension Analytics {
/// Tracks an Application Hidden event (macOS only).
public func trackApplicationHidden() {
track(name: "Application Hidden")
}

/// Tracks an Application Unhidden event (macOS only).
/// - Parameters:
/// - version: The app version (defaults to current version)
/// - build: The app build (defaults to current build)
public func trackApplicationUnhidden(version: String? = nil, build: String? = nil) {
track(name: "Application Unhidden", properties: [
"from_background": true,
"version": version ?? Self.appCurrentVersion,
"build": build ?? Self.appCurrentBuild
])
}

/// Tracks an Application Terminated event (macOS only).
public func trackApplicationTerminated() {
track(name: "Application Terminated")
}
}

#endif
107 changes: 107 additions & 0 deletions Sources/Segment/Plugins/EventDebugger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// EventDebugger.swift
// Segment
//
// Created by Brandon Sneed on 11/1/25.
//

import Foundation
import OSLog

public class EventDebugger: EventPlugin {
public var type: PluginType = .after
public weak var analytics: Analytics? = nil

/// If true, prints full event JSON. If false, prints compact summary.
public var verbose: Bool = false

private let logger: OSLog

required public init() {
self.logger = OSLog(subsystem: "com.segment.analytics", category: "events")
}

public func identify(event: IdentifyEvent) -> IdentifyEvent? {
log(event: event, dot: "🟣", type: "Analytics.IDENTIFY")
return event
}

public func track(event: TrackEvent) -> TrackEvent? {
log(event: event, dot: "🔵", type: "Analytics.TRACK")
return event
}

public func group(event: GroupEvent) -> GroupEvent? {
log(event: event, dot: "🟡", type: "Analytics.GROUP")
return event
}

public func alias(event: AliasEvent) -> AliasEvent? {
log(event: event, dot: "🟢", type: "Analytics.ALIAS")
return event
}

public func screen(event: ScreenEvent) -> ScreenEvent? {
log(event: event, dot: "🟠", type: "Analytics.SCREEN")
return event
}

public func reset() {
os_log("🔴 [Analytics.RESET]", log: logger, type: .info)
}

public func flush() {
os_log("⚪ [Analytics.FLUSH]", log: logger, type: .info)
}

// MARK: - Private Helpers

private func log(event: RawEvent, dot: String, type: String) {
if verbose {
logVerbose(event: event, dot: dot, type: type)
} else {
logCompact(event: event, dot: dot, type: type)
}
}

private func logCompact(event: RawEvent, dot: String, type: String) {
var summary = "\(dot) [\(type)]"

// Add event-specific details
if let track = event as? TrackEvent {
summary += " \(track.event)"
} else if let screen = event as? ScreenEvent {
summary += " \(screen.name ?? screen.category ?? "Screen")"
} else if let identify = event as? IdentifyEvent {
summary += " userId: \(identify.userId ?? "nil")"
} else if let group = event as? GroupEvent {
summary += " groupId: \(group.groupId ?? "nil")"
} else if let alias = event as? AliasEvent {
summary += " \(alias.previousId ?? "nil") → \(alias.userId ?? "nil")"
}

os_log("%{public}@", log: logger, type: .debug, summary)
}

private func logVerbose(event: RawEvent, dot: String, type: String) {
// Pretty-print the JSON
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

if let data = try? encoder.encode(event),
let jsonString = String(data: data, encoding: .utf8) {
os_log("%{public}@ [%{public}@]\n%{public}@",
log: logger,
type: .debug,
dot,
type,
jsonString)
} else {
os_log("%{public}@ [%{public}@] Failed to encode event",
log: logger,
type: .error,
dot,
type)
}
}
}
72 changes: 21 additions & 51 deletions Sources/Segment/Plugins/Platforms/Mac/macOSLifecycleEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,45 @@
// Created by Cody on 4/20/22.
//

import Foundation

#if os(macOS)

import Foundation
import Cocoa

class macOSLifecycleEvents: PlatformPlugin, macOSLifecycle {
static var versionKey = "SEGVersionKey"
static var buildKey = "SEGBuildKeyV2"

let type = PluginType.before
weak var analytics: Analytics?

/// Since application:didFinishLaunchingWithOptions is not automatically called with Scenes / SwiftUI,
/// this gets around by using a flag in user defaults to check for big events like application updating,
/// being installed or even opening.
@Atomic
private var didFinishLaunching = false

func application(didFinishLaunchingWithOptions launchOptions: [String : Any]?) {
// Make sure we aren't double calling application:didFinishLaunchingWithOptions
// by resetting the check at the start
_didFinishLaunching.set(true)

let previousVersion = UserDefaults.standard.string(forKey: Self.versionKey)
let previousBuild = UserDefaults.standard.string(forKey: Self.buildKey)

let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
let currentBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
@Atomic
private var didCheckInstallOrUpdate = false

func configure(analytics: Analytics) {
self.analytics = analytics

if previousBuild == nil {
if analytics?.configuration.values.trackedApplicationLifecycleEvents.contains(.applicationInstalled) == true {
analytics?.track(name: "Application Installed", properties: [
"version": currentVersion ?? "",
"build": currentBuild ?? ""
])
}
} else if currentBuild != previousBuild {
if analytics?.configuration.values.trackedApplicationLifecycleEvents.contains(.applicationUpdated) == true {
analytics?.track(name: "Application Updated", properties: [
"previous_version": previousVersion ?? "",
"previous_build": previousBuild ?? "",
"version": currentVersion ?? "",
"build": currentBuild ?? ""
])
}
// Check install/update immediately to catch first launch
if !didCheckInstallOrUpdate {
analytics.checkAndTrackInstallOrUpdate()
_didCheckInstallOrUpdate.set(true)
}
}

func application(didFinishLaunchingWithOptions launchOptions: [String : Any]?) {
_didFinishLaunching.set(true)

if analytics?.configuration.values.trackedApplicationLifecycleEvents.contains(.applicationOpened) == true {
analytics?.track(name: "Application Opened", properties: [
"from_background": false,
"version": currentVersion ?? "",
"build": currentBuild ?? ""
])
analytics?.trackApplicationOpened(fromBackground: false)
}

UserDefaults.standard.setValue(currentVersion, forKey: Self.versionKey)
UserDefaults.standard.setValue(currentBuild, forKey: Self.buildKey)
}

func applicationDidUnhide() {
if analytics?.configuration.values.trackedApplicationLifecycleEvents.contains(.applicationUnhidden) == true {
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
let currentBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String

analytics?.track(name: "Application Unhidden", properties: [
"from_background": true,
"version": currentVersion ?? "",
"build": currentBuild ?? ""
"version": Analytics.appCurrentVersion,
"build": Analytics.appCurrentBuild
])
}
}
Expand All @@ -83,17 +53,17 @@ class macOSLifecycleEvents: PlatformPlugin, macOSLifecycle {
analytics?.track(name: "Application Hidden")
}
}

func applicationDidResignActive() {
if analytics?.configuration.values.trackedApplicationLifecycleEvents.contains(.applicationBackgrounded) == true {
analytics?.track(name: "Application Backgrounded")
analytics?.trackApplicationBackgrounded()
}
}

func applicationDidBecomeActive() {
if analytics?.configuration.values.trackedApplicationLifecycleEvents.contains(.applicationForegrounded) == false {
return
if analytics?.configuration.values.trackedApplicationLifecycleEvents.contains(.applicationForegrounded) == true {
analytics?.trackApplicationForegrounded()
}
analytics?.track(name: "Application Foregrounded")

// Lets check if we skipped application:didFinishLaunchingWithOptions,
// if so, lets call it.
Expand Down
Loading
Loading