| import Foundation |
|
|
| private struct StoredPushRelayRegistrationState: Codable { |
| var relayHandle: String |
| var sendGrant: String |
| var relayOrigin: String? |
| var gatewayDeviceId: String |
| var relayHandleExpiresAtMs: Int64? |
| var tokenDebugSuffix: String? |
| var lastAPNsTokenHashHex: String |
| var installationId: String |
| var lastTransport: String |
| } |
|
|
| enum PushRelayRegistrationStore { |
| private static let service = "ai.openclaw.pushrelay" |
| private static let registrationStateAccount = "registration-state" |
| private static let appAttestKeyIDAccount = "app-attest-key-id" |
| private static let appAttestedKeyIDAccount = "app-attested-key-id" |
|
|
| struct RegistrationState: Codable { |
| var relayHandle: String |
| var sendGrant: String |
| var relayOrigin: String? |
| var gatewayDeviceId: String |
| var relayHandleExpiresAtMs: Int64? |
| var tokenDebugSuffix: String? |
| var lastAPNsTokenHashHex: String |
| var installationId: String |
| var lastTransport: String |
| } |
|
|
| static func loadRegistrationState() -> RegistrationState? { |
| guard let raw = KeychainStore.loadString( |
| service: self.service, |
| account: self.registrationStateAccount), |
| let data = raw.data(using: .utf8), |
| let decoded = try? JSONDecoder().decode(StoredPushRelayRegistrationState.self, from: data) |
| else { |
| return nil |
| } |
| return RegistrationState( |
| relayHandle: decoded.relayHandle, |
| sendGrant: decoded.sendGrant, |
| relayOrigin: decoded.relayOrigin, |
| gatewayDeviceId: decoded.gatewayDeviceId, |
| relayHandleExpiresAtMs: decoded.relayHandleExpiresAtMs, |
| tokenDebugSuffix: decoded.tokenDebugSuffix, |
| lastAPNsTokenHashHex: decoded.lastAPNsTokenHashHex, |
| installationId: decoded.installationId, |
| lastTransport: decoded.lastTransport) |
| } |
|
|
| @discardableResult |
| static func saveRegistrationState(_ state: RegistrationState) -> Bool { |
| let stored = StoredPushRelayRegistrationState( |
| relayHandle: state.relayHandle, |
| sendGrant: state.sendGrant, |
| relayOrigin: state.relayOrigin, |
| gatewayDeviceId: state.gatewayDeviceId, |
| relayHandleExpiresAtMs: state.relayHandleExpiresAtMs, |
| tokenDebugSuffix: state.tokenDebugSuffix, |
| lastAPNsTokenHashHex: state.lastAPNsTokenHashHex, |
| installationId: state.installationId, |
| lastTransport: state.lastTransport) |
| guard let data = try? JSONEncoder().encode(stored), |
| let raw = String(data: data, encoding: .utf8) |
| else { |
| return false |
| } |
| return KeychainStore.saveString(raw, service: self.service, account: self.registrationStateAccount) |
| } |
|
|
| @discardableResult |
| static func clearRegistrationState() -> Bool { |
| KeychainStore.delete(service: self.service, account: self.registrationStateAccount) |
| } |
|
|
| static func loadAppAttestKeyID() -> String? { |
| let value = KeychainStore.loadString(service: self.service, account: self.appAttestKeyIDAccount)? |
| .trimmingCharacters(in: .whitespacesAndNewlines) |
| if value?.isEmpty == false { return value } |
| return nil |
| } |
|
|
| @discardableResult |
| static func saveAppAttestKeyID(_ keyID: String) -> Bool { |
| KeychainStore.saveString(keyID, service: self.service, account: self.appAttestKeyIDAccount) |
| } |
|
|
| @discardableResult |
| static func clearAppAttestKeyID() -> Bool { |
| KeychainStore.delete(service: self.service, account: self.appAttestKeyIDAccount) |
| } |
|
|
| static func loadAttestedKeyID() -> String? { |
| let value = KeychainStore.loadString(service: self.service, account: self.appAttestedKeyIDAccount)? |
| .trimmingCharacters(in: .whitespacesAndNewlines) |
| if value?.isEmpty == false { return value } |
| return nil |
| } |
|
|
| @discardableResult |
| static func saveAttestedKeyID(_ keyID: String) -> Bool { |
| KeychainStore.saveString(keyID, service: self.service, account: self.appAttestedKeyIDAccount) |
| } |
|
|
| @discardableResult |
| static func clearAttestedKeyID() -> Bool { |
| KeychainStore.delete(service: self.service, account: self.appAttestedKeyIDAccount) |
| } |
| } |
|
|