Skip to content

Commit

Permalink
Add notification rate limit sensor
Browse files Browse the repository at this point in the history
  • Loading branch information
bgoncal committed Dec 6, 2024
1 parent bd45612 commit 65d2b4a
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 99 deletions.
14 changes: 11 additions & 3 deletions HomeAssistant.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,6 @@
11C4628F24B128EF00031902 /* WebhookResponseUnhandled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C4628D24B128EF00031902 /* WebhookResponseUnhandled.swift */; };
11C4629124B14E6B00031902 /* XCGLogger+UNNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C4629024B14E6B00031902 /* XCGLogger+UNNotification.swift */; };
11C4629224B14E6B00031902 /* XCGLogger+UNNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C4629024B14E6B00031902 /* XCGLogger+UNNotification.swift */; };
11C4629424B189B100031902 /* NotificationRateLimitsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C4629324B189B100031902 /* NotificationRateLimitsAPI.swift */; };
11C4629624B19FC700031902 /* URLSessionTask+WebhookPersisted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C4629524B19FC700031902 /* URLSessionTask+WebhookPersisted.swift */; };
11C4629724B19FC800031902 /* URLSessionTask+WebhookPersisted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C4629524B19FC700031902 /* URLSessionTask+WebhookPersisted.swift */; };
11C590ED24A832CA0066085D /* YamlSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C590EC24A832CA0066085D /* YamlSection.swift */; };
Expand Down Expand Up @@ -580,6 +579,10 @@
422F951F2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F951E2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift */; };
4235075D2CDB756800A19902 /* HAServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4235075C2CDB756800A19902 /* HAServices.swift */; };
4235075E2CDB756800A19902 /* HAServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4235075C2CDB756800A19902 /* HAServices.swift */; };
4237CD302D0322F800424EF6 /* NotificationRateLimitSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4237CD2F2D0322F800424EF6 /* NotificationRateLimitSensor.swift */; };
4237CD312D0322F800424EF6 /* NotificationRateLimitSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4237CD2F2D0322F800424EF6 /* NotificationRateLimitSensor.swift */; };
4237CD322D03240100424EF6 /* NotificationRateLimitsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C4629324B189B100031902 /* NotificationRateLimitsAPI.swift */; };
4237CD332D03240100424EF6 /* NotificationRateLimitsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C4629324B189B100031902 /* NotificationRateLimitsAPI.swift */; };
4239D1832C4FFCCE003497FC /* WatchUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4239D1802C4FFB75003497FC /* WatchUserDefaults.swift */; };
423F44F02C17238200766A99 /* ChatBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423F44EF2C17238200766A99 /* ChatBubbleView.swift */; };
423F44FF2C186E4500766A99 /* WatchCommunicatorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423F44FE2C186E4500766A99 /* WatchCommunicatorService.swift */; };
Expand Down Expand Up @@ -1858,6 +1861,7 @@
422E626B2CDCF00A00987BD0 /* AreaProvider.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AreaProvider.test.swift; sourceTree = "<group>"; };
422F951E2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAApplicationShortcutItem.swift; sourceTree = "<group>"; };
4235075C2CDB756800A19902 /* HAServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAServices.swift; sourceTree = "<group>"; };
4237CD2F2D0322F800424EF6 /* NotificationRateLimitSensor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRateLimitSensor.swift; sourceTree = "<group>"; };
4239D1802C4FFB75003497FC /* WatchUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchUserDefaults.swift; sourceTree = "<group>"; };
423F44EF2C17238200766A99 /* ChatBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatBubbleView.swift; sourceTree = "<group>"; };
423F44FE2C186E4500766A99 /* WatchCommunicatorService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchCommunicatorService.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3141,7 +3145,6 @@
B68EDD04215F12C900DD6B28 /* NotificationActionConfigurator.swift */,
B6DA3C7022690B1F00DE811C /* NotificationSoundsViewController.swift */,
B65C0B512282BA13007E057B /* NotificationSettingsViewController.swift */,
11C4629324B189B100031902 /* NotificationRateLimitsAPI.swift */,
11F55EBB25D3A2A3003977AC /* NotificationCategoryListViewController.swift */,
11F55ECC25D3A364003977AC /* NotificationRateLimitViewController.swift */,
11F55EEC25D3B088003977AC /* NotificationDebugNotificationsViewController.swift */,
Expand Down Expand Up @@ -3234,6 +3237,7 @@
children = (
B6D3B4EB225B26300082BB4F /* SensorContainer.swift */,
42E9AFFE2CE63944009DDA46 /* AudioOutputSensor.swift */,
4237CD2F2D0322F800424EF6 /* NotificationRateLimitSensor.swift */,
11AF4D10249C7DFD006C74C0 /* ActivitySensor.swift */,
11AF4D1B249C8AA0006C74C0 /* BatterySensor.swift */,
11AF4D1E249C8AF0006C74C0 /* ConnectivitySensor.swift */,
Expand Down Expand Up @@ -4926,6 +4930,7 @@
D03D891820E0A85300D4F28D /* Shared */ = {
isa = PBXGroup;
children = (
11C4629324B189B100031902 /* NotificationRateLimitsAPI.swift */,
4278CB822D01F09400CFAAC9 /* HAGesture.swift */,
424D2D0F2C89DACE00C610F1 /* HAAppEntity.swift */,
42BB4C362CD26490003E47FD /* HATypedRequest+App.swift */,
Expand Down Expand Up @@ -6843,7 +6848,6 @@
119D765F2492F8FA00183C5F /* UIApplication+BackgroundTask.swift in Sources */,
11195F6F267EFC8E003DF674 /* NotificationManagerLocalPushInterfaceDirect.swift in Sources */,
FD3BC66329B9FF8F00B19FBE /* CarPlaySceneDelegate.swift in Sources */,
11C4629424B189B100031902 /* NotificationRateLimitsAPI.swift in Sources */,
1161C01B24D7634300A0E3C4 /* NFCListViewController.swift in Sources */,
11A71C6B24A463FC00D9565F /* ZoneManagerState.swift in Sources */,
42D5ACCC2C636F1F00D9C4E2 /* WatchConfigurationView.swift in Sources */,
Expand Down Expand Up @@ -7081,6 +7085,7 @@
42CE8FA82B45D1E900C707F9 /* CoreStrings.swift in Sources */,
B67CE87722200F220034C1D0 /* Strings.swift in Sources */,
11C9E43C2505B04E00492A88 /* HACoreAudioObjectSystem.swift in Sources */,
4237CD312D0322F800424EF6 /* NotificationRateLimitSensor.swift in Sources */,
11F2F1ED2586ED6100F61F7C /* NotificationAttachmentManager.swift in Sources */,
3997926F2B7F907B00231B54 /* MobileAppConfigPush.swift in Sources */,
42A3B63C2BD91891007BC0F3 /* Color+Codable.swift in Sources */,
Expand Down Expand Up @@ -7203,6 +7208,7 @@
B67CE8B322200F220034C1D0 /* Realm+Initialization.swift in Sources */,
113D29DF24946EDA0014067C /* CLLocationManager+OneShotLocation.swift in Sources */,
11CFD78227364F450082D557 /* Identifier.swift in Sources */,
4237CD332D03240100424EF6 /* NotificationRateLimitsAPI.swift in Sources */,
11AF4D17249C8083006C74C0 /* With.swift in Sources */,
11B38EF7275C54A300205C7B /* UpdateSensorsIntentHandler.swift in Sources */,
1141182B24AFA10900E6525C /* WebhookResponseHandler.swift in Sources */,
Expand Down Expand Up @@ -7379,6 +7385,7 @@
B6B74CBD228399AB00D58A68 /* Action.swift in Sources */,
11CB98CA249E62E700B05222 /* Version+HA.swift in Sources */,
420F53EA2C4E9D54003C8415 /* WidgetsKind.swift in Sources */,
4237CD322D03240100424EF6 /* NotificationRateLimitsAPI.swift in Sources */,
11EE9B4924C5116F00404AF8 /* LegacyModelManager.swift in Sources */,
42CE8FB62B46D14C00C707F9 /* FrontendStrings+Values.swift in Sources */,
D0C3DC142134CD4E000C9EE1 /* CMMotion+StringExtensions.swift in Sources */,
Expand Down Expand Up @@ -7533,6 +7540,7 @@
42FCCFDA2B9B19F70057783F /* ThreadClientService.swift in Sources */,
1101568724D7712F009424C9 /* TagManagerProtocol.swift in Sources */,
42E9B0002CE63944009DDA46 /* AudioOutputSensor.swift in Sources */,
4237CD302D0322F800424EF6 /* NotificationRateLimitSensor.swift in Sources */,
1141182624AF9A0500E6525C /* WebhookManager.swift in Sources */,
119A7E0F2529769A00D7000D /* UIImageView+UIActivityIndicator.swift in Sources */,
111858D624CB620500B8CDDC /* Intents.intentdefinition in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,48 @@ class NotificationRateLimitListViewController: HAFormViewController {
row.updateCell()
}
}

public extension RateLimitResponse.RateLimits {
func row(for keyPath: KeyPath<Self, Int>) -> BaseRow {
LabelRow {
$0.value = NumberFormatter.localizedString(
from: NSNumber(value: self[keyPath: keyPath]),
number: .none
)
$0.title = { () -> String in
switch keyPath {
case \.attempts:
return L10n.SettingsDetails.Notifications.RateLimits.attempts
case \.successful:
return L10n.SettingsDetails.Notifications.RateLimits.delivered
case \.errors:
return L10n.SettingsDetails.Notifications.RateLimits.errors
case \.total:
return L10n.SettingsDetails.Notifications.RateLimits.total
case \.maximum:
return ""
default:
fatalError("missing key: \(keyPath)")
}
}()
}
}

func row(for keyPath: KeyPath<Self, Date>) -> BaseRow {
LabelRow { row in
row.value = DateFormatter.localizedString(
from: self[keyPath: keyPath],
dateStyle: .none,
timeStyle: .medium
)

switch keyPath {
case \.resetsAt:
row.title = L10n.SettingsDetails.Notifications.RateLimits.resetsIn
row.tag = "resetsIn"
default:
fatalError("missing key: \(keyPath)")
}
}
}
}
96 changes: 0 additions & 96 deletions Sources/App/Settings/Notifications/NotificationRateLimitsAPI.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Combine
import Foundation
import HAKit
import PromiseKit

final class NotificationRateLimitSensor: SensorProvider {
let request: SensorProviderRequest
init(request: SensorProviderRequest) {
self.request = request
}

func sensors() -> Promise<[WebhookSensor]> {
#if !os(watchOS)
return .init { resolver in
if let pushID = Current.settingsStore.pushID {
NotificationRateLimitsAPI.rateLimits(pushID: pushID).done { response in
resolver.fulfill([.init(
name: "Notification Rate Limit",
uniqueID: "notification-rate-limit",
icon: "mdi:message-badge-outline",
state: response.rateLimits.remaining
)])
}.cauterize()
} else {
resolver.fulfill([])
}
}
#else
return .value([])
#endif
}
}
1 change: 1 addition & 0 deletions Sources/Shared/Environment/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ public class AppEnvironment {
$0.register(provider: AppVersionSensor.self)
$0.register(provider: LocationPermissionSensor.self)
$0.register(provider: AudioOutputSensor.self)
$0.register(provider: NotificationRateLimitSensor.self)
}

public var localized = LocalizedManager()
Expand Down
49 changes: 49 additions & 0 deletions Sources/Shared/NotificationRateLimitsAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Foundation
import PromiseKit

public struct RateLimitResponse: Decodable {
public var target: String

public struct RateLimits: Decodable {
public var attempts: Int
public var successful: Int
public var errors: Int
public var total: Int
public var maximum: Int
public var remaining: Int
public var resetsAt: Date
}

public var rateLimits: RateLimits
}

public class NotificationRateLimitsAPI {
public class func rateLimits(pushID: String) -> Promise<RateLimitResponse> {
firstly { () -> Promise<URLRequest> in
do {
var urlRequest = URLRequest(url: URL(
string: "https://mobile-apps.home-assistant.io/api/checkRateLimits"
)!)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: [
"push_token": pushID,
])
return .value(urlRequest)
} catch {
return .init(error: error)
}
}.then {
URLSession.shared.dataTask(.promise, with: $0)
}.map { data, _ throws -> RateLimitResponse in
let decoder = with(JSONDecoder()) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.sss'Z'"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(identifier: "UTC")
$0.dateDecodingStrategy = .formatted(dateFormatter)
}
return try decoder.decode(RateLimitResponse.self, from: data)
}
}
}

0 comments on commit 65d2b4a

Please sign in to comment.