diff --git a/Amplitude-Swift.xcodeproj/project.pbxproj b/Amplitude-Swift.xcodeproj/project.pbxproj index 684d2a0b..05e16163 100644 --- a/Amplitude-Swift.xcodeproj/project.pbxproj +++ b/Amplitude-Swift.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 8EDECEC5F98F9974DF3E576F /* ObjCIdentify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDEC5A50E197C9C5067C19E /* ObjCIdentify.swift */; }; 8EDECF81C2B1B38D472FD7EF /* ObjCConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDECEC5AAE15FD05E76359A /* ObjCConfiguration.swift */; }; 8EDECFCCF4219767F26210D6 /* Sessions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDEC2B8B38E04CDB51F0E83 /* Sessions.swift */; }; + B6EDB4D02B643C8400454B90 /* NetworkConnectivityCheckerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EDB4CF2B643C8400454B90 /* NetworkConnectivityCheckerPlugin.swift */; }; BA0359CA2A51585D007C383B /* legacy_v3.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = BA0359C92A51585D007C383B /* legacy_v3.sqlite */; }; BA0639F62A4DD491000F1CEE /* LegacyDatabaseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0639F52A4DD491000F1CEE /* LegacyDatabaseStorage.swift */; }; BA1EC0F42A9F2FC700C2D547 /* DefaultTrackingOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1EC0F32A9F2FC700C2D547 /* DefaultTrackingOptionsTests.swift */; }; @@ -168,6 +169,7 @@ 8EDECEC5AAE15FD05E76359A /* ObjCConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjCConfiguration.swift; sourceTree = ""; }; 8EDECF8CF745F7339B65D6DB /* ObjCStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjCStorage.swift; sourceTree = ""; }; B6DF481F2B5B45BE00B3E6AA /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + B6EDB4CF2B643C8400454B90 /* NetworkConnectivityCheckerPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectivityCheckerPlugin.swift; sourceTree = ""; }; BA0359C92A51585D007C383B /* legacy_v3.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = legacy_v3.sqlite; sourceTree = ""; }; BA0639F52A4DD491000F1CEE /* LegacyDatabaseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyDatabaseStorage.swift; sourceTree = ""; }; BA1EC0F32A9F2FC700C2D547 /* DefaultTrackingOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTrackingOptionsTests.swift; sourceTree = ""; }; @@ -334,6 +336,7 @@ 8EDEC448C42C8C0A464FAA15 /* BasePlugins.swift */, 8EDECD39BAA97DD4320C0AA5 /* AnalyticsConnectorPlugin.swift */, 8EDEC48916EFEF6D5B3EEF9A /* AnalyticsConnectorIdentityPlugin.swift */, + B6EDB4CF2B643C8400454B90 /* NetworkConnectivityCheckerPlugin.swift */, ); path = Plugins; sourceTree = ""; @@ -719,6 +722,7 @@ 8EDECA4DAFA67CD4785D0161 /* ObjCDefaultTrackingOptions.swift in Sources */, 8EDEC43520B2DCF584F1035D /* ObjCScreenViewedEvent.swift in Sources */, 8EDECC1FC97DDF0BEFAA96E7 /* ObjCDeepLinkOpenedEvent.swift in Sources */, + B6EDB4D02B643C8400454B90 /* NetworkConnectivityCheckerPlugin.swift in Sources */, 8EDEC5F7208B1C327C8703D7 /* ObjCStorage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index ffd9bc89..32a3d692 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -53,6 +53,9 @@ public class Amplitude { state.userId = userId } + if self.configuration.offline != NetworkConnectivityCheckerPlugin.Disabled { + _ = add(plugin: NetworkConnectivityCheckerPlugin()) + } // required plugin for specific platform, only has lifecyclePlugin now if let requiredPlugin = VendorSystem.current.requiredPlugin { _ = add(plugin: requiredPlugin) diff --git a/Sources/Amplitude/Configuration.swift b/Sources/Amplitude/Configuration.swift index 4046c3d7..e0a757a4 100644 --- a/Sources/Amplitude/Configuration.swift +++ b/Sources/Amplitude/Configuration.swift @@ -33,6 +33,7 @@ public class Configuration { public var identifyBatchIntervalMillis: Int public internal(set) var migrateLegacyData: Bool public var defaultTracking: DefaultTrackingOptions + public var offline: Bool? public init( apiKey: String, @@ -60,7 +61,8 @@ public class Configuration { // `trackingSessionEvents` has been replaced by `defaultTracking.sessions` defaultTracking: DefaultTrackingOptions = DefaultTrackingOptions(), identifyBatchIntervalMillis: Int = Constants.Configuration.IDENTIFY_BATCH_INTERVAL_MILLIS, - migrateLegacyData: Bool = true + migrateLegacyData: Bool = true, + offline: Bool = false ) { let normalizedInstanceName = instanceName == "" ? Constants.Configuration.DEFAULT_INSTANCE : instanceName @@ -93,6 +95,7 @@ public class Configuration { self.migrateLegacyData = migrateLegacyData // Logging is OFF by default self.loggerProvider.logLevel = logLevel.rawValue + self.offline = offline } func isValid() -> Bool { diff --git a/Sources/Amplitude/Mediator.swift b/Sources/Amplitude/Mediator.swift index e53910a3..130c50a0 100644 --- a/Sources/Amplitude/Mediator.swift +++ b/Sources/Amplitude/Mediator.swift @@ -15,7 +15,11 @@ internal class Mediator { internal func remove(plugin: Plugin) { plugins.removeAll { (storedPlugin) -> Bool in - return storedPlugin === plugin + if storedPlugin === plugin { + storedPlugin.teardown() + return true + } + return false } } diff --git a/Sources/Amplitude/Plugins/BasePlugins.swift b/Sources/Amplitude/Plugins/BasePlugins.swift index a20bcf61..e1b63376 100644 --- a/Sources/Amplitude/Plugins/BasePlugins.swift +++ b/Sources/Amplitude/Plugins/BasePlugins.swift @@ -13,6 +13,10 @@ open class BasePlugin { open func execute(event: BaseEvent) -> BaseEvent? { return event } + + public func teardown(){ + // Clean up any resources from setup if necessary + } } open class BeforePlugin: BasePlugin, Plugin { diff --git a/Sources/Amplitude/Plugins/NetworkConnectivityCheckerPlugin.swift b/Sources/Amplitude/Plugins/NetworkConnectivityCheckerPlugin.swift new file mode 100644 index 00000000..532a9825 --- /dev/null +++ b/Sources/Amplitude/Plugins/NetworkConnectivityCheckerPlugin.swift @@ -0,0 +1,40 @@ +// +// NetworkConnectivityCheckerPlugin.swift +// Amplitude-Swift +// +// Created by Xinyi.Ye on 1/26/24. +// + +import Foundation +import Network + +open class NetworkConnectivityCheckerPlugin: BeforePlugin { + public static let Disabled: Bool? = nil + let monitor = NWPathMonitor() + + + open override func setup(amplitude: Amplitude) { + super.setup(amplitude: amplitude) + self.amplitude?.logger?.debug(message: "Installing AndroidNetworkConnectivityPlugin, offline feature should be supported.") + + // Define handler for network changes + monitor.pathUpdateHandler = { path in + if path.status == .satisfied { + self.amplitude?.logger?.debug(message: "Network connectivity changed to online.") + self.amplitude?.configuration.offline = false + } else { + self.amplitude?.logger?.debug(message: "Network connectivity changed to offline.") + self.amplitude?.configuration.offline = true + } + } + + // Start network monitor + let queue = DispatchQueue(label: "networkConnectivityChecker.amplitude.com") + monitor.start(queue: queue) + } + + open override func teardown() { + monitor.cancel() + } + +} diff --git a/Sources/Amplitude/Types.swift b/Sources/Amplitude/Types.swift index 2cd0669f..cd2c012c 100644 --- a/Sources/Amplitude/Types.swift +++ b/Sources/Amplitude/Types.swift @@ -98,6 +98,7 @@ public protocol Plugin: AnyObject { var type: PluginType { get } func setup(amplitude: Amplitude) func execute(event: BaseEvent) -> BaseEvent? + func teardown() } public protocol EventPlugin: Plugin { @@ -116,6 +117,10 @@ extension Plugin { public func setup(amplitude: Amplitude) { } + + public func teardown(){ + // Clean up any resources from setup if necessary + } } public protocol ResponseHandler { diff --git a/Sources/Amplitude/Utilities/EventPipeline.swift b/Sources/Amplitude/Utilities/EventPipeline.swift index 122462ea..7ff5e85b 100644 --- a/Sources/Amplitude/Utilities/EventPipeline.swift +++ b/Sources/Amplitude/Utilities/EventPipeline.swift @@ -45,6 +45,11 @@ public class EventPipeline { } func flush(completion: (() -> Void)? = nil) { + if self.amplitude.configuration.offline == true { + self.amplitude.logger?.debug(message: "Skipping flush while offline.") + return + } + amplitude.logger?.log(message: "Start flushing \(eventCount) events") eventCount = 0 guard let storage = self.storage else { return }