Skip to content
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

Allows for the use of "item:imageItem" for attachments #771

Merged
merged 1 commit into from
Jun 29, 2024
Merged
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
10 changes: 10 additions & 0 deletions NotificationService/NotificationService.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.openhab.app</string>
</array>
</dict>
</plist>
120 changes: 93 additions & 27 deletions NotificationService/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
//
// SPDX-License-Identifier: EPL-2.0

import Foundation
import OpenHABCore
import os.log
import UniformTypeIdentifiers
import UserNotifications
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
}
}
13 changes: 13 additions & 0 deletions openHAB.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 = "<group>"; };
657144522C1E438700C8A1F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
657144972C30A3E300C8A1F3 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; };
6595667D28E0BE8E00E8A53B /* MulticastDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MulticastDelegate.swift; sourceTree = "<group>"; };
931384B324F259BC00A73AB5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
931384B424F259BD00A73AB5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -500,6 +502,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
657144962C30A16700C8A1F3 /* OpenHABCore in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -601,6 +604,7 @@
6571444F2C1E438700C8A1F3 /* NotificationService */ = {
isa = PBXGroup;
children = (
657144972C30A3E300C8A1F3 /* NotificationService.entitlements */,
657144502C1E438700C8A1F3 /* NotificationService.swift */,
657144522C1E438700C8A1F3 /* Info.plist */,
);
Expand Down Expand Up @@ -1050,6 +1054,9 @@
dependencies = (
);
name = NotificationService;
packageProductDependencies = (
657144952C30A16700C8A1F3 /* OpenHABCore */,
);
productName = NotificationService;
productReference = 6571444E2C1E438700C8A1F3 /* NotificationService.appex */;
productType = "com.apple.product-type.app-extension";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2398,6 +2407,10 @@
package = 93F8063327AE6C620035A6B0 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseMessaging;
};
657144952C30A16700C8A1F3 /* OpenHABCore */ = {
isa = XCSwiftPackageProductDependency;
productName = OpenHABCore;
};
934E592428F16EBA00162004 /* OpenHABCore */ = {
isa = XCSwiftPackageProductDependency;
productName = OpenHABCore;
Expand Down
7 changes: 6 additions & 1 deletion openHABWatch Extension/openHABWatch Extension.entitlements
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.openhab.app</string>
</array>
</dict>
</plist>
Loading