Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Configuration/Entitlements/Extension-catalyst.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.developer.usernotifications.communication</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(BUNDLE_ID_PREFIX).HomeAssistant$(BUNDLE_ID_SUFFIX)</string>
Expand Down
2 changes: 2 additions & 0 deletions Configuration/Entitlements/Extension-ios.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<array>
<string>group.$(BUNDLE_ID_PREFIX).homeassistant$(BUNDLE_ID_SUFFIX)</string>
</array>
<key>com.apple.developer.usernotifications.communication</key>
Comment thread
bgoncal marked this conversation as resolved.
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(BUNDLE_ID_PREFIX).HomeAssistant$(BUNDLE_ID_SUFFIX)</string>
Expand Down
117 changes: 78 additions & 39 deletions HomeAssistant.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"aps": {
"alert": {
"title": "Dishwasher",
"body": "Cycle complete."
},
"sound": "default",
"category": "notification",
"mutable-content": 1
},
"notification_icon": "mdi:dishwasher",
"color": "#4CAF50",
"webhook_id": "REPLACE_WITH_YOUR_WEBHOOK_ID"
}
24 changes: 18 additions & 6 deletions Sources/Extensions/NotificationService/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,43 @@ import Shared
import UserNotifications

final class NotificationService: UNNotificationServiceExtension {
private let notificationCommunicationDecorator = NotificationCommunicationDecoratorImpl()

override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
Current.Log.info("didReceive \(request), user info \(request.content.userInfo)")

guard let server = Current.servers.server(for: request.content), let api = Current.api(for: server) else {
contentHandler(request.content)
guard let server = Current.servers.server(for: request.content),
let api = Current.api(for: server) else {
if let sender = NotificationSenderParser.parse(from: request.content) {
notificationCommunicationDecorator
.decorate(content: request.content, sender: sender, api: nil)
.done { contentHandler($0) }
} else {
contentHandler(request.content)
}
return
}

firstly {
Current.notificationAttachmentManager.content(from: request.content, api: api)
}.recover { error in
}.recover { error -> Guarantee<UNNotificationContent> in
Current.Log.error("failed to get content, giving default: \(error)")
return .value(request.content)
}.then { content -> Guarantee<UNNotificationContent> in
guard let sender = NotificationSenderParser.parse(from: content) else {
return .value(content)
}
return self.notificationCommunicationDecorator
.decorate(content: content, sender: sender, api: api)
}.done {
contentHandler($0)
}
}

override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content,
// otherwise the original push payload will be used.
Current.Log.warning("serviceExtensionTimeWillExpire")
}
}
11 changes: 11 additions & 0 deletions Sources/Extensions/NotificationService/Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,19 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSUserActivityTypes</key>
<array>
<string>INSendMessageIntent</string>
</array>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>IntentsSupported</key>
<array>
<string>INSendMessageIntent</string>
</array>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ public struct LegacyNotificationParserImpl: LegacyNotificationParser {
addAttachment(key: "image", contentType: "jpeg")
addAttachment(key: "audio", contentType: "waveformaudio")

for key in NotificationDecorationPayloadKey.notificationDecorationKeys {
if let value = data[key.rawValue] {
payload[key.rawValue] = value
}
}
if payload[NotificationDecorationPayloadKey.iconURL.rawValue] != nil ||
payload[NotificationDecorationPayloadKey.notificationIcon.rawValue] != nil {
needsMutableContent = true
}

payload["url"] = data["url"]
payload["shortcut"] = data["shortcut"]
payload["presentation_options"] = data["presentation_options"]
Expand Down Expand Up @@ -257,6 +267,20 @@ enum LegacyNotificationCommandType: String {
case updateWidgets = "update_widgets"
}

enum NotificationDecorationPayloadKey: String, CaseIterable {
case iconURL = "icon_url"
case notificationIcon = "notification_icon"
case notificationIconColor = "notification_icon_color"
case color

static let notificationDecorationKeys: [Self] = [
.iconURL,
.notificationIcon,
.notificationIconColor,
.color,
]
}

private extension Dictionary where Value == Any {
mutating func mutate<SomeValue>(
_ key: Key,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Foundation
import SharedPush
@testable import SharedPush
import XCTest

class NotificationParserLegacyTests: XCTestCase {
Expand Down Expand Up @@ -65,4 +65,11 @@ class NotificationParserLegacyTests: XCTestCase {
XCTAssertEqual(resultString, expectedString, data.name)
}
}

func testNotificationDecorationKeyRawValues() {
XCTAssertEqual(NotificationDecorationPayloadKey.iconURL.rawValue, "icon_url")
XCTAssertEqual(NotificationDecorationPayloadKey.notificationIcon.rawValue, "notification_icon")
XCTAssertEqual(NotificationDecorationPayloadKey.notificationIconColor.rawValue, "notification_icon_color")
XCTAssertEqual(NotificationDecorationPayloadKey.color.rawValue, "color")
}
}
Comment thread
bgoncal marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"input": {
"message": "test",
"title": "Phone",
"data": {
"notification_icon": "mdi:cellphone",
"notification_icon_color": "#FFFFFF",
"color": "#03A9F4"
},
"registration_info": {
"app_id": "io.robbie.HomeAssistant.dev",
"os_version": "10.15",
"app_version": "2021.5"
}
},
"rate_limit": true,
"headers": {
"apns-push-type": "alert"
},
"payload": {
"aps": {
"alert": {
"body": "test",
"title": "Phone"
},
"mutable-content": true,
"sound": "default"
},
"color": "#03A9F4",
"notification_icon": "mdi:cellphone",
"notification_icon_color": "#FFFFFF"
}
}
14 changes: 13 additions & 1 deletion Sources/Shared/Notifications/LocalPush/LocalPushManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public protocol LocalPushManagerDelegate: AnyObject {
public class LocalPushManager {
public let server: Server
public weak var delegate: LocalPushManagerDelegate?
private let notificationCommunicationDecorator: NotificationCommunicationDecorator

public static let stateDidChange: Notification.Name = .init(rawValue: "LocalPushManagerStateDidChange")

Expand Down Expand Up @@ -82,8 +83,13 @@ public class LocalPushManager {

private var tokens = [HACancellable]()

public init(server: Server) {
public init(
server: Server,
notificationCommunicationDecorator: NotificationCommunicationDecorator =
NotificationCommunicationDecoratorImpl()
) {
self.server = server
self.notificationCommunicationDecorator = notificationCommunicationDecorator

updateSubscription()
tokens.append(server.observe { [weak self] _ in
Expand Down Expand Up @@ -181,6 +187,12 @@ public class LocalPushManager {
}.recover { error in
Current.Log.error("failed to get content, giving default: \(error)")
return .value(baseContent)
}.then { content -> Guarantee<UNNotificationContent> in
if let sender = NotificationSenderParser.parse(from: content) {
return self.notificationCommunicationDecorator.decorate(content: content, sender: sender, api: api)
} else {
return .value(content)
}
}.then { [add] content -> Promise<Void> in
add(UNNotificationRequest(identifier: event.identifier, content: content, trigger: nil))
}.then { [subscription] () -> Promise<Void> in
Expand Down
Loading
Loading