feat: added telemetry module

This commit is contained in:
Serhiy Mytrovtsiy
2023-06-23 17:23:46 +02:00
parent 55f14febb8
commit 7e90f65577
5 changed files with 178 additions and 2 deletions

View File

@@ -0,0 +1,93 @@
//
// Telemetry.swift
// Kit
//
// Created by Serhiy Mytrovtsiy on 18/06/2023
// Using Swift 5.0
// Running on macOS 13.4
//
// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved.
//
import Foundation
private struct Report: Codable {
let deviceID: UUID
let modules: [String]
let language: String?
let device: String?
let macOS: String?
let version: String?
}
public class Telemetry {
public var isEnabled: Bool {
get {
self._isEnabled
}
set {
self.toggle(newValue)
}
}
private var url: URL = URL(string: "https://api.serhiy.io/v1/stats/telemetry")!
private var _isEnabled: Bool = true
private let id: UUID
private let repeater = NSBackgroundActivityScheduler(identifier: "eu.exelban.Stats.Telemetry")
private var modules: UnsafePointer<[Module]>
public init(_ modules: UnsafePointer<[Module]>) {
self._isEnabled = Store.shared.bool(key: "telemetry", defaultValue: true)
self.id = UUID(uuidString: Store.shared.string(key: "telemetry_id", defaultValue: UUID().uuidString)) ?? UUID()
self.modules = modules
if !Store.shared.exist(key: "telemetry_id") {
Store.shared.set(key: "telemetry_id", value: self.id.uuidString)
self.toggle(self.isEnabled)
}
self.report()
}
@objc public func report() {
guard self.isEnabled else { return }
let obj: Report = Report(
deviceID: self.id,
modules: self.modules.pointee.filter({ $0.available && $0.enabled }).compactMap({ $0.name }),
language: Locale.current.languageCode,
device: SystemKit.shared.device.model.id,
macOS: SystemKit.shared.device.os?.version.getFullVersion(),
version: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
)
let jsonData = try? JSONEncoder().encode(obj)
var request = URLRequest(url: self.url)
request.httpMethod = "POST"
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request)
task.resume()
}
private func toggle(_ newValue: Bool) {
self._isEnabled = newValue
Store.shared.set(key: "telemetry", value: newValue)
self.repeater.invalidate()
if newValue {
self.repeater.repeats = true
self.repeater.interval = 60 * 60 * 24
self.repeater.schedule { (completion: @escaping NSBackgroundActivityScheduler.CompletionHandler) in
self.report()
completion(NSBackgroundActivityScheduler.Result.finished)
}
}
}
}

View File

@@ -23,6 +23,7 @@
5C23BC0A29A0EDA300DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0929A0EDA300DBA990 /* portal.swift */; };
5C23BC0C29A10BE000DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0B29A10BE000DBA990 /* portal.swift */; };
5C23BC1029A3B5AE00DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0F29A3B5AE00DBA990 /* portal.swift */; };
5C5647F82A3F6B100098FFE9 /* Telemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5647F72A3F6B100098FFE9 /* Telemetry.swift */; };
5C8E001029269C7F0027C75A /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; };
5CFE492A29264DF1000F2856 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE492929264DF1000F2856 /* main.swift */; };
5CFE493929265055000F2856 /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; };
@@ -390,6 +391,7 @@
5C23BC0929A0EDA300DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = "<group>"; };
5C23BC0B29A10BE000DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = "<group>"; };
5C23BC0F29A3B5AE00DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = "<group>"; };
5C5647F72A3F6B100098FFE9 /* Telemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Telemetry.swift; sourceTree = "<group>"; };
5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = eu.exelban.Stats.SMC.Helper; sourceTree = BUILT_PRODUCTS_DIR; };
5CFE492929264DF1000F2856 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
5CFE493829265055000F2856 /* protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = protocol.swift; sourceTree = "<group>"; };
@@ -935,6 +937,7 @@
9A6EEBBD2685259500897371 /* Logger.swift */,
9A5A8446271895B700BC40A4 /* Reachability.swift */,
9A302613286A2A3B00B41D57 /* Repeater.swift */,
5C5647F72A3F6B100098FFE9 /* Telemetry.swift */,
);
path = plugins;
sourceTree = "<group>";
@@ -1726,6 +1729,7 @@
9A2847612666AA2700EC1F6D /* PieChart.swift in Sources */,
9A2847672666AA2700EC1F6D /* BarChart.swift in Sources */,
9A28477B2666AA5000EC1F6D /* popup.swift in Sources */,
5C5647F82A3F6B100098FFE9 /* Telemetry.swift in Sources */,
9A2848202666AB3600EC1F6D /* types.swift in Sources */,
9A28481E2666AB3600EC1F6D /* extensions.swift in Sources */,
9A2848092666AB3000EC1F6D /* Store.swift in Sources */,

