Compare commits

..

2 Commits

Author SHA1 Message Date
Shadowfacts ff8a83ca2d Decrypt push notifications 2024-04-09 22:39:58 -04:00
Shadowfacts 4c957b86ae Fix push subscription policy/alerts not persisting 2024-04-09 21:07:14 -04:00
11 changed files with 463 additions and 2 deletions

View File

@ -0,0 +1,22 @@
<?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>TuskerInfo</key>
<dict>
<key>PushProxyHost</key>
<string>$(TUSKER_PUSH_PROXY_HOST)</string>
<key>PushProxyScheme</key>
<string>$(TUSKER_PUSH_PROXY_SCHEME)</string>
<key>SentryDSN</key>
<string>$(SENTRY_DSN)</string>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,14 @@
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(BUNDLE_ID_PREFIX).Tusker</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -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<SHA256>.deriveKey(inputKeyMaterial: pseudoRandomKey, salt: salt, info: contentEncryptionKeyInfo, outputByteCount: 16)
let nonceInfo = info(encoding: "nonce")
let nonce = HKDF<SHA256>.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<T>(_ 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)
}

View File

@ -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
}
}

View File

@ -24,6 +24,9 @@ class DisabledPushManager: _PushManager {
func removeSubscription(account: UserAccountInfo) { func removeSubscription(account: UserAccountInfo) {
} }
func updateSubscription(account: UserAccountInfo, alerts: PushSubscription.Alerts, policy: PushSubscription.Policy) {
}
func pushSubscription(account: UserAccountInfo) -> PushSubscription? { func pushSubscription(account: UserAccountInfo) -> PushSubscription? {
nil nil
} }

View File

@ -47,6 +47,7 @@ public protocol _PushManager {
func createSubscription(account: UserAccountInfo) throws -> PushSubscription func createSubscription(account: UserAccountInfo) throws -> PushSubscription
func removeSubscription(account: UserAccountInfo) func removeSubscription(account: UserAccountInfo)
func updateSubscription(account: UserAccountInfo, alerts: PushSubscription.Alerts, policy: PushSubscription.Policy)
func pushSubscription(account: UserAccountInfo) -> PushSubscription? func pushSubscription(account: UserAccountInfo) -> PushSubscription?
func register(transactionID: UInt64) async throws -> PushProxyRegistration func register(transactionID: UInt64) async throws -> PushProxyRegistration

View File

@ -95,6 +95,16 @@ class PushManagerImpl: _PushManager {
subscriptions.removeAll { $0.accountID == account.id } subscriptions.removeAll { $0.accountID == account.id }
} }
func updateSubscription(account: UserAccountInfo, alerts: PushSubscription.Alerts, policy: PushSubscription.Policy) {
guard let index = subscriptions.firstIndex(where: { $0.accountID == account.id }) else {
return
}
var copy = subscriptions[index]
copy.alerts = alerts
copy.policy = policy
subscriptions[index] = copy
}
func pushSubscription(account: UserAccountInfo) -> PushSubscription? { func pushSubscription(account: UserAccountInfo) -> PushSubscription? {
subscriptions.first { $0.accountID == account.id } subscriptions.first { $0.accountID == account.id }
} }

View File

@ -6,7 +6,7 @@
<true/> <true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.space.vaccor.Tusker</string> <string>group.$(BUNDLE_ID_PREFIX).Tusker</string>
</array> </array>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>

View File

@ -95,6 +95,12 @@
D630C3C82BC43AFD00208903 /* PushNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3C72BC43AFD00208903 /* PushNotifications */; }; D630C3C82BC43AFD00208903 /* PushNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3C72BC43AFD00208903 /* PushNotifications */; };
D630C3CA2BC59FF500208903 /* MastodonController+Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = D630C3C92BC59FF500208903 /* MastodonController+Push.swift */; }; D630C3CA2BC59FF500208903 /* MastodonController+Push.swift in Sources */ = {isa = PBXBuildFile; fileRef = D630C3C92BC59FF500208903 /* MastodonController+Push.swift */; };
D630C3CC2BC5FD4600208903 /* GetAuthorizationTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D630C3CB2BC5FD4600208903 /* GetAuthorizationTokenService.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 */; }; D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; };
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.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 */; }; D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
@ -359,6 +365,13 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
D630C3D62BC61B6100208903 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
proxyType = 1;
remoteGlobalIDString = D630C3D02BC61B6000208903;
remoteInfo = NotificationExtension;
};
D6A4531B29EF64BA00032932 /* PBXContainerItemProxy */ = { D6A4531B29EF64BA00032932 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */; containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
@ -397,6 +410,7 @@
dstSubfolderSpec = 13; dstSubfolderSpec = 13;
files = ( files = (
D6A4531D29EF64BA00032932 /* ShareExtension.appex in Embed Foundation Extensions */, D6A4531D29EF64BA00032932 /* ShareExtension.appex in Embed Foundation Extensions */,
D630C3D82BC61B6100208903 /* NotificationExtension.appex in Embed Foundation Extensions */,
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */, D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */,
); );
name = "Embed Foundation Extensions"; name = "Embed Foundation Extensions";
@ -501,6 +515,10 @@
D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringHelperTests.swift; sourceTree = "<group>"; }; D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringHelperTests.swift; sourceTree = "<group>"; };
D630C3C92BC59FF500208903 /* MastodonController+Push.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonController+Push.swift"; sourceTree = "<group>"; }; D630C3C92BC59FF500208903 /* MastodonController+Push.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonController+Push.swift"; sourceTree = "<group>"; };
D630C3CB2BC5FD4600208903 /* GetAuthorizationTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAuthorizationTokenService.swift; sourceTree = "<group>"; }; D630C3CB2BC5FD4600208903 /* GetAuthorizationTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAuthorizationTokenService.swift; sourceTree = "<group>"; };
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 = "<group>"; };
D630C3D52BC61B6100208903 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D630C3D92BC61B6100208903 /* NotificationExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationExtension.entitlements; sourceTree = "<group>"; };
D6311C4F25B3765B00B27539 /* ImageDataCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDataCache.swift; sourceTree = "<group>"; }; D6311C4F25B3765B00B27539 /* ImageDataCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDataCache.swift; sourceTree = "<group>"; };
D6333B362137838300CE884A /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = "<group>"; }; D6333B362137838300CE884A /* AttributedString+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Helpers.swift"; sourceTree = "<group>"; };
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; }; D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
@ -778,6 +796,17 @@
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase 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 */ = { D6A4531029EF64BA00032932 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -970,6 +999,16 @@
path = Shortcuts; path = Shortcuts;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D630C3D22BC61B6100208903 /* NotificationExtension */ = {
isa = PBXGroup;
children = (
D630C3D92BC61B6100208903 /* NotificationExtension.entitlements */,
D630C3D32BC61B6100208903 /* NotificationService.swift */,
D630C3D52BC61B6100208903 /* Info.plist */,
);
path = NotificationExtension;
sourceTree = "<group>";
};
D6370B9924421FE00092A7FF /* CoreData */ = { D6370B9924421FE00092A7FF /* CoreData */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1489,6 +1528,7 @@
D6D4DDEE212518A200E1C4BB /* TuskerUITests */, D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
D6E343A9265AAD6B00C4AA01 /* OpenInTusker */, D6E343A9265AAD6B00C4AA01 /* OpenInTusker */,
D6A4531429EF64BA00032932 /* ShareExtension */, D6A4531429EF64BA00032932 /* ShareExtension */,
D630C3D22BC61B6100208903 /* NotificationExtension */,
D6D4DDCD212518A000E1C4BB /* Products */, D6D4DDCD212518A000E1C4BB /* Products */,
D65A37F221472F300087646E /* Frameworks */, D65A37F221472F300087646E /* Frameworks */,
); );
@ -1502,6 +1542,7 @@
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */, D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */, D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */,
D6A4531329EF64BA00032932 /* ShareExtension.appex */, D6A4531329EF64BA00032932 /* ShareExtension.appex */,
D630C3D12BC61B6000208903 /* NotificationExtension.appex */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1678,6 +1719,29 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget 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 */ = { D6A4531229EF64BA00032932 /* ShareExtension */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = D6A4532229EF64BA00032932 /* Build configuration list for PBXNativeTarget "ShareExtension" */; buildConfigurationList = D6A4532229EF64BA00032932 /* Build configuration list for PBXNativeTarget "ShareExtension" */;
@ -1719,6 +1783,7 @@
dependencies = ( dependencies = (
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */, D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */,
D6A4531C29EF64BA00032932 /* PBXTargetDependency */, D6A4531C29EF64BA00032932 /* PBXTargetDependency */,
D630C3D72BC61B6100208903 /* PBXTargetDependency */,
); );
name = Tusker; name = Tusker;
packageProductDependencies = ( packageProductDependencies = (
@ -1804,10 +1869,13 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES; BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1430; LastSwiftUpdateCheck = 1530;
LastUpgradeCheck = 1500; LastUpgradeCheck = 1500;
ORGANIZATIONNAME = Shadowfacts; ORGANIZATIONNAME = Shadowfacts;
TargetAttributes = { TargetAttributes = {
D630C3D02BC61B6000208903 = {
CreatedOnToolsVersion = 15.3;
};
D6A4531229EF64BA00032932 = { D6A4531229EF64BA00032932 = {
CreatedOnToolsVersion = 14.3; CreatedOnToolsVersion = 14.3;
}; };
@ -1860,11 +1928,19 @@
D6D4DDEA212518A200E1C4BB /* TuskerUITests */, D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
D6E343A7265AAD6B00C4AA01 /* OpenInTusker */, D6E343A7265AAD6B00C4AA01 /* OpenInTusker */,
D6A4531229EF64BA00032932 /* ShareExtension */, D6A4531229EF64BA00032932 /* ShareExtension */,
D630C3D02BC61B6000208903 /* NotificationExtension */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
D630C3CF2BC61B6000208903 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D6A4531129EF64BA00032932 /* Resources */ = { D6A4531129EF64BA00032932 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1965,6 +2041,14 @@
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
D630C3CD2BC61B6000208903 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D630C3D42BC61B6100208903 /* NotificationService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D6A4530F29EF64BA00032932 /* Sources */ = { D6A4530F29EF64BA00032932 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -2312,6 +2396,11 @@
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
D630C3D72BC61B6100208903 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D630C3D02BC61B6000208903 /* NotificationExtension */;
targetProxy = D630C3D62BC61B6100208903 /* PBXContainerItemProxy */;
};
D6A4531C29EF64BA00032932 /* PBXTargetDependency */ = { D6A4531C29EF64BA00032932 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = D6A4531229EF64BA00032932 /* ShareExtension */; target = D6A4531229EF64BA00032932 /* ShareExtension */;
@ -2370,6 +2459,100 @@
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration 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 */ = { D63CC705290ECE77000E19DE /* Dist */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = D63CC703290EC472000E19DE /* Dist.xcconfig */; baseConfigurationReference = D63CC703290EC472000E19DE /* Dist.xcconfig */;
@ -2934,6 +3117,16 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList 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" */ = { D6A4532229EF64BA00032932 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
@ -3045,6 +3238,23 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = PushNotifications; 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 */ = { D635237029B78A7D009ED5E7 /* TuskerComponents */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = TuskerComponents; productName = TuskerComponents;

View File

@ -47,6 +47,10 @@ public extension MainActor {
/// ///
/// It will crash if run on any non-main thread. /// It will crash if run on any non-main thread.
@_unavailableFromAsync @_unavailableFromAsync
@available(macOS, obsoleted: 14.0)
@available(iOS, obsoleted: 17.0)
@available(watchOS, obsoleted: 10.0)
@available(tvOS, obsoleted: 17.0)
static func runUnsafely<T>(_ body: @MainActor () throws -> T) rethrows -> T { static func runUnsafely<T>(_ body: @MainActor () throws -> T) rethrows -> T {
if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
return try MainActor.assumeIsolated(body) return try MainActor.assumeIsolated(body)

View File

@ -108,6 +108,7 @@ struct PushInstanceSettingsView: View {
do { do {
let result = try await mastodonController.updatePushSubscription(alerts: alerts, policy: policy) let result = try await mastodonController.updatePushSubscription(alerts: alerts, policy: policy)
PushManager.logger.debug("Push subscription \(result.id) updated on \(account.instanceURL)") PushManager.logger.debug("Push subscription \(result.id) updated on \(account.instanceURL)")
await PushManager.shared.updateSubscription(account: account, alerts: alerts, policy: policy)
subscription?.alerts = alerts subscription?.alerts = alerts
subscription?.policy = policy subscription?.policy = policy
return true return true