96 lines
3.2 KiB
Swift
96 lines
3.2 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Network
|
|
import CoreTelephony
|
|
|
|
struct DeviceHealth: Sendable {
|
|
let batteryPercent: Int
|
|
let thermalState: ThermalState
|
|
let networkType: String
|
|
}
|
|
|
|
struct ClientInfoPayload: Encodable, Sendable {
|
|
let os: String
|
|
let appVersion: String?
|
|
let appBuild: String?
|
|
let deviceManufacturer: String?
|
|
let deviceModel: String?
|
|
let osVersion: String?
|
|
let carrier: String?
|
|
}
|
|
|
|
enum DeviceTelemetry {
|
|
static func snapshot(thermalState: ThermalState? = nil) -> DeviceHealth {
|
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
let level = Int((UIDevice.current.batteryLevel >= 0 ? UIDevice.current.batteryLevel : 1) * 100)
|
|
let thermal = thermalState ?? ThermalState.from(ProcessInfo.processInfo.thermalState)
|
|
return DeviceHealth(
|
|
batteryPercent: level,
|
|
thermalState: thermal,
|
|
networkType: currentNetworkType()
|
|
)
|
|
}
|
|
|
|
static func clientInfo() -> ClientInfoPayload {
|
|
let bundle = Bundle.main
|
|
let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
|
let build = bundle.object(forInfoDictionaryKey: "CFBundleVersion") as? String
|
|
let model = UIDevice.current.model
|
|
// Prefer machine identifier when available (e.g. iPhone15,2)
|
|
let machine = utsnameMachine()
|
|
return ClientInfoPayload(
|
|
os: "ios",
|
|
appVersion: version,
|
|
appBuild: build,
|
|
deviceManufacturer: "Apple",
|
|
deviceModel: machine ?? model,
|
|
osVersion: UIDevice.current.systemVersion,
|
|
carrier: carrierName()
|
|
)
|
|
}
|
|
|
|
private static func currentNetworkType() -> String {
|
|
let monitor = NWPathMonitor()
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
var result = L10n.t("network.type.unknown")
|
|
monitor.pathUpdateHandler = { path in
|
|
if path.usesInterfaceType(.wifi) {
|
|
result = L10n.t("network.type.wifi")
|
|
} else if path.usesInterfaceType(.cellular) {
|
|
result = L10n.t("network.type.cellular")
|
|
} else if path.usesInterfaceType(.wiredEthernet) {
|
|
result = L10n.t("network.type.ethernet")
|
|
}
|
|
semaphore.signal()
|
|
}
|
|
let queue = DispatchQueue(label: "network.type")
|
|
monitor.start(queue: queue)
|
|
_ = semaphore.wait(timeout: .now() + 0.5)
|
|
monitor.cancel()
|
|
return result
|
|
}
|
|
|
|
private static func carrierName() -> String? {
|
|
let info = CTTelephonyNetworkInfo()
|
|
if let providers = info.serviceSubscriberCellularProviders {
|
|
for carrier in providers.values {
|
|
if let name = carrier.carrierName?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
!name.isEmpty {
|
|
return name
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private static func utsnameMachine() -> String? {
|
|
var systemInfo = utsname()
|
|
uname(&systemInfo)
|
|
return withUnsafePointer(to: &systemInfo.machine) {
|
|
$0.withMemoryRebound(to: CChar.self, capacity: 1) {
|
|
String(validatingUTF8: $0)
|
|
}
|
|
}
|
|
}
|
|
}
|