diff --git a/Sources/TelemetryDeck/Helpers/DictionaryExt.swift b/Sources/TelemetryDeck/Helpers/DictionaryExt.swift new file mode 100644 index 0000000..03f0712 --- /dev/null +++ b/Sources/TelemetryDeck/Helpers/DictionaryExt.swift @@ -0,0 +1,27 @@ +import Foundation + +// Source: https://github.com/FlineDev/HandySwift/blob/main/Sources/HandySwift/Extensions/DictionaryExt.swift + +extension Dictionary { + /// Transforms the keys of the dictionary using the given closure, returning a new dictionary with the transformed keys. + /// + /// - Parameter transform: A closure that takes a key from the dictionary as its argument and returns a new key. + /// - Returns: A dictionary with keys transformed by the `transform` closure and the same values as the original dictionary. + /// - Throws: Rethrows any error thrown by the `transform` closure. + /// + /// - Warning: If the `transform` closure produces duplicate keys, the values of earlier keys will be overridden by the values of later keys in the resulting dictionary. + /// + /// - Example: + /// ``` + /// let originalDict = ["one": 1, "two": 2, "three": 3] + /// let transformedDict = originalDict.mapKeys { $0.uppercased() } + /// // transformedDict will be ["ONE": 1, "TWO": 2, "THREE": 3] + /// ``` + func mapKeys(_ transform: (Key) throws -> K) rethrows -> [K: Value] { + var transformedDict: [K: Value] = [:] + for (key, value) in self { + transformedDict[try transform(key)] = value + } + return transformedDict + } +} diff --git a/Sources/TelemetryDeck/TelemetryClient.swift b/Sources/TelemetryDeck/TelemetryClient.swift index 89a7fe1..78493a8 100644 --- a/Sources/TelemetryDeck/TelemetryClient.swift +++ b/Sources/TelemetryDeck/TelemetryClient.swift @@ -41,6 +41,20 @@ public struct TelemetryManagerConfiguration: Sendable { /// Instead it is used to create a hash, which is included in your signal to allow you to count distinct users. public var defaultUser: String? + /// Specify this if you want us to prefix all your signals with a specific text. + /// For example, if you are already adding `AppName.` in front of every signal, just specify it here and no need to repeat over and over again. + public var defaultSignalPrefix: String? + + /// Specify this if you want us to prefix all your signal parameters with a specific text. + /// For example, if you are already adding `AppName.` in front of every parameter name, just specify it here and no need to repeat over and over again. + public var defaultParameterPrefix: String? + + /// Specify this if you want to attach some parameters globally to all signals you're sending. + /// For example, this could be useful to report your users' paid status or their user preferences to be able to filter by these fields in various insights. + /// + /// - NOTE: If you are using ``defaultParameterPrefix``, note that it applies even here, so no need to add your prefix in all the default parameters. + public var defaultParameters: @Sendable () -> [String: String] = { [:] } + /// If `true`, sends a "newSessionBegan" Signal on each app foreground or cold launch /// /// Defaults to true. Set to false to prevent automatically sending this signal. diff --git a/Sources/TelemetryDeck/TelemetryDeck.swift b/Sources/TelemetryDeck/TelemetryDeck.swift index 61ef54f..75a1506 100644 --- a/Sources/TelemetryDeck/TelemetryDeck.swift +++ b/Sources/TelemetryDeck/TelemetryDeck.swift @@ -36,9 +36,14 @@ public enum TelemetryDeck { // make sure to not send any signals when run by Xcode via SwiftUI previews guard !configuration.swiftUIPreviewMode, !configuration.analyticsDisabled else { return } + let combinedSignalName = (configuration.defaultSignalPrefix ?? "") + signalName + let combinedParameters = configuration.defaultParameters() + .merging(parameters) { $1 } + .mapKeys { (configuration.defaultParameterPrefix ?? "") + $0 } + manager.signalManager.processSignal( - signalName, - parameters: parameters, + combinedSignalName, + parameters: combinedParameters, floatValue: floatValue, customUserID: customUserID, configuration: configuration