Analytics Live is a powerful enhancement suite that enables dynamic Analytics-Swift, and gives Segment customers the ability to deploy Destination Filters, transformations and even Auto-Instrumentation without app updates, while maintaining enterprise-grade security and performance.
LivePlugins revolutionizes how you handle analytics transformations by allowing you to write and deploy JavaScript-based transformation plugins directly to your mobile app.
Instead of embedding transformation logic in native Swift code and waiting for app release cycles, you can now:
- Write and update transformation logic in JavaScript
- Deploy changes instantly via server-side updates
- Maintain native-level performance with optimized JavaScript execution
- Seamlessly integrate with your existing analytics implementation
Signals provides automated user activity tracking through a sophisticated breadcrumb system. It captures crucial user interactions and allows you to transform them into meaningful analytics events using JavaScript.
Key Capabilities:
-
Comprehensive Activity Tracking
- Navigation events and screen transitions
- User interface interactions
- Network activity monitoring (inbound/outbound)
- Local data access patterns
- Integration with existing analytics events
-
Enterprise-Grade Privacy
- Built-in PII protection
- Automatic data obfuscation in release builds
- Configurable privacy rules
-
Flexible Event Generation
- Transform breadcrumbs into Segment events using JavaScript
- Create custom event generation rules
- Process and filter data in real-time
Destination Filters brings Segment's powerful server-side filtering capability directly to your mobile app. This feature:
- Uses the same JavaScript filtering logic as Segment's server-side implementation
- Extends filtering capabilities to device-mode destinations
- Ensures consistent filtering behavior across your entire analytics pipeline
- Optimizes network usage by filtering events before transmission
- Reduces data points consumed by preventing unwanted events from being sent
- Analytics-Swift version 1.6.2 or higher
- macOS 10.15+ or iOS 13+
Add Analytics Live as a dependency in your Package.swift
file:
dependencies: [
// your existing reference to analytics-swift
.package(url: "https://github.com/segmentio/analytics-swift.git", from: "1.6.2")
// add analytics-live ...
.package(url: "https://github.com/segment-integrations/analytics-swift-live.git", from: "1.0.0")
]
- In Xcode, select File → Add Packages...
- Enter the package URL: https://github.com/segment-integrations/analytics-swift-live.git
- Click Add Package
Analytics Live integrates with your existing Analytics-Swift setup through three main plugins:
import AnalyticsLive
LivePlugins allows you to transform analytics data using JavaScript. You can provide a fallback JavaScript file that will be used when network access is unavailable or during first launch.
// Set up fallback file
let fallbackURL: URL? = Bundle.main.url(forResource: "myFallback", withExtension: "js")
// Initialize and add LivePlugins
let lp = LivePlugins(fallbackFileURL: fallbackURL)
Analytics.main.add(plugin: lp)
Signals is implemented as a singleton to enable signal emission from anywhere in your app. Basic setup requires only a write key, with additional configuration options covered in the Signals Configuration section.
let config = SignalsConfiguration(writeKey: "<YOUR WRITE KEY>")
Signals.shared.useConfiguration(config)
Analytics.main.add(plugin: Signals.shared)
Destination Filters allows you to filter analytics events on-device using rules configured in the Segment web app.
let filters = DestinationFilters()
Analytics.main.add(plugin: filters)
LivePlugins allows you to write JavaScript-based transformation plugins that process analytics events in your app. These plugins run in a secure, sandboxed JavaScript environment with no network access. Also see the LivePlugins Javascript API Reference for more information.
LivePlugins can be created with different types that determine when they run in the analytics timeline:
before
- Runs before any processing occursenrichment
- Runs during the main processing phaseafter
- Runs after all processing is completeutility
- Helper plugins that don't directly process events
Here's a simple plugin that fixes a misnamed property:
class FixProductViewed extends LivePlugin {
track(event) {
if (event.event == "Product Viewed") {
// set the correct property name
event.properties.product_id = event.properties.product_did
// remove old property
delete event.properties.product_did
}
return event
}
}
// Create and add the plugin
let productViewFix = new FixProductViewed(LivePluginType.enrichment, null)
analytics.add(productViewFix)
Plugins can target specific destinations by specifying a destination key:
// Remove advertisingId only from Amplitude events
class RemoveAdvertisingId extends LivePlugin {
process(event) {
delete event.context.device.advertisingId
return super.process(event)
}
}
let deleteAdID = new RemoveAdvertisingId(LivePluginType.enrichment, "Amplitude")
analytics.add(deleteAdID)
LivePlugins runs in a limited JavaScript environment:
- No network access
- Basic JavaScript functionality only
- Console logging available via
console.log()
- Access to analytics instance via pre-defined
analytics
variable
LivePlugins can implement several lifecycle methods:
process(event)
- Called for all eventstrack(event)
- Called for track eventsidentify(event)
- Called for identify eventsscreen(event)
- Called for screen eventsgroup(event)
- Called for group eventsalias(event)
- Called for alias eventsupdate(settings, type)
- Called when settings are updatedreset()
- Called when analytics is resetflush()
- Called when analytics is flushed
Signals are lightweight data points that capture user interactions and system events in your app. While individual signals provide small pieces of information, they become powerful when combined to generate rich analytics events. Also see the Signals Javascript API Reference for more information.
Signals represent discrete app activities, such as:
- Button taps (e.g., "Add To Cart" button clicked)
- Navigation changes (e.g., user entered Product Detail screen)
- Network requests/responses
- User interactions
- System events
The Signals system maintains a buffer of recent signals (default: 1000) that can be used by JavaScript event generators. This buffer allows you to:
- Access historical signals
- Correlate related signals
- Build rich context for events
When signals are emitted, they're processed through your custom signal processing function:
function processSignal(signal) {
trackScreens(signal)
trackAddToCart(signal)
}
This will then reach out to the individual event generators to see if Segment events can be formed.
Event generators are JavaScript functions that process signals and generate Segment events. Here's a simple example that creates screen events:
function trackScreens(signal) {
if (signal.type === SignalType.Navigation && signal.data.action === NavigationAction.Entering) {
analytics.screen("Screen Viewed", null, {
"screenName": signal.data.screen
})
}
}
Event generators can look back through the signal buffer to correlate related signals. Here's an example that combines user interaction with network data:
function trackAddToCart(signal) {
// Check for "Add To Cart" button tap
if (signal.type === SignalType.Interaction && signal.data.title === "Add To Cart") {
let properties = {}
// Look for recent product network response
const network = signals.find(signal, SignalType.Network, (s) => {
return (s.data.action === NetworkAction.Response && s.data.url.includes("product"))
})
// Enrich event with product data
if (network) {
properties.price = network.data.data.content.price
properties.currency = network.data.data.content.currency ?? "USD"
properties.productId = network.data.data.content.id
properties.productName = network.data.data.content.title
}
// Send enriched track event
analytics.track("Add To Cart", properties)
}
}
This approach to signal correlation allows you to:
- Build rich, contextual events
- Combine data from multiple sources
- Process data efficiently on-device
- Reduce need for server-side data correlation
Signals can be configured through the SignalsConfiguration
struct, which offers various options to control buffer size, automatic signal generation, network monitoring, and broadcasting behavior.
let config = SignalsConfiguration(
writeKey: "<YOUR WRITE KEY>",
useSwiftUIAutoSignal: true,
useNetworkAutoSignal: true
)
writeKey: String
- Your Segment write key
- No default value, must be provided
maximumBufferSize: Int
- Maximum number of signals kept in memory
- Default: 1000
relayCount: Int
- Number of signals to collect before processing
- Default: 20
relayInterval: TimeInterval
- Time interval between signal processing in seconds
- Default: 60
useUIKitAutoSignal: Bool
- Enable automatic UIKit interaction signals
- Default: false
useSwiftUIAutoSignal: Bool
- Enable automatic SwiftUI interaction signals
- Default: false
useNetworkAutoSignal: Bool
- Enable automatic network request/response signals
- Default: false
allowedNetworkHosts: [String]
- List of hosts to monitor for network signals
- Default: ["*"] (all hosts)
blockedNetworkHosts: [String]
- List of hosts to exclude from network signals
- Default: [] (empty array)
- Automatically includes Segment endpoints:
api.segment.com cdn-settings.segment.com signals.segment.com api.segment.build cdn.segment.build signals.segment.build
broadcasters: [SignalBroadcaster]?
- Array of broadcasters to handle signals
- Default: [SegmentBroadcaster()]
- Available broadcasters:
SegmentBroadcaster
: Sends signals to Segment when in Debug modeDebugBroadcaster
: Prints signals to the consoleWebhookBroadcaster
: Delivers signals to a specified webhook URL
let config = SignalsConfiguration(
writeKey: "<YOUR WRITE KEY>"
)
let config = SignalsConfiguration(
writeKey: "<YOUR WRITE KEY>",
maximumBufferSize: 2000,
relayCount: 10,
relayInterval: 30,
useSwiftUIAutoSignal: true,
useNetworkAutoSignal: true
)
Note: For SwiftUI, you'll also need to add these typealiases somewhere in your project to allow interaction signals to be captured. These are thin wrappers over SwiftUI's structs, no UI element behavior changes will occur.
typealias Button = SignalButton
typealias NavigationLink = SignalNavigationLink
typealias NavigationStack = SignalNavigationStack
typealias TextField = SignalTextField
typealias SecureField = SignalSecureField
let config = SignalsConfiguration(
writeKey: "<YOUR WRITE KEY>",
useNetworkAutoSignal: true,
allowedNetworkHosts: ["api.myapp.com", "api.myanalytics.com"],
blockedNetworkHosts: ["internal.myapp.com"]
)
All signals share common fields:
anonymousId
: The anonymous identifier of the usertimestamp
: ISO8601 formatted timestamp of when the signal was createdindex
: Sequential index of the signaltype
: The type of signal (navigation, interaction, network, etc.)data
: Signal-specific data as detailed below
Captures navigation events within your app.
action
: Type of navigationforward
: Forward navigationbackward
: Backward navigationmodal
: Modal presentationentering
: Entering a screenleaving
: Leaving a screenpage
: Page changepopup
: Popup displayscreen
: Name or identifier of the screen
Captures user interactions with UI components.
component
: Type of UI component interacted withtitle
: Text or identifier associated with the component (optional)data
: Additional contextual data about the interaction in JSON format (optional)
Monitors network activity in your app.
action
: Type of network activityrequest
: Outgoing network requestresponse
: Incoming network responseurl
: The URL being accessedstatusCode
: HTTP status code (for responses)data
: Response data in JSON format (optional)
Monitors interactions with local data storage.
action
: Type of data operationloaded
: Data was loadedupdated
: Data was updatedsaved
: Data was saveddeleted
: Data was deletedundefined
: Other operationsidentifier
: Identifier for the data being operated ondata
: The data being operated on in JSON format (optional)
Captures analytics events from your existing instrumentation.
type
: Type of analytics eventtrack
: Track eventsscreen
: Screen eventsidentify
: Identify eventsgroup
: Group eventsalias
: Alias eventsunknown
: Unknown event typesrawEvent
: The original analytics event data in JSON format
Create custom signals to capture app-specific events.
type
: Always.userDefined
data
: Custom data structure defined by you
Example of creating a custom signal:
struct MyKindaSignal: RawSignal {
struct MyKindaData {
let that: String
}
var anonymousId: String = Signals.shared.anonymousId
var type: SignalType = .userDefined
var timestamp: String = Date().iso8601()
var index: Int = Signals.shared.nextIndex
var data: MyKindaData
init(that: String) {
self.data = MyKindaData(that: that)
}
}
...
// Manually emit the signal in your code ...
Signals.emit(myKindaSignal("Robbie Ray Rana")
Signals are broadcast according to these rules:
SegmentBroadcaster
: Only broadcasts signals to Segment when app is built with DEBUG enabledDebugBroadcaster
: Always broadcasts signals to console, regardless of build configurationWebhookBroadcaster
: Always broadcasts signals to specified webhook URL, regardless of build configuration
You can create your own broadcasters in two ways:
- Conform to
SignalBroadcaster
to work with typed signals:
public protocol SignalBroadcaster {
var analytics: Analytics? { get set }
func added(signal: any RawSignal)
func relay()
}
- Conform to
SignalJSONBroadcaster
to work with raw dictionary data:
public protocol SignalJSONBroadcaster: SignalBroadcaster {
func added(signal: [String: Any])
}
The JSON broadcaster is useful when you need to work with the raw dictionary representation of signals before they're converted to JSON.
Note: Signals is a paid feature that may need to be enabled on your workspace by your Segment representative.
Destination Filters allows you to run your Segment workspace's destination filters directly on-device. This feature requires:
- A Segment workspace with Destination Filters enabled (contact your Segment representative)
- Destination Filters configured in your workspace at https://app.segment.com
Add the Destination Filters plugin to your analytics instance:
let filters = DestinationFilters()
Analytics.main.add(plugin: filters)
Once configured, your device-mode destinations will automatically respect the same filtering rules you've set up in your Segment workspace.
Note: Destination Filters is a paid feature that may need to be enabled on your workspace by your Segment representative.
console.log(message)
Log a message to the native console.
A pre-configured Analytics instance using your write key:
let analytics = Analytics("<YOUR WRITE KEY>")
class Analytics {
constructor(writeKey) {} // Create new Analytics instance
// Properties
get traits() {} // Get current user traits
get userId() {} // Get current user ID
get anonymousId() {} // Get current anonymous ID
// Event Methods
track(event, properties) {} // Send track event
identify(userId, traits) {} // Send identify event
screen(title, category, properties) {} // Send screen event
group(groupId, traits) {} // Send group event
alias(newId) {} // Send alias event
// System Methods
reset() {} // Reset analytics state
flush() {} // Flush queued events
add(livePlugin) {} // Add a LivePlugin
}
class LivePlugin {
constructor(type, destination) {} // Create plugin with type and optional destination
// Properties
get type() {} // Get plugin type
get destination() {} // Get destination key or null
// Lifecycle Methods
update(settings, type) {} // Handle settings updates
process(event) {} // Process any event type
// Event Methods
track(event) {} // Handle track events
identify(event) {} // Handle identify events
group(event) {} // Handle group events
alias(event) {} // Handle alias events
screen(event) {} // Handle screen events
// System Methods
reset() {} // Handle analytics reset
flush() {} // Handle analytics flush
}
Available plugin types:
const LivePluginType = {
before: "before", // Run before processing
enrichment: "enrichment", // Run during processing
after: "after", // Run after processing
utility: "utility" // Utility plugins
}
Settings update types:
const UpdateType = {
initial: true, // Initial settings load
refresh: false // Settings refresh
}
const SignalType = {
Interaction: "interaction",
Navigation: "navigation",
Network: "network",
LocalData: "localData",
Instrumentation: "instrumentation",
UserDefined: "userDefined"
}
Base class for all signals:
class RawSignal {
constructor(type, data) {} // Create new signal with type and data
// Properties
anonymousId: string // Anonymous ID from analytics instance
type: SignalType // Type of signal
data: object // Signal-specific data
timestamp: Date // Creation timestamp
index: number // Sequential index (set by signals.add())
}
Navigation action constants:
const NavigationAction = {
Forward: "forward", // Forward navigation
Backward: "backward", // Backward navigation
Modal: "modal", // Modal presentation
Entering: "entering", // Screen entry
Leaving: "leaving", // Screen exit
Page: "page", // Page change
Popup: "popup" // Popup display
}
Navigation signal class:
class NavigationSignal extends RawSignal {
constructor(action, screen) {} // Create navigation signal
// Data Properties
data.action: string // NavigationAction value
data.screen: string // Screen identifier
}
class InteractionSignal extends RawSignal {
constructor(component, info, object) {} // Create interaction signal
// Data Properties
data.component: string // UI component type
data.info: string // Additional information
data.data: object // Custom interaction data
}
Network action constants:
const NetworkAction = {
Request: "request", // Outgoing request
Response: "response" // Incoming response
}
Network signal class:
class NetworkSignal extends RawSignal {
constructor(action, url, object) {} // Create network signal
// Data Properties
data.action: string // NetworkAction value
data.url: string // Request/response URL
data.data: object // Network payload data
}
Local data action constants:
const LocalDataAction = {
Loaded: "loaded", // Data loaded
Updated: "updated", // Data updated
Saved: "saved", // Data saved
Deleted: "deleted", // Data deleted
Undefined: "undefined" // Other operations
}
Local data signal class:
class LocalDataSignal extends RawSignal {
constructor(action, identifier, object) {} // Create local data signal
// Data Properties
data.action: string // LocalDataAction value
data.identifier: string // Data identifier
data.data: object // Associated data
}
Event type constants:
const EventType = {
Track: "track", // Track events
Screen: "screen", // Screen events
Identify: "identify", // Identify events
Group: "group", // Group events
Alias: "alias" // Alias events
}
Instrumentation signal class:
class InstrumentationSignal extends RawSignal {
constructor(rawEvent) {} // Create instrumentation signal
// Data Properties
data.type: string // EventType value
data.rawEvent: object // Original analytics event
}
The Signals class manages a buffer of recently collected signals:
class Signals {
constructor() {} // Create new signals buffer
// Properties
signalBuffer: RawSignal[] // Array of signals
signalCounter: number // Current signal count
maxBufferSize: number // Maximum buffer size (default: 1000)
// Methods
add(signal) {} // Add signal to buffer
getNextIndex() {} // Get next signal index
// Signal Search Methods
find(fromSignal, // Starting signal (optional)
signalType, // Signal type to find (optional)
predicate) {} // Search predicate function
findAndApply(fromSignal, // Starting signal (optional)
signalType, // Signal type to find (optional)
searchPredicate, // Search criteria
applyPredicate) {} // Function to apply to found signal
}
A global instance is automatically created and available:
let signals = new Signals() // Global signals buffer instance
Creating and adding signals:
// Create navigation signal
let navSignal = new NavigationSignal(
NavigationAction.Entering,
"ProductDetail"
)
signals.add(navSignal)
// Create interaction signal
let buttonSignal = new InteractionSignal(
"button",
"Add to Cart",
{ productId: "123" }
)
signals.add(buttonSignal)
Finding related signals:
// Find most recent network response
let networkSignal = signals.find(
currentSignal,
SignalType.Network,
(signal) => {
return signal.data.action === NetworkAction.Response
}
)
// Find and process related signals
signals.findAndApply(
currentSignal,
SignalType.Interaction,
(signal) => signal.data.component === "button",
(found) => {
// Process found signal
console.log("Found related interaction:", found)
}
)