forked from shadowfacts/Tusker
Decrypt push notifications
This commit is contained in:
parent
4c957b86ae
commit
ff8a83ca2d
|
@ -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>
|
|
@ -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>
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue