-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathSignal.swift
More file actions
288 lines (254 loc) · 9.49 KB
/
Signal.swift
File metadata and controls
288 lines (254 loc) · 9.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import Foundation
#if os(iOS)
import UIKit
#elseif os(macOS)
import AppKit
import IOKit
#elseif os(watchOS)
import WatchKit
#elseif os(tvOS)
import TVUIKit
#endif
internal struct SignalPostBody: Codable, Equatable {
/// When was this signal generated
let receivedAt: Date
/// The App ID of this signal
let appID: UUID
/// A user identifier. This should be hashed on the client, and will be hashed + salted again
/// on the server to break any connection to personally identifiable data.
let clientUser: String
/// A randomly generated session identifier. Should be the same over the course of the session
let sessionID: String
/// A type name for this signal that describes the event that triggered the signal
let type: String
/// An optional numerical value to send along with the signal.
let floatValue: Double?
/// Tags in the form "key:value" to attach to the signal
let payload: [String]
/// If "true", mark the signal as a testing signal and only show it in a dedicated test mode UI
let isTestMode: String
}
/// 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 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 [:]
}
}
}
// MARK: - Helpers
extension DefaultSignalPayload {
static var isSimulatorOrTestFlight: Bool {
isSimulator || isTestFlight
}
static var isSimulator: Bool {
#if targetEnvironment(simulator)
return true
#else
return false
#endif
}
static var isDebug: Bool {
#if DEBUG
return true
#else
return false
#endif
}
static var isTestFlight: Bool {
guard !isDebug, let path = Bundle.main.appStoreReceiptURL?.path else {
return false
}
return path.contains("sandboxReceipt")
}
static var isAppStore: Bool {
#if DEBUG
return false
#elseif TARGET_OS_OSX || TARGET_OS_MACCATALYST
return false
#elseif targetEnvironment(simulator)
return false
#else
return !isSimulatorOrTestFlight
#endif
}
/// The operating system and its version
static var systemVersion: String {
let majorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
let minorVersion = ProcessInfo.processInfo.operatingSystemVersion.minorVersion
let patchVersion = ProcessInfo.processInfo.operatingSystemVersion.patchVersion
return "\(platform) \(majorVersion).\(minorVersion).\(patchVersion)"
}
/// The major system version, i.e. iOS 15
static var majorSystemVersion: String {
return "\(platform) \(ProcessInfo.processInfo.operatingSystemVersion.majorVersion)"
}
/// The major system version, i.e. iOS 15
static var majorMinorSystemVersion: String {
let majorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
let minorVersion = ProcessInfo.processInfo.operatingSystemVersion.minorVersion
return "\(platform) \(majorVersion).\(minorVersion)"
}
/// The Bundle Short Version String, as described in Info.plist
static var appVersion: String {
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
return appVersion ?? "0"
}
/// The Bundle Version String, as described in Info.plist
static var buildNumber: String {
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
return buildNumber ?? "0"
}
/// The extension identifer for the active resource, if available.
///
/// This provides a value such as `com.apple.widgetkit-extension` when TelemetryDeck is run from a widget.
static var extensionIdentifier: String? {
let container = Bundle.main.infoDictionary?["NSExtension"] as? [String: Any]
return container?["NSExtensionPointIdentifier"] as? String
}
/// The modelname as reported by systemInfo.machine
static var modelName: String {
#if os(iOS)
if #available(iOS 14.0, *) {
if ProcessInfo.processInfo.isiOSAppOnMac {
var size = 0
sysctlbyname("hw.model", nil, &size, nil, 0)
var machine = [CChar](repeating: 0, count: size)
sysctlbyname("hw.model", &machine, &size, nil, 0)
return String(cString: machine)
}
}
#endif
#if os(macOS)
if #available(macOS 11, *) {
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
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) {
modelIdentifier = String(cString: modelIdentifierCString)
}
}
IOObjectRelease(service)
if let modelIdentifier = modelIdentifier {
return modelIdentifier
}
}
#endif
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}
/// The build architecture
static var architecture: String {
#if arch(x86_64)
return "x86_64"
#elseif arch(arm)
return "arm"
#elseif arch(arm64)
return "arm64"
#elseif arch(i386)
return "i386"
#elseif arch(powerpc64)
return "powerpc64"
#elseif arch(powerpc64le)
return "powerpc64le"
#elseif arch(s390x)
return "s390x"
#else
return "unknown"
#endif
}
/// The operating system as reported by Swift. Note that this will report catalyst apps and iOS apps running on
/// macOS as "iOS". See `platform` for an alternative.
static var operatingSystem: String {
#if os(macOS)
return "macOS"
#elseif os(xrOS)
return "visionOS"
#elseif os(iOS)
if UIDevice.current.userInterfaceIdiom == .pad {
return = "iPadOS"
}
else {
return "iOS"
}
#elseif os(watchOS)
return "watchOS"
#elseif os(tvOS)
return "tvOS"
#else
return "Unknown Operating System"
#endif
}
/// Based on the operating version reported by swift, but adding some smartness to better detect the actual
/// platform. Should correctly identify catalyst apps and iOS apps on macOS.
static var platform: String {
#if os(macOS)
return "macOS"
#elseif os(xrOS)
return "visionOS"
#elseif os(iOS)
#if targetEnvironment(macCatalyst)
return "macCatalyst"
#else
if #available(iOS 14.0, *), ProcessInfo.processInfo.isiOSAppOnMac {
return "isiOSAppOnMac"
}
if UIDevice.current.userInterfaceIdiom == .pad {
return = "iPadOS"
}
else {
return "iOS"
}
#endif
#elseif os(watchOS)
return "watchOS"
#elseif os(tvOS)
return "tvOS"
#else
return "Unknown Platform"
#endif
}
/// The target environment as reported by swift. Either "simulator", "macCatalyst" or "native"
static var targetEnvironment: String {
#if targetEnvironment(simulator)
return "simulator"
#elseif targetEnvironment(macCatalyst)
return "macCatalyst"
#else
return "native"
#endif
}
/// The locale identifier
static var locale: String {
return Locale.current.identifier
}
}