grammargem/Sources/GrammaGem/Licensing/DeviceFingerprint.swift
BigMarc f5faa27f88 GrammaGem — open-source, on-device macOS writing assistant
Native SwiftUI menu-bar app: global hotkey → capture selected text (AX API /
clipboard fallback) → grammar (Harper) / local AI (MLX) → replace in place.
Free/paid entitlement gate + Lemon Squeezy device-cap licensing, $0 marginal
cost per user. Heavyweight engines behind protocol stubs so it builds out of the box.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:56:26 +02:00

43 lines
1.6 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
}
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
}
}