From 4831e4d80a75b2dbe97611e54a07be70fcee6240 Mon Sep 17 00:00:00 2001 From: Matus Tomlein Date: Tue, 16 Jan 2024 10:58:58 +0100 Subject: [PATCH] Add support for visionOS (#830) Co-authored-by: Miranda Wilson --- .github/workflows/build.yml | 41 ++++++++++++++--- Package.resolved | 42 ++++++++--------- Package.swift | 3 +- Package@swift-5.9.swift | 46 +++++++++++++++++++ SnowplowTracker.podspec | 5 ++ Sources/Core/Storage/Database.swift | 2 +- Sources/Core/Storage/SQLiteEventStore.swift | 2 +- Sources/Core/Subject/PlatformContext.swift | 8 ++-- Sources/Core/Tracker/Tracker.swift | 2 +- .../Core/Tracker/WebViewMessageHandler.swift | 2 +- Sources/Core/Utils/DeviceInfoMonitor.swift | 16 ++++--- Sources/Core/Utils/SNOWReachability.swift | 4 +- Sources/Core/Utils/Utilities.swift | 6 +-- Sources/Snowplow/Tracker/DevicePlatform.swift | 5 +- Tests/Storage/TestDatabase.swift | 2 +- Tests/Storage/TestSQLiteEventStore.swift | 2 +- Tests/TestUtils.swift | 4 ++ Tests/TestWebViewMessageHandler.swift | 2 +- Tests/Utils/DatabaseHelpers.swift | 2 +- Tests/Utils/MockWKScriptMessage.swift | 2 +- 20 files changed, 143 insertions(+), 55 deletions(-) create mode 100644 Package@swift-5.9.swift diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a1850cd9..2f5d20219 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: [push] jobs: podspec: name: Lint Podspec for ${{ matrix.platform }} - runs-on: macos-latest + runs-on: macos-13 strategy: matrix: platform: [ios, ios, osx, tvos, watchos] @@ -53,6 +53,7 @@ jobs: xcode-version: 13.4 destination: "platform=tvOS Simulator,name=Apple TV" target: Tests + steps: - name: Checkout uses: actions/checkout@v3 @@ -110,8 +111,22 @@ jobs: fail-fast: false matrix: version: - - {ios: 15.5, iphone: iPhone 12 Pro, watchos: 8.5, watch: Apple Watch Series 5 - 44mm, macos: '12', xcode: 13.4} - - {ios: 14.4, iphone: iPhone 8, watchos: 7.2, watch: Apple Watch Series 4 - 40mm, macos: '11', xcode: 12.4} + - { + ios: 15.5, + iphone: iPhone 12 Pro, + watchos: 8.5, + watch: Apple Watch Series 5 - 44mm, + macos: "12", + xcode: 13.4, + } + - { + ios: 14.4, + iphone: iPhone 8, + watchos: 7.2, + watch: Apple Watch Series 4 - 40mm, + macos: "11", + xcode: 12.4, + } steps: - name: Checkout @@ -141,7 +156,14 @@ jobs: fail-fast: false matrix: version: - - {ios: '14.4', iphone: iPhone 12 Pro, watchos: '7.2', watch: Apple Watch Series 5 - 44mm, macos: '11', xcode: 12.4} + - { + ios: "14.4", + iphone: iPhone 12 Pro, + watchos: "7.2", + watch: Apple Watch Series 5 - 44mm, + macos: "11", + xcode: 12.4, + } steps: - name: Checkout @@ -171,7 +193,14 @@ jobs: fail-fast: false matrix: version: - - {ios: '14.4', iphone: iPhone 11 Pro, watchos: '7.2', watch: Apple Watch Series 5 - 44mm, macos: '11', xcode: 12.4} + - { + ios: "14.4", + iphone: iPhone 11 Pro, + watchos: "7.2", + watch: Apple Watch Series 5 - 44mm, + macos: "11", + xcode: 12.4, + } steps: - name: Checkout @@ -217,7 +246,7 @@ jobs: fail-fast: false matrix: version: - - {ios: 15.5, iphone: iPhone 12 Pro, macos: '12', xcode: 13.4} + - { ios: 15.5, iphone: iPhone 12 Pro, macos: "12", xcode: 13.4 } steps: - name: Checkout diff --git a/Package.resolved b/Package.resolved index 0627bd344..4fc33c6bc 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,25 +1,23 @@ { - "object": { - "pins": [ - { - "package": "Mocker", - "repositoryURL": "https://github.com/WeTransfer/Mocker.git", - "state": { - "branch": null, - "revision": "5d86f27a8f80d4ba388bc1a379a3c2289a1f3d18", - "version": "2.6.0" - } - }, - { - "package": "SwiftDocCPlugin", - "repositoryURL": "https://github.com/apple/swift-docc-plugin", - "state": { - "branch": null, - "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", - "version": "1.0.0" - } + "pins" : [ + { + "identity" : "mocker", + "kind" : "remoteSourceControl", + "location" : "https://github.com/WeTransfer/Mocker.git", + "state" : { + "revision" : "5d86f27a8f80d4ba388bc1a379a3c2289a1f3d18", + "version" : "2.6.0" } - ] - }, - "version": 1 + }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version" : "1.0.0" + } + } + ], + "version" : 2 } diff --git a/Package.swift b/Package.swift index 666795f11..2d7a1c745 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,10 @@ -// swift-tools-version:5.2 +// swift-tools-version:5.3.0 import PackageDescription let package = Package( name: "SnowplowTracker", + defaultLocalization: "en", platforms: [ .macOS("10.13"), .iOS("11.0"), diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift new file mode 100644 index 000000000..1db62ef7d --- /dev/null +++ b/Package@swift-5.9.swift @@ -0,0 +1,46 @@ +// swift-tools-version:5.9 + +import PackageDescription + +let package = Package( + name: "SnowplowTracker", + defaultLocalization: "en", + platforms: [ + .macOS("10.13"), + .iOS("11.0"), + .tvOS("12.0"), + .watchOS("6.0"), + .visionOS("1.0") + ], + products: [ + .library( + name: "SnowplowTracker", + targets: ["SnowplowTracker"]), + ], + dependencies: [ + .package(name: "Mocker", url: "https://github.com/WeTransfer/Mocker.git", from: "2.5.4"), + ], + targets: [ + .target( + name: "SnowplowTracker", + path: "./Sources"), + .testTarget( + name: "Tests", + dependencies: [ + "SnowplowTracker", + "Mocker" + ], + path: "Tests"), + .testTarget( + name: "IntegrationTests", + dependencies: [ + "SnowplowTracker" + ], + path: "IntegrationTests") + ] +) +#if swift(>=5.6) +package.dependencies += [ + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), +] +#endif diff --git a/SnowplowTracker.podspec b/SnowplowTracker.podspec index 8f40318dd..bfb22ae1f 100644 --- a/SnowplowTracker.podspec +++ b/SnowplowTracker.podspec @@ -24,6 +24,11 @@ Pod::Spec.new do |s| s.ios.frameworks = 'CoreTelephony', 'UIKit', 'Foundation' s.osx.frameworks = 'AppKit', 'Foundation' s.tvos.frameworks = 'UIKit', 'Foundation' + + if s.respond_to?(:visionos) + s.visionos.deployment_target = '1.0' + s.visionos.frameworks = 'UIKit', 'Foundation' + end s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES" } end diff --git a/Sources/Core/Storage/Database.swift b/Sources/Core/Storage/Database.swift index 3897f148e..6aa11723d 100644 --- a/Sources/Core/Storage/Database.swift +++ b/Sources/Core/Storage/Database.swift @@ -11,7 +11,7 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) || os(macOS) +#if os(iOS) || os(macOS) || os(visionOS) import Foundation import SQLite3 diff --git a/Sources/Core/Storage/SQLiteEventStore.swift b/Sources/Core/Storage/SQLiteEventStore.swift index eea61c97f..c81705dac 100644 --- a/Sources/Core/Storage/SQLiteEventStore.swift +++ b/Sources/Core/Storage/SQLiteEventStore.swift @@ -11,7 +11,7 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) || os(macOS) +#if os(iOS) || os(macOS) || os(visionOS) import Foundation diff --git a/Sources/Core/Subject/PlatformContext.swift b/Sources/Core/Subject/PlatformContext.swift index 67410d130..27b84d0ec 100644 --- a/Sources/Core/Subject/PlatformContext.swift +++ b/Sources/Core/Subject/PlatformContext.swift @@ -12,7 +12,7 @@ // language governing permissions and limitations there under. import Foundation -#if os(iOS) +#if os(iOS) || os(visionOS) import UIKit #endif @@ -44,7 +44,7 @@ class PlatformContext { self.mobileDictUpdateFrequency = mobileDictUpdateFrequency self.networkDictUpdateFrequency = networkDictUpdateFrequency self.deviceInfoMonitor = deviceInfoMonitor - #if os(iOS) + #if os(iOS) || os(visionOS) UIDevice.current.isBatteryMonitoringEnabled = true #endif setPlatformDict() @@ -53,7 +53,7 @@ class PlatformContext { /// Updates and returns payload dictionary with device context information. /// - Parameter userAnonymisation: Whether to anonymise user identifiers (IDFA values) func fetchPlatformDict(userAnonymisation: Bool, advertisingIdentifierRetriever: (() -> UUID?)?) -> Payload { - #if os(iOS) + #if os(iOS) || os(visionOS) let now = Date().timeIntervalSince1970 if now - lastUpdatedEphemeralMobileDict >= mobileDictUpdateFrequency { setEphemeralMobileDict() @@ -87,7 +87,7 @@ class PlatformContext { platformDict[kSPPlatformDeviceManu] = deviceInfoMonitor.deviceVendor platformDict[kSPPlatformDeviceModel] = deviceInfoMonitor.deviceModel - #if os(iOS) + #if os(iOS) || os(visionOS) setMobileDict() #endif } diff --git a/Sources/Core/Tracker/Tracker.swift b/Sources/Core/Tracker/Tracker.swift index 255e9131f..1752e0976 100644 --- a/Sources/Core/Tracker/Tracker.swift +++ b/Sources/Core/Tracker/Tracker.swift @@ -260,7 +260,7 @@ class Tracker: NSObject { super.init() builder(self) - #if os(iOS) + #if os(iOS) || os(visionOS) platformContextSchema = kSPMobileContextSchema #else platformContextSchema = kSPDesktopContextSchema diff --git a/Sources/Core/Tracker/WebViewMessageHandler.swift b/Sources/Core/Tracker/WebViewMessageHandler.swift index 04873c70b..fc5e1eb10 100644 --- a/Sources/Core/Tracker/WebViewMessageHandler.swift +++ b/Sources/Core/Tracker/WebViewMessageHandler.swift @@ -13,7 +13,7 @@ import Foundation -#if os(iOS) || os(macOS) +#if os(iOS) || os(macOS) || os(visionOS) import WebKit /// Handler for messages from the JavaScript library embedded in Web views. diff --git a/Sources/Core/Utils/DeviceInfoMonitor.swift b/Sources/Core/Utils/DeviceInfoMonitor.swift index e333d4b13..bbf7f05f0 100644 --- a/Sources/Core/Utils/DeviceInfoMonitor.swift +++ b/Sources/Core/Utils/DeviceInfoMonitor.swift @@ -19,7 +19,7 @@ import WatchKit #if os(iOS) import CoreTelephony #endif -#if os(iOS) || os(tvOS) +#if os(iOS) || os(tvOS) || os(visionOS) import UIKit #endif @@ -28,7 +28,7 @@ class DeviceInfoMonitor { /// Returns the generated identifier for vendors. More info can be found in UIDevice's identifierForVendor documentation. /// - Returns: A string containing a formatted UUID for example E621E1F8-C36C-495A-93FC-0C247A3E6E5F. var appleIdfv: String? { - #if os(iOS) || os(tvOS) + #if os(iOS) || os(tvOS) || os(visionOS) if let idfv = UIDevice.current.identifierForVendor?.uuidString { return idfv } @@ -60,7 +60,7 @@ class DeviceInfoMonitor { /// This is to detect what the version of mobile OS of the current device. /// - Returns: The current device's OS version type as a string. var osVersion: String? { - #if os(iOS) || os(tvOS) + #if os(iOS) || os(tvOS) || os(visionOS) return UIDevice.current.systemVersion #elseif os(watchOS) return WKInterfaceDevice.current().systemVersion @@ -85,6 +85,8 @@ class DeviceInfoMonitor { return "tvos" #elseif os(watchOS) return "watchos" + #elseif os(visionOS) + return "visionos" #else return "osx" #endif @@ -146,7 +148,7 @@ class DeviceInfoMonitor { /// Returns the Network Type the device is connected to. /// - Returns: A string containing the Network Type. var networkType: String? { - #if os(iOS) + #if os(iOS) || os(visionOS) let networkStatus = SNOWReachability.forInternetConnection()?.networkStatus switch networkStatus { case .offline: @@ -165,7 +167,7 @@ class DeviceInfoMonitor { /// Returns remaining battery level as an integer percentage of total battery capacity. /// - Returns: Battery level. var batteryLevel: Int? { - #if os(iOS) + #if os(iOS) || os(visionOS) let batteryLevel = UIDevice.current.batteryLevel if batteryLevel != Float(UIDevice.BatteryState.unknown.rawValue) && batteryLevel >= 0 { return Int(batteryLevel * 100) @@ -177,7 +179,7 @@ class DeviceInfoMonitor { /// Returns battery state for the device. /// - Returns: One of "charging", "full", "unplugged" or NULL var batteryState: String? { - #if os(iOS) + #if os(iOS) || os(visionOS) switch UIDevice.current.batteryState { case .charging: return "charging" @@ -196,7 +198,7 @@ class DeviceInfoMonitor { /// Returns whether low power mode is activated. /// - Returns: Boolean indicating the state of low power mode. var isLowPowerModeEnabled: Bool? { - #if os(iOS) + #if os(iOS) || os(visionOS) return ProcessInfo.processInfo.isLowPowerModeEnabled #else return nil diff --git a/Sources/Core/Utils/SNOWReachability.swift b/Sources/Core/Utils/SNOWReachability.swift index 84d4d9ff1..21913a52d 100644 --- a/Sources/Core/Utils/SNOWReachability.swift +++ b/Sources/Core/Utils/SNOWReachability.swift @@ -11,7 +11,7 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) +#if os(iOS) || os(visionOS) import Foundation import SystemConfiguration @@ -65,7 +65,7 @@ class SNOWReachability: NSObject { return .offline } - #if os(iOS) + #if os(iOS) || os(visionOS) let isWWAN = (flags.rawValue & SCNetworkReachabilityFlags.isWWAN.rawValue) == SCNetworkReachabilityFlags.isWWAN.rawValue if isWWAN { return .wwan diff --git a/Sources/Core/Utils/Utilities.swift b/Sources/Core/Utils/Utilities.swift index 9dc060bda..c528d0bd9 100644 --- a/Sources/Core/Utils/Utilities.swift +++ b/Sources/Core/Utils/Utilities.swift @@ -11,12 +11,10 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) +#if os(iOS) || os(tvOS) || os(visionOS) import UIKit #elseif os(macOS) import AppKit -#elseif os(tvOS) -import UIKit #elseif os(watchOS) import WatchKit #endif @@ -40,6 +38,8 @@ class Utilities { class var platform: DevicePlatform { #if os(iOS) return .mobile + #elseif os(visionOS) + return .headset #else return .desktop #endif diff --git a/Sources/Snowplow/Tracker/DevicePlatform.swift b/Sources/Snowplow/Tracker/DevicePlatform.swift index cbe7d6e2b..c8fdf96d9 100644 --- a/Sources/Snowplow/Tracker/DevicePlatform.swift +++ b/Sources/Snowplow/Tracker/DevicePlatform.swift @@ -23,6 +23,7 @@ public enum DevicePlatform : Int { case connectedTV case gameConsole case internetOfThings + case headset } func devicePlatformToString(_ devicePlatform: DevicePlatform) -> String? { @@ -43,11 +44,13 @@ func devicePlatformToString(_ devicePlatform: DevicePlatform) -> String? { return "cnsl" case .internetOfThings: return "iot" + case .headset: + return "headset" } } func stringToDevicePlatform(_ devicePlatformString: String) -> DevicePlatform? { - if let index = ["web", "mob", "pc", "srv", "app", "tv", "cnsl", "iot"].firstIndex(of: devicePlatformString) { + if let index = ["web", "mob", "pc", "srv", "app", "tv", "cnsl", "iot", "headset"].firstIndex(of: devicePlatformString) { return DevicePlatform(rawValue: index) } return nil diff --git a/Tests/Storage/TestDatabase.swift b/Tests/Storage/TestDatabase.swift index 645c588a7..c4c8add8d 100644 --- a/Tests/Storage/TestDatabase.swift +++ b/Tests/Storage/TestDatabase.swift @@ -11,7 +11,7 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) || os(macOS) +#if os(iOS) || os(macOS) || os(visionOS) import XCTest @testable import SnowplowTracker diff --git a/Tests/Storage/TestSQLiteEventStore.swift b/Tests/Storage/TestSQLiteEventStore.swift index 074d1b322..d4b3c9515 100644 --- a/Tests/Storage/TestSQLiteEventStore.swift +++ b/Tests/Storage/TestSQLiteEventStore.swift @@ -11,7 +11,7 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) || os(macOS) +#if os(iOS) || os(macOS) || os(visionOS) import XCTest @testable import SnowplowTracker diff --git a/Tests/TestUtils.swift b/Tests/TestUtils.swift index 601d75a02..d4c9d877f 100644 --- a/Tests/TestUtils.swift +++ b/Tests/TestUtils.swift @@ -36,14 +36,18 @@ class TestUtils: XCTestCase { func testGetPlatform() { #if os(iOS) XCTAssertEqual(Utilities.platform, .mobile) +#elseif os(visionOS) + XCTAssertEqual(Utilities.platform, .headset) #else XCTAssertEqual(Utilities.platform, .desktop) #endif } func testGetResolution() { + #if !os(visionOS) let actualResolution = Utilities.resolution XCTAssertTrue(actualResolution != nil) + #endif } func testGetEventId() { diff --git a/Tests/TestWebViewMessageHandler.swift b/Tests/TestWebViewMessageHandler.swift index a71aa39d6..7cee99f9e 100644 --- a/Tests/TestWebViewMessageHandler.swift +++ b/Tests/TestWebViewMessageHandler.swift @@ -11,7 +11,7 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) || os(macOS) +#if os(iOS) || os(macOS) || os(visionOS) import XCTest @testable import SnowplowTracker diff --git a/Tests/Utils/DatabaseHelpers.swift b/Tests/Utils/DatabaseHelpers.swift index 3a106e5cd..0c08ae32c 100644 --- a/Tests/Utils/DatabaseHelpers.swift +++ b/Tests/Utils/DatabaseHelpers.swift @@ -11,7 +11,7 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) || os(macOS) +#if os(iOS) || os(macOS) || os(visionOS) import Foundation @testable import SnowplowTracker diff --git a/Tests/Utils/MockWKScriptMessage.swift b/Tests/Utils/MockWKScriptMessage.swift index 9ba06d742..153dd2c64 100644 --- a/Tests/Utils/MockWKScriptMessage.swift +++ b/Tests/Utils/MockWKScriptMessage.swift @@ -11,7 +11,7 @@ // express or implied. See the Apache License Version 2.0 for the specific // language governing permissions and limitations there under. -#if os(iOS) || os(macOS) +#if os(iOS) || os(macOS) || os(visionOS) import WebKit class MockWKScriptMessage: WKScriptMessage {