diff --git a/NotificationService/NotificationService.entitlements b/NotificationService/NotificationService.entitlements
new file mode 100644
index 00000000..b6063e1b
--- /dev/null
+++ b/NotificationService/NotificationService.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.org.openhab.app
+
+
+
diff --git a/NotificationService/NotificationService.swift b/NotificationService/NotificationService.swift
index 654a59c9..80c7e071 100644
--- a/NotificationService/NotificationService.swift
+++ b/NotificationService/NotificationService.swift
@@ -9,6 +9,8 @@
//
// SPDX-License-Identifier: EPL-2.0
+import Foundation
+import OpenHABCore
import os.log
import UniformTypeIdentifiers
import UserNotifications
@@ -52,7 +54,7 @@ class NotificationService: UNNotificationServiceExtension {
intentIdentifiers: [],
options: .customDismissAction
)
- UNUserNotificationCenter.current().getNotificationCategories { (existingCategories) in
+ UNUserNotificationCenter.current().getNotificationCategories { existingCategories in
// Check if the new category already exists, this is a hash of the actions string done by the cloud service
let existingCategoryIdentifiers = existingCategories.map(\.identifier)
if !existingCategoryIdentifiers.contains(category) {
@@ -69,14 +71,24 @@ class NotificationService: UNNotificationServiceExtension {
// this should be last as we need to wait for media
// TODO: we should support relative paths and try the user's openHAB (local,remote) for content
if let attachmentURLString = userInfo["media-attachment-url"] as? String, let attachmentURL = URL(string: attachmentURLString) {
- os_log("handleNotification downloading %{PUBLIC}@", log: .default, type: .info, attachmentURLString)
- downloadAndAttachMedia(url: attachmentURL) { attachment in
- if let attachment {
- os_log("handleNotification attaching %{PUBLIC}@", log: .default, type: .info, attachmentURLString)
- bestAttemptContent.attachments = [attachment]
+ if let scheme = attachmentURL.scheme {
+ let isItem = scheme == "item"
+ let downloadHandler: (URL, @escaping (UNNotificationAttachment?) -> Void) -> Void = if isItem {
+ downloadAndAttachItemImage
} else {
- os_log("handleNotification could not attach %{PUBLIC}@", log: .default, type: .info, attachmentURLString)
+ downloadAndAttachMedia
}
+
+ downloadHandler(attachmentURL) { attachment in
+ if let attachment {
+ os_log("handleNotification attaching %{PUBLIC}@", log: .default, type: .info, attachmentURLString)
+ bestAttemptContent.attachments = [attachment]
+ } else {
+ os_log("handleNotification could not attach %{PUBLIC}@", log: .default, type: .info, attachmentURLString)
+ }
+ contentHandler(bestAttemptContent)
+ }
+ } else {
contentHandler(bestAttemptContent)
}
} else {
@@ -124,33 +136,87 @@ class NotificationService: UNNotificationServiceExtension {
completion(nil)
return
}
+ self.attachFile(localURL: localURL, mimeType: response?.mimeType, completion: completion)
+ }
+ task.resume()
+ }
- do {
- let fileManager = FileManager.default
- let tempDirectory = NSTemporaryDirectory()
- let tempFile = URL(fileURLWithPath: tempDirectory).appendingPathComponent(UUID().uuidString)
+ func downloadAndAttachItemImage(attachmentURL: URL, completion: @escaping (UNNotificationAttachment?) -> Void) {
+ guard let scheme = attachmentURL.scheme else {
+ os_log("Could not find scheme %{PUBLIC}@", log: .default, type: .info)
+ return
+ }
- try fileManager.moveItem(at: localURL, to: tempFile)
+ let itemName = String(attachmentURL.absoluteString.dropFirst(scheme.count + 1))
- let attachment: UNNotificationAttachment?
+ OpenHABItemCache.instance.getItem(name: itemName) { item in
+ guard let item else {
+ os_log("Could not find item %{PUBLIC}@", log: .default, type: .info, itemName)
+ completion(nil)
+ return
+ }
+ if let state = item.state {
+ do {
+ // Extract MIME type and base64 string
+ let pattern = "^data:(.*?);base64,(.*)$"
+ let regex = try NSRegularExpression(pattern: pattern, options: [])
- if let mimeType = response?.mimeType,
- let utType = UTType(mimeType: mimeType),
- utType.conforms(to: .data) {
- let newTempFile = tempFile.appendingPathExtension(utType.preferredFilenameExtension ?? "")
- try fileManager.moveItem(at: tempFile, to: newTempFile)
- attachment = try UNNotificationAttachment(identifier: UUID().uuidString, url: newTempFile, options: nil)
- } else {
- os_log("Unrecognized MIME type or file extension", log: .default, type: .error)
- attachment = nil
- }
+ if let match = regex.firstMatch(in: state, options: [], range: NSRange(location: 0, length: state.utf16.count)) {
+ let mimeTypeRange = Range(match.range(at: 1), in: state)
+ let base64Range = Range(match.range(at: 2), in: state)
- completion(attachment)
- } catch {
- os_log("Failed to create UNNotificationAttachment: %{PUBLIC}@", log: .default, type: .error, error.localizedDescription)
+ if let mimeTypeRange, let base64Range {
+ let mimeType = String(state[mimeTypeRange])
+ let base64String = String(state[base64Range])
+ if let imageData = Data(base64Encoded: base64String) {
+ // Create a temporary file URL
+ let tempDirectory = FileManager.default.temporaryDirectory
+ let tempFileURL = tempDirectory.appendingPathComponent(UUID().uuidString)
+ do {
+ try imageData.write(to: tempFileURL)
+ os_log("Image saved to temporary file: %{PUBLIC}@", log: .default, type: .info, tempFileURL.absoluteString)
+ self.attachFile(localURL: tempFileURL, mimeType: mimeType, completion: completion)
+ return
+ } catch {
+ os_log("Failed to write image data to file: %{PUBLIC}@", log: .default, type: .error, error.localizedDescription)
+ }
+ } else {
+ os_log("Failed to decode base64 string to Data", log: .default, type: .error)
+ }
+ }
+ }
+ } catch {
+ os_log("Failed to parse data: %{PUBLIC}@", log: .default, type: .error, error.localizedDescription)
+ }
completion(nil)
}
}
- task.resume()
+ }
+
+ func attachFile(localURL: URL, mimeType: String?, completion: @escaping (UNNotificationAttachment?) -> Void) {
+ do {
+ let fileManager = FileManager.default
+ let tempDirectory = NSTemporaryDirectory()
+ let tempFile = URL(fileURLWithPath: tempDirectory).appendingPathComponent(UUID().uuidString)
+
+ try fileManager.moveItem(at: localURL, to: tempFile)
+ let attachment: UNNotificationAttachment?
+
+ if let mimeType,
+ let utType = UTType(mimeType: mimeType),
+ utType.conforms(to: .data) {
+ let newTempFile = tempFile.appendingPathExtension(utType.preferredFilenameExtension ?? "")
+ try fileManager.moveItem(at: tempFile, to: newTempFile)
+ attachment = try UNNotificationAttachment(identifier: UUID().uuidString, url: newTempFile, options: nil)
+ } else {
+ os_log("Unrecognized MIME type or file extension", log: .default, type: .error)
+ attachment = nil
+ }
+
+ completion(attachment)
+ } catch {
+ os_log("Failed to create UNNotificationAttachment: %{PUBLIC}@", log: .default, type: .error, error.localizedDescription)
+ completion(nil)
+ }
}
}
diff --git a/openHAB.xcodeproj/project.pbxproj b/openHAB.xcodeproj/project.pbxproj
index 4f55b54f..b6130765 100644
--- a/openHAB.xcodeproj/project.pbxproj
+++ b/openHAB.xcodeproj/project.pbxproj
@@ -20,6 +20,7 @@
656916D91FCB82BC00667B2A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 656916D81FCB82BC00667B2A /* GoogleService-Info.plist */; };
657144512C1E438700C8A1F3 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657144502C1E438700C8A1F3 /* NotificationService.swift */; };
657144552C1E438700C8A1F3 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6571444E2C1E438700C8A1F3 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ 657144962C30A16700C8A1F3 /* OpenHABCore in Frameworks */ = {isa = PBXBuildFile; productRef = 657144952C30A16700C8A1F3 /* OpenHABCore */; };
6595667E28E0BE8E00E8A53B /* MulticastDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6595667D28E0BE8E00E8A53B /* MulticastDelegate.swift */; };
932602EE2382892B00EAD685 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAC6608B236F6F4200F4501E /* Assets.xcassets */; };
933D7F0722E7015100621A03 /* OpenHABUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 933D7F0622E7015000621A03 /* OpenHABUITests.swift */; };
@@ -280,6 +281,7 @@
6571444E2C1E438700C8A1F3 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
657144502C1E438700C8A1F3 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; };
657144522C1E438700C8A1F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 657144972C30A3E300C8A1F3 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; };
6595667D28E0BE8E00E8A53B /* MulticastDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MulticastDelegate.swift; sourceTree = ""; };
931384B324F259BC00A73AB5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; };
931384B424F259BD00A73AB5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; };
@@ -500,6 +502,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 657144962C30A16700C8A1F3 /* OpenHABCore in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -601,6 +604,7 @@
6571444F2C1E438700C8A1F3 /* NotificationService */ = {
isa = PBXGroup;
children = (
+ 657144972C30A3E300C8A1F3 /* NotificationService.entitlements */,
657144502C1E438700C8A1F3 /* NotificationService.swift */,
657144522C1E438700C8A1F3 /* Info.plist */,
);
@@ -1050,6 +1054,9 @@
dependencies = (
);
name = NotificationService;
+ packageProductDependencies = (
+ 657144952C30A16700C8A1F3 /* OpenHABCore */,
+ );
productName = NotificationService;
productReference = 6571444E2C1E438700C8A1F3 /* NotificationService.appex */;
productType = "com.apple.product-type.app-extension";
@@ -1695,6 +1702,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -1740,6 +1748,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -2398,6 +2407,10 @@
package = 93F8063327AE6C620035A6B0 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseMessaging;
};
+ 657144952C30A16700C8A1F3 /* OpenHABCore */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = OpenHABCore;
+ };
934E592428F16EBA00162004 /* OpenHABCore */ = {
isa = XCSwiftPackageProductDependency;
productName = OpenHABCore;
diff --git a/openHABWatch Extension/openHABWatch Extension.entitlements b/openHABWatch Extension/openHABWatch Extension.entitlements
index 0c67376e..b6063e1b 100644
--- a/openHABWatch Extension/openHABWatch Extension.entitlements
+++ b/openHABWatch Extension/openHABWatch Extension.entitlements
@@ -1,5 +1,10 @@
-
+
+ com.apple.security.application-groups
+
+ group.org.openhab.app
+
+