Skip to content

Commit

Permalink
chore(ios): add custom event tracking (#1676)
Browse files Browse the repository at this point in the history
  • Loading branch information
adwinross authored Jan 8, 2025
1 parent e66ec9e commit 9517e8e
Show file tree
Hide file tree
Showing 35 changed files with 777 additions and 48 deletions.
7 changes: 7 additions & 0 deletions ios/DemoApp/Controller/ObjcDetailViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ - (void)viewDidLoad {

UIView *headerView = [self createTableHeaderView];
tableView.tableHeaderView = headerView;

NSDictionary *userAttributes = @{
@"user_name": @"Alice",
@"paid_user": @YES,
@"credit_balance": @1000,
@"latitude": @30.2661403415387};
[[Measure shared] trackEvent:@"event-name" attributes:userAttributes timestamp:nil];

[self setTitle:@"Objc View Controller"];

Expand Down
9 changes: 9 additions & 0 deletions ios/DemoApp/Controller/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ import MeasureSDK
view.addSubview(tableView)
}

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
let attributes: [String: AttributeValue] = ["user_name": .string("Alice"),
"paid_user": .boolean(true),
"credit_balance": .int(1000),
"latitude": .double(30.2661403415387)]
Measure.shared.trackEvent(name: "custom_event", attributes: attributes, timestamp: nil)
}

// MARK: - Table Header View with Buttons

func createTableHeaderView() -> UIView {
Expand Down
16 changes: 16 additions & 0 deletions ios/MeasureSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
5225D0332D088B7100FD240D /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D02C2D088B7100FD240D /* URLSessionTaskSwizzler.swift */; };
5225D0352D0AEB1A00FD240D /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D0342D0AEB1A00FD240D /* String+Extension.swift */; };
5225D0502D0FECFF00FD240D /* InputStream+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D04F2D0FECFF00FD240D /* InputStream+Extension.swift */; };
522532CA2D295F3F001B5D7C /* AttributeValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522532C92D295F3F001B5D7C /* AttributeValue.swift */; };
5229D16E2CCB533C00EFFE44 /* RecentSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5229D16D2CCB533C00EFFE44 /* RecentSession.swift */; };
523287692C85E07B000EE268 /* LifecycleObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523287682C85E07B000EE268 /* LifecycleObserverTests.swift */; };
523287732C86195E000EE268 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523287722C86195E000EE268 /* SessionManagerTests.swift */; };
Expand Down Expand Up @@ -115,6 +116,8 @@
52AE72032CABAE9000F2830A /* GestureEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52AE71FD2CABAE9000F2830A /* GestureEvents.swift */; };
52AE72082CABAEAB00F2830A /* UIWindow+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52AE72062CABAEAB00F2830A /* UIWindow+Extension.swift */; };
52AE72092CABAEAB00F2830A /* NSObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52AE72072CABAEAB00F2830A /* NSObject+Extension.swift */; };
52B2A8772D1A790200C6B5CF /* CustomEventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B2A8762D1A790200C6B5CF /* CustomEventData.swift */; };
52B2A8792D1A89EF00C6B5CF /* CustomEventCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B2A8782D1A89EF00C6B5CF /* CustomEventCollector.swift */; };
52BCF1DC2CB42026003102DF /* MeasureModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 52BCF1DA2CB42026003102DF /* MeasureModel.xcdatamodeld */; };
52BCF1DD2CB42383003102DF /* MeasureModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 52BCF1DA2CB42026003102DF /* MeasureModel.xcdatamodeld */; };
52BCF20A2CB59FBF003102DF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BCF2092CB59FBF003102DF /* AppDelegate.swift */; };
Expand Down Expand Up @@ -177,6 +180,7 @@
52E303E42D1818EA008FE733 /* NetworkChangeDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E303E32D1818EA008FE733 /* NetworkChangeDetector.swift */; };
52E303E62D191A05008FE733 /* NetworkChangeCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E303E52D191A05008FE733 /* NetworkChangeCallback.swift */; };
52EB380C2C8C7334002D63EC /* SignPost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EB380B2C8C7334002D63EC /* SignPost.swift */; };
52F0C6352D2D46980060FD08 /* CustomEventCollectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F0C6342D2D46980060FD08 /* CustomEventCollectorTests.swift */; };
52F3773B2CB41DDF006147E8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F3773A2CB41DDF006147E8 /* AppDelegate.swift */; };
52F3773D2CB41DE0006147E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F3773C2CB41DDF006147E8 /* SceneDelegate.swift */; };
52F377422CB41DE0006147E8 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 52F377412CB41DE0006147E8 /* Base */; };
Expand Down Expand Up @@ -361,6 +365,7 @@
5225D02C2D088B7100FD240D /* URLSessionTaskSwizzler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzler.swift; sourceTree = "<group>"; };
5225D0342D0AEB1A00FD240D /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
5225D04F2D0FECFF00FD240D /* InputStream+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InputStream+Extension.swift"; sourceTree = "<group>"; };
522532C92D295F3F001B5D7C /* AttributeValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeValue.swift; sourceTree = "<group>"; };
5229D16D2CCB533C00EFFE44 /* RecentSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentSession.swift; sourceTree = "<group>"; };
523287682C85E07B000EE268 /* LifecycleObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifecycleObserverTests.swift; sourceTree = "<group>"; };
523287722C86195E000EE268 /* SessionManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManagerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -430,6 +435,8 @@
52AE71FD2CABAE9000F2830A /* GestureEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GestureEvents.swift; sourceTree = "<group>"; };
52AE72062CABAEAB00F2830A /* UIWindow+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIWindow+Extension.swift"; sourceTree = "<group>"; };
52AE72072CABAEAB00F2830A /* NSObject+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Extension.swift"; sourceTree = "<group>"; };
52B2A8762D1A790200C6B5CF /* CustomEventData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEventData.swift; sourceTree = "<group>"; };
52B2A8782D1A89EF00C6B5CF /* CustomEventCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEventCollector.swift; sourceTree = "<group>"; };
52BCF1DB2CB42026003102DF /* MeasureModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeasureModel.xcdatamodel; sourceTree = "<group>"; };
52BCF2072CB59FBF003102DF /* TestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
52BCF2092CB59FBF003102DF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -486,6 +493,7 @@
52E303E32D1818EA008FE733 /* NetworkChangeDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkChangeDetector.swift; sourceTree = "<group>"; };
52E303E52D191A05008FE733 /* NetworkChangeCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkChangeCallback.swift; sourceTree = "<group>"; };
52EB380B2C8C7334002D63EC /* SignPost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignPost.swift; sourceTree = "<group>"; };
52F0C6342D2D46980060FD08 /* CustomEventCollectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEventCollectorTests.swift; sourceTree = "<group>"; };
52F377382CB41DDF006147E8 /* DemoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
52F3773A2CB41DDF006147E8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
52F3773C2CB41DDF006147E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -572,6 +580,7 @@
5202BE302C895FC800A3496E /* AppAttributeProcessor.swift */,
5202BE312C895FC800A3496E /* Attribute.swift */,
5202BE322C895FC800A3496E /* AttributeProcessor.swift */,
522532C92D295F3F001B5D7C /* AttributeValue.swift */,
5202BE332C895FC800A3496E /* ComputeOnceAttributeProcessor.swift */,
5202BE342C895FC800A3496E /* DeviceAttributeProcessor.swift */,
5202BE352C895FC800A3496E /* InstallationIdAttributeProcessor.swift */,
Expand Down Expand Up @@ -619,6 +628,8 @@
children = (
5202BE732C8B117900A3496E /* Attachment.swift */,
5202BE742C8B117900A3496E /* AttachmentType.swift */,
52B2A8782D1A89EF00C6B5CF /* CustomEventCollector.swift */,
52B2A8762D1A790200C6B5CF /* CustomEventData.swift */,
5202BE752C8B117900A3496E /* Event.swift */,
5202BE762C8B117900A3496E /* EventProcessor.swift */,
5202BE772C8B117900A3496E /* EventType.swift */,
Expand Down Expand Up @@ -897,6 +908,7 @@
isa = PBXGroup;
children = (
52A1A9612CA592A100461103 /* EventProcessorTests.swift */,
52F0C6342D2D46980060FD08 /* CustomEventCollectorTests.swift */,
);
path = Event;
sourceTree = "<group>";
Expand Down Expand Up @@ -1412,12 +1424,14 @@
52AE72022CABAE9000F2830A /* GestureDetector.swift in Sources */,
52CC63C12C9C608E00F7CA0A /* CrashDataPersistence.swift in Sources */,
52EB380C2C8C7334002D63EC /* SignPost.swift in Sources */,
52B2A8792D1A89EF00C6B5CF /* CustomEventCollector.swift in Sources */,
52A8533E2C994CC200B2A39F /* ExceptionDetail.swift in Sources */,
5225D02F2D088B7100FD240D /* HttpEventCollector.swift in Sources */,
526E30F62CE77BCB00F484B4 /* AppLaunchEvents.swift in Sources */,
52A853402C994D7900B2A39F /* Exception.swift in Sources */,
5222C9E82D14605000B198DA /* NetworkInterceptor.swift in Sources */,
52159E272CC802A800486F54 /* EventSerializer.swift in Sources */,
522532CA2D295F3F001B5D7C /* AttributeValue.swift in Sources */,
528EAB892C804AA100CB1574 /* SessionManager.swift in Sources */,
52CD91262C7C3D90000189BA /* MeasureInitializer.swift in Sources */,
52A1A94A2CA3CF9B00461103 /* EventStore.swift in Sources */,
Expand All @@ -1437,6 +1451,7 @@
524576712CC1128600B288E5 /* BatchCreator.swift in Sources */,
52F93B9E2CAE6B3C00168AB4 /* GestureTargetFinder.swift in Sources */,
52D51ABB2CD0E5C9008F30A6 /* MsrMoniterView.swift in Sources */,
52B2A8772D1A790200C6B5CF /* CustomEventData.swift in Sources */,
524576732CC116DD00B288E5 /* BatchStore.swift in Sources */,
52816B6A2CCE399B00B160A4 /* LifecycleCollector.swift in Sources */,
528EAB962C84553500CB1574 /* LifecycleObserver.swift in Sources */,
Expand Down Expand Up @@ -1505,6 +1520,7 @@
52A1A9662CA5AC9900461103 /* Attachment+Extension.swift in Sources */,
52CC63D72C9EC5FA00F7CA0A /* CrashReportingManagerTests.swift in Sources */,
52D3D7452CC6191B004E404B /* HeartbeatTests.swift in Sources */,
52F0C6352D2D46980060FD08 /* CustomEventCollectorTests.swift in Sources */,
52BCF1DD2CB42383003102DF /* MeasureModel.xcdatamodeld in Sources */,
52FA6A8F2CE222360091F089 /* MemoryUsageCalculatorTests.swift in Sources */,
52159E292CC8091E00486F54 /* EventSerializerTests.swift in Sources */,
Expand Down
9 changes: 6 additions & 3 deletions ios/MeasureSDK/AppLaunch/AppLaunchCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ final class BaseAppLaunchCollector: AppLaunchCollector {
type: .coldLaunch,
attributes: nil,
sessionId: nil,
attachments: nil)
attachments: nil,
userDefinedAttributes: nil)
}

func onWarmLaunchCallback(_ data: WarmLaunchData) {
Expand All @@ -59,7 +60,8 @@ final class BaseAppLaunchCollector: AppLaunchCollector {
type: .warmLaunch,
attributes: nil,
sessionId: nil,
attachments: nil)
attachments: nil,
userDefinedAttributes: nil)
}

func onHotLaunchCallback(_ data: HotLaunchData) {
Expand All @@ -68,6 +70,7 @@ final class BaseAppLaunchCollector: AppLaunchCollector {
type: .hotLaunch,
attributes: nil,
sessionId: nil,
attachments: nil)
attachments: nil,
userDefinedAttributes: nil)
}
}
68 changes: 68 additions & 0 deletions ios/MeasureSDK/Attribute/AttributeValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// AttributeValue.swift
// MeasureSDK
//
// Created by Adwin Ross on 04/01/25.
//

import Foundation

/// Represents a value of an attribute. It can be a string, boolean, integer, or double.
public enum AttributeValue {
case string(String)
case boolean(Bool)
case int(Int)
case long(Int64)
case float(Float)
case double(Double)

/// Returns the underlying value as `Any`.
var value: Any {
switch self {
case .string(let value): return value
case .boolean(let value): return value
case .int(let value): return value
case .long(let value): return value
case .float(let value): return value
case .double(let value): return value
}
}

func serialize() -> Any {
switch self {
case .string(let stringValue):
return "\"\(stringValue)\""
default:
return self.value
}
}
}

/// Serializer for `AttributeValue` to handle encoding and decoding.
enum AttributeValueSerializer {
static func serialize(_ value: AttributeValue) -> Any {
switch value {
case .string(let stringValue):
return "\"\(stringValue)\""
default:
return value.value
}
}

static func deserialize(from value: Any) -> AttributeValue? {
if let stringValue = value as? String {
return .string(stringValue)
} else if let boolValue = value as? Bool {
return .boolean(boolValue)
} else if let intValue = value as? Int {
return .int(intValue)
} else if let longValue = value as? Int64 {
return .long(longValue)
} else if let floatValue = value as? Float {
return .float(floatValue)
} else if let doubleValue = value as? Double {
return .double(doubleValue)
}
return nil
}
}
20 changes: 20 additions & 0 deletions ios/MeasureSDK/Config/BaseConfigProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ final class BaseConfigProvider: ConfigProvider {
self.cachedConfig = configLoader.getCachedConfig()
}

var maxUserDefinedAttributesPerEvent: Int {
return getMergedConfig(\.maxUserDefinedAttributesPerEvent)
}

var maxUserDefinedAttributeKeyLength: Int {
return getMergedConfig(\.maxUserDefinedAttributeKeyLength)
}

var maxUserDefinedAttributeValueLength: Int {
return getMergedConfig(\.maxUserDefinedAttributeValueLength)
}

var maxEventNameLength: Int {
return getMergedConfig(\.maxEventNameLength)
}

var customEventNameRegex: String {
return getMergedConfig(\.customEventNameRegex)
}

var maxSessionDurationMs: Number {
return getMergedConfig(\.maxSessionDurationMs)
}
Expand Down
10 changes: 10 additions & 0 deletions ios/MeasureSDK/Config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ struct Config: InternalConfig, MeasureConfig {
let memoryTrackingIntervalMs: UnsignedNumber
let httpContentTypeAllowlist: [String]
let defaultHttpHeadersBlocklist: [String]
let customEventNameRegex: String
let maxEventNameLength: Int
let maxUserDefinedAttributeKeyLength: Int
let maxUserDefinedAttributeValueLength: Int
let maxUserDefinedAttributesPerEvent: Int

internal init(enableLogging: Bool = DefaultConfig.enableLogging,
trackScreenshotOnCrash: Bool = DefaultConfig.trackScreenshotOnCrash,
Expand All @@ -54,5 +59,10 @@ struct Config: InternalConfig, MeasureConfig {
"Proxy-Authorization",
"WWW-Authenticate",
"X-Api-Key"]
self.customEventNameRegex = "^[a-zA-Z0-9_-]+$"
self.maxEventNameLength = 64 // 64 chars
self.maxUserDefinedAttributeKeyLength = 256 // 256 chars
self.maxUserDefinedAttributeValueLength = 256 // 256 chars
self.maxUserDefinedAttributesPerEvent = 100
}
}
15 changes: 15 additions & 0 deletions ios/MeasureSDK/Config/InternalConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,19 @@ protocol InternalConfig {

/// Default list of HTTP headers to not capture for network request and response.
var defaultHttpHeadersBlocklist: [String] { get }

/// The maximum length of a custom event. Defaults to 64 chars.
var maxEventNameLength: Int { get }

/// The regex to validate a custom event name.
var customEventNameRegex: String { get }

/// The maximum length of user defined attribute key. Defaults to 256 chars.
var maxUserDefinedAttributeKeyLength: Int { get }

/// The maximum length of a user defined attribute value. Defaults to 256 chars.
var maxUserDefinedAttributeValueLength: Int { get }

/// The maximum number of user defined attributes for an event. Defaults to 100.
var maxUserDefinedAttributesPerEvent: Int { get }
}
Loading

0 comments on commit 9517e8e

Please sign in to comment.