From be9db688d4455acf1405e3f59905dc58d41028d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Wed, 17 Apr 2024 11:07:56 +0200 Subject: [PATCH 01/16] Make it easier to check for appLanguage, preferredLanguage, and region --- Sources/TelemetryClient/Signal.swift | 29 +++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Sources/TelemetryClient/Signal.swift b/Sources/TelemetryClient/Signal.swift index 99dd528..95974ad 100644 --- a/Sources/TelemetryClient/Signal.swift +++ b/Sources/TelemetryClient/Signal.swift @@ -55,6 +55,9 @@ public struct DefaultSignalPayload: Encodable { public let operatingSystem = Self.operatingSystem public let targetEnvironment = Self.targetEnvironment public let locale = Self.locale + public let region = Self.region + public let appLanguage = Self.appLanguage + public let preferredLanguage = Self.preferredLanguage public let extensionIdentifier: String? = Self.extensionIdentifier public let telemetryClientVersion = TelemetryClientVersion @@ -271,8 +274,32 @@ extension DefaultSignalPayload { #endif } - /// The locale identifier + /// The locale identifier the app currently runs in. E.g. `en_DE` for an app that does not support German on a device with preferences `[German, English]`, and region Germany. static var locale: String { return Locale.current.identifier } + + /// The region identifier both the user most prefers and also the app is set to. They are always the same because formatters in apps always auto-adjust to the users preferences. + static var region: String { + if #available(iOS 16, macOS 13, tvOS 16, visionOS 1, watchOS 9, *) { + return Locale.current.region?.identifier ?? Locale.current.identifier.components(separatedBy: .init(charactersIn: "-_")).last! + } else { + return Locale.current.regionCode ?? Locale.current.identifier.components(separatedBy: .init(charactersIn: "-_")).last! + } + } + + /// The language identifier the app is currently running in. This represents the language the system (or the user) has chosen for the app to run in. + static var appLanguage: String { + if #available(iOS 16, macOS 13, tvOS 16, visionOS 1, watchOS 9, *) { + return Locale.current.language.minimalIdentifier + } else { + return Locale.current.languageCode ?? Locale.current.identifier.components(separatedBy: .init(charactersIn: "-_"))[0] + } + } + + /// The language identifier of the users most preferred language set on the device. Returns also languages the current app is not even localized to. + static var preferredLanguage: String { + let preferredLocaleIdentifier = Locale.preferredLanguages.first ?? "zz-ZZ" + return preferredLocaleIdentifier.components(separatedBy: .init(charactersIn: "-_"))[0] + } } From 28065ca1d90f3d2ce251bc2f309a96519c365a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Thu, 25 Apr 2024 23:21:25 +0200 Subject: [PATCH 02/16] Adjust new parameters names, introduce more & send alongside old names --- Sources/TelemetryClient/Signal.swift | 157 ++++++++++++++---- Sources/TelemetryClient/SignalManager.swift | 2 +- Sources/TelemetryClient/TelemetryClient.swift | 2 +- 3 files changed, 128 insertions(+), 33 deletions(-) diff --git a/Sources/TelemetryClient/Signal.swift b/Sources/TelemetryClient/Signal.swift index 95974ad..8891677 100644 --- a/Sources/TelemetryClient/Signal.swift +++ b/Sources/TelemetryClient/Signal.swift @@ -40,38 +40,71 @@ internal struct SignalPostBody: Codable, Equatable { /// The default payload that is included in payloads processed by TelemetryDeck. public struct DefaultSignalPayload: Encodable { - public let platform = Self.platform - public let systemVersion = Self.systemVersion - public let majorSystemVersion = Self.majorSystemVersion - public let majorMinorSystemVersion = Self.majorMinorSystemVersion - public let appVersion = Self.appVersion - public let buildNumber = Self.buildNumber - public let isSimulator = "\(Self.isSimulator)" - public let isDebug = "\(Self.isDebug)" - public let isTestFlight = "\(Self.isTestFlight)" - public let isAppStore = "\(Self.isAppStore)" - public let modelName = Self.modelName - public let architecture = Self.architecture - public let operatingSystem = Self.operatingSystem - public let targetEnvironment = Self.targetEnvironment - public let locale = Self.locale - public let region = Self.region - public let appLanguage = Self.appLanguage - public let preferredLanguage = Self.preferredLanguage - public let extensionIdentifier: String? = Self.extensionIdentifier - public let telemetryClientVersion = TelemetryClientVersion - - public init() { } - - public func toDictionary() -> [String: String] { - do { - let encoder = JSONEncoder() - let data = try encoder.encode(self) - let dict = try JSONSerialization.jsonObject(with: data) as? [String: String] - return dict ?? [:] - } catch { - return [:] + public static var parameters: [String: String] { + var parameters: [String: String] = [ + // deprecated names + "platform": Self.platform, + "systemVersion": Self.systemVersion, + "majorSystemVersion": Self.majorSystemVersion, + "majorMinorSystemVersion": Self.majorMinorSystemVersion, + "appVersion": Self.appVersion, + "buildNumber": Self.buildNumber, + "isSimulator": "\(Self.isSimulator)", + "isDebug": "\(Self.isDebug)", + "isTestFlight": "\(Self.isTestFlight)", + "isAppStore": "\(Self.isAppStore)", + "modelName": Self.modelName, + "architecture": Self.architecture, + "operatingSystem": Self.operatingSystem, + "targetEnvironment": Self.targetEnvironment, + "locale": Self.locale, + "region": Self.region, + "appLanguage": Self.appLanguage, + "preferredLanguage": Self.preferredLanguage, + "telemetryClientVersion": TelemetryClientVersion, + + // new names + "TelemetryDeck.AppInfo.buildNumber": Self.buildNumber, + "TelemetryDeck.AppInfo.version": Self.appVersion, + "TelemetryDeck.AppInfo.versionAndBuildNumber": "\(Self.appVersion) (build \(Self.buildNumber))", + + "TelemetryDeck.Device.architecture": Self.architecture, + "TelemetryDeck.Device.modelName": Self.modelName, + "TelemetryDeck.Device.operatingSystem": Self.operatingSystem, + "TelemetryDeck.Device.orientation": Self.orientation, + "TelemetryDeck.Device.platform": Self.platform, + "TelemetryDeck.Device.screenResolutionHeight": Self.screenResolutionHeight, + "TelemetryDeck.Device.screenResolutionWidth": Self.screenResolutionWidth, + "TelemetryDeck.Device.systemMajorMinorVersion": Self.majorMinorSystemVersion, + "TelemetryDeck.Device.systemMajorVersion": Self.majorSystemVersion, + "TelemetryDeck.Device.systemVersion": Self.systemVersion, + "TelemetryDeck.Device.timeZone": Self.timeZone, + + "TelemetryDeck.RunContext.isAppStore": "\(Self.isAppStore)", + "TelemetryDeck.RunContext.isDebug": "\(Self.isDebug)", + "TelemetryDeck.RunContext.isSimulator": "\(Self.isSimulator)", + "TelemetryDeck.RunContext.isTestFlight": "\(Self.isTestFlight)", + "TelemetryDeck.RunContext.language": Self.appLanguage, + "TelemetryDeck.RunContext.locale": Self.locale, + "TelemetryDeck.RunContext.targetEnvironment": Self.targetEnvironment, + + "TelemetryDeck.SDK.name": "SwiftSDK", + "TelemetryDeck.SDK.nameAndVersion": "SwiftSDK \(TelemetryClientVersion)", + "TelemetryDeck.SDK.version": TelemetryClientVersion, + + "TelemetryDeck.UserPreference.language": Self.preferredLanguage, + "TelemetryDeck.UserPreference.region": Self.region, + ] + + if let extensionIdentifier = Self.extensionIdentifier { + // deprecated name + parameters["extensionIdentifier"] = extensionIdentifier + + // new name + parameters["TelemetryDeck.RunContext.extensionIdentifier"] } + + return parameters } } @@ -302,4 +335,66 @@ extension DefaultSignalPayload { let preferredLocaleIdentifier = Locale.preferredLanguages.first ?? "zz-ZZ" return preferredLocaleIdentifier.components(separatedBy: .init(charactersIn: "-_"))[0] } + + /// The current devices screen resolution width in points. + static var screenResolutionWidth: String { + #if os(iOS) || os(tvOS) + return "\(UIScreen.main.bounds.width)" + #elseif os(watchOS) + return "\(WKInterfaceDevice.current().screenBounds.width)" + #elseif os(macOS) + if let screen = NSScreen.main { + return "\(screen.frame.width)" + } + return "Unknown" + #else + return "N/A" + #endif + } + + /// The current devices screen resolution height in points. + static var screenResolutionHeight: String { + #if os(iOS) || os(tvOS) + return "\(UIScreen.main.bounds.height)" + #elseif os(watchOS) + return "\(WKInterfaceDevice.current().screenBounds.height)" + #elseif os(macOS) + if let screen = NSScreen.main { + return "\(screen.frame.height)" + } + return "Unknown" + #else + return "N/A" + #endif + } + + /// The current devices screen orientation. Returns `Fixed` for devices that don't support an orientation change. + static var orientation: String { + #if os(iOS) + switch UIDevice.current.orientation { + case .portrait, .portraitUpsideDown: + return "Portrait" + case .landscapeLeft, .landscapeRight: + return "Landscape" + default: + return "Unknown" + } + #else + return "Fixed" + #endif + } + + /// The devices current time zone in the modern `UTC` format, such as `UTC+1`, or `UTC-3:30`. + static var timeZone: String { + let secondsFromGMT = TimeZone.current.secondsFromGMT() + let hours = secondsFromGMT / 3600 + let minutes = abs(secondsFromGMT / 60 % 60) + + let sign = secondsFromGMT >= 0 ? "+" : "-" + if minutes > 0 { + return "UTC\(sign)\(hours):\(String(format: "%02d", minutes))" + } else { + return "UTC\(sign)\(hours)" + } + } } diff --git a/Sources/TelemetryClient/SignalManager.swift b/Sources/TelemetryClient/SignalManager.swift index fe78026..24ced7d 100644 --- a/Sources/TelemetryClient/SignalManager.swift +++ b/Sources/TelemetryClient/SignalManager.swift @@ -79,7 +79,7 @@ internal class SignalManager: SignalManageable { .map { $0.enrich(signalType: signalType, for: clientUser, floatValue: floatValue) } .reduce([String: String](), { $0.applying($1) }) - let payload = DefaultSignalPayload().toDictionary() + let payload = DefaultSignalPayload.parameters .applying(enrichedMetadata) .applying(additionalPayload) diff --git a/Sources/TelemetryClient/TelemetryClient.swift b/Sources/TelemetryClient/TelemetryClient.swift index d579423..2c8bdca 100644 --- a/Sources/TelemetryClient/TelemetryClient.swift +++ b/Sources/TelemetryClient/TelemetryClient.swift @@ -10,7 +10,7 @@ import Foundation import TVUIKit #endif -let TelemetryClientVersion = "SwiftClient 1.5.1" +let TelemetryClientVersion = "1.5.1" public typealias TelemetrySignalType = String From 18f90562577c6bfbdd931f8f579072b696d3eae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 26 Apr 2024 00:11:41 +0200 Subject: [PATCH 03/16] Fix tools version & provide proper support for visionOS --- Package.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index e8ea19c..296dd70 100644 --- a/Package.swift +++ b/Package.swift @@ -1,6 +1,4 @@ -// swift-tools-version:5.7 -// The swift-tools-version declares the minimum version of Swift required to build this package. - +// swift-tools-version:5.9 import PackageDescription let package = Package( @@ -9,7 +7,8 @@ let package = Package( .macOS(.v10_13), .iOS(.v12), .watchOS(.v5), - .tvOS(.v13) + .tvOS(.v13), + .visionOS(.v1), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. From 514393ceac78a279d08faaefeac4096c89b01df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 26 Apr 2024 10:42:41 +0200 Subject: [PATCH 04/16] Fix minor issue --- Sources/TelemetryClient/Signal.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TelemetryClient/Signal.swift b/Sources/TelemetryClient/Signal.swift index 8891677..a5d4a4d 100644 --- a/Sources/TelemetryClient/Signal.swift +++ b/Sources/TelemetryClient/Signal.swift @@ -101,7 +101,7 @@ public struct DefaultSignalPayload: Encodable { parameters["extensionIdentifier"] = extensionIdentifier // new name - parameters["TelemetryDeck.RunContext.extensionIdentifier"] + parameters["TelemetryDeck.RunContext.extensionIdentifier"] = extensionIdentifier } return parameters From b6c7d436987bce3f5f8d64c74cf023d925844ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 3 May 2024 18:43:37 +0200 Subject: [PATCH 05/16] Apply grand rename by deprecating existing APIs and introducing new --- Sources/TelemetryClient/SignalEnricher.swift | 2 +- Sources/TelemetryClient/SignalManager.swift | 18 +++---- Sources/TelemetryClient/TelemetryClient.swift | 54 ++++++++++++++----- Sources/TelemetryClient/TelemetryDeck.swift | 30 +++++++++++ .../SignalPayloadTests.swift | 2 +- .../TelemetryClientTests.swift | 46 ++++++++-------- 6 files changed, 105 insertions(+), 47 deletions(-) create mode 100644 Sources/TelemetryClient/TelemetryDeck.swift diff --git a/Sources/TelemetryClient/SignalEnricher.swift b/Sources/TelemetryClient/SignalEnricher.swift index a3423af..516a853 100644 --- a/Sources/TelemetryClient/SignalEnricher.swift +++ b/Sources/TelemetryClient/SignalEnricher.swift @@ -2,7 +2,7 @@ import Foundation public protocol SignalEnricher { func enrich( - signalType: TelemetrySignalType, + signalType: String, for clientUser: String?, floatValue: Double? ) -> [String: String] diff --git a/Sources/TelemetryClient/SignalManager.swift b/Sources/TelemetryClient/SignalManager.swift index 24ced7d..74214b6 100644 --- a/Sources/TelemetryClient/SignalManager.swift +++ b/Sources/TelemetryClient/SignalManager.swift @@ -11,7 +11,7 @@ import TVUIKit #endif internal protocol SignalManageable { - func processSignal(_ signalType: TelemetrySignalType, for clientUser: String?, floatValue: Double?, with additionalPayload: [String: String], configuration: TelemetryManagerConfiguration) + func processSignal(_ signalName: String, parameters: [String: String], floatValue: Double?, customUserID: String?, configuration: TelemetryManagerConfiguration) func attemptToSendNextBatchOfCachedSignals() } @@ -68,27 +68,27 @@ internal class SignalManager: SignalManageable { /// Adds a signal to the process queue func processSignal( - _ signalType: TelemetrySignalType, - for clientUser: String? = nil, - floatValue: Double? = nil, - with additionalPayload: [String: String] = [:], + _ signalName: String, + parameters: [String: String], + floatValue: Double?, + customUserID: String?, configuration: TelemetryManagerConfiguration ) { DispatchQueue.global(qos: .utility).async { let enrichedMetadata: [String: String] = configuration.metadataEnrichers - .map { $0.enrich(signalType: signalType, for: clientUser, floatValue: floatValue) } + .map { $0.enrich(signalType: signalName, for: customUserID, floatValue: floatValue) } .reduce([String: String](), { $0.applying($1) }) let payload = DefaultSignalPayload.parameters .applying(enrichedMetadata) - .applying(additionalPayload) + .applying(parameters) let signalPostBody = SignalPostBody( receivedAt: Date(), appID: UUID(uuidString: configuration.telemetryAppID)!, - clientUser: CryptoHashing.sha256(str: clientUser ?? self.defaultUserIdentifier, salt: configuration.salt), + clientUser: CryptoHashing.sha256(str: customUserID ?? self.defaultUserIdentifier, salt: configuration.salt), sessionID: configuration.sessionID.uuidString, - type: "\(signalType)", + type: "\(signalName)", floatValue: floatValue, payload: payload.toMultiValueDimension(), isTestMode: configuration.testMode ? "true" : "false" diff --git a/Sources/TelemetryClient/TelemetryClient.swift b/Sources/TelemetryClient/TelemetryClient.swift index 2c8bdca..efc261f 100644 --- a/Sources/TelemetryClient/TelemetryClient.swift +++ b/Sources/TelemetryClient/TelemetryClient.swift @@ -12,8 +12,6 @@ import Foundation let TelemetryClientVersion = "1.5.1" -public typealias TelemetrySignalType = String - /// Configuration for TelemetryManager /// /// Use an instance of this class to specify settings for TelemetryManager. If these settings change during the course of @@ -55,7 +53,14 @@ public final class TelemetryManagerConfiguration { /// more fine-grained session support, write a new random session identifier into this property each time a new session begins. /// /// Beginning a new session automatically sends a "newSessionBegan" Signal if `sendNewSessionBeganSignal` is `true` - public var sessionID = UUID() { didSet { if sendNewSessionBeganSignal { TelemetryManager.send("newSessionBegan") } } } + public var sessionID = UUID() { + didSet { + if sendNewSessionBeganSignal { + TelemetryManager.send("newSessionBegan") + TelemetryDeck.signal("TelemetryDeck.Session.started") + } + } + } @available(*, deprecated, message: "Please use the testMode property instead") public var sendSignalsInDebugConfiguration: Bool = false @@ -183,6 +188,7 @@ public class TelemetryManager { initializedTelemetryManager != nil } + @available(*, deprecated, renamed: "TelemetryDeck.initialize(configuration:)", message: "This call was renamed to `TelemetryDeck.initialize(configuration:)`. Please migrate – a fix-it is available.") public static func initialize(with configuration: TelemetryManagerConfiguration) { initializedTelemetryManager = TelemetryManager(configuration: configuration) } @@ -197,13 +203,23 @@ public class TelemetryManager { initializedTelemetryManager = nil } + /// Send a Signal to TelemetryDeck, to record that an event has occurred. + /// + /// If you specify a payload, it will be sent in addition to the default payload which includes OS Version, App Version, and more. + @available(*, deprecated, renamed: "TelemetryDeck.signal(_:parameters:)", message: "This call was renamed to `TelemetryDeck.signal(_:parameters:)`. Please migrate – a fix-it is available.") + public static func send(_ signalName: String, with parameters: [String: String] = [:]) { + send(signalName, for: nil, floatValue: nil, with: parameters) + } + /// Send a Signal to TelemetryDeck, to record that an event has occurred. /// /// If you specify a user identifier here, it will take precedence over the default user identifier specified in the `TelemetryManagerConfiguration`. /// /// If you specify a payload, it will be sent in addition to the default payload which includes OS Version, App Version, and more. - public static func send(_ signalType: TelemetrySignalType, for clientUser: String? = nil, floatValue: Double? = nil, with additionalPayload: [String: String] = [:]) { - TelemetryManager.shared.send(signalType, for: clientUser, floatValue: floatValue, with: additionalPayload) + @_disfavoredOverload + @available(*, deprecated, message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments.") + public static func send(_ signalName: String, for customUserID: String? = nil, floatValue: Double? = nil, with parameters: [String: String] = [:]) { + TelemetryManager.shared.send(signalName, for: customUserID, floatValue: floatValue, with: parameters) } /// Do not call this method unless you really know what you're doing. The signals will automatically sync with the server at appropriate times, there's no need to call this. @@ -261,11 +277,23 @@ public class TelemetryManager { /// If you specify a user identifier here, it will take precedence over the default user identifier specified in the `TelemetryManagerConfiguration`. /// /// If you specify a payload, it will be sent in addition to the default payload which includes OS Version, App Version, and more. - public func send(_ signalType: TelemetrySignalType, for clientUser: String? = nil, floatValue: Double? = nil, with additionalPayload: [String: String] = [:]) { + @available(*, deprecated, message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments.") + public func send(_ signalName: String, with parameters: [String: String] = [:]) { + send(signalName, for: nil, floatValue: nil, with: parameters) + } + + /// Send a Signal to TelemetryDeck, to record that an event has occurred. + /// + /// If you specify a user identifier here, it will take precedence over the default user identifier specified in the `TelemetryManagerConfiguration`. + /// + /// If you specify a payload, it will be sent in addition to the default payload which includes OS Version, App Version, and more. + @_disfavoredOverload + @available(*, deprecated, message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments.") + public func send(_ signalName: String, for customUserID: String? = nil, floatValue: Double? = nil, with parameters: [String: String] = [:]) { // make sure to not send any signals when run by Xcode via SwiftUI previews guard !self.configuration.swiftUIPreviewMode, !self.configuration.analyticsDisabled else { return } - signalManager.processSignal(signalType, for: clientUser, floatValue: floatValue, with: additionalPayload, configuration: configuration) + signalManager.processSignal(signalName, parameters: parameters, floatValue: floatValue, customUserID: customUserID, configuration: configuration) } /// Do not call this method unless you really know what you're doing. The signals will automatically sync with the server at appropriate times, there's no need to call this. @@ -366,16 +394,16 @@ public final class TelemetryManagerObjCProxy: NSObject { TelemetryManager.terminate() } - @objc public static func send(_ signalType: TelemetrySignalType, for clientUser: String? = nil, with additionalPayload: [String: String] = [:]) { - TelemetryManager.send(signalType, for: clientUser, with: additionalPayload) + @objc public static func send(_ signalName: String, for clientUser: String? = nil, with additionalPayload: [String: String] = [:]) { + TelemetryManager.send(signalName, for: clientUser, with: additionalPayload) } - @objc public static func send(_ signalType: TelemetrySignalType, with additionalPayload: [String: String] = [:]) { - TelemetryManager.send(signalType, with: additionalPayload) + @objc public static func send(_ signalName: String, with additionalPayload: [String: String] = [:]) { + TelemetryManager.send(signalName, with: additionalPayload) } - @objc public static func send(_ signalType: TelemetrySignalType) { - TelemetryManager.send(signalType) + @objc public static func send(_ signalName: String) { + TelemetryManager.send(signalName) } @objc public static func updateDefaultUser(to newDefaultUser: String?) { diff --git a/Sources/TelemetryClient/TelemetryDeck.swift b/Sources/TelemetryClient/TelemetryDeck.swift new file mode 100644 index 0000000..4fd5ed2 --- /dev/null +++ b/Sources/TelemetryClient/TelemetryDeck.swift @@ -0,0 +1,30 @@ +import Foundation + +/// A namespace for TelemetryDeck related functionalities. +public enum TelemetryDeck { + /// This alias makes it easier to migrate the configuration type into the TelemetryDeck namespace in future versions when deprecated code is fully removed. + public typealias Configuration = TelemetryManagerConfiguration + + /// Initializes TelemetryDeck with a customizable configuration. + /// + /// - Parameter configuration: An instance of `Configuration` which includes all the settings required to configure TelemetryDeck. + /// + /// This function sets up the telemetry system with the specified configuration. It is necessary to call this method before sending any telemetry signals. + /// For example, you might want to call this in your `init` method of your app's `@main` entry point. + public static func initialize(configuration: Configuration) { + TelemetryManager.initialize(with: configuration) + } + + /// Sends a telemetry signal with optional parameters to TelemetryDeck. + /// + /// - Parameters: + /// - signalName: The name of the signal to be sent. This is a string that identifies the type of event or action being reported. + /// - parameters: A dictionary of additional string key-value pairs that provide further context about the signal. Default is empty. + /// - floatValue: An optional floating-point number that can be used to provide numerical data about the signal. Default is `nil`. + /// - customUserID: An optional string specifying a custom user identifier. If provided, it will override the default user identifier from the configuration. Default is `nil`. + /// + /// This function wraps the `TelemetryManager.send` method, providing a streamlined way to send signals from anywhere in the app. + static func signal(_ signalName: String, parameters: [String: String] = [:], floatValue: Double? = nil, customUserID: String? = nil) { + TelemetryManager.send(signalName, for: customUserID, floatValue: floatValue, with: parameters) + } +} diff --git a/Tests/TelemetryClientTests/SignalPayloadTests.swift b/Tests/TelemetryClientTests/SignalPayloadTests.swift index 6adf25a..4d3be53 100644 --- a/Tests/TelemetryClientTests/SignalPayloadTests.swift +++ b/Tests/TelemetryClientTests/SignalPayloadTests.swift @@ -76,7 +76,7 @@ final class DefaultSignalPayloadTests: XCTestCase { #elseif os(Linux) expectedResult = "Linux" #elseif os(visionOS) - expectedResult = "VisionOS" + expectedResult = "visionOS" #else return "Unknown Operating System" #endif diff --git a/Tests/TelemetryClientTests/TelemetryClientTests.swift b/Tests/TelemetryClientTests/TelemetryClientTests.swift index 6a117ff..527fa1a 100644 --- a/Tests/TelemetryClientTests/TelemetryClientTests.swift +++ b/Tests/TelemetryClientTests/TelemetryClientTests.swift @@ -7,10 +7,10 @@ final class TelemetryClientTests: XCTestCase { let YOUR_APP_ID = "44e0f59a-60a2-4d4a-bf27-1f96ccb4aaa3" let configuration = TelemetryManagerConfiguration(appID: YOUR_APP_ID) - TelemetryManager.initialize(with: configuration) - TelemetryManager.send("appOpenedRegularly") - TelemetryManager.send("userLoggedIn", for: "email") - TelemetryManager.send("databaseUpdated", with: ["numberOfDatabaseEntries": "3831"]) + TelemetryDeck.initialize(configuration: configuration) + TelemetryDeck.signal("appOpenedRegularly") + TelemetryDeck.signal("userLoggedIn", customUserID: "email") + TelemetryDeck.signal("databaseUpdated", parameters: ["numberOfDatabaseEntries": "3831"]) } func testPushAndPop() { @@ -56,7 +56,7 @@ final class TelemetryClientTests: XCTestCase { func testSignalEnrichers() throws { struct BasicEnricher: SignalEnricher { - func enrich(signalType: TelemetrySignalType, for clientUser: String?, floatValue: Double?) -> [String : String] { + func enrich(signalType: String, for clientUser: String?, floatValue: Double?) -> [String : String] { ["isTestEnricher": "true"] } } @@ -66,8 +66,8 @@ final class TelemetryClientTests: XCTestCase { let signalManager = FakeSignalManager() TelemetryManager.initialize(with: configuration, signalManager: signalManager) - TelemetryManager.send("testSignal") - + TelemetryDeck.signal("testSignal") + let bodyItems = signalManager.processedSignals XCTAssertEqual(bodyItems.count, 1) let bodyItem = try XCTUnwrap(bodyItems.first) @@ -76,7 +76,7 @@ final class TelemetryClientTests: XCTestCase { func testSignalEnrichers_precedence() throws { struct BasicEnricher: SignalEnricher { - func enrich(signalType: TelemetrySignalType, for clientUser: String?, floatValue: Double?) -> [String : String] { + func enrich(signalType: String, for clientUser: String?, floatValue: Double?) -> [String : String] { ["item": "A", "isDebug": "banana"] } } @@ -86,8 +86,8 @@ final class TelemetryClientTests: XCTestCase { let signalManager = FakeSignalManager() TelemetryManager.initialize(with: configuration, signalManager: signalManager) - TelemetryManager.send("testSignal", with: ["item": "B"]) - + TelemetryDeck.signal("testSignal", parameters: ["item": "B"]) + let bodyItems = signalManager.processedSignals XCTAssertEqual(bodyItems.count, 1) let bodyItem = try XCTUnwrap(bodyItems.first) @@ -103,7 +103,7 @@ final class TelemetryClientTests: XCTestCase { let signalManager = FakeSignalManager() TelemetryManager.initialize(with: configuration, signalManager: signalManager) - TelemetryManager.send("appOpenedRegularly") + TelemetryDeck.signal("appOpenedRegularly") XCTAssertEqual(signalManager.processedSignalTypes.count, 1) } @@ -117,7 +117,7 @@ final class TelemetryClientTests: XCTestCase { let signalManager = FakeSignalManager() TelemetryManager.initialize(with: configuration, signalManager: signalManager) - TelemetryManager.send("appOpenedRegularly") + TelemetryDeck.signal("appOpenedRegularly") XCTAssertEqual(signalManager.processedSignalTypes.count, 1) } @@ -131,7 +131,7 @@ final class TelemetryClientTests: XCTestCase { let signalManager = FakeSignalManager() TelemetryManager.initialize(with: configuration, signalManager: signalManager) - TelemetryManager.send("appOpenedRegularly") + TelemetryDeck.signal("appOpenedRegularly") XCTAssertTrue(signalManager.processedSignalTypes.isEmpty) } @@ -147,7 +147,7 @@ final class TelemetryClientTests: XCTestCase { let signalManager = FakeSignalManager() TelemetryManager.initialize(with: configuration, signalManager: signalManager) - TelemetryManager.send("appOpenedRegularly") + TelemetryDeck.signal("appOpenedRegularly") XCTAssertTrue(signalManager.processedSignalTypes.isEmpty) @@ -162,30 +162,30 @@ final class TelemetryClientTests: XCTestCase { let signalManager = FakeSignalManager() TelemetryManager.initialize(with: configuration, signalManager: signalManager) - TelemetryManager.send("appOpenedRegularly", floatValue: 42) - + TelemetryDeck.signal("appOpenedRegularly", floatValue: 42) + XCTAssertEqual(signalManager.processedSignals.first?.floatValue, 42) } } private class FakeSignalManager: SignalManageable { - var processedSignalTypes = [TelemetrySignalType]() + var processedSignalTypes = [String]() var processedSignals = [SignalPostBody]() - func processSignal(_ signalType: TelemetrySignalType, for clientUser: String?, floatValue: Double?, with additionalPayload: [String : String], configuration: TelemetryManagerConfiguration) { + func processSignal(_ signalType: String, parameters: [String : String], floatValue: Double?, customUserID: String?, configuration: TelemetryManagerConfiguration) { processedSignalTypes.append(signalType) let enrichedMetadata: [String: String] = configuration.metadataEnrichers - .map { $0.enrich(signalType: signalType, for: clientUser, floatValue: floatValue) } + .map { $0.enrich(signalType: signalType, for: customUserID, floatValue: floatValue) } .reduce([String: String](), { $0.applying($1) }) - let payload = DefaultSignalPayload().toDictionary() + let payload = DefaultSignalPayload.parameters .applying(enrichedMetadata) - .applying(additionalPayload) - + .applying(parameters) + let signalPostBody = SignalPostBody( receivedAt: Date(), appID: UUID(uuidString: configuration.telemetryAppID)!, - clientUser: clientUser ?? "no user", + clientUser: customUserID ?? "no user", sessionID: configuration.sessionID.uuidString, type: "\(signalType)", floatValue: floatValue, From 59ea4ced24e8222bbc34608639a8f87fa59a94fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 3 May 2024 19:04:26 +0200 Subject: [PATCH 06/16] Bump version to new major version 2.0.0 --- Sources/TelemetryClient/TelemetryClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TelemetryClient/TelemetryClient.swift b/Sources/TelemetryClient/TelemetryClient.swift index efc261f..0583851 100644 --- a/Sources/TelemetryClient/TelemetryClient.swift +++ b/Sources/TelemetryClient/TelemetryClient.swift @@ -10,7 +10,7 @@ import Foundation import TVUIKit #endif -let TelemetryClientVersion = "1.5.1" +let TelemetryClientVersion = "2.0.0" /// Configuration for TelemetryManager /// From 23502924ed9e08b45d8212d7b6d6dc5baa832ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 3 May 2024 19:14:01 +0200 Subject: [PATCH 07/16] Shorten configuration parameter to config --- Sources/TelemetryClient/TelemetryClient.swift | 2 +- Sources/TelemetryClient/TelemetryDeck.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/TelemetryClient/TelemetryClient.swift b/Sources/TelemetryClient/TelemetryClient.swift index 0583851..43a7559 100644 --- a/Sources/TelemetryClient/TelemetryClient.swift +++ b/Sources/TelemetryClient/TelemetryClient.swift @@ -188,7 +188,7 @@ public class TelemetryManager { initializedTelemetryManager != nil } - @available(*, deprecated, renamed: "TelemetryDeck.initialize(configuration:)", message: "This call was renamed to `TelemetryDeck.initialize(configuration:)`. Please migrate – a fix-it is available.") + @available(*, deprecated, renamed: "TelemetryDeck.initialize(config:)", message: "This call was renamed to `TelemetryDeck.initialize(config:)`. Please migrate – a fix-it is available.") public static func initialize(with configuration: TelemetryManagerConfiguration) { initializedTelemetryManager = TelemetryManager(configuration: configuration) } diff --git a/Sources/TelemetryClient/TelemetryDeck.swift b/Sources/TelemetryClient/TelemetryDeck.swift index 4fd5ed2..52e6831 100644 --- a/Sources/TelemetryClient/TelemetryDeck.swift +++ b/Sources/TelemetryClient/TelemetryDeck.swift @@ -3,7 +3,7 @@ import Foundation /// A namespace for TelemetryDeck related functionalities. public enum TelemetryDeck { /// This alias makes it easier to migrate the configuration type into the TelemetryDeck namespace in future versions when deprecated code is fully removed. - public typealias Configuration = TelemetryManagerConfiguration + public typealias Config = TelemetryManagerConfiguration /// Initializes TelemetryDeck with a customizable configuration. /// @@ -11,8 +11,8 @@ public enum TelemetryDeck { /// /// This function sets up the telemetry system with the specified configuration. It is necessary to call this method before sending any telemetry signals. /// For example, you might want to call this in your `init` method of your app's `@main` entry point. - public static func initialize(configuration: Configuration) { - TelemetryManager.initialize(with: configuration) + public static func initialize(config: Config) { + TelemetryManager.initialize(with: config) } /// Sends a telemetry signal with optional parameters to TelemetryDeck. From cb3ad0cad4dc5a7732ded17022ed6cb3130f72ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 3 May 2024 19:19:24 +0200 Subject: [PATCH 08/16] Add a renamed TelemetryDeck library while also keeping the old one --- Package.swift | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 296dd70..7a33204 100644 --- a/Package.swift +++ b/Package.swift @@ -11,16 +11,10 @@ let package = Package( .visionOS(.v1), ], products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "TelemetryClient", - targets: ["TelemetryClient"] - ), - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), + .library(name: "TelemetryDeck", targets: ["TelemetryClient"]), // new name + .library(name: "TelemetryClient", targets: ["TelemetryClient"]), // old name ], + dependencies: [], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. From 3ab5db35241638736fc900237cac9991cac18a84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 18 May 2024 10:28:40 +0200 Subject: [PATCH 09/16] Add TelemetryDeck target to fix potential import issues --- Package.swift | 9 ++++-- Sources/TelemetryDeck/Exports.swift | 2 ++ Sources/TelemetryDeck/PrivacyInfo.xcprivacy | 35 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 Sources/TelemetryDeck/Exports.swift create mode 100644 Sources/TelemetryDeck/PrivacyInfo.xcprivacy diff --git a/Package.swift b/Package.swift index 7a33204..d63b041 100644 --- a/Package.swift +++ b/Package.swift @@ -11,13 +11,16 @@ let package = Package( .visionOS(.v1), ], products: [ - .library(name: "TelemetryDeck", targets: ["TelemetryClient"]), // new name + .library(name: "TelemetryDeck", targets: ["TelemetryDeck"]), // new name .library(name: "TelemetryClient", targets: ["TelemetryClient"]), // old name ], dependencies: [], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "TelemetryDeck", + dependencies: ["TelemetryClient"], + resources: [.copy("PrivacyInfo.xcprivacy")] + ), .target( name: "TelemetryClient", resources: [.copy("PrivacyInfo.xcprivacy")] diff --git a/Sources/TelemetryDeck/Exports.swift b/Sources/TelemetryDeck/Exports.swift new file mode 100644 index 0000000..88af186 --- /dev/null +++ b/Sources/TelemetryDeck/Exports.swift @@ -0,0 +1,2 @@ +// This file ensures there's a target named `TelemetryDeck` so `import TelemetryDeck` is already possible without renaming `TelemetryClient`. +@_exported import TelemetryClient diff --git a/Sources/TelemetryDeck/PrivacyInfo.xcprivacy b/Sources/TelemetryDeck/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..23592d4 --- /dev/null +++ b/Sources/TelemetryDeck/PrivacyInfo.xcprivacy @@ -0,0 +1,35 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyTracking + + + From 1dd9b98967c697453dbf40db8dee11b59ce34c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 18 May 2024 10:32:31 +0200 Subject: [PATCH 10/16] Make TelemetryDeck.signal method public --- Sources/TelemetryClient/TelemetryDeck.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TelemetryClient/TelemetryDeck.swift b/Sources/TelemetryClient/TelemetryDeck.swift index 52e6831..e82c8b6 100644 --- a/Sources/TelemetryClient/TelemetryDeck.swift +++ b/Sources/TelemetryClient/TelemetryDeck.swift @@ -24,7 +24,7 @@ public enum TelemetryDeck { /// - customUserID: An optional string specifying a custom user identifier. If provided, it will override the default user identifier from the configuration. Default is `nil`. /// /// This function wraps the `TelemetryManager.send` method, providing a streamlined way to send signals from anywhere in the app. - static func signal(_ signalName: String, parameters: [String: String] = [:], floatValue: Double? = nil, customUserID: String? = nil) { + public static func signal(_ signalName: String, parameters: [String: String] = [:], floatValue: Double? = nil, customUserID: String? = nil) { TelemetryManager.send(signalName, for: customUserID, floatValue: floatValue, with: parameters) } } From c8dcc83668c6cbf16fe434f64f5ec5124799de97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 18 May 2024 10:58:00 +0200 Subject: [PATCH 11/16] Fix incorrect app language code detection on modern OSes --- Sources/TelemetryClient/Signal.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TelemetryClient/Signal.swift b/Sources/TelemetryClient/Signal.swift index a5d4a4d..4e78a4b 100644 --- a/Sources/TelemetryClient/Signal.swift +++ b/Sources/TelemetryClient/Signal.swift @@ -324,7 +324,7 @@ extension DefaultSignalPayload { /// The language identifier the app is currently running in. This represents the language the system (or the user) has chosen for the app to run in. static var appLanguage: String { if #available(iOS 16, macOS 13, tvOS 16, visionOS 1, watchOS 9, *) { - return Locale.current.language.minimalIdentifier + return Locale.current.language.languageCode?.identifier ?? Locale.current.identifier.components(separatedBy: .init(charactersIn: "-_"))[0] } else { return Locale.current.languageCode ?? Locale.current.identifier.components(separatedBy: .init(charactersIn: "-_"))[0] } From fd834b96094666dd38466fdead4fb9534642dc8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Thu, 23 May 2024 17:51:55 +0200 Subject: [PATCH 12/16] Update Xcode version to 15 on CI --- .github/workflows/ci.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f4dfa72..e5aa9d9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,8 +23,7 @@ jobs: fail-fast: true matrix: xcode: - - ^14 - # - ^15 + - ^15 xcodebuildCommand: - "-sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14'" - "-sdk macosx -destination 'platform=macOS'" From d52db7e88dcb2325520f769f591d1250003b18fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Thu, 23 May 2024 18:08:55 +0200 Subject: [PATCH 13/16] Fix tests + specify package as scheme as it contains the test target --- .github/workflows/ci.yaml | 2 +- Tests/TelemetryClientTests/TelemetryClientTests.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e5aa9d9..9f28ee9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,4 +37,4 @@ jobs: with: xcode-version: ${{ matrix.xcode }} - name: Build and Test - run: xcodebuild test -scheme TelemetryClient ${{ matrix.xcodebuildCommand }} + run: xcodebuild test -scheme TelemetryClient-Package ${{ matrix.xcodebuildCommand }} diff --git a/Tests/TelemetryClientTests/TelemetryClientTests.swift b/Tests/TelemetryClientTests/TelemetryClientTests.swift index 527fa1a..83e1f62 100644 --- a/Tests/TelemetryClientTests/TelemetryClientTests.swift +++ b/Tests/TelemetryClientTests/TelemetryClientTests.swift @@ -6,8 +6,8 @@ final class TelemetryClientTests: XCTestCase { func testSending() { let YOUR_APP_ID = "44e0f59a-60a2-4d4a-bf27-1f96ccb4aaa3" - let configuration = TelemetryManagerConfiguration(appID: YOUR_APP_ID) - TelemetryDeck.initialize(configuration: configuration) + let config = TelemetryManagerConfiguration(appID: YOUR_APP_ID) + TelemetryDeck.initialize(config: config) TelemetryDeck.signal("appOpenedRegularly") TelemetryDeck.signal("userLoggedIn", customUserID: "email") TelemetryDeck.signal("databaseUpdated", parameters: ["numberOfDatabaseEntries": "3831"]) From b7ecc1a5b85baa41fd8620506b512aa400ef9f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Thu, 23 May 2024 18:25:34 +0200 Subject: [PATCH 14/16] Fix all SwiftLint warnings --- .swiftlint.yml | 1 - Sources/TelemetryClient/CryptoHashing.swift | 4 +-- Sources/TelemetryClient/Signal.swift | 10 +++---- Sources/TelemetryClient/SignalEnricher.swift | 6 ++-- Sources/TelemetryClient/SignalManager.swift | 28 +++++++++---------- Sources/TelemetryClient/TelemetryClient.swift | 17 ++++++++--- 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 07cbce1..41adafd 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,6 +1,5 @@ # By default, SwiftLint uses a set of sensible default rules you can adjust: disabled_rules: # rule identifiers turned on by default to exclude from running - - identifier_name - function_body_length - opening_brace - trailing_comma diff --git a/Sources/TelemetryClient/CryptoHashing.swift b/Sources/TelemetryClient/CryptoHashing.swift index aa6bd20..777f196 100644 --- a/Sources/TelemetryClient/CryptoHashing.swift +++ b/Sources/TelemetryClient/CryptoHashing.swift @@ -11,8 +11,8 @@ enum CryptoHashing { /// should be preferred where available. /// [CommonCrypto](https://github.com/apple-oss-distributions/CommonCrypto) provides compatibility with older OS versions, /// apps built with Xcode versions lower than 11 and non-Apple platforms like Linux. - static func sha256(str: String, salt: String) -> String { - if let strData = (str + salt).data(using: String.Encoding.utf8) { + static func sha256(string: String, salt: String) -> String { + if let strData = (string + salt).data(using: String.Encoding.utf8) { #if canImport(CryptoKit) if #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) { let digest = SHA256.hash(data: strData) diff --git a/Sources/TelemetryClient/Signal.swift b/Sources/TelemetryClient/Signal.swift index 4e78a4b..1a14ccc 100644 --- a/Sources/TelemetryClient/Signal.swift +++ b/Sources/TelemetryClient/Signal.swift @@ -61,13 +61,13 @@ public struct DefaultSignalPayload: Encodable { "region": Self.region, "appLanguage": Self.appLanguage, "preferredLanguage": Self.preferredLanguage, - "telemetryClientVersion": TelemetryClientVersion, + "telemetryClientVersion": telemetryClientVersion, // new names "TelemetryDeck.AppInfo.buildNumber": Self.buildNumber, "TelemetryDeck.AppInfo.version": Self.appVersion, "TelemetryDeck.AppInfo.versionAndBuildNumber": "\(Self.appVersion) (build \(Self.buildNumber))", - + "TelemetryDeck.Device.architecture": Self.architecture, "TelemetryDeck.Device.modelName": Self.modelName, "TelemetryDeck.Device.operatingSystem": Self.operatingSystem, @@ -89,8 +89,8 @@ public struct DefaultSignalPayload: Encodable { "TelemetryDeck.RunContext.targetEnvironment": Self.targetEnvironment, "TelemetryDeck.SDK.name": "SwiftSDK", - "TelemetryDeck.SDK.nameAndVersion": "SwiftSDK \(TelemetryClientVersion)", - "TelemetryDeck.SDK.version": TelemetryClientVersion, + "TelemetryDeck.SDK.nameAndVersion": "SwiftSDK \(telemetryClientVersion)", + "TelemetryDeck.SDK.version": telemetryClientVersion, "TelemetryDeck.UserPreference.language": Self.preferredLanguage, "TelemetryDeck.UserPreference.region": Self.region, @@ -210,7 +210,7 @@ extension DefaultSignalPayload { var modelIdentifier: String? if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data { - if let modelIdentifierCString = String(data: modelData, encoding: .utf8)?.cString(using: .utf8) { + if let modelIdentifierCString = String(decoding: modelData, as: UTF8.self).cString(using: .utf8) { modelIdentifier = String(cString: modelIdentifierCString) } } diff --git a/Sources/TelemetryClient/SignalEnricher.swift b/Sources/TelemetryClient/SignalEnricher.swift index 516a853..aedeb12 100644 --- a/Sources/TelemetryClient/SignalEnricher.swift +++ b/Sources/TelemetryClient/SignalEnricher.swift @@ -9,9 +9,9 @@ public protocol SignalEnricher { } extension Dictionary where Key == String, Value == String { - func applying(_ rhs: [String: String]) -> [String: String] { - merging(rhs) { _, rhs in - rhs + func applying(_ other: [String: String]) -> [String: String] { + merging(other) { _, other in + other } } diff --git a/Sources/TelemetryClient/SignalManager.swift b/Sources/TelemetryClient/SignalManager.swift index 74214b6..0432733 100644 --- a/Sources/TelemetryClient/SignalManager.swift +++ b/Sources/TelemetryClient/SignalManager.swift @@ -130,7 +130,7 @@ internal class SignalManager: SignalManageable { } if let data = data { - configuration.logHandler?.log(.debug, message: String(data: data, encoding: .utf8)!) + configuration.logHandler?.log(.debug, message: String(decoding: data, as: UTF8.self)) } } } @@ -189,7 +189,7 @@ private extension SignalManager { } urlRequest.httpBody = body - self.configuration.logHandler?.log(.debug, message: String(data: urlRequest.httpBody!, encoding: .utf8)!) + self.configuration.logHandler?.log(.debug, message: String(decoding: urlRequest.httpBody!, as: UTF8.self)) let task = URLSession.shared.dataTask(with: urlRequest, completionHandler: completionHandler) task.resume() @@ -253,13 +253,13 @@ private extension URLResponse { // Check for valid response in the 200-299 range guard (200 ... 299).contains(statusCode() ?? 0) else { if statusCode() == 401 { - return TelemetryError.Unauthorised + return TelemetryError.unauthorised } else if statusCode() == 403 { - return TelemetryError.Forbidden + return TelemetryError.forbidden } else if statusCode() == 413 { - return TelemetryError.PayloadTooLarge + return TelemetryError.payloadTooLarge } else { - return TelemetryError.InvalidStatusCode(statusCode: statusCode() ?? 0) + return TelemetryError.invalidStatusCode(statusCode: statusCode() ?? 0) } } return nil @@ -269,22 +269,22 @@ private extension URLResponse { // MARK: - Errors private enum TelemetryError: Error { - case Unauthorised - case Forbidden - case PayloadTooLarge - case InvalidStatusCode(statusCode: Int) + case unauthorised + case forbidden + case payloadTooLarge + case invalidStatusCode(statusCode: Int) } extension TelemetryError: LocalizedError { public var errorDescription: String? { switch self { - case .InvalidStatusCode(let statusCode): + case .invalidStatusCode(let statusCode): return "Invalid status code \(statusCode)" - case .Unauthorised: + case .unauthorised: return "Unauthorized (401)" - case .Forbidden: + case .forbidden: return "Forbidden (403)" - case .PayloadTooLarge: + case .payloadTooLarge: return "Payload is too large (413)" } } diff --git a/Sources/TelemetryClient/TelemetryClient.swift b/Sources/TelemetryClient/TelemetryClient.swift index 43a7559..12f1b24 100644 --- a/Sources/TelemetryClient/TelemetryClient.swift +++ b/Sources/TelemetryClient/TelemetryClient.swift @@ -10,7 +10,7 @@ import Foundation import TVUIKit #endif -let TelemetryClientVersion = "2.0.0" +let telemetryClientVersion = "2.0.0" /// Configuration for TelemetryManager /// @@ -217,7 +217,10 @@ public class TelemetryManager { /// /// If you specify a payload, it will be sent in addition to the default payload which includes OS Version, App Version, and more. @_disfavoredOverload - @available(*, deprecated, message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments.") + @available( + *, deprecated, + message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments." + ) public static func send(_ signalName: String, for customUserID: String? = nil, floatValue: Double? = nil, with parameters: [String: String] = [:]) { TelemetryManager.shared.send(signalName, for: customUserID, floatValue: floatValue, with: parameters) } @@ -277,7 +280,10 @@ public class TelemetryManager { /// If you specify a user identifier here, it will take precedence over the default user identifier specified in the `TelemetryManagerConfiguration`. /// /// If you specify a payload, it will be sent in addition to the default payload which includes OS Version, App Version, and more. - @available(*, deprecated, message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments.") + @available( + *, deprecated, + message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments." + ) public func send(_ signalName: String, with parameters: [String: String] = [:]) { send(signalName, for: nil, floatValue: nil, with: parameters) } @@ -288,7 +294,10 @@ public class TelemetryManager { /// /// If you specify a payload, it will be sent in addition to the default payload which includes OS Version, App Version, and more. @_disfavoredOverload - @available(*, deprecated, message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments.") + @available( + *, deprecated, + message: "This call was renamed to `TelemetryDeck.signal(_:parameters:floatValue:customUserID:)`. Please migrate – no fix-it possible due to the changed order of arguments." + ) public func send(_ signalName: String, for customUserID: String? = nil, floatValue: Double? = nil, with parameters: [String: String] = [:]) { // make sure to not send any signals when run by Xcode via SwiftUI previews guard !self.configuration.swiftUIPreviewMode, !self.configuration.analyticsDisabled else { return } From e64fae376870a4f4f64b165b9d6de14dd3025ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Thu, 23 May 2024 18:32:38 +0200 Subject: [PATCH 15/16] Adjust tests after fixing SwiftLint warnings + switch to Ubuntu-based SwiftLint on CI --- .github/workflows/ci.yaml | 6 ++++-- Sources/TelemetryClient/SignalManager.swift | 4 ++-- Tests/TelemetryClientTests/CryptoHashingTests.swift | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9f28ee9..d16c095 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,12 +10,14 @@ on: jobs: lint: name: Lint Code - runs-on: macos-latest + runs-on: ubuntu-latest steps: - name: Repository checkout uses: actions/checkout@v4 - name: Lint - run: swiftlint + uses: norio-nomura/action-swiftlint@3.2.1 + with: + args: --strict test: name: Test Xcode ${{ matrix.xcode }} - ${{ matrix.xcodebuildCommand }} runs-on: "macos-latest" diff --git a/Sources/TelemetryClient/SignalManager.swift b/Sources/TelemetryClient/SignalManager.swift index 0432733..a30aac9 100644 --- a/Sources/TelemetryClient/SignalManager.swift +++ b/Sources/TelemetryClient/SignalManager.swift @@ -86,7 +86,7 @@ internal class SignalManager: SignalManageable { let signalPostBody = SignalPostBody( receivedAt: Date(), appID: UUID(uuidString: configuration.telemetryAppID)!, - clientUser: CryptoHashing.sha256(str: customUserID ?? self.defaultUserIdentifier, salt: configuration.salt), + clientUser: CryptoHashing.sha256(string: customUserID ?? self.defaultUserIdentifier, salt: configuration.salt), sessionID: configuration.sessionID.uuidString, type: "\(signalName)", floatValue: floatValue, @@ -203,7 +203,7 @@ private extension SignalManager { #if os(macOS) /// A custom ``UserDefaults`` instance specific to TelemetryDeck and the current application. private var customDefaults: UserDefaults? { - let appIdHash = CryptoHashing.sha256(str: self.configuration.telemetryAppID, salt: "") + let appIdHash = CryptoHashing.sha256(string: self.configuration.telemetryAppID, salt: "") return UserDefaults(suiteName: "com.telemetrydeck.\(appIdHash.suffix(12))") } #endif diff --git a/Tests/TelemetryClientTests/CryptoHashingTests.swift b/Tests/TelemetryClientTests/CryptoHashingTests.swift index 4428021..78472ce 100644 --- a/Tests/TelemetryClientTests/CryptoHashingTests.swift +++ b/Tests/TelemetryClientTests/CryptoHashingTests.swift @@ -12,7 +12,7 @@ final class CryptoHashingTests: XCTestCase { let expectedDigestString = "5b8fab7cf45fcece0e99a05950611b7b355917e4fb6daa73fd3d7590764fa53b" - XCTAssertEqual(expectedDigestString, CryptoHashing.sha256(str: stringToHash, salt: "")) + XCTAssertEqual(expectedDigestString, CryptoHashing.sha256(string: stringToHash, salt: "")) XCTAssertEqual(expectedDigestString, CryptoHashing.commonCryptoSha256(strData: dataToHash)) // even though we already test if we can import CryptoKit, somehow this still fails on iOS 12, @@ -31,9 +31,9 @@ final class CryptoHashingTests: XCTestCase { let secondSalt = "x21MTSq3MRSmLjVFsYIe" let expectedSecondDigestString = "acb027bb031c0f73de26c6b8d0441d9c98449d582a538014c44ca49b4c299aa8" - XCTAssertEqual(expectedDigestString, CryptoHashing.sha256(str: stringToHash, salt: salt)) - XCTAssertEqual(expectedSecondDigestString, CryptoHashing.sha256(str: stringToHash, salt: secondSalt)) - XCTAssertNotEqual(CryptoHashing.sha256(str: stringToHash, salt: salt), CryptoHashing.sha256(str: stringToHash, salt: secondSalt)) + XCTAssertEqual(expectedDigestString, CryptoHashing.sha256(string: stringToHash, salt: salt)) + XCTAssertEqual(expectedSecondDigestString, CryptoHashing.sha256(string: stringToHash, salt: secondSalt)) + XCTAssertNotEqual(CryptoHashing.sha256(string: stringToHash, salt: salt), CryptoHashing.sha256(string: stringToHash, salt: secondSalt)) } #endif } From 3a45e618a7a541b7649131f078a31f91a4db614f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Thu, 23 May 2024 18:46:36 +0200 Subject: [PATCH 16/16] Update device destinations on CI builds + add Vision Pro --- .github/workflows/ci.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d16c095..d6fb200 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,10 +27,11 @@ jobs: xcode: - ^15 xcodebuildCommand: - - "-sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14'" + - "-sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15'" - "-sdk macosx -destination 'platform=macOS'" - - "-sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV 4K (2nd generation)'" - - "-sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch Series 8 (45mm)'" + - "-sdk xrsimulator -destination 'platform=visionOS Simulator,name=Apple Vision Pro'" + - "-sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation)'" + - "-sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch Series 9 (45mm)'" steps: - name: Repository checkout uses: actions/checkout@v4