View File

@@ -33,6 +33,7 @@ var modules: [Module] = [
Bluetooth(),
Clock()
]
let telemetry: Telemetry = Telemetry(&modules)
@main
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate {

View File

@@ -49,6 +49,7 @@ class ApplicationSettings: NSStackView {
private var startAtLoginBtn: NSButton?
private var uninstallHelperButton: NSButton?
private var buttonsContainer: NSStackView?
private var telemetryBtn: NSButton?
private var combinedModules: NSView?
private var combinedModulesSeparator: NSView?
@@ -88,6 +89,7 @@ class ApplicationSettings: NSStackView {
public func viewWillAppear() {
self.startAtLoginBtn?.state = LaunchAtLogin.isEnabled ? .on : .off
self.telemetryBtn?.state = telemetry.isEnabled ? .on : .off
var idx = self.updateSelector?.indexOfSelectedItem ?? 0
if let items = self.updateSelector?.menu?.items {
@@ -202,6 +204,14 @@ class ApplicationSettings: NSStackView {
text: localizedString("Start at login")
)
grid.addRow(with: [NSGridCell.emptyContentView, self.startAtLoginBtn!])
self.telemetryBtn = self.toggleView(
action: #selector(self.toggleTelemetry),
state: telemetry.isEnabled,
text: localizedString("Share anonymous telemetry")
)
grid.addRow(with: [NSGridCell.emptyContentView, self.telemetryBtn!])
grid.addRow(with: [NSGridCell.emptyContentView, self.toggleView(
action: #selector(self.toggleCombinedModules),
state: self.combinedModulesState,
@@ -417,6 +427,10 @@ class ApplicationSettings: NSStackView {
self.combinedModulesSpacing = key
NotificationCenter.default.post(name: .moduleRearrange, object: nil, userInfo: nil)
}
@objc private func toggleTelemetry(_ sender: NSButton) {
telemetry.isEnabled = sender.state == NSControl.StateValue.on
}
}
private class ModuleSelectorView: NSStackView {
@@ -550,7 +564,7 @@ internal class ModulePreview: NSStackView {
self.wantsLayer = true
self.layer?.cornerRadius = 2
self.layer?.borderColor = NSColor(hexString: "#dddddd").cgColor
self.layer?.borderColor = NSColor(red: 221/255, green: 221/255, blue: 221/255, alpha: 1).cgColor
self.layer?.borderWidth = 1
self.layer?.backgroundColor = NSColor.white.cgColor

View File

@@ -71,7 +71,7 @@ internal class SetupWindow: NSWindow, NSWindowDelegate {
}
private class SetupContainer: NSStackView {
private let pages: [NSView] = [SetupView_1(), SetupView_2(), SetupView_3(), SetupView_4()]
private let pages: [NSView] = [SetupView_1(), SetupView_2(), SetupView_3(), SetupView_4(), SetupView_end()]
private var main: NSView = NSView()
private var prevBtn: NSButton = NSButton()
@@ -373,6 +373,70 @@ private class SetupView_3: NSStackView {
}
private class SetupView_4: NSStackView {
init() {
super.init(frame: NSRect(x: 0, y: 0, width: setupSize.width, height: setupSize.height - 60))
let container: NSGridView = NSGridView()
container.rowSpacing = 0
container.yPlacement = .center
container.xPlacement = .center
let title: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: container.frame.width, height: 22))
title.alignment = .center
title.font = NSFont.systemFont(ofSize: 20, weight: .semibold)
title.stringValue = localizedString("Share anonymous telemetry for better development decisions")
title.toolTip = localizedString("Share anonymous telemetry for better development decisions")
title.isSelectable = false
container.addRow(with: [title])
container.addRow(with: [self.content()])
container.row(at: 0).height = 100
self.addArrangedSubview(container)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func content() -> NSView {
let container: NSGridView = NSGridView()
container.addRow(with: [self.option(
tag: 1,
state: telemetry.isEnabled,
text: localizedString("Share anonymous telemetry data")
)])
container.addRow(with: [self.option(
tag: 2,
state: !telemetry.isEnabled,
text: localizedString("Do not share anonymous telemetry data")
)])
return container
}
private func option(tag: Int, state: Bool, text: String) -> NSView {
let button: NSButton = NSButton(frame: NSRect(x: 0, y: 0, width: 30, height: 20))
button.setButtonType(.radio)
button.state = state ? .on : .off
button.title = text
button.action = #selector(self.toggle)
button.isBordered = false
button.isTransparent = false
button.target = self
button.tag = tag
return button
}
@objc private func toggle(_ sender: NSButton) {
telemetry.isEnabled = sender.tag == 1
}
}
private class SetupView_end: NSStackView {
init() {
super.init(frame: NSRect(x: 0, y: 0, width: setupSize.width, height: setupSize.height - 60))