-
Notifications
You must be signed in to change notification settings - Fork 480
Add push-based widget updates #4939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
hariharanjagan
wants to merge
7
commits into
home-assistant:main
Choose a base branch
from
hariharanjagan:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
78226af
Extend widget push subscriptions to todo and custom widgets
hariharanjagan 563eeb7
Merge branch 'main' of https://github.com/hariharanjagan/Home-Assista…
hariharanjagan ceee07d
Wire up WidgetKit push for the sensor widget
hariharanjagan 4096859
Register Todo and Custom widget entities for push subscriptions
hariharanjagan 213c638
Potential fix for pull request finding
hariharanjagan 58fd51a
Address review feedback on widget push handler
hariharanjagan bb3c963
Merge branch 'main' into main
hariharanjagan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
Sources/Extensions/Widgets/Common/WidgetPushHandlerModifier.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import SwiftUI | ||
| import WidgetKit | ||
|
|
||
| extension WidgetConfiguration { | ||
| func haWidgetPushHandlerIfAvailable() -> some WidgetConfiguration { | ||
| if #available(iOS 26.0, *) { | ||
| return self.pushHandler(HAWidgetPushHandler.self) | ||
| } else { | ||
| return self | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
Sources/Extensions/Widgets/Sensor/HAWidgetPushHandler.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import Shared | ||
| import WidgetKit | ||
|
|
||
| @available(iOS 26.0, *) | ||
| struct HAWidgetPushHandler: WidgetPushHandler { | ||
| static let webhookType = "register_push_subscription" | ||
|
|
||
| /// One push subscription per widget kind. Every entity a kind tracks is | ||
| /// registered under a stable, kind-specific subscription_id so kinds do not | ||
| /// clobber each other's entity sets in core (registration is idempotent on | ||
| /// subscription_id). | ||
| struct Subscription { | ||
| let subscriptionID: String | ||
| let target: String | ||
| let entityIDs: Set<String> | ||
| } | ||
|
|
||
| func pushTokenDidChange(_ pushInfo: WidgetPushInfo, widgets: [WidgetInfo]) { | ||
| let tokenHex = pushInfo.token.map { String(format: "%02x", $0) }.joined() | ||
| let subscriptions = Self.subscriptions(from: widgets).filter { !$0.entityIDs.isEmpty } | ||
|
|
||
| Current.Log.info("HAWidgetPushHandler: token changed -> \(subscriptions.count) subscription(s)") | ||
|
|
||
| guard !subscriptions.isEmpty else { | ||
| Current.Log.info("HAWidgetPushHandler: no trackable entities, skipping register") | ||
| return | ||
| } | ||
| guard !Current.servers.all.isEmpty else { | ||
| Current.Log.error("HAWidgetPushHandler: no servers available in extension") | ||
| return | ||
| } | ||
|
|
||
| for server in Current.servers.all { | ||
| for subscription in subscriptions { | ||
| let request = WebhookRequest( | ||
| type: Self.webhookType, | ||
| data: [ | ||
| "subscription_id": subscription.subscriptionID, | ||
| "push_token": tokenHex, | ||
| "entity_ids": subscription.entityIDs.sorted(), | ||
| "target": subscription.target, | ||
| ] | ||
| ) | ||
| Current.Log.info( | ||
| "HAWidgetPushHandler: registering \(subscription.subscriptionID) " + | ||
| "(\(subscription.entityIDs.count) entities) with \(server.identifier.rawValue)" | ||
| ) | ||
| Task { | ||
| do { | ||
| try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in | ||
| Current.webhooks.sendEphemeral(server: server, request: request) | ||
| .done { continuation.resume() } | ||
| .catch { continuation.resume(throwing: $0) } | ||
| } | ||
| } catch { | ||
| Current.Log.error( | ||
| "HAWidgetPushHandler: failed to register \(subscription.subscriptionID) " + | ||
| "with \(server.identifier.rawValue): \(error)" | ||
| ) | ||
| } | ||
| } | ||
|
hariharanjagan marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Collect tracked entity IDs for every push-capable widget kind present, | ||
| /// grouped into one subscription per kind. | ||
| /// | ||
| /// Only kinds whose entities are known at configuration time are included. | ||
| /// Template-driven kinds (gauge, details) render Jinja server-side and have | ||
| /// no fixed entity list, and commonlyUsedEntities is computed dynamically, so | ||
| /// those cannot be expressed as entity subscriptions and are excluded. | ||
| static func subscriptions(from widgets: [WidgetInfo]) -> [Subscription] { | ||
| var entitiesByKind: [WidgetsKind: Set<String>] = [:] | ||
|
|
||
| for info in widgets { | ||
| guard let kind = WidgetsKind(rawValue: info.kind) else { continue } | ||
| switch kind { | ||
| case .sensors: | ||
| if let intent = try? info.widgetConfigurationIntent(of: WidgetSensorsAppIntent.self), | ||
| let sensors = intent.sensors { | ||
| entitiesByKind[.sensors, default: []].formUnion(sensors.map(\.entityId)) | ||
| } | ||
| case .todoList: | ||
| if let intent = try? info.widgetConfigurationIntent(of: WidgetTodoListAppIntent.self), | ||
| let entityId = intent.list?.entityId { | ||
| entitiesByKind[.todoList, default: []].insert(entityId) | ||
| } | ||
| case .custom: | ||
| if let intent = try? info.widgetConfigurationIntent(of: WidgetCustomAppIntent.self), | ||
| let widgetId = intent.widget?.id, | ||
| let widget = try? CustomWidget.widgets()?.first(where: { $0.id == widgetId }) { | ||
| let entityIDs = widget.items | ||
| .filter { $0.type == .entity } | ||
| .map(\.id) | ||
| entitiesByKind[.custom, default: []].formUnion(entityIDs) | ||
| } | ||
| default: | ||
| continue | ||
| } | ||
| } | ||
|
|
||
| return entitiesByKind.map { kind, entityIDs in | ||
| Subscription( | ||
| subscriptionID: "ios-widget-\(kind.rawValue)", | ||
| target: kind.rawValue, | ||
| entityIDs: entityIDs | ||
| ) | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this fails, when will it be retried?