From ff8a83ca2ddf3d906282f6d4df65b6bbfa8c7c1d Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 9 Apr 2024 22:39:58 -0400 Subject: [PATCH] Decrypt push notifications --- NotificationExtension/Info.plist | 22 ++ .../NotificationExtension.entitlements | 14 ++ .../NotificationService.swift | 150 +++++++++++++ .../Pachyderm/Model/PushNotification.swift | 46 ++++ ShareExtension/ShareExtension.entitlements | 2 +- Tusker.xcodeproj/project.pbxproj | 212 +++++++++++++++++- Tusker/Extensions/MainActor+Unsafe.swift | 4 + 7 files changed, 448 insertions(+), 2 deletions(-) create mode 100644 NotificationExtension/Info.plist create mode 100644 NotificationExtension/NotificationExtension.entitlements create mode 100644 NotificationExtension/NotificationService.swift create mode 100644 Packages/Pachyderm/Sources/Pachyderm/Model/PushNotification.swift diff --git a/NotificationExtension/Info.plist b/NotificationExtension/Info.plist new file mode 100644 index 00000000..31726763 --- /dev/null +++ b/NotificationExtension/Info.plist @@ -0,0 +1,22 @@ + + + + + TuskerInfo + + PushProxyHost + $(TUSKER_PUSH_PROXY_HOST) + PushProxyScheme + $(TUSKER_PUSH_PROXY_SCHEME) + SentryDSN + $(SENTRY_DSN) + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/NotificationExtension/NotificationExtension.entitlements b/NotificationExtension/NotificationExtension.entitlements new file mode 100644 index 00000000..75da0284 --- /dev/null +++ b/NotificationExtension/NotificationExtension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.$(BUNDLE_ID_PREFIX).Tusker + + com.apple.security.network.client + + + diff --git a/NotificationExtension/NotificationService.swift b/NotificationExtension/NotificationService.swift new file mode 100644 index 00000000..2657316c --- /dev/null +++ b/NotificationExtension/NotificationService.swift @@ -0,0 +1,150 @@ +// +// NotificationService.swift +// NotificationExtension +// +// Created by Shadowfacts on 4/9/24. +// Copyright © 2024 Shadowfacts. All rights reserved. +// + +import UserNotifications +import UserAccounts +import PushNotifications +import CryptoKit +import OSLog +import Pachyderm + +private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "NotificationService") + +class NotificationService: UNNotificationServiceExtension { + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else { + contentHandler(request.content) + return + } + + guard request.content.userInfo["v"] as? Int == 1, + let accountID = request.content.userInfo["ctx"] as? String, + let account = UserAccountsManager.shared.getAccount(id: accountID), + let subscription = getSubscription(account: account), + let encryptedBody = (request.content.userInfo["data"] as? String).flatMap({ Data(base64Encoded: $0) }), + let salt = (request.content.userInfo["salt"] as? String).flatMap(decodeBase64URL(_:)), + let serverPublicKeyData = (request.content.userInfo["pk"] as? String).flatMap(decodeBase64URL(_:)) else { + logger.error("Missing info from push notification") + contentHandler(request.content) + return + } + + guard let body = decryptNotification(subscription: subscription, serverPublicKeyData: serverPublicKeyData, salt: salt, encryptedBody: encryptedBody) else { + contentHandler(request.content) + return + } + + let withoutPadding = body.dropFirst(2) + + let notification: PushNotification + do { + notification = try JSONDecoder().decode(PushNotification.self, from: withoutPadding) + } catch { + logger.error("Unable to decode push payload: \(String(describing: error))") + contentHandler(request.content) + return + } + + mutableContent.title = notification.title + mutableContent.body = notification.body + + contentHandler(mutableContent) + } + + override func serviceExtensionTimeWillExpire() { + } + + private func getSubscription(account: UserAccountInfo) -> PushNotifications.PushSubscription? { + DispatchQueue.main.sync { + // this is necessary because of a swift bug: https://github.com/apple/swift/pull/72507 + MainActor.runUnsafely { + PushManager.shared.pushSubscription(account: account) + } + } + } + + private func decryptNotification(subscription: PushNotifications.PushSubscription, serverPublicKeyData: Data, salt: Data, encryptedBody: Data) -> Data? { + // See https://github.com/ClearlyClaire/webpush/blob/f14a4d52e201128b1b00245d11b6de80d6cfdcd9/lib/webpush/encryption.rb + + var context = Data() + context.append(0) + let clientPublicKey = subscription.secretKey.publicKey.x963Representation + let clientPublicKeyLength = UInt16(clientPublicKey.count) + context.append(UInt8((clientPublicKeyLength >> 8) & 0xFF)) + context.append(UInt8(clientPublicKeyLength & 0xFF)) + context.append(clientPublicKey) + let serverPublicKeyLength = UInt16(serverPublicKeyData.count) + context.append(UInt8((serverPublicKeyLength >> 8) & 0xFF)) + context.append(UInt8(serverPublicKeyLength & 0xFF)) + context.append(serverPublicKeyData) + + func info(encoding: String) -> Data { + var info = Data("Content-Encoding: \(encoding)\0P-256".utf8) + info.append(context) + return info + } + + let sharedSecret: SharedSecret + do { + let serverPublicKey = try P256.KeyAgreement.PublicKey(x963Representation: serverPublicKeyData) + sharedSecret = try subscription.secretKey.sharedSecretFromKeyAgreement(with: serverPublicKey) + } catch { + logger.error("Error getting shared secret: \(String(describing: error))") + return nil + } + + let sharedInfo = Data("Content-Encoding: auth\0".utf8) + let pseudoRandomKey = sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self, salt: subscription.authSecret, sharedInfo: sharedInfo, outputByteCount: 32) + let contentEncryptionKeyInfo = info(encoding: "aesgcm") + let contentEncryptionKey = HKDF.deriveKey(inputKeyMaterial: pseudoRandomKey, salt: salt, info: contentEncryptionKeyInfo, outputByteCount: 16) + let nonceInfo = info(encoding: "nonce") + let nonce = HKDF.deriveKey(inputKeyMaterial: pseudoRandomKey, salt: salt, info: nonceInfo, outputByteCount: 12) + + let nonceAndEncryptedBody = nonce.withUnsafeBytes { noncePtr in + var data = Data(buffer: noncePtr.bindMemory(to: UInt8.self)) + data.append(encryptedBody) + return data + } + do { + let sealedBox = try AES.GCM.SealedBox(combined: nonceAndEncryptedBody) + let decrypted = try AES.GCM.open(sealedBox, using: contentEncryptionKey) + return decrypted + } catch { + logger.error("Error decrypting push: \(String(describing: error))") + return nil + } + } + +} + +extension MainActor { + @_unavailableFromAsync + @available(macOS, obsoleted: 14.0) + @available(iOS, obsoleted: 17.0) + @available(watchOS, obsoleted: 10.0) + @available(tvOS, obsoleted: 17.0) + static func runUnsafely(_ body: @MainActor () throws -> T) rethrows -> T { + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + return try MainActor.assumeIsolated(body) + } + + dispatchPrecondition(condition: .onQueue(.main)) + return try withoutActuallyEscaping(body) { fn in + try unsafeBitCast(fn, to: (() throws -> T).self)() + } + } +} + +private func decodeBase64URL(_ s: String) -> Data? { + var str = s.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/") + if str.count % 4 != 0 { + str.append(String(repeating: "=", count: 4 - str.count % 4)) + } + return Data(base64Encoded: str) +} diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/PushNotification.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/PushNotification.swift new file mode 100644 index 00000000..178cab03 --- /dev/null +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/PushNotification.swift @@ -0,0 +1,46 @@ +// +// PushNotification.swift +// Pachyderm +// +// Created by Shadowfacts on 4/9/24. +// + +import Foundation +import WebURL + +public struct PushNotification: Decodable { + public let accessToken: String + public let preferredLocale: String + public let notificationID: String + public let notificationType: Notification.Kind + public let icon: WebURL + public let title: String + public let body: String + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.accessToken = try container.decode(String.self, forKey: .accessToken) + self.preferredLocale = try container.decode(String.self, forKey: .preferredLocale) + // this should be a string, but mastodon encodes it as a json number + if let s = try? container.decode(String.self, forKey: .notificationID) { + self.notificationID = s + } else { + let i = try container.decode(Int.self, forKey: .notificationID) + self.notificationID = i.description + } + self.notificationType = try container.decode(Notification.Kind.self, forKey: .notificationType) + self.icon = try container.decode(WebURL.self, forKey: .icon) + self.title = try container.decode(String.self, forKey: .title) + self.body = try container.decode(String.self, forKey: .body) + } + + private enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case preferredLocale = "preferred_locale" + case notificationID = "notification_id" + case notificationType = "notification_type" + case icon + case title + case body + } +} diff --git a/ShareExtension/ShareExtension.entitlements b/ShareExtension/ShareExtension.entitlements index d5e9f9a9..75da0284 100644 --- a/ShareExtension/ShareExtension.entitlements +++ b/ShareExtension/ShareExtension.entitlements @@ -6,7 +6,7 @@ com.apple.security.application-groups - group.space.vaccor.Tusker + group.$(BUNDLE_ID_PREFIX).Tusker com.apple.security.network.client diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index d260e3ec..829c528e 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -95,6 +95,12 @@ D630C3C82BC43AFD00208903 /* PushNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3C72BC43AFD00208903 /* PushNotifications */; }; D630C3CA2BC59FF500208903 /* MastodonController+Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = D630C3C92BC59FF500208903 /* MastodonController+Push.swift */; }; D630C3CC2BC5FD4600208903 /* GetAuthorizationTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D630C3CB2BC5FD4600208903 /* GetAuthorizationTokenService.swift */; }; + D630C3D42BC61B6100208903 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D630C3D32BC61B6100208903 /* NotificationService.swift */; }; + D630C3D82BC61B6100208903 /* NotificationExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D630C3D12BC61B6000208903 /* NotificationExtension.appex */; platformFilters = (ios, maccatalyst, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + D630C3DF2BC61C4900208903 /* PushNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3DE2BC61C4900208903 /* PushNotifications */; }; + D630C3E12BC61C6700208903 /* UserAccounts in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3E02BC61C6700208903 /* UserAccounts */; }; + D630C3E52BC6313400208903 /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3E42BC6313400208903 /* Pachyderm */; }; + D630C3E72BC6313F00208903 /* WebURLFoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3E62BC6313F00208903 /* WebURLFoundationExtras */; }; D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; }; D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; }; D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; }; @@ -359,6 +365,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + D630C3D62BC61B6100208903 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */; + proxyType = 1; + remoteGlobalIDString = D630C3D02BC61B6000208903; + remoteInfo = NotificationExtension; + }; D6A4531B29EF64BA00032932 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */; @@ -397,6 +410,7 @@ dstSubfolderSpec = 13; files = ( D6A4531D29EF64BA00032932 /* ShareExtension.appex in Embed Foundation Extensions */, + D630C3D82BC61B6100208903 /* NotificationExtension.appex in Embed Foundation Extensions */, D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; @@ -501,6 +515,10 @@ D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringHelperTests.swift; sourceTree = ""; }; D630C3C92BC59FF500208903 /* MastodonController+Push.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonController+Push.swift"; sourceTree = ""; }; D630C3CB2BC5FD4600208903 /* GetAuthorizationTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAuthorizationTokenService.swift; sourceTree = ""; }; + D630C3D12BC61B6000208903 /* NotificationExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + D630C3D32BC61B6100208903 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + D630C3D52BC61B6100208903 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D630C3D92BC61B6100208903 /* NotificationExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationExtension.entitlements; sourceTree = ""; }; D6311C4F25B3765B00B27539 /* ImageDataCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDataCache.swift; sourceTree = ""; }; D6333B362137838300CE884A /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = ""; }; D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = ""; }; @@ -778,6 +796,17 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + D630C3CE2BC61B6000208903 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D630C3E52BC6313400208903 /* Pachyderm in Frameworks */, + D630C3E72BC6313F00208903 /* WebURLFoundationExtras in Frameworks */, + D630C3DF2BC61C4900208903 /* PushNotifications in Frameworks */, + D630C3E12BC61C6700208903 /* UserAccounts in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D6A4531029EF64BA00032932 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -970,6 +999,16 @@ path = Shortcuts; sourceTree = ""; }; + D630C3D22BC61B6100208903 /* NotificationExtension */ = { + isa = PBXGroup; + children = ( + D630C3D92BC61B6100208903 /* NotificationExtension.entitlements */, + D630C3D32BC61B6100208903 /* NotificationService.swift */, + D630C3D52BC61B6100208903 /* Info.plist */, + ); + path = NotificationExtension; + sourceTree = ""; + }; D6370B9924421FE00092A7FF /* CoreData */ = { isa = PBXGroup; children = ( @@ -1489,6 +1528,7 @@ D6D4DDEE212518A200E1C4BB /* TuskerUITests */, D6E343A9265AAD6B00C4AA01 /* OpenInTusker */, D6A4531429EF64BA00032932 /* ShareExtension */, + D630C3D22BC61B6100208903 /* NotificationExtension */, D6D4DDCD212518A000E1C4BB /* Products */, D65A37F221472F300087646E /* Frameworks */, ); @@ -1502,6 +1542,7 @@ D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */, D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */, D6A4531329EF64BA00032932 /* ShareExtension.appex */, + D630C3D12BC61B6000208903 /* NotificationExtension.appex */, ); name = Products; sourceTree = ""; @@ -1678,6 +1719,29 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + D630C3D02BC61B6000208903 /* NotificationExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = D630C3DA2BC61B6100208903 /* Build configuration list for PBXNativeTarget "NotificationExtension" */; + buildPhases = ( + D630C3CD2BC61B6000208903 /* Sources */, + D630C3CE2BC61B6000208903 /* Frameworks */, + D630C3CF2BC61B6000208903 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NotificationExtension; + packageProductDependencies = ( + D630C3DE2BC61C4900208903 /* PushNotifications */, + D630C3E02BC61C6700208903 /* UserAccounts */, + D630C3E42BC6313400208903 /* Pachyderm */, + D630C3E62BC6313F00208903 /* WebURLFoundationExtras */, + ); + productName = NotificationExtension; + productReference = D630C3D12BC61B6000208903 /* NotificationExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; D6A4531229EF64BA00032932 /* ShareExtension */ = { isa = PBXNativeTarget; buildConfigurationList = D6A4532229EF64BA00032932 /* Build configuration list for PBXNativeTarget "ShareExtension" */; @@ -1719,6 +1783,7 @@ dependencies = ( D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */, D6A4531C29EF64BA00032932 /* PBXTargetDependency */, + D630C3D72BC61B6100208903 /* PBXTargetDependency */, ); name = Tusker; packageProductDependencies = ( @@ -1804,10 +1869,13 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1430; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1500; ORGANIZATIONNAME = Shadowfacts; TargetAttributes = { + D630C3D02BC61B6000208903 = { + CreatedOnToolsVersion = 15.3; + }; D6A4531229EF64BA00032932 = { CreatedOnToolsVersion = 14.3; }; @@ -1860,11 +1928,19 @@ D6D4DDEA212518A200E1C4BB /* TuskerUITests */, D6E343A7265AAD6B00C4AA01 /* OpenInTusker */, D6A4531229EF64BA00032932 /* ShareExtension */, + D630C3D02BC61B6000208903 /* NotificationExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + D630C3CF2BC61B6000208903 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D6A4531129EF64BA00032932 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1965,6 +2041,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + D630C3CD2BC61B6000208903 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D630C3D42BC61B6100208903 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D6A4530F29EF64BA00032932 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2312,6 +2396,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + D630C3D72BC61B6100208903 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D630C3D02BC61B6000208903 /* NotificationExtension */; + targetProxy = D630C3D62BC61B6100208903 /* PBXContainerItemProxy */; + }; D6A4531C29EF64BA00032932 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D6A4531229EF64BA00032932 /* ShareExtension */; @@ -2370,6 +2459,100 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + D630C3DB2BC61B6100208903 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.NotificationExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D630C3DC2BC61B6100208903 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.NotificationExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + D630C3DD2BC61B6100208903 /* Dist */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.NotificationExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Dist; + }; D63CC705290ECE77000E19DE /* Dist */ = { isa = XCBuildConfiguration; baseConfigurationReference = D63CC703290EC472000E19DE /* Dist.xcconfig */; @@ -2934,6 +3117,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + D630C3DA2BC61B6100208903 /* Build configuration list for PBXNativeTarget "NotificationExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D630C3DB2BC61B6100208903 /* Debug */, + D630C3DC2BC61B6100208903 /* Release */, + D630C3DD2BC61B6100208903 /* Dist */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D6A4532229EF64BA00032932 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3045,6 +3238,23 @@ isa = XCSwiftPackageProductDependency; productName = PushNotifications; }; + D630C3DE2BC61C4900208903 /* PushNotifications */ = { + isa = XCSwiftPackageProductDependency; + productName = PushNotifications; + }; + D630C3E02BC61C6700208903 /* UserAccounts */ = { + isa = XCSwiftPackageProductDependency; + productName = UserAccounts; + }; + D630C3E42BC6313400208903 /* Pachyderm */ = { + isa = XCSwiftPackageProductDependency; + productName = Pachyderm; + }; + D630C3E62BC6313F00208903 /* WebURLFoundationExtras */ = { + isa = XCSwiftPackageProductDependency; + package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */; + productName = WebURLFoundationExtras; + }; D635237029B78A7D009ED5E7 /* TuskerComponents */ = { isa = XCSwiftPackageProductDependency; productName = TuskerComponents; diff --git a/Tusker/Extensions/MainActor+Unsafe.swift b/Tusker/Extensions/MainActor+Unsafe.swift index ee3639b6..d4e4ee5f 100644 --- a/Tusker/Extensions/MainActor+Unsafe.swift +++ b/Tusker/Extensions/MainActor+Unsafe.swift @@ -47,6 +47,10 @@ public extension MainActor { /// /// It will crash if run on any non-main thread. @_unavailableFromAsync + @available(macOS, obsoleted: 14.0) + @available(iOS, obsoleted: 17.0) + @available(watchOS, obsoleted: 10.0) + @available(tvOS, obsoleted: 17.0) static func runUnsafely(_ body: @MainActor () throws -> T) rethrows -> T { if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { return try MainActor.assumeIsolated(body)