From 237ac38557d5b0de6288896a82a968c4561f9080 Mon Sep 17 00:00:00 2001 From: Schwifty Date: Sat, 21 Mar 2026 10:02:58 +0000 Subject: [PATCH] refactor: consolidate UUID formatting into shared String extension Created UUIDFormatting.swift with .normalizedUUID and .uuidWithDashes String extensions, replacing 4 duplicate formatUuidWithDashes() methods and 6+ inline .replacingOccurrences(of: "-", with: "").uppercased() calls across Api.swift, BeaconScanner.swift, ScanView.swift, ServicePointListView.swift, BeaconBanList.swift, and BeaconProvisioner.swift. Co-Authored-By: Claude Opus 4.6 (1M context) --- PayfritBeacon/Api.swift | 10 ++++----- PayfritBeacon/BeaconBanList.swift | 17 +++++---------- PayfritBeacon/BeaconProvisioner.swift | 2 +- PayfritBeacon/BeaconScanner.swift | 11 ++-------- PayfritBeacon/ScanView.swift | 13 ++++-------- PayfritBeacon/ServicePointListView.swift | 13 ++++-------- PayfritBeacon/UUIDFormatting.swift | 27 ++++++++++++++++++++++++ 7 files changed, 48 insertions(+), 45 deletions(-) create mode 100644 PayfritBeacon/UUIDFormatting.swift diff --git a/PayfritBeacon/Api.swift b/PayfritBeacon/Api.swift index b924ad1..41c6fb2 100644 --- a/PayfritBeacon/Api.swift +++ b/PayfritBeacon/Api.swift @@ -155,7 +155,7 @@ class Api { var result: [String: Int] = [:] for item in items { guard let uuid = ((item["UUID"] ?? item["uuid"] ?? item["BeaconUUID"] ?? item["BEACONUUID"]) as? String)? - .replacingOccurrences(of: "-", with: "").uppercased(), + .normalizedUUID, let beaconId = parseIntValue(item["BeaconID"] ?? item["BEACONID"]) else { continue } @@ -175,7 +175,7 @@ class Api { return beacons.compactMap { b in guard let uuid = ((b["UUID"] ?? b["uuid"]) as? String)? - .replacingOccurrences(of: "-", with: "").uppercased() else { + .normalizedUUID else { return nil } return BeaconLookupResult( @@ -204,7 +204,7 @@ class Api { let beaconId = parseIntValue(b["BeaconID"] ?? b["BEACONID"] ?? b["ID"]) ?? 0 let name = ((b["Name"] ?? b["NAME"] ?? b["BeaconName"] ?? b["BEACONNAME"]) as? String) ?? "" let uuid = ((b["UUID"] ?? b["uuid"]) as? String)? - .replacingOccurrences(of: "-", with: "").uppercased() ?? "" + .normalizedUUID ?? "" let isActive = parseBool(b["IsActive"] ?? b["ISACTIVE"] ?? true) return BeaconInfo(beaconId: beaconId, name: name, uuid: uuid, isActive: isActive) } @@ -322,7 +322,7 @@ class Api { DebugLog.shared.log("[API] getBeaconConfig parsed: uuid=\(uuid) major=\(major) minor=\(minor) measuredPower=\(measuredPower) advInterval=\(advInterval) txPower=\(txPower)") return BeaconConfigResponse( - uuid: uuid.replacingOccurrences(of: "-", with: "").uppercased(), + uuid: uuid.normalizedUUID, major: UInt16(major), minor: UInt16(minor), measuredPower: Int8(clamping: measuredPower), @@ -430,7 +430,7 @@ class Api { return BusinessNamespace( shardId: parseIntValue(json["ShardID"] ?? json["SHARDID"]) ?? 0, uuid: uuid, - uuidClean: uuid.replacingOccurrences(of: "-", with: "").uppercased(), + uuidClean: uuid.normalizedUUID, major: UInt16(major), alreadyAllocated: parseBool(json["AlreadyAllocated"] ?? json["ALREADYALLOCATED"]) ) diff --git a/PayfritBeacon/BeaconBanList.swift b/PayfritBeacon/BeaconBanList.swift index e72a7f3..facfec6 100644 --- a/PayfritBeacon/BeaconBanList.swift +++ b/PayfritBeacon/BeaconBanList.swift @@ -38,7 +38,7 @@ enum BeaconBanList { /// Check if a UUID is on the ban list. static func isBanned(_ uuid: String) -> Bool { - let normalized = uuid.replacingOccurrences(of: "-", with: "").uppercased() + let normalized = uuid.normalizedUUID // Check full UUID match if BANNED_FULL_UUIDS[normalized] != nil { return true } @@ -52,7 +52,7 @@ enum BeaconBanList { /// Get the reason a UUID is banned, or nil if not banned. static func getBanReason(_ uuid: String) -> String? { - let normalized = uuid.replacingOccurrences(of: "-", with: "").uppercased() + let normalized = uuid.normalizedUUID // Check full UUID match first if let reason = BANNED_FULL_UUIDS[normalized] { return reason } @@ -64,16 +64,9 @@ enum BeaconBanList { return nil } - /// Format a raw UUID string (32 hex chars) into standard UUID format with dashes. + /// Format a raw UUID string into standard UUID format with dashes. + /// Delegates to String.uuidWithDashes (UUIDFormatting.swift). static func formatUuid(_ uuid: String) -> String { - let hex = uuid.replacingOccurrences(of: "-", with: "").uppercased() - guard hex.count == 32 else { return uuid } - let s = hex - let i0 = s.startIndex - let i8 = s.index(i0, offsetBy: 8) - let i12 = s.index(i0, offsetBy: 12) - let i16 = s.index(i0, offsetBy: 16) - let i20 = s.index(i0, offsetBy: 20) - return "\(s[i0.. Data? { - let clean = hex.replacingOccurrences(of: "-", with: "").uppercased() + let clean = hex.normalizedUUID guard clean.count == 32 else { return nil } var data = Data() diff --git a/PayfritBeacon/BeaconScanner.swift b/PayfritBeacon/BeaconScanner.swift index f74155e..83ac47c 100644 --- a/PayfritBeacon/BeaconScanner.swift +++ b/PayfritBeacon/BeaconScanner.swift @@ -121,7 +121,7 @@ class BeaconScanner: NSObject, ObservableObject, CLLocationManagerDelegate { let rssiValue = beacon.rssi guard rssiValue >= BeaconScanner.MIN_RSSI && rssiValue < 0 else { continue } - let uuid = beacon.uuid.uuidString.replacingOccurrences(of: "-", with: "").uppercased() + let uuid = beacon.uuid.uuidString.normalizedUUID let major = beacon.major.uint16Value let minor = beacon.minor.uint16Value let key = "\(uuid)|\(major)|\(minor)" @@ -148,13 +148,6 @@ struct DetectedBeacon { /// Format for clipboard (for pasting into manufacturer beacon config apps) func copyableConfig() -> String { // Format UUID with dashes for standard display - let formattedUuid = formatUuidWithDashes(uuid) - return "UUID: \(formattedUuid)\nMajor: \(major)\nMinor: \(minor)" - } - - private func formatUuidWithDashes(_ raw: String) -> String { - guard raw.count == 32 else { return raw } - let chars = Array(raw) - return "\(String(chars[0..<8]))-\(String(chars[8..<12]))-\(String(chars[12..<16]))-\(String(chars[16..<20]))-\(String(chars[20..<32]))" + return "UUID: \(uuid.uuidWithDashes)\nMajor: \(major)\nMinor: \(minor)" } } diff --git a/PayfritBeacon/ScanView.swift b/PayfritBeacon/ScanView.swift index b551461..209cc84 100644 --- a/PayfritBeacon/ScanView.swift +++ b/PayfritBeacon/ScanView.swift @@ -808,7 +808,7 @@ struct ScanView: View { do { // Format UUID with dashes for API call - let uuidWithDashes = formatUuidWithDashes(beacon.uuid) + let uuidWithDashes = beacon.uuid.uuidWithDashes let result = try await Api.shared.resolveBusiness(uuid: uuidWithDashes, major: beacon.major) await MainActor.run { beaconOwnership[key] = (businessId: result.businessId, businessName: result.businessName) @@ -895,7 +895,7 @@ struct ScanView: View { case .success(let macAddress): do { // Use MAC address as hardware ID, fallback to iBeacon UUID if unavailable - let uuidWithDashes = formatUuidWithDashes(config.uuid) + let uuidWithDashes = config.uuid.uuidWithDashes let hardwareId = macAddress ?? uuidWithDashes DebugLog.shared.log("[ScanView] Registering beacon - MAC: \(macAddress ?? "nil"), hardwareId: \(hardwareId), uuid: \(uuidWithDashes)") try await Api.shared.registerBeaconHardware( @@ -978,7 +978,7 @@ struct ScanView: View { // Register in backend (use UUID with dashes for API) do { // Use MAC address as hardware ID, fallback to iBeacon UUID if unavailable - let uuidWithDashes = formatUuidWithDashes(config.uuid) + let uuidWithDashes = config.uuid.uuidWithDashes let hardwareId = macAddress ?? uuidWithDashes DebugLog.shared.log("[ScanView] Registering beacon - MAC: \(macAddress ?? "nil"), hardwareId: \(hardwareId), uuid: \(uuidWithDashes)") try await Api.shared.registerBeaconHardware( @@ -1039,10 +1039,5 @@ struct ScanView: View { provisioningError = error } - private func formatUuidWithDashes(_ raw: String) -> String { - let clean = raw.replacingOccurrences(of: "-", with: "").uppercased() - guard clean.count == 32 else { return raw } - let chars = Array(clean) - return "\(String(chars[0..<8]))-\(String(chars[8..<12]))-\(String(chars[12..<16]))-\(String(chars[16..<20]))-\(String(chars[20..<32]))" - } + // UUID formatting now handled by String.uuidWithDashes (UUIDFormatting.swift) } diff --git a/PayfritBeacon/ServicePointListView.swift b/PayfritBeacon/ServicePointListView.swift index 4346083..d70f663 100644 --- a/PayfritBeacon/ServicePointListView.swift +++ b/PayfritBeacon/ServicePointListView.swift @@ -58,10 +58,10 @@ struct ServicePointListView: View { .font(.caption) .foregroundColor(.secondary) Spacer() - Text(formatUuidWithDashes(ns.uuid)) + Text(ns.uuid.uuidWithDashes) .font(.system(.caption, design: .monospaced)) Button { - UIPasteboard.general.string = formatUuidWithDashes(ns.uuid) + UIPasteboard.general.string = ns.uuid.uuidWithDashes } label: { Image(systemName: "doc.on.doc") .font(.caption) @@ -217,7 +217,7 @@ struct ServicePointListView: View { HStack { VStack(alignment: .leading) { Text("UUID").font(.caption2).foregroundColor(.secondary) - Text(formatUuidWithDashes(ns.uuid)) + Text(ns.uuid.uuidWithDashes) .font(.system(.caption2, design: .monospaced)) } } @@ -333,11 +333,6 @@ struct ServicePointListView: View { return maxNumber + 1 } - private func formatUuidWithDashes(_ raw: String) -> String { - let clean = raw.replacingOccurrences(of: "-", with: "").uppercased() - guard clean.count == 32 else { return raw } - let chars = Array(clean) - return "\(String(chars[0..<8]))-\(String(chars[8..<12]))-\(String(chars[12..<16]))-\(String(chars[16..<20]))-\(String(chars[20..<32]))" - } + // UUID formatting now handled by String.uuidWithDashes (UUIDFormatting.swift) } diff --git a/PayfritBeacon/UUIDFormatting.swift b/PayfritBeacon/UUIDFormatting.swift new file mode 100644 index 0000000..27d141d --- /dev/null +++ b/PayfritBeacon/UUIDFormatting.swift @@ -0,0 +1,27 @@ +import Foundation + +// MARK: - UUID Formatting Utilities +// Consolidated UUID normalization and formatting logic. +// Previously duplicated across Api.swift, BeaconScanner.swift, ScanView.swift, +// ServicePointListView.swift, and BeaconBanList.swift. + +extension String { + + /// Normalize a UUID string: strip dashes, uppercase. + /// Input: "e2c56db5-dffb-48d2-b060-d0f5a71096e0" or "E2C56DB5DFFB48D2B060D0F5A71096E0" + /// Output: "E2C56DB5DFFB48D2B060D0F5A71096E0" + var normalizedUUID: String { + replacingOccurrences(of: "-", with: "").uppercased() + } + + /// Format a 32-char hex string into standard UUID format (8-4-4-4-12). + /// Input: "E2C56DB5DFFB48D2B060D0F5A71096E0" + /// Output: "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0" + /// Returns the original string unchanged if it's not exactly 32 hex chars after normalization. + var uuidWithDashes: String { + let clean = normalizedUUID + guard clean.count == 32 else { return self } + let c = Array(clean) + return "\(String(c[0..<8]))-\(String(c[8..<12]))-\(String(c[12..<16]))-\(String(c[16..<20]))-\(String(c[20..<32]))" + } +} -- 2.43.0