grammargem/Sources/GrammarGem/Licensing/DeviceFingerprint.swift
BigMarc 3d749e8139 feat: Implement LiveMonitor for real-time grammar and spelling checks across applications
- Added LiveMonitor class to scan text areas in the frontmost window for grammar and spelling issues.
- Introduced DetectedField struct to represent text areas with issues, including suggestions for corrections.
- Implemented functionality to apply corrections and dismiss suggestions.
- Added Permissions class to manage Accessibility permissions required for monitoring.
- Created SnippetStore for managing text-expansion snippets, including persistence to UserDefaults.
- Developed TextCapture class to read and write text from the frontmost application using Accessibility API and clipboard fallback.
- Implemented TextFieldScanner to traverse the Accessibility tree and collect editable text elements.
- Added TextReplacementCoordinator to orchestrate the text correction process.
- Integrated UpdaterManager for handling application updates using Sparkle.
- Created comprehensive unit tests for various components, including grammar corrections and entitlement checks.
2026-06-18 16:49:55 +02:00

48 lines
1.8 KiB
Swift

import Foundation
import IOKit
import CryptoKit
/// Derives a stable, privacy-preserving device identifier.
///
/// Lemon Squeezy has no hardware fingerprinting of its own, so we build one
/// (spec §6): take the hardware `IOPlatformUUID`, SHA-256 it, and pass the hash
/// as the `instance_name` on activation. Same Mac → same instance → a reinstall
/// doesn't consume a second device slot. The raw UUID never leaves the device;
/// only its hash is sent.
enum DeviceFingerprint {
/// SHA-256 of the hardware platform UUID, hex-encoded. Falls back to a
/// persisted random ID if IOKit is somehow unavailable.
static func stableID() -> String {
if let uuid = platformUUID() {
let digest = SHA256.hash(data: Data(uuid.utf8))
return digest.map { String(format: "%02x", $0) }.joined()
}
return fallbackID()
}
private static func platformUUID() -> String? {
let service = IOServiceGetMatchingService(
kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
guard service != 0 else { return nil }
defer { IOObjectRelease(service) }
guard let cf = IORegistryEntryCreateCFProperty(
service, kIOPlatformUUIDKey as CFString, kCFAllocatorDefault, 0)
else { return nil }
return cf.takeRetainedValue() as? String
}
/// A friendly, human-readable name for this Mac (for the Devices screen).
static func deviceName() -> String {
Host.current().localizedName ?? ProcessInfo.processInfo.hostName
}
private static func fallbackID() -> String {
let key = "GrammaGem.fallbackDeviceID"
if let existing = UserDefaults.standard.string(forKey: key) { return existing }
let id = UUID().uuidString
UserDefaults.standard.set(id, forKey: key)
return id
}
}