diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 3e2b76ad..4e13cd6c 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -16,7 +16,7 @@ jobs: build_and_test_spm_mac: needs: cancel_previous - runs-on: macos-11 + runs-on: macos-latest steps: - uses: maxim-lobanov/setup-xcode@v1 with: @@ -48,9 +48,9 @@ jobs: build_and_test_ios: needs: cancel_previous - runs-on: macos-11 + runs-on: macos-latest steps: - - uses: maxim-lobanov/setup-xcode@v1.5.1 + - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable - uses: actions/checkout@v2 @@ -62,7 +62,7 @@ jobs: build_and_test_tvos: needs: cancel_previous - runs-on: macos-11 + runs-on: macos-latest steps: - uses: maxim-lobanov/setup-xcode@v1 with: @@ -75,7 +75,7 @@ jobs: build_and_test_watchos: needs: cancel_previous - runs-on: macos-11 + runs-on: macos-latest steps: - uses: maxim-lobanov/setup-xcode@v1 with: @@ -84,13 +84,13 @@ jobs: - uses: webfactory/ssh-agent@v0.5.3 with: ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }} - - run: xcodebuild -scheme Segment-Package test -sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch Series 5 - 40mm' + - run: xcodebuild -scheme Segment-Package test -sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch Series 8 (45mm)' build_and_test_examples: needs: cancel_previous - runs-on: macos-12 + runs-on: macos-latest steps: - - uses: maxim-lobanov/setup-xcode@v1.5.1 + - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable - uses: actions/checkout@v2 diff --git a/Examples/apps/ObjCExample/ObjCExample/AppDelegate.m b/Examples/apps/ObjCExample/ObjCExample/AppDelegate.m index 5cc6b75d..8566eead 100644 --- a/Examples/apps/ObjCExample/ObjCExample/AppDelegate.m +++ b/Examples/apps/ObjCExample/ObjCExample/AppDelegate.m @@ -20,8 +20,9 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. - SEGConfiguration *config = [[SEGConfiguration alloc] initWithWriteKey:@""]; + SEGConfiguration *config = [[SEGConfiguration alloc] initWithWriteKey:@""]; config.trackApplicationLifecycleEvents = YES; + config.flushAt = 1; _analytics = [[SEGAnalytics alloc] initWithConfiguration: config]; @@ -33,23 +34,44 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( SEGTestDestination *testDestination = [[SEGTestDestination alloc] init]; [self.analytics addPlugin:testDestination]; - [self.analytics addSourceMiddleware:^NSDictionary * _Nullable(NSDictionary * _Nullable event) { - // drop all events named booya - NSString *eventType = event[@"type"]; - if ([eventType isEqualToString:@"track"]) { - NSString *eventName = event[@"event"]; - if ([eventName isEqualToString:@"booya"]) { - return nil; - } + SEGBlockPlugin *customizeAllTrackCalls = [[SEGBlockPlugin alloc] initWithBlock:^id _Nullable(id _Nullable event) { + if ([event isKindOfClass: [SEGTrackEvent class]]) { + SEGTrackEvent *track = (SEGTrackEvent *)event; + // change the name + NSString *newName = [NSString stringWithFormat: @"[New] %@", track.event]; + track.event = newName; + // add a property + NSMutableDictionary *newProps = (track.properties != nil) ? [track.properties mutableCopy] : [@{} mutableCopy]; + newProps[@"customAttribute"] = @"Hello"; + track.properties = newProps; + + return track; } return event; }]; - //[self.analytics addDestination:[[SEGMixpanelDestination alloc] init]]; + [self.analytics addPlugin:customizeAllTrackCalls]; + SEGBlockPlugin *booyaAllTrackCalls = [[SEGBlockPlugin alloc] initWithBlock:^id _Nullable(id _Nullable event) { + if ([event isKindOfClass: [SEGTrackEvent class]]) { + SEGTrackEvent *track = (SEGTrackEvent *)event; + // change the name + NSString *newName = [NSString stringWithFormat: @"[Booya] %@", track.event]; + track.event = newName; + // add a property + NSMutableDictionary *newProps = (track.properties != nil) ? [track.properties mutableCopy] : [@{} mutableCopy]; + newProps[@"customAttribute"] = @"Booya!"; + track.properties = newProps; + + return track; + } + return event; + }]; + [self.analytics addPlugin:booyaAllTrackCalls destinationKey:@"Segment.io"]; + dispatch_async(dispatch_get_main_queue(), ^{ - [self.analytics track:@"booya"]; + [self.analytics track:@"schneeble schnobble"]; }); return YES; diff --git a/Examples/apps/ObjCExample/ObjCExample/TestDestination.swift b/Examples/apps/ObjCExample/ObjCExample/TestDestination.swift index b03d7a7c..23846958 100644 --- a/Examples/apps/ObjCExample/ObjCExample/TestDestination.swift +++ b/Examples/apps/ObjCExample/ObjCExample/TestDestination.swift @@ -19,8 +19,18 @@ public class TestDestination: DestinationPlugin { public let type = PluginType.destination public var analytics: Analytics? = nil - public func execute(event: T?) -> T? { - print("some event came to visit us for xmas din din.") + public func configure(analytics: Analytics) { + analytics.manuallyEnableDestination(plugin: self) + self.analytics = analytics + } + + public func update(settings: Settings, type: UpdateType) { + // analytics?.manuallyEnableDestination(plugin: self) + } + + public func track(event: TrackEvent) -> TrackEvent? { + print("Event Received: \n") + print(event.prettyPrint()) return event } } diff --git a/Segment.xcodeproj/project.pbxproj b/Segment.xcodeproj/project.pbxproj index a6deb0aa..0041ea27 100644 --- a/Segment.xcodeproj/project.pbxproj +++ b/Segment.xcodeproj/project.pbxproj @@ -36,13 +36,13 @@ 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 */; }; + 4689231329F7391500AB26E5 /* ObjCPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4689231129F7391500AB26E5 /* ObjCPlugin.swift */; }; + 4689231429F7391500AB26E5 /* ObjCEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4689231229F7391500AB26E5 /* ObjCEvents.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 */; }; 46A018EE25E9A74F00F9CCD8 /* VendorSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018ED25E9A74F00F9CCD8 /* VendorSystem.swift */; }; - 46A9439F29CE162A00D65DC3 /* ObjCDestinationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A9439D29CE162A00D65DC3 /* ObjCDestinationSupport.swift */; }; - 46A943A029CE162A00D65DC3 /* ObjCPluginSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A9439E29CE162A00D65DC3 /* ObjCPluginSupport.swift */; }; 46B1AC6927346D3D00846DE8 /* StressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B1AC6827346D3D00846DE8 /* StressTests.swift */; }; 46E382E72654429A00BA2502 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46E382E62654429A00BA2502 /* Utils.swift */; }; 46F7485D26C718710042798E /* ObjCAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F7485B26C718710042798E /* ObjCAnalytics.swift */; }; @@ -125,13 +125,13 @@ 465879B92686560C00180335 /* watchOSLifecycleMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = watchOSLifecycleMonitor.swift; sourceTree = ""; }; 4663C728267A799100ADDD1A /* QueueTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTimer.swift; sourceTree = ""; }; 466EC2CD28FB7D5D001B384E /* OutputFileStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutputFileStream.swift; sourceTree = ""; }; + 4689231129F7391500AB26E5 /* ObjCPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjCPlugin.swift; sourceTree = ""; }; + 4689231229F7391500AB26E5 /* ObjCEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjCEvents.swift; sourceTree = ""; }; 4697A237290341EF00FAE14F /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 46A018C125E5857D00F9CCD8 /* Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; }; 46A018D325E6C9C200F9CCD8 /* LinuxUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinuxUtils.swift; sourceTree = ""; }; 46A018D925E97FDF00F9CCD8 /* AppleUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleUtils.swift; sourceTree = ""; }; 46A018ED25E9A74F00F9CCD8 /* VendorSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VendorSystem.swift; sourceTree = ""; }; - 46A9439D29CE162A00D65DC3 /* ObjCDestinationSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjCDestinationSupport.swift; sourceTree = ""; }; - 46A9439E29CE162A00D65DC3 /* ObjCPluginSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjCPluginSupport.swift; sourceTree = ""; }; 46B1AC6827346D3D00846DE8 /* StressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StressTests.swift; sourceTree = ""; }; 46D98E3D26D6FEF300E7A86A /* FlurryDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlurryDestination.swift; sourceTree = ""; }; 46D98E3E26D6FEF300E7A86A /* AdjustDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdjustDestination.swift; sourceTree = ""; }; @@ -223,9 +223,9 @@ isa = PBXGroup; children = ( 46F7485B26C718710042798E /* ObjCAnalytics.swift */, + 4689231229F7391500AB26E5 /* ObjCEvents.swift */, + 4689231129F7391500AB26E5 /* ObjCPlugin.swift */, 46F7485C26C718710042798E /* ObjCConfiguration.swift */, - 46A9439D29CE162A00D65DC3 /* ObjCDestinationSupport.swift */, - 46A9439E29CE162A00D65DC3 /* ObjCPluginSupport.swift */, ); path = ObjC; sourceTree = ""; @@ -493,7 +493,7 @@ attributes = { LastSwiftMigration = 9999; LastSwiftUpdateCheck = 1220; - LastUpgradeCheck = 1340; + LastUpgradeCheck = 1430; }; buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Segment" */; compatibilityVersion = "Xcode 3.2"; @@ -531,7 +531,6 @@ 46022764261E64A800A9E913 /* iOSLifecycleEvents.swift in Sources */, 96A9624E2810C6B80011DE54 /* macOSLifecycleEvents.swift in Sources */, 460FF30B29BA525900635FF9 /* Logging.swift in Sources */, - 46A9439F29CE162A00D65DC3 /* ObjCDestinationSupport.swift in Sources */, 4621080C2605332D00EBC4A8 /* KeyPath.swift in Sources */, A31A16262576B6F200C9CDDF /* Timeline.swift in Sources */, 96C33AB1258961F500F3D538 /* Settings.swift in Sources */, @@ -543,15 +542,16 @@ OBJ_23 /* Analytics.swift in Sources */, A31A16342576B7AF00C9CDDF /* Types.swift in Sources */, 46A018DA25E97FDF00F9CCD8 /* AppleUtils.swift in Sources */, + 4689231429F7391500AB26E5 /* ObjCEvents.swift in Sources */, A31A16E12579779600C9CDDF /* Version.swift in Sources */, 46210836260BBEE400EBC4A8 /* DeviceToken.swift in Sources */, 9692724E25A4E5B7009B5298 /* Startup.swift in Sources */, 4663C729267A799100ADDD1A /* QueueTimer.swift in Sources */, + 4689231329F7391500AB26E5 /* ObjCPlugin.swift in Sources */, 46FE4C9C25A3F41C003A7362 /* LinuxLifecycleMonitor.swift in Sources */, 460227422612987300A9E913 /* watchOSLifecycleEvents.swift in Sources */, 46F7485E26C718710042798E /* ObjCConfiguration.swift in Sources */, 759D6CD127B48ABB00AB900A /* DestinationMetadataPlugin.swift in Sources */, - 46A943A029CE162A00D65DC3 /* ObjCPluginSupport.swift in Sources */, A31A162F2576B73F00C9CDDF /* State.swift in Sources */, 9692726825A583A6009B5298 /* SegmentDestination.swift in Sources */, 4602276C261E7BF900A9E913 /* iOSDelegation.swift in Sources */, @@ -616,6 +616,7 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + DEAD_CODE_STRIPPING = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -646,6 +647,7 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + DEAD_CODE_STRIPPING = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -677,6 +679,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; + DEAD_CODE_STRIPPING = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -703,6 +706,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; + DEAD_CODE_STRIPPING = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PLATFORM_DIR)/Developer/Library/Frameworks", @@ -748,6 +752,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = YES; @@ -785,6 +790,7 @@ OBJ_37 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + DEAD_CODE_STRIPPING = YES; LD = /usr/bin/true; OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk -package-description-version 5.3.0"; SWIFT_VERSION = 5.0; @@ -794,6 +800,7 @@ OBJ_38 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + DEAD_CODE_STRIPPING = YES; LD = /usr/bin/true; OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk -package-description-version 5.3.0"; SWIFT_VERSION = 5.0; @@ -824,6 +831,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -858,12 +866,14 @@ OBJ_43 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + DEAD_CODE_STRIPPING = YES; }; name = Debug; }; OBJ_44 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + DEAD_CODE_STRIPPING = YES; }; name = Release; }; diff --git a/Segment.xcodeproj/xcshareddata/xcschemes/Segment-Package.xcscheme b/Segment.xcodeproj/xcshareddata/xcschemes/Segment-Package.xcscheme index f4b70cb4..2ab81b19 100644 --- a/Segment.xcodeproj/xcshareddata/xcschemes/Segment-Package.xcscheme +++ b/Segment.xcodeproj/xcshareddata/xcschemes/Segment-Package.xcscheme @@ -1,6 +1,6 @@ Configuration { values.application = value return self } + /// Opt-in/out of tracking lifecycle events. The default value is `false`. + /// + /// - Parameter enabled: A bool value + /// - Returns: The current Configuration. @discardableResult func trackApplicationLifecycleEvents(_ enabled: Bool) -> Configuration { values.trackApplicationLifecycleEvents = enabled return self } + /// Set the number of events necessary to automatically flush. The default + /// value is `20`. + /// + /// - Parameter count: Event count to trigger a flush. + /// - Returns: The current Configuration. @discardableResult func flushAt(_ count: Int) -> Configuration { values.flushAt = count return self } + /// Set a time interval (in seconds) by which to trigger an automatic flush. + /// The default value is `30`. + /// + /// - Parameter interval: A time interval + /// - Returns: The current Configuration. @discardableResult func flushInterval(_ interval: TimeInterval) -> Configuration { values.flushInterval = interval return self } + /// Sets a default set of Settings. Normally these will come from Segment's + /// api.segment.com/v1/projects//settings, however in instances such + /// as first app launch, it can be useful to have a pre-set batch of settings to + /// ensure that the proper destinations and other settings are enabled prior + /// to receiving them from the Settings endpoint. The default is `nil`. + /// + /// You can retrieve a copy of your settings from the following URL: + /// + /// https://cdn-settings.segment.com/v1/projects//settings + /// + /// Example: + /// ``` + /// let defaults = Settings.load(resource: "mySegmentSettings.json") + /// let config = Configuration(writeKey: "1234").defaultSettings(defaults) + /// ``` + /// + /// - Parameter settings: + /// - Returns: The current Configuration. @discardableResult - func defaultSettings(_ settings: Settings) -> Configuration { + func defaultSettings(_ settings: Settings?) -> Configuration { values.defaultSettings = settings return self } + /// Enable/Disable the automatic adding of Segment as a destination. + /// This can be useful in instances such as Consent Management, or in device + /// mode only setups. The default value is `true`. + /// + /// - Parameter value: true/false + /// - Returns: The current Configuration. @discardableResult func autoAddSegmentDestination(_ value: Bool) -> Configuration { values.autoAddSegmentDestination = value return self } + /// Sets an alternative API host. This is useful when a proxy is in use, or + /// events need to be routed to certain locales at all times (such as the EU). + /// The default value is `api.segment.io/v1`. + /// + /// - Parameter value: A string representing the desired API host. + /// - Returns: The current Configuration. @discardableResult func apiHost(_ value: String) -> Configuration { values.apiHost = value return self } + /// Sets an alternative CDN host for settings retrieval. This is useful when + /// a proxy is in use, or settings need to be queried from certain locales at + /// all times (such as the EU). The default value is `cdn-settings.segment.com/v1`. + /// + /// - Parameter value: A string representing the desired CDN host. + /// - Returns: The current Configuration. @discardableResult func cdnHost(_ value: String) -> Configuration { values.cdnHost = value return self } + /// Sets a block to be used when generating outgoing HTTP requests. Useful in + /// proxying, or adding additional header information for outbound traffic. + /// + /// - Parameter value: A block to call when requests are made. + /// - Returns: The current Configuration. @discardableResult func requestFactory(_ value: @escaping (URLRequest) -> URLRequest) -> Configuration { values.requestFactory = value return self } + /// Sets an error handler to be called when errors are encountered by the Segment + /// library. See `AnalyticsError` for a list of possible errors that can be + /// encountered. + /// + /// - Parameter value: A block to be called when an error occurs. + /// - Returns: The current Configuration. @discardableResult func errorHandler(_ value: @escaping (Error) -> Void) -> Configuration { values.errorHandler = value diff --git a/Sources/Segment/Events.swift b/Sources/Segment/Events.swift index ecff0e85..f8e8664b 100644 --- a/Sources/Segment/Events.swift +++ b/Sources/Segment/Events.swift @@ -124,7 +124,7 @@ extension Analytics { } public func alias(newId: String) { - let event = AliasEvent(newId: newId) + let event = AliasEvent(newId: newId, previousId: self.userId) store.dispatch(action: UserInfo.SetUserIdAction(userId: newId)) process(incomingEvent: event) } diff --git a/Sources/Segment/ObjC/ObjCAnalytics.swift b/Sources/Segment/ObjC/ObjCAnalytics.swift index e80115ba..7285724b 100644 --- a/Sources/Segment/ObjC/ObjCAnalytics.swift +++ b/Sources/Segment/ObjC/ObjCAnalytics.swift @@ -1,5 +1,5 @@ // -// File.swift +// ObjCAnalytics.swift // // // Created by Cody Garvin on 6/10/21. diff --git a/Sources/Segment/ObjC/ObjCConfiguration.swift b/Sources/Segment/ObjC/ObjCConfiguration.swift index bbaca664..83ae35d1 100644 --- a/Sources/Segment/ObjC/ObjCConfiguration.swift +++ b/Sources/Segment/ObjC/ObjCConfiguration.swift @@ -1,5 +1,5 @@ // -// File.swift +// ObjCConfiguration.swift // // // Created by Brandon Sneed on 8/13/21. @@ -13,6 +13,9 @@ import Foundation public class ObjCConfiguration: NSObject { internal var configuration: Configuration + /// Sets a reference to your application. This can be useful in instances + /// where referring back to your application is necessary, such as within plugins + /// or async code. The default value is `nil`. @objc public var application: Any? { get { @@ -23,6 +26,7 @@ public class ObjCConfiguration: NSObject { } } + /// Opt-in/out of tracking lifecycle events. The default value is `false`. @objc public var trackApplicationLifecycleEvents: Bool { get { @@ -33,6 +37,8 @@ public class ObjCConfiguration: NSObject { } } + /// Set the number of events necessary to automatically flush. The default + /// value is `20`. @objc public var flushAt: Int { get { @@ -43,6 +49,8 @@ public class ObjCConfiguration: NSObject { } } + /// Set a time interval (in seconds) by which to trigger an automatic flush. + /// The default value is `30`. @objc public var flushInterval: TimeInterval { get { @@ -53,6 +61,15 @@ public class ObjCConfiguration: NSObject { } } + /// Sets a default set of Settings. Normally these will come from Segment's + /// api.segment.com/v1/projects//settings, however in instances such + /// as first app launch, it can be useful to have a pre-set batch of settings to + /// ensure that the proper destinations and other settings are enabled prior + /// to receiving them from the Settings endpoint. The default is `nil`. + /// + /// You can retrieve a copy of your settings from the following URL: + /// + /// https://cdn-settings.segment.com/v1/projects//settings @objc public var defaultSettings: [String: Any] { get { @@ -81,6 +98,9 @@ public class ObjCConfiguration: NSObject { } } + /// Enable/Disable the automatic adding of Segment as a destination. + /// This can be useful in instances such as Consent Management, or in device + /// mode only setups. The default value is `true`. @objc public var autoAddSegmentDestination: Bool { get { @@ -91,6 +111,9 @@ public class ObjCConfiguration: NSObject { } } + /// Sets an alternative API host. This is useful when a proxy is in use, or + /// events need to be routed to certain locales at all times (such as the EU). + /// The default value is `api.segment.io/v1`. @objc public var apiHost: String { get { @@ -101,6 +124,9 @@ public class ObjCConfiguration: NSObject { } } + /// Sets an alternative CDN host for settings retrieval. This is useful when + /// a proxy is in use, or settings need to be queried from certain locales at + /// all times (such as the EU). The default value is `cdn-settings.segment.com/v1`. @objc public var cdnHost: String { get { @@ -110,8 +136,25 @@ public class ObjCConfiguration: NSObject { configuration.cdnHost(value) } } + + /// Sets a block to be used when generating outgoing HTTP requests. Useful in + /// proxying, or adding additional header information for outbound traffic. + /// + /// - Parameter value: A block to call when requests are made. + /// - Returns: The current Configuration. + @objc + public var requestFactory: ((URLRequest) -> URLRequest)? { + get { + return configuration.values.requestFactory + } + set(value) { + configuration.values.requestFactory = value + } + } - + /// Initialize a configuration object to pass along to an Analytics instance. + /// + /// - Parameter writeKey: Your Segment write key value @objc public init(writeKey: String) { self.configuration = Configuration(writeKey: writeKey) diff --git a/Sources/Segment/ObjC/ObjCDestinationSupport.swift b/Sources/Segment/ObjC/ObjCDestinationSupport.swift deleted file mode 100644 index ba7c5020..00000000 --- a/Sources/Segment/ObjC/ObjCDestinationSupport.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// ObjCDestinationSupport.swift -// -// -// Created by Brandon Sneed on 3/23/23. -// - -#if !os(Linux) - -import Foundation - -@objc(SEGPlugin) -public protocol ObjCPlugin {} - -public protocol ObjCPluginShim { - func instance() -> EventPlugin -} - -// NOTE: Destination plugins need something similar to the following to work -// in objective-c. - -/* - -@objc(SEGMixpanelDestination) -public class ObjCSegmentMixpanel: NSObject, ObjCPlugin, ObjCPluginShim { - public func instance() -> EventPlugin { return MixpanelDestination() } -} - -*/ - - -#endif // !os(Linux) diff --git a/Sources/Segment/ObjC/ObjCEvents.swift b/Sources/Segment/ObjC/ObjCEvents.swift new file mode 100644 index 00000000..019a7b6a --- /dev/null +++ b/Sources/Segment/ObjC/ObjCEvents.swift @@ -0,0 +1,372 @@ +// +// ObjCEvents.swift +// +// +// Created by Brandon Sneed on 4/17/23. +// + +#if !os(Linux) + +import Foundation + +internal protocol ObjCEvent { + associatedtype EventType + var _event: EventType { get set } +} + +@objc(SEGDestinationMetadata) +public class ObjCDestinationMetadata: NSObject { + internal var _metadata: DestinationMetadata + + public var bundled: [String] { + get { return _metadata.bundled } + set(v) { _metadata.bundled = v } + } + + public var unbundled: [String] { + get { return _metadata.unbundled } + set(v) { _metadata.unbundled = v } + } + + public var bundledIds: [String] { + get { return _metadata.bundledIds } + set(v) { _metadata.bundledIds = v } + } + + internal init?(_metadata: DestinationMetadata?) { + guard let m = _metadata else { return nil } + self._metadata = m + } + + init(bundled: [String], unbundled: [String], bundledIds: [String]) { + _metadata = DestinationMetadata(bundled: bundled, unbundled: unbundled, bundledIds: bundledIds) + } +} + +@objc(SEGRawEvent) +public protocol ObjCRawEvent: NSObjectProtocol { + var type: String? { get } + var messageId: String? { get } + var timestamp: String? { get } + var anonymousId: String? { get set } + var userId: String? { get set } + + var context: [String: Any]? { get set } + var integrations: [String: Any]? { get set } + + var metadata: ObjCDestinationMetadata? { get set } +} + +internal func eventFromObjCEvent(_ event: ObjCRawEvent?) -> RawEvent? { + guard let event = event as? (any ObjCEvent) else { return nil } + return event._event as? RawEvent +} + +internal func objcEventFromEvent(_ event: T?) -> ObjCRawEvent? { + guard let event = event else { return nil } + switch event { + case let e as TrackEvent: + return ObjCTrackEvent(event: e) + case let e as IdentifyEvent: + return ObjCIdentifyEvent(event: e) + case let e as ScreenEvent: + return ObjCScreenEvent(event: e) + case let e as GroupEvent: + return ObjCGroupEvent(event: e) + case let e as AliasEvent: + return ObjCAliasEvent(event: e) + default: + return nil + } +} + +@objc(SEGTrackEvent) +public class ObjCTrackEvent: NSObject, ObjCEvent, ObjCRawEvent { + internal var _event: TrackEvent + + // RawEvent components + + public var type: String? { return _event.type } + public var messageId: String? { return _event.messageId } + public var timestamp: String? { return _event.timestamp } + + public var anonymousId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var userId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var context: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var integrations: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var metadata: ObjCDestinationMetadata? { + get { return ObjCDestinationMetadata(_metadata: _event._metadata) } + set(v) { _event._metadata = v?._metadata } + } + + // Event Specific + + @objc + public var event: String { + get { return _event.event } + set(v) { _event.event = v } + } + + @objc + public var properties: [String: Any]? { + get { return _event.properties?.dictionaryValue } + set(v) { _event.properties = try? JSON(nilOrObject: v)} + } + + @objc + public init(name: String, properties: [String: Any]? = nil) { + _event = TrackEvent(event: name, properties: try? JSON(nilOrObject: properties)) + } + + internal init(event: EventType) { + self._event = event + } +} + +@objc(SEGIdentifyEvent) +public class ObjCIdentifyEvent: NSObject, ObjCEvent, ObjCRawEvent { + internal var _event: IdentifyEvent + + // RawEvent components + + public var type: String? { return _event.type } + public var messageId: String? { return _event.messageId } + public var timestamp: String? { return _event.timestamp } + + public var anonymousId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var userId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var context: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var integrations: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var metadata: ObjCDestinationMetadata? { + get { return ObjCDestinationMetadata(_metadata: _event._metadata) } + set(v) { _event._metadata = v?._metadata } + } + + // Event Specific + + @objc + public var traits: [String: Any]? { + get { return _event.traits?.dictionaryValue } + set(v) { _event.traits = try? JSON(nilOrObject: v)} + } + + @objc + public init(userId: String, traits: [String: Any]? = nil) { + _event = IdentifyEvent(userId: userId, traits: try? JSON(nilOrObject: traits)) + } + + internal init(event: EventType) { + self._event = event + } +} + +@objc(SEGScreenEvent) +public class ObjCScreenEvent: NSObject, ObjCEvent, ObjCRawEvent { + internal var _event: ScreenEvent + + // RawEvent components + + public var type: String? { return _event.type } + public var messageId: String? { return _event.messageId } + public var timestamp: String? { return _event.timestamp } + + public var anonymousId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var userId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var context: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var integrations: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var metadata: ObjCDestinationMetadata? { + get { return ObjCDestinationMetadata(_metadata: _event._metadata) } + set(v) { _event._metadata = v?._metadata } + } + + // Event Specific + + @objc + public var name: String? { + get { return _event.name } + set(v) { _event.name = v} + } + + @objc + public var category: String? { + get { return _event.category } + set(v) { _event.category = v} + } + + @objc + public var properties: [String: Any]? { + get { return _event.properties?.dictionaryValue } + set(v) { _event.properties = try? JSON(nilOrObject: v)} + } + + @objc + public init(name: String, category: String?, properties: [String: Any]? = nil) { + _event = ScreenEvent(title: name, category: category, properties: try? JSON(nilOrObject: properties)) + } + + internal init(event: EventType) { + self._event = event + } +} + +@objc(SEGGroupEvent) +public class ObjCGroupEvent: NSObject, ObjCEvent, ObjCRawEvent { + internal var _event: GroupEvent + + // RawEvent components + + public var type: String? { return _event.type } + public var messageId: String? { return _event.messageId } + public var timestamp: String? { return _event.timestamp } + + public var anonymousId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var userId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var context: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var integrations: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var metadata: ObjCDestinationMetadata? { + get { return ObjCDestinationMetadata(_metadata: _event._metadata) } + set(v) { _event._metadata = v?._metadata } + } + + // Event Specific + + @objc + public var groupId: String? { + get { return _event.groupId } + set(v) { _event.groupId = v} + } + + @objc + public var traits: [String: Any]? { + get { return _event.traits?.dictionaryValue } + set(v) { _event.traits = try? JSON(nilOrObject: v)} + } + + @objc + public init(groupId: String?, traits: [String: Any]? = nil) { + _event = GroupEvent(groupId: groupId, traits: try? JSON(nilOrObject: traits)) + } + + internal init(event: EventType) { + self._event = event + } +} + +@objc(SEGAliasEvent) +public class ObjCAliasEvent: NSObject, ObjCEvent, ObjCRawEvent { + internal var _event: AliasEvent + + // RawEvent components + + public var type: String? { return _event.type } + public var messageId: String? { return _event.messageId } + public var timestamp: String? { return _event.timestamp } + + public var anonymousId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var userId: String? { + get { return _event.anonymousId } + set(v) { _event.anonymousId = v} + } + + public var context: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var integrations: [String: Any]? { + get { return _event.context?.dictionaryValue } + set(v) { _event.context = try? JSON(nilOrObject: v)} + } + + public var metadata: ObjCDestinationMetadata? { + get { return ObjCDestinationMetadata(_metadata: _event._metadata) } + set(v) { _event._metadata = v?._metadata } + } + + // Event Specific + + @objc + public var previousId: String? { + get { return _event.previousId } + set(v) { _event.previousId = v} + } + + @objc + public init(newId: String?) { + _event = AliasEvent(newId: newId) + } + + internal init(event: EventType) { + self._event = event + } +} + +#endif diff --git a/Sources/Segment/ObjC/ObjCPlugin.swift b/Sources/Segment/ObjC/ObjCPlugin.swift new file mode 100644 index 00000000..9504b824 --- /dev/null +++ b/Sources/Segment/ObjC/ObjCPlugin.swift @@ -0,0 +1,90 @@ +// +// ObjCPlugin.swift +// +// +// Created by Brandon Sneed on 4/17/23. +// + + +#if !os(Linux) + +import Foundation + +@objc(SEGPlugin) +public protocol ObjCPlugin {} + +public protocol ObjCPluginShim { + func instance() -> EventPlugin +} + +// NOTE: Destination plugins need something similar to the following to work/ +/* + +@objc(SEGMixpanelDestination) +public class ObjCSegmentMixpanel: NSObject, ObjCPlugin, ObjCPluginShim { + public func instance() -> EventPlugin { return MixpanelDestination() } +} + +*/ + +@objc(SEGEventPlugin) +public class ObjCEventPlugin: NSObject, EventPlugin, ObjCPlugin { + public var type: PluginType = .enrichment + public var analytics: Analytics? = nil + + @objc(executeEvent:) + public func execute(event: ObjCRawEvent?) -> ObjCRawEvent? { + #if DEBUG + print("SEGEventPlugin's execute: method must be overridden!") + #endif + return event + } + + public func execute(event: T?) -> T? where T : RawEvent { + let objcEvent = objcEventFromEvent(event) + let result = execute(event: objcEvent) + let newEvent = eventFromObjCEvent(result) + return newEvent as? T + } +} + +@objc(SEGBlockPlugin) +public class ObjCBlockPlugin: ObjCEventPlugin { + let block: (ObjCRawEvent?) -> ObjCRawEvent? + + @objc(executeEvent:) + public override func execute(event: ObjCRawEvent?) -> ObjCRawEvent? { + return block(event) + } + + @objc(initWithBlock:) + public init(block: @escaping (ObjCRawEvent?) -> ObjCRawEvent?) { + self.block = block + } +} + +@objc +extension ObjCAnalytics { + @objc(addPlugin:) + public func add(plugin: ObjCPlugin?) { + if let p = plugin as? ObjCPluginShim { + analytics.add(plugin: p.instance()) + } else if let p = plugin as? ObjCEventPlugin { + analytics.add(plugin: p) + } + } + + @objc(addPlugin:destinationKey:) + public func add(plugin: ObjCPlugin?, destinationKey: String) { + guard let d = analytics.find(key: destinationKey) else { return } + + if let p = plugin as? ObjCPluginShim { + _ = d.add(plugin: p.instance()) + } else if let p = plugin as? ObjCEventPlugin { + _ = d.add(plugin: p) + } + } +} + +#endif + diff --git a/Sources/Segment/ObjC/ObjCPluginSupport.swift b/Sources/Segment/ObjC/ObjCPluginSupport.swift deleted file mode 100644 index 9fc9347f..00000000 --- a/Sources/Segment/ObjC/ObjCPluginSupport.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// ObjCPlugin.swift -// -// -// Created by Brandon Sneed on 3/14/23. -// - -#if !os(Linux) - -import Foundation -import Sovran - -internal class ObjCShimPlugin: Plugin, Subscriber { - var type: PluginType = .enrichment - var analytics: Analytics? = nil - var executionBlock: (([String: Any]?) -> [String: Any]?)? = nil - - required init(middleware: @escaping ([String: Any]?) -> [String: Any]?) { - executionBlock = middleware - } - - func execute(event: T?) -> T? where T : RawEvent { - // is our event actually valid? - guard let event = event else { return event } - // do we actually have an execution block? - guard let executionBlock = executionBlock else { return event } - // can we conver this to a JSON dictionary? - guard let dictEvent = try? JSON(with: event).dictionaryValue else { return event } - // is it valid json? - guard JSONSerialization.isValidJSONObject(dictEvent as Any) == true else { return event } - // run the execution block, a nil result tells us to drop the event. - guard let result = executionBlock(dictEvent) else { return nil } - - if let jsonData = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted) { - let decoder = JSONDecoder() - var newEvent: RawEvent? = nil - switch event { - case is IdentifyEvent: - newEvent = try? decoder.decode(IdentifyEvent.self, from: jsonData) - case is TrackEvent: - newEvent = try? decoder.decode(TrackEvent.self, from: jsonData) - case is ScreenEvent: - newEvent = try? decoder.decode(ScreenEvent.self, from: jsonData) - case is AliasEvent: - newEvent = try? decoder.decode(AliasEvent.self, from: jsonData) - case is GroupEvent: - newEvent = try? decoder.decode(GroupEvent.self, from: jsonData) - default: - break - } - // return the decoded event ... - return newEvent as? T - } else { - // we weren't able to serialize, so return the original event. - return event - } - } -} - -// MARK: - ObjC Plugin Functionality - -@objc -extension ObjCAnalytics { - /// This method allows you to add middleware to an Analytics instance, similar to Analytics-iOS. - /// However, it is **strongly encouraged** that Enrichments/Plugins/Middlewares be written in swift - /// to avoid the overhead of type conversion back and forth. This exists solely for compatibility - /// purposes. - /// - /// Example: - /// [self.analytics addSourceMiddleware:^NSDictionary * _Nullable(NSDictionary * _Nullable event) { - /// // drop all events named booya - /// NSString *eventType = event[@"type"]; - /// if ([eventType isEqualToString:@"track"]) { - /// NSString *eventName = event[@"event"]; - /// if ([eventName isEqualToString:@"booya"]) { - /// return nil; - /// } - /// } - /// return event; - /// }]; - /// - /// - Parameter middleware: The middleware to execute at the source level. - @objc(addSourceMiddleware:) - public func addSourceMiddleware(middleware: @escaping ((_ event: [String: Any]?) -> [String: Any]?)) { - analytics.add(plugin: ObjCShimPlugin(middleware: middleware)) - } - - /// This method allows you to add middleware to an Analytics instance, similar to Analytics-iOS. - /// However, it is **strongly encouraged** that Enrichments/Plugins/Middlewares be written in swift - /// to avoid the overhead of type conversion back and forth. This exists solely for compatibility - /// purposes. - /// - /// Example: - /// [self.analytics addDestinationMiddleware:^NSDictionary * _Nullable(NSDictionary * _Nullable event) { - /// // drop all events named booya on the amplitude destination - /// NSString *eventType = event[@"type"]; - /// if ([eventType isEqualToString:@"track"]) { - /// NSString *eventName = event[@"event"]; - /// if ([eventName isEqualToString:@"booya"]) { - /// return nil; - /// } - /// } - /// return event; - /// }, forKey: @"Amplitude"]; - /// - /// - Parameters: - /// - middleware: The middleware to execute at the source level. - /// - destinationKey: A string value representing the destination. ie: @"Amplitude" - @objc(addDestinationMiddleware:forKey:) - public func addDestinationMiddleware(middleware: @escaping ((_ event: [String: Any]?) -> [String: Any]?), destinationKey: String) { - // couldn't find the destination they wanted - guard let dest = analytics.find(key: destinationKey) else { return } - _ = dest.add(plugin: ObjCShimPlugin(middleware: middleware)) - } - - @objc(addPlugin:) - public func add(plugin: ObjCPlugin) { - guard let bouncer = plugin as? ObjCPluginShim else { return } - let p = bouncer.instance() - analytics.add(plugin: p) - } -} - - -#endif diff --git a/Sources/Segment/Plugins/Platforms/iOS/iOSLifecycleEvents.swift b/Sources/Segment/Plugins/Platforms/iOS/iOSLifecycleEvents.swift index d1181d89..93bfe1d2 100644 --- a/Sources/Segment/Plugins/Platforms/iOS/iOSLifecycleEvents.swift +++ b/Sources/Segment/Plugins/Platforms/iOS/iOSLifecycleEvents.swift @@ -61,8 +61,8 @@ class iOSLifecycleEvents: PlatformPlugin, iOSLifecycle { "from_background": false, "version": currentVersion ?? "", "build": currentBuild ?? "", - "referring_application": sourceApp, - "url": url + "referring_application": sourceApp ?? "", + "url": url ?? "" ]) UserDefaults.standard.setValue(currentVersion, forKey: Self.versionKey) diff --git a/Sources/Segment/Plugins/Platforms/watchOS/watchOSDelegation.swift b/Sources/Segment/Plugins/Platforms/watchOS/watchOSDelegation.swift index 46618446..b7a09934 100644 --- a/Sources/Segment/Plugins/Platforms/watchOS/watchOSDelegation.swift +++ b/Sources/Segment/Plugins/Platforms/watchOS/watchOSDelegation.swift @@ -1,5 +1,5 @@ // -// File.swift +// watchOSDelegation.swift // // // Created by Brandon Sneed on 6/24/21. diff --git a/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleMonitor.swift b/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleMonitor.swift index 4bf30324..38421f1c 100644 --- a/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleMonitor.swift +++ b/Sources/Segment/Plugins/Platforms/watchOS/watchOSLifecycleMonitor.swift @@ -1,5 +1,5 @@ // -// File.swift +// watchOSLifecycleMonitor.swift // // // Created by Brandon Sneed on 6/24/21. diff --git a/Sources/Segment/Settings.swift b/Sources/Segment/Settings.swift index 05d7d52a..a434cf7e 100644 --- a/Sources/Segment/Settings.swift +++ b/Sources/Segment/Settings.swift @@ -39,6 +39,18 @@ public struct Settings: Codable { self.middlewareSettings = try? values.decode(JSON.self, forKey: CodingKeys.middlewareSettings) } + static public func load(from url: URL?) -> Settings? { + guard let url = url else { return nil } + guard let data = try? Data(contentsOf: url) else { return nil } + let settings = try? JSONDecoder().decode(Settings.self, from: data) + return settings + } + + static public func load(resource: String, bundle: Bundle = Bundle.main) -> Settings? { + let url = bundle.url(forResource: resource, withExtension: nil) + return load(from: url) + } + enum CodingKeys: String, CodingKey { case integrations case plan diff --git a/Sources/Segment/Types.swift b/Sources/Segment/Types.swift index 8597d8f4..49931a38 100644 --- a/Sources/Segment/Types.swift +++ b/Sources/Segment/Types.swift @@ -143,15 +143,16 @@ public struct AliasEvent: RawEvent { public var metrics: [JSON]? = nil public var _metadata: DestinationMetadata? = nil - public var userId: String? - public var previousId: String? + public var userId: String? = nil + public var previousId: String? = nil - public init(newId: String? = nil) { + public init(newId: String?, previousId: String? = nil) { self.userId = newId + self.previousId = previousId } public init(existing: AliasEvent) { - self.init(newId: existing.userId) + self.init(newId: existing.userId, previousId: existing.previousId) applyRawEventData(event: existing) } } diff --git a/Sources/Segment/Utilities/JSON.swift b/Sources/Segment/Utilities/JSON.swift index 308040f4..9eddb299 100644 --- a/Sources/Segment/Utilities/JSON.swift +++ b/Sources/Segment/Utilities/JSON.swift @@ -28,6 +28,11 @@ public enum JSON: Equatable { self = .object(try object.mapValues(JSON.init)) } + public init?(nilOrObject object: [String: Any]?) throws { + guard let object = object else { return nil } + try self.init(object) + } + // For Value types public init(with value: T) throws { let encoder = JSONEncoder() diff --git a/Tests/Segment-Tests/JSON_Tests.swift b/Tests/Segment-Tests/JSON_Tests.swift index 1fa42166..65b90529 100644 --- a/Tests/Segment-Tests/JSON_Tests.swift +++ b/Tests/Segment-Tests/JSON_Tests.swift @@ -63,7 +63,7 @@ class JSONTests: XCTestCase { } func testJSONNil() throws { - let traits = try JSON(["type": NSNull(), "preferences": ["bwack"], "key": nil]) + let traits = try JSON(["type": NSNull(), "preferences": ["bwack"], "key": nil] as [String : Any?]) let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -218,7 +218,7 @@ class JSONTests: XCTestCase { func testKeyMappingWithValueTransform() { let keys = ["Key1": "AKey1", "Key2": "AKey2"] - let dict: [String: Any] = ["Key1": 1, "Key2": 2, "Key3": 3, "Key4": ["Key1": 1], "Key5": [1, 2, ["Key1": 1]]] + let dict: [String: Any] = ["Key1": 1, "Key2": 2, "Key3": 3, "Key4": ["Key1": 1], "Key5": [1, 2, ["Key1": 1]] as [Any]] let json = try! JSON(dict) diff --git a/Tests/Segment-Tests/KeyPath_Tests.swift b/Tests/Segment-Tests/KeyPath_Tests.swift index 6c942450..263f99f2 100644 --- a/Tests/Segment-Tests/KeyPath_Tests.swift +++ b/Tests/Segment-Tests/KeyPath_Tests.swift @@ -62,7 +62,7 @@ class KeyPath_Tests: XCTestCase { "else": [ "@path": "$.context.device.id" ] - ] + ] as [String : Any] ] ] diff --git a/Tests/Segment-Tests/ObjC_Tests.swift b/Tests/Segment-Tests/ObjC_Tests.swift index 2849f5a9..a7689580 100644 --- a/Tests/Segment-Tests/ObjC_Tests.swift +++ b/Tests/Segment-Tests/ObjC_Tests.swift @@ -101,22 +101,19 @@ class ObjC_Tests: XCTestCase { let outputReader = OutputReaderPlugin() analytics.analytics.add(plugin: outputReader) - analytics.addSourceMiddleware { event in + let sourcePlugin = ObjCBlockPlugin { event in print("source enrichment applied") sourceHit = true return event } + analytics.add(plugin: sourcePlugin) - analytics.addDestinationMiddleware(middleware: { event in + let destPlugin = ObjCBlockPlugin { event in print("destination enrichment applied") destHit = true return event - }, destinationKey: "Segment.io") - - //analytics.add(enrichment: sourceEnrichment) - - //let segment = analytics.find(pluginType: SegmentDestination.self) - //segment?.add(enrichment: destEnrichment) + } + analytics.add(plugin: destPlugin, destinationKey: "Segment.io") waitUntilStarted(analytics: analytics.analytics)