diff --git a/AppShared/AppName.swift b/AppShared/AppName.swift new file mode 100644 index 00000000..9dbca78d --- /dev/null +++ b/AppShared/AppName.swift @@ -0,0 +1,12 @@ +// +// AppName.swift +// AppShared +// +// Created by MainasuK Cirno on 2021-4-27. +// + +import Foundation + +public enum AppName { + public static let groupID = "group.org.joinmastodon.mastodon-temp" +} diff --git a/AppShared/AppSecret.swift b/AppShared/AppSecret.swift new file mode 100644 index 00000000..e2305ef1 --- /dev/null +++ b/AppShared/AppSecret.swift @@ -0,0 +1,103 @@ +// +// AppSecret.swift +// AppShared +// +// Created by MainasuK Cirno on 2021-4-27. +// + + +import Foundation +import CryptoKit +import KeychainAccess +import Keys + +public final class AppSecret { + + public static let keychain = Keychain(service: "org.joinmastodon.Mastodon.keychain", accessGroup: AppName.groupID) + + static let notificationPrivateKeyName = "notification-private-key-base64" + static let notificationAuthName = "notification-auth-base64" + + public let notificationEndpoint: String + + public var notificationPrivateKey: P256.KeyAgreement.PrivateKey { + AppSecret.createOrFetchNotificationPrivateKey() + } + public var notificationPublicKey: P256.KeyAgreement.PublicKey { + notificationPrivateKey.publicKey + } + public var notificationAuth: Data { + AppSecret.createOrFetchNotificationAuth() + } + + public static let `default`: AppSecret = { + return AppSecret() + }() + + init() { + let keys = MastodonKeys() + + #if DEBUG + self.notificationEndpoint = keys.notification_endpoint_debug + #else + self.notificationEndpoint = keys.notification_endpoint + #endif + } + + public func register() { + _ = AppSecret.createOrFetchNotificationPrivateKey() + _ = AppSecret.createOrFetchNotificationAuth() + } + +} + +extension AppSecret { + + private static func createOrFetchNotificationPrivateKey() -> P256.KeyAgreement.PrivateKey { + if let encoded = AppSecret.keychain[AppSecret.notificationPrivateKeyName], + let data = Data(base64Encoded: encoded) { + do { + let privateKey = try P256.KeyAgreement.PrivateKey(rawRepresentation: data) + return privateKey + } catch { + assertionFailure() + return AppSecret.resetNotificationPrivateKey() + } + } else { + return AppSecret.resetNotificationPrivateKey() + } + } + + private static func resetNotificationPrivateKey() -> P256.KeyAgreement.PrivateKey { + let privateKey = P256.KeyAgreement.PrivateKey() + keychain[AppSecret.notificationPrivateKeyName] = privateKey.rawRepresentation.base64EncodedString() + return privateKey + } + +} + +extension AppSecret { + + private static func createOrFetchNotificationAuth() -> Data { + if let encoded = keychain[AppSecret.notificationAuthName], + let data = Data(base64Encoded: encoded) { + return data + } else { + return AppSecret.resetNotificationAuth() + } + } + + private static func resetNotificationAuth() -> Data { + let auth = AppSecret.createRandomAuthBytes() + keychain[AppSecret.notificationAuthName] = auth.base64EncodedString() + return auth + } + + private static func createRandomAuthBytes() -> Data { + let byteCount = 16 + var bytes = Data(count: byteCount) + _ = bytes.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, byteCount, $0.baseAddress!) } + return bytes + } + +} diff --git a/AppShared/AppShared.h b/AppShared/AppShared.h new file mode 100644 index 00000000..3258d4fc --- /dev/null +++ b/AppShared/AppShared.h @@ -0,0 +1,18 @@ +// +// AppShared.h +// AppShared +// +// Created by MainasuK Cirno on 2021-4-27. +// + +#import + +//! Project version number for AppShared. +FOUNDATION_EXPORT double AppSharedVersionNumber; + +//! Project version string for AppShared. +FOUNDATION_EXPORT const unsigned char AppSharedVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/AppShared/Info.plist b/AppShared/Info.plist new file mode 100644 index 00000000..9bcb2444 --- /dev/null +++ b/AppShared/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/AppShared/UserDefaults.swift b/AppShared/UserDefaults.swift new file mode 100644 index 00000000..9cecdcf6 --- /dev/null +++ b/AppShared/UserDefaults.swift @@ -0,0 +1,12 @@ +// +// UserDefaults.swift +// AppShared +// +// Created by MainasuK Cirno on 2021-4-27. +// + +import UIKit + +extension UserDefaults { + public static let shared = UserDefaults(suiteName: AppName.groupID)! +} diff --git a/CoreDataStack/CoreDataStack.swift b/CoreDataStack/CoreDataStack.swift index 766bcf4d..64bf9c85 100644 --- a/CoreDataStack/CoreDataStack.swift +++ b/CoreDataStack/CoreDataStack.swift @@ -8,6 +8,7 @@ import os import Foundation import CoreData +import AppShared public final class CoreDataStack { @@ -18,7 +19,7 @@ public final class CoreDataStack { } public convenience init(databaseName: String = "shared") { - let storeURL = URL.storeURL(for: AppSharedName.groupID, databaseName: databaseName) + let storeURL = URL.storeURL(for: AppName.groupID, databaseName: databaseName) let storeDescription = NSPersistentStoreDescription(url: storeURL) self.init(persistentStoreDescriptions: [storeDescription]) } diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 512c8993..37b92876 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -167,6 +167,7 @@ 5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; }; 7D603B3DC600BB75956FAD7D /* Pods_Mastodon_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79D7EB88A6A8B34DFDFC96FC /* Pods_Mastodon_NotificationService.framework */; }; 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; }; + D86919F5080C3F228CCD17D1 /* Pods_Mastodon_AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46531DECCAB422F507B2274D /* Pods_Mastodon_AppShared.framework */; }; DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; }; DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */; }; DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */; }; @@ -180,7 +181,6 @@ DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; }; DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; }; DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; }; - DB1E05E1263180F500201847 /* AppSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E05E0263180F500201847 /* AppSecret.swift */; }; DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */; }; DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E347725F519300079D7DF /* PickServerItem.swift */; }; DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; }; @@ -248,6 +248,21 @@ DB66728C25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */; }; DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; }; DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; }; + DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; + DB6804662636DC9000430867 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; + DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; + DB6804832637CD4C00430867 /* AppShared.h in Headers */ = {isa = PBXBuildFile; fileRef = DB6804812637CD4C00430867 /* AppShared.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; + DB6804872637CD4C00430867 /* AppShared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DB6804922637CD8700430867 /* AppName.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804912637CD8700430867 /* AppName.swift */; }; + DB6804A52637CDCC00430867 /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; + DB6804A62637CDCC00430867 /* AppShared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DB6804C82637CE2F00430867 /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; + DB6804D12637CE4700430867 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804D02637CE4700430867 /* UserDefaults.swift */; }; + DB6804FD2637CFEC00430867 /* AppSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6804FC2637CFEC00430867 /* AppSecret.swift */; }; + DB6805102637D0F800430867 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = DB68050F2637D0F800430867 /* KeychainAccess */; }; + DB6805262637D7DD00430867 /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; + DB6805272637D7DD00430867 /* AppShared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */; }; DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68A04925E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift */; }; DB68A05D25E9055900CFDF14 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DB68A05C25E9055900CFDF14 /* Settings.bundle */; }; @@ -256,13 +271,9 @@ DB6B351E2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */; }; DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */; }; DB6D1B24263684C600ACB481 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; - DB6D1B2B2636852000ACB481 /* AppSharedName.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B2A2636852000ACB481 /* AppSharedName.swift */; }; - DB6D1B312636853100ACB481 /* AppSharedName.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B2A2636852000ACB481 /* AppSharedName.swift */; }; DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */; }; DB6D1B44263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */; }; - DB6D9F232635195E008423CD /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F222635195E008423CD /* String.swift */; }; DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */; }; - DB6D9F3B26352019008423CD /* AppSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E05E0263180F500201847 /* AppSecret.swift */; }; DB6D9F42263527CE008423CD /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB6D9F41263527CE008423CD /* AlamofireImage */; }; DB6D9F4926353FD7008423CD /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F4826353FD6008423CD /* Subscription.swift */; }; DB6D9F502635761F008423CD /* SubscriptionAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F4F2635761F008423CD /* SubscriptionAlerts.swift */; }; @@ -392,7 +403,6 @@ DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */; }; DBE3CE0D261D767100430CC6 /* FavoriteViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE0C261D767100430CC6 /* FavoriteViewController+StatusProvider.swift */; }; DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */; }; - DBE54AB92636C87B004E7C0B /* AppSharedName.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B2A2636852000ACB481 /* AppSharedName.swift */; }; DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; @@ -419,6 +429,34 @@ remoteGlobalIDString = DB427DD125BAA00100D1B89D; remoteInfo = Mastodon; }; + DB6804842637CD4C00430867 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; + proxyType = 1; + remoteGlobalIDString = DB68047E2637CD4C00430867; + remoteInfo = AppShared; + }; + DB6804A72637CDCC00430867 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; + proxyType = 1; + remoteGlobalIDString = DB68047E2637CD4C00430867; + remoteInfo = AppShared; + }; + DB6804C92637CE3000430867 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; + proxyType = 1; + remoteGlobalIDString = DB68047E2637CD4C00430867; + remoteInfo = AppShared; + }; + DB6805282637D7DD00430867 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; + proxyType = 1; + remoteGlobalIDString = DB68047E2637CD4C00430867; + remoteInfo = AppShared; + }; DB89B9F825C10FD0008580ED /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; @@ -450,12 +488,35 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + DB6804A92637CDCC00430867 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + DB6804A62637CDCC00430867 /* AppShared.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + DB68052A2637D7DD00430867 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + DB6805272637D7DD00430867 /* AppShared.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; DB89BA0825C10FD0008580ED /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( + DB6804872637CD4C00430867 /* AppShared.framework in Embed Frameworks */, DBE64A8C260C49D200E6359A /* TwitterTextEditor in Embed Frameworks */, DB89BA0425C10FD0008580ED /* CoreDataStack.framework in Embed Frameworks */, ); @@ -608,6 +669,7 @@ 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_MastodonUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.debug.xcconfig"; sourceTree = ""; }; + 46531DECCAB422F507B2274D /* Pods_Mastodon_AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5B90C456262599800002E742 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; 5B90C459262599800002E742 /* SettingsToggleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsToggleTableViewCell.swift; sourceTree = ""; }; 5B90C45A262599800002E742 /* SettingsAppearanceTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppearanceTableViewCell.swift; sourceTree = ""; }; @@ -636,9 +698,11 @@ 8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.debug.xcconfig"; sourceTree = ""; }; 9780A4C98FFC65B32B50D1C0 /* Pods-MastodonTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release.xcconfig"; sourceTree = ""; }; A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B31D44635FCF6452F7E1B865 /* Pods-Mastodon-AppShared.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.release.xcconfig"; sourceTree = ""; }; B44342AC2B6585F8295F1DDF /* Pods-Mastodon-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.release.xcconfig"; sourceTree = ""; }; BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.release.xcconfig"; sourceTree = ""; }; CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = ""; }; DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewController.swift; sourceTree = ""; }; DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift; sourceTree = ""; }; DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModel.swift; sourceTree = ""; }; @@ -650,7 +714,6 @@ DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = ""; }; DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableView.swift; sourceTree = ""; }; - DB1E05E0263180F500201847 /* AppSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSecret.swift; sourceTree = ""; }; DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = ""; }; DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = ""; }; DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = ""; }; @@ -724,6 +787,14 @@ DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+Diffable.swift"; sourceTree = ""; }; DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = ""; }; DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = ""; }; + DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = ""; }; + DB68047F2637CD4C00430867 /* AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DB6804812637CD4C00430867 /* AppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppShared.h; sourceTree = ""; }; + DB6804822637CD4C00430867 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DB6804912637CD8700430867 /* AppName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppName.swift; sourceTree = ""; }; + DB6804D02637CE4700430867 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; + DB6804FC2637CFEC00430867 /* AppSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSecret.swift; sourceTree = ""; }; + DB68053E2638011000430867 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSKeyValueObservation.swift; sourceTree = ""; }; DB68A04925E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveStatusBarStyleNavigationController.swift; sourceTree = ""; }; DB68A05C25E9055900CFDF14 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; @@ -732,10 +803,8 @@ DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentCollectionViewCell.swift; sourceTree = ""; }; DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error.swift"; sourceTree = ""; }; DB6D1B23263684C600ACB481 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; - DB6D1B2A2636852000ACB481 /* AppSharedName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSharedName.swift; sourceTree = ""; }; DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePreference.swift; sourceTree = ""; }; DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+API+Subscriptions+Policy.swift"; sourceTree = ""; }; - DB6D9F222635195E008423CD /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationService+Decrypt.swift"; sourceTree = ""; }; DB6D9F4826353FD6008423CD /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; DB6D9F4F2635761F008423CD /* SubscriptionAlerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAlerts.swift; sourceTree = ""; }; @@ -888,6 +957,7 @@ 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */, DBB525082611EAC0002F1F29 /* Tabman in Frameworks */, 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */, + DB6804862637CD4C00430867 /* AppShared.framework in Frameworks */, DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */, DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */, 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, @@ -895,6 +965,7 @@ DBE64A8B260C49D200E6359A /* TwitterTextEditor in Frameworks */, 2D5981BA25E4D7F8000FB903 /* ThirdPartyMailer in Frameworks */, 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */, + DB6804C82637CE2F00430867 /* AppShared.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -915,10 +986,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DB68047C2637CD4C00430867 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DB6805102637D0F800430867 /* KeychainAccess in Frameworks */, + D86919F5080C3F228CCD17D1 /* Pods_Mastodon_AppShared.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DB89B9EB25C10FD0008580ED /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DB6805262637D7DD00430867 /* AppShared.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -937,6 +1018,7 @@ DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */, DB6D9F42263527CE008423CD /* AlamofireImage in Frameworks */, DBF8AE862632992800C9C23C /* Base85 in Frameworks */, + DB6804A52637CDCC00430867 /* AppShared.framework in Frameworks */, 7D603B3DC600BB75956FAD7D /* Pods_Mastodon_NotificationService.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1021,6 +1103,8 @@ 9780A4C98FFC65B32B50D1C0 /* Pods-MastodonTests.release.xcconfig */, 8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */, B44342AC2B6585F8295F1DDF /* Pods-Mastodon-NotificationService.release.xcconfig */, + D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */, + B31D44635FCF6452F7E1B865 /* Pods-Mastodon-AppShared.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1351,6 +1435,7 @@ 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */, 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */, 79D7EB88A6A8B34DFDFC96FC /* Pods_Mastodon_NotificationService.framework */, + 46531DECCAB422F507B2274D /* Pods_Mastodon_AppShared.framework */, ); name = Frameworks; sourceTree = ""; @@ -1453,8 +1538,6 @@ children = ( DB427DD525BAA00100D1B89D /* AppDelegate.swift */, DB427DD725BAA00100D1B89D /* SceneDelegate.swift */, - DB1E05E0263180F500201847 /* AppSecret.swift */, - DB6D1B2A2636852000ACB481 /* AppSharedName.swift */, DB427DDB25BAA00100D1B89D /* Main.storyboard */, DB427DE025BAA00100D1B89D /* LaunchScreen.storyboard */, DB68A05C25E9055900CFDF14 /* Settings.bundle */, @@ -1485,6 +1568,7 @@ DB89B9EF25C10FD0008580ED /* CoreDataStack */, DB89B9FC25C10FD0008580ED /* CoreDataStackTests */, DBF8AE14263293E400C9C23C /* NotificationService */, + DB6804802637CD4C00430867 /* AppShared */, DB427DD325BAA00100D1B89D /* Products */, 1EBA4F56E920856A3FC84ACB /* Pods */, 3FE14AD363ED19AE7FF210A6 /* Frameworks */, @@ -1501,6 +1585,7 @@ DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */, DB89B9F625C10FD0008580ED /* CoreDataStackTests.xctest */, DBF8AE13263293E400C9C23C /* NotificationService.appex */, + DB68047F2637CD4C00430867 /* AppShared.framework */, ); name = Products; sourceTree = ""; @@ -1628,6 +1713,18 @@ path = View; sourceTree = ""; }; + DB6804802637CD4C00430867 /* AppShared */ = { + isa = PBXGroup; + children = ( + DB6804812637CD4C00430867 /* AppShared.h */, + DB6804822637CD4C00430867 /* Info.plist */, + DB6804912637CD8700430867 /* AppName.swift */, + DB6804FC2637CFEC00430867 /* AppSecret.swift */, + DB6804D02637CE4700430867 /* UserDefaults.swift */, + ); + path = AppShared; + sourceTree = ""; + }; DB68A03825E900CC00CFDF14 /* Share */ = { isa = PBXGroup; children = ( @@ -1659,14 +1756,6 @@ path = MastodonSDK; sourceTree = ""; }; - DB6D9F2926351961008423CD /* Extension */ = { - isa = PBXGroup; - children = ( - DB6D9F222635195E008423CD /* String.swift */, - ); - path = Extension; - sourceTree = ""; - }; DB72602125E36A2500235243 /* ServerRules */ = { isa = PBXGroup; children = ( @@ -2108,9 +2197,10 @@ DBF8AE14263293E400C9C23C /* NotificationService */ = { isa = PBXGroup; children = ( + DB68053E2638011000430867 /* NotificationService.entitlements */, DBF8AE15263293E400C9C23C /* NotificationService.swift */, DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */, - DB6D9F2926351961008423CD /* Extension */, + DB68045A2636DC6A00430867 /* MastodonNotification.swift */, DBF8AE17263293E400C9C23C /* Info.plist */, ); path = NotificationService; @@ -2119,6 +2209,14 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + DB68047A2637CD4C00430867 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DB6804832637CD4C00430867 /* AppShared.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DB89B9E925C10FD0008580ED /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -2148,6 +2246,8 @@ dependencies = ( DB89BA0225C10FD0008580ED /* PBXTargetDependency */, DBF8AE19263293E400C9C23C /* PBXTargetDependency */, + DB6804852637CD4C00430867 /* PBXTargetDependency */, + DB6804CA2637CE3000430867 /* PBXTargetDependency */, ); name = Mastodon; packageProductDependencies = ( @@ -2206,6 +2306,28 @@ productReference = DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + DB68047E2637CD4C00430867 /* AppShared */ = { + isa = PBXNativeTarget; + buildConfigurationList = DB6804882637CD4C00430867 /* Build configuration list for PBXNativeTarget "AppShared" */; + buildPhases = ( + C6B7D3A8ACD77F6620D0E0AD /* [CP] Check Pods Manifest.lock */, + DB68047A2637CD4C00430867 /* Headers */, + DB68047B2637CD4C00430867 /* Sources */, + DB68047C2637CD4C00430867 /* Frameworks */, + DB68047D2637CD4C00430867 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppShared; + packageProductDependencies = ( + DB68050F2637D0F800430867 /* KeychainAccess */, + ); + productName = AppShared; + productReference = DB68047F2637CD4C00430867 /* AppShared.framework */; + productType = "com.apple.product-type.framework"; + }; DB89B9ED25C10FD0008580ED /* CoreDataStack */ = { isa = PBXNativeTarget; buildConfigurationList = DB89BA0525C10FD0008580ED /* Build configuration list for PBXNativeTarget "CoreDataStack" */; @@ -2214,10 +2336,12 @@ DB89B9EA25C10FD0008580ED /* Sources */, DB89B9EB25C10FD0008580ED /* Frameworks */, DB89B9EC25C10FD0008580ED /* Resources */, + DB68052A2637D7DD00430867 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + DB6805292637D7DD00430867 /* PBXTargetDependency */, ); name = CoreDataStack; productName = CoreDataStack; @@ -2251,10 +2375,12 @@ DBF8AE0F263293E400C9C23C /* Sources */, DBF8AE10263293E400C9C23C /* Frameworks */, DBF8AE11263293E400C9C23C /* Resources */, + DB6804A92637CDCC00430867 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + DB6804A82637CDCC00430867 /* PBXTargetDependency */, ); name = NotificationService; packageProductDependencies = ( @@ -2287,6 +2413,10 @@ CreatedOnToolsVersion = 12.4; TestTargetID = DB427DD125BAA00100D1B89D; }; + DB68047E2637CD4C00430867 = { + CreatedOnToolsVersion = 12.4; + LastSwiftMigration = 1240; + }; DB89B9ED25C10FD0008580ED = { CreatedOnToolsVersion = 12.4; LastSwiftMigration = 1240; @@ -2321,6 +2451,7 @@ DBE64A89260C49D200E6359A /* XCRemoteSwiftPackageReference "TwitterTextEditor" */, DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */, DBF8AE842632992700C9C23C /* XCRemoteSwiftPackageReference "Base85" */, + DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */, ); productRefGroup = DB427DD325BAA00100D1B89D /* Products */; projectDirPath = ""; @@ -2332,6 +2463,7 @@ DB89B9ED25C10FD0008580ED /* CoreDataStack */, DB89B9F525C10FD0008580ED /* CoreDataStackTests */, DBF8AE12263293E400C9C23C /* NotificationService */, + DB68047E2637CD4C00430867 /* AppShared */, ); }; /* End PBXProject section */ @@ -2365,6 +2497,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DB68047D2637CD4C00430867 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DB89B9EC25C10FD0008580ED /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2472,6 +2611,28 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + C6B7D3A8ACD77F6620D0E0AD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Mastodon-AppShared-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; DB3D100425BAA71500EAA174 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2772,6 +2933,7 @@ DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */, DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */, DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */, + DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */, DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */, DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */, DB6D9F57263577D2008423CD /* APIService+CoreData+Setting.swift in Sources */, @@ -2780,7 +2942,6 @@ DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */, DB98338725C945ED00AD9700 /* Strings.swift in Sources */, 2D7867192625B77500211898 /* NotificationItem.swift in Sources */, - DB6D1B2B2636852000ACB481 /* AppSharedName.swift in Sources */, DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */, DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */, 2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */, @@ -2853,7 +3014,6 @@ 2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */, DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */, DBCC3B89261454BA0045B23D /* CGImage.swift in Sources */, - DB1E05E1263180F500201847 /* AppSecret.swift in Sources */, DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */, DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToStatusContentCollectionViewCell.swift in Sources */, DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */, @@ -2877,6 +3037,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DB68047B2637CD4C00430867 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DB6804D12637CE4700430867 /* UserDefaults.swift in Sources */, + DB6804922637CD8700430867 /* AppName.swift in Sources */, + DB6804FD2637CFEC00430867 /* AppSecret.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DB89B9EA25C10FD0008580ED /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2898,7 +3068,6 @@ 2D152A9225C2980C009AA50C /* UIFont.swift in Sources */, DB4481B325EE16D000BEFB67 /* PollOption.swift in Sources */, DB89BA4425C1165F008580ED /* Managed.swift in Sources */, - DB6D1B312636853100ACB481 /* AppSharedName.swift in Sources */, 2D6125472625436B00299647 /* Notification.swift in Sources */, DB89BA4325C1165F008580ED /* NetworkUpdatable.swift in Sources */, DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */, @@ -2926,11 +3095,10 @@ buildActionMask = 2147483647; files = ( DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */, - DB6D9F232635195E008423CD /* String.swift in Sources */, - DBE54AB92636C87B004E7C0B /* AppSharedName.swift in Sources */, + DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */, DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */, - DB6D9F3B26352019008423CD /* AppSecret.swift in Sources */, DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */, + DB6804662636DC9000430867 /* String.swift in Sources */, DBF8AE16263293E400C9C23C /* NotificationService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2948,6 +3116,26 @@ target = DB427DD125BAA00100D1B89D /* Mastodon */; targetProxy = DB427DF425BAA00100D1B89D /* PBXContainerItemProxy */; }; + DB6804852637CD4C00430867 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DB68047E2637CD4C00430867 /* AppShared */; + targetProxy = DB6804842637CD4C00430867 /* PBXContainerItemProxy */; + }; + DB6804A82637CDCC00430867 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DB68047E2637CD4C00430867 /* AppShared */; + targetProxy = DB6804A72637CDCC00430867 /* PBXContainerItemProxy */; + }; + DB6804CA2637CE3000430867 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DB68047E2637CD4C00430867 /* AppShared */; + targetProxy = DB6804C92637CE3000430867 /* PBXContainerItemProxy */; + }; + DB6805292637D7DD00430867 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DB68047E2637CD4C00430867 /* AppShared */; + targetProxy = DB6805282637D7DD00430867 /* PBXContainerItemProxy */; + }; DB89B9F925C10FD0008580ED /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DB89B9ED25C10FD0008580ED /* CoreDataStack */; @@ -3126,7 +3314,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -3154,7 +3341,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -3259,6 +3445,65 @@ }; name = Release; }; + DB6804892637CD4C00430867 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 7LFDZ96332; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = AppShared/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.Mastodon.AppShared; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + DB68048A2637CD4C00430867 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B31D44635FCF6452F7E1B865 /* Pods-Mastodon-AppShared.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 7LFDZ96332; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = AppShared/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.Mastodon.AppShared; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; DB89BA0625C10FD0008580ED /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3360,10 +3605,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = 8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = NotificationService/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3381,10 +3626,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = B44342AC2B6585F8295F1DDF /* Pods-Mastodon-NotificationService.release.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 7LFDZ96332; INFOPLIST_FILE = NotificationService/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3437,6 +3682,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DB6804882637CD4C00430867 /* Build configuration list for PBXNativeTarget "AppShared" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DB6804892637CD4C00430867 /* Debug */, + DB68048A2637CD4C00430867 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DB89BA0525C10FD0008580ED /* Build configuration list for PBXNativeTarget "CoreDataStack" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3523,6 +3777,14 @@ minimumVersion = 6.1.0; }; }; + DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.2.2; + }; + }; DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/MainasuK/UITextView-Placeholder"; @@ -3602,6 +3864,11 @@ package = DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = Kingfisher; }; + DB68050F2637D0F800430867 /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; DB6D9F41263527CE008423CD /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 3e5f0c5d..083bcfbb 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,10 +4,15 @@ SchemeUserState + AppShared.xcscheme_^#shared#^_ + + orderHint + 18 + CoreDataStack.xcscheme_^#shared#^_ orderHint - 13 + 17 Mastodon - RTL.xcscheme_^#shared#^_ @@ -27,7 +32,7 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 14 + 18 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index a8c0df34..47136a2c 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -55,6 +55,15 @@ "version": "0.1.1" } }, + { + "package": "KeychainAccess", + "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git", + "state": { + "branch": null, + "revision": "84e546727d66f1adc5439debad16270d0fdd04e7", + "version": "4.2.2" + } + }, { "package": "Kingfisher", "repositoryURL": "https://github.com/onevcat/Kingfisher.git", diff --git a/Mastodon/Extension/String.swift b/Mastodon/Extension/String.swift index 87028ffd..bf70c893 100644 --- a/Mastodon/Extension/String.swift +++ b/Mastodon/Extension/String.swift @@ -16,3 +16,25 @@ extension String { self = self.capitalizingFirstLetter() } } + +extension String { + static func normalize(base64String: String) -> String { + let base64 = base64String + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + .padding() + return base64 + } + + private func padding() -> String { + let remainder = self.count % 4 + if remainder > 0 { + return self.padding( + toLength: self.count + 4 - remainder, + withPad: "=", + startingAt: 0 + ) + } + return self + } +} diff --git a/Mastodon/Extension/UserDefaults.swift b/Mastodon/Extension/UserDefaults.swift index 5e067bbe..619d6c25 100644 --- a/Mastodon/Extension/UserDefaults.swift +++ b/Mastodon/Extension/UserDefaults.swift @@ -6,10 +6,7 @@ // import Foundation - -extension UserDefaults { - static let shared = UserDefaults(suiteName: AppSharedName.groupID)! -} +import AppShared extension UserDefaults { diff --git a/Mastodon/Preference/AppearancePreference.swift b/Mastodon/Preference/AppearancePreference.swift index 8f2818c3..78cf3d33 100644 --- a/Mastodon/Preference/AppearancePreference.swift +++ b/Mastodon/Preference/AppearancePreference.swift @@ -14,7 +14,7 @@ extension UserDefaults { register(defaults: [#function: UIUserInterfaceStyle.unspecified.rawValue]) return UIUserInterfaceStyle(rawValue: integer(forKey: #function)) ?? .unspecified } - set { UserDefaults.shared[#function] = newValue.rawValue } + set { self[#function] = newValue.rawValue } } } diff --git a/Mastodon/Preference/NotificationPreference.swift b/Mastodon/Preference/NotificationPreference.swift index b634d77f..289cd1fd 100644 --- a/Mastodon/Preference/NotificationPreference.swift +++ b/Mastodon/Preference/NotificationPreference.swift @@ -14,7 +14,7 @@ extension UserDefaults { register(defaults: [#function: 0]) return integer(forKey: #function) } - set { UserDefaults.shared[#function] = newValue } + set { self[#function] = newValue } } } diff --git a/Mastodon/Service/APIService/APIService+Subscriptions.swift b/Mastodon/Service/APIService/APIService+Subscriptions.swift index 3e2d2a0a..ceaff45f 100644 --- a/Mastodon/Service/APIService/APIService+Subscriptions.swift +++ b/Mastodon/Service/APIService/APIService+Subscriptions.swift @@ -29,7 +29,7 @@ extension APIService { query: query ) .flatMap { response -> AnyPublisher, Error> in - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: create subscription successful ", ((#file as NSString).lastPathComponent), #line, #function) + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: create subscription successful %s", ((#file as NSString).lastPathComponent), #line, #function, response.value.endpoint) let managedObjectContext = self.backgroundManagedObjectContext return managedObjectContext.performChanges { @@ -45,7 +45,8 @@ extension APIService { .setFailureType(to: Error.self) .map { _ in return response } .eraseToAnyPublisher() - }.eraseToAnyPublisher() + } + .eraseToAnyPublisher() } func cancelSubscription( diff --git a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Setting.swift b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Setting.swift index fb6879da..0c23eab6 100644 --- a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Setting.swift +++ b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Setting.swift @@ -27,35 +27,50 @@ extension APIService.CoreData { }() if let oldSetting = oldSetting { + setupSettingSubscriptions(managedObjectContext: managedObjectContext, setting: oldSetting) return (oldSetting, false) } else { let setting = Setting.insert( into: managedObjectContext, property: property ) - let policies: [Mastodon.API.Subscriptions.Policy] = [ - .all, - .followed, - .follower, - .none - ] - let now = Date() - policies.forEach { policy in - let (subscription, _) = createOrFetchSubscription( - into: managedObjectContext, - setting: setting, - policy: policy - ) - if policy == .all { - subscription.update(activedAt: now) - } else { - subscription.update(activedAt: now.addingTimeInterval(-10)) - } - } - - + setupSettingSubscriptions(managedObjectContext: managedObjectContext, setting: setting) return (setting, true) } } } + +extension APIService.CoreData { + + static func setupSettingSubscriptions( + managedObjectContext: NSManagedObjectContext, + setting: Setting + ) { + guard (setting.subscriptions ?? Set()).isEmpty else { return } + + let now = Date() + let policies: [Mastodon.API.Subscriptions.Policy] = [ + .all, + .followed, + .follower, + .none + ] + policies.forEach { policy in + let (subscription, _) = createOrFetchSubscription( + into: managedObjectContext, + setting: setting, + policy: policy + ) + if policy == .all { + subscription.update(activedAt: now) + } else { + subscription.update(activedAt: now.addingTimeInterval(-10)) + } + } + + // trigger setting update + setting.didUpdate(at: now) + } + +} diff --git a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Subscriptions.swift b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Subscriptions.swift index 5e42a8ab..6eebc9e5 100644 --- a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Subscriptions.swift +++ b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Subscriptions.swift @@ -27,6 +27,7 @@ extension APIService.CoreData { }() if let oldSubscription = oldSubscription { + oldSubscription.setting = setting return (oldSubscription, false) } else { let subscriptionProperty = Subscription.Property(policyRaw: policy.rawValue) diff --git a/Mastodon/Service/NotificationService.swift b/Mastodon/Service/NotificationService.swift index 526c3588..90680e78 100644 --- a/Mastodon/Service/NotificationService.swift +++ b/Mastodon/Service/NotificationService.swift @@ -11,6 +11,7 @@ import Combine import CoreData import CoreDataStack import MastodonSDK +import AppShared final class NotificationService { @@ -32,6 +33,16 @@ final class NotificationService { ) { self.authenticationService = authenticationService + authenticationService.mastodonAuthentications + .sink(receiveValue: { [weak self] mastodonAuthentications in + guard let self = self else { return } + + // request permission when sign-in + guard !mastodonAuthentications.isEmpty else { return } + self.requestNotificationPermission() + }) + .store(in: &disposeBag) + deviceToken .receive(on: DispatchQueue.main) .sink { [weak self] deviceToken in @@ -83,13 +94,7 @@ extension NotificationService { } return _notificationSubscription } - - static func createRandomAuthBytes() -> Data { - let byteCount = 16 - var bytes = Data(count: byteCount) - _ = bytes.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, byteCount, $0.baseAddress!) } - return bytes - } + } extension NotificationService { @@ -120,7 +125,7 @@ extension NotificationService.NotificationViewModel { let appSecret = AppSecret.default let endpoint = appSecret.notificationEndpoint + "/" + deviceToken - let p256dh = appSecret.uncompressionNotificationPublicKeyData + let p256dh = appSecret.notificationPublicKey.x963Representation let auth = appSecret.notificationAuth let query = Mastodon.API.Subscriptions.CreateSubscriptionQuery( diff --git a/Mastodon/Service/SettingService.swift b/Mastodon/Service/SettingService.swift index e097b4dc..8683b397 100644 --- a/Mastodon/Service/SettingService.swift +++ b/Mastodon/Service/SettingService.swift @@ -5,6 +5,7 @@ // Created by MainasuK Cirno on 2021-4-25. // +import os.log import UIKit import Combine import CoreDataStack @@ -108,16 +109,17 @@ final class SettingService { } .store(in: &disposeBag) - Publishers.CombineLatest( + Publishers.CombineLatest3( notificationService.deviceToken, - currentSetting + currentSetting.eraseToAnyPublisher(), + authenticationService.activeMastodonAuthenticationBox ) - .compactMap { [weak self] deviceToken, setting -> AnyPublisher, Error>? in + .compactMap { [weak self] deviceToken, setting, activeMastodonAuthenticationBox -> AnyPublisher, Error>? in guard let self = self else { return nil } - guard let apiService = self.apiService else { return nil } guard let deviceToken = deviceToken else { return nil } - guard let authenticationBox = self.authenticationService?.activeMastodonAuthenticationBox.value else { return nil } guard let setting = setting else { return nil } + guard let authenticationBox = activeMastodonAuthenticationBox else { return nil } + guard let subscription = setting.activeSubscription else { return nil } guard setting.domain == authenticationBox.domain, @@ -142,21 +144,30 @@ final class SettingService { queryData: queryData, mastodonAuthenticationBox: authenticationBox ) - + return apiService.createSubscription( subscriptionObjectID: subscription.objectID, query: query, mastodonAuthenticationBox: authenticationBox ) } - .switchToLatest() - .sink { _ in - // do nothing - } receiveValue: { _ in - // do nothing - } + .debounce(for: .seconds(3), scheduler: DispatchQueue.main) // limit subscribe request emit time interval + .sink(receiveValue: { [weak self] publisher in + guard let self = self else { return } + publisher + .sink { completion in + switch completion { + case .failure(let error): + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification] subscribe failure: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) + case .finished: + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification] subscribe success", ((#file as NSString).lastPathComponent), #line, #function) + } + } receiveValue: { _ in + // do nothing + } + .store(in: &self.disposeBag) + }) .store(in: &disposeBag) - } } diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index fd4c2300..79017e29 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -7,6 +7,7 @@ import os.log import UIKit +import AppShared @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -15,11 +16,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + AppSecret.default.register() + // Update app version info. See: `Settings.bundle` UserDefaults.standard.setValue(UIApplication.appVersion(), forKey: "Mastodon.appVersion") UserDefaults.standard.setValue(UIApplication.appBuild(), forKey: "Mastodon.appBundle") -// UNUserNotificationCenter.current().delegate = self + UNUserNotificationCenter.current().delegate = self application.registerForRemoteNotifications() return true @@ -57,7 +60,28 @@ extension AppDelegate { // MARK: - UNUserNotificationCenterDelegate extension AppDelegate: UNUserNotificationCenterDelegate { + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification]", ((#file as NSString).lastPathComponent), #line, #function) + if let plaintext = notification.request.content.userInfo["plaintext"] as? Data, + let mastodonPushNotification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintext) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification] present", ((#file as NSString).lastPathComponent), #line, #function) + + } + completionHandler(.banner) + } + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Push Notification]", ((#file as NSString).lastPathComponent), #line, #function) + + } } extension AppContext { diff --git a/Mastodon/Supporting Files/AppSecret.swift b/Mastodon/Supporting Files/AppSecret.swift deleted file mode 100644 index 0a30553a..00000000 --- a/Mastodon/Supporting Files/AppSecret.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// AppSecret.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-4-22. -// - -import Foundation -import CryptoKit -import Keys - -final class AppSecret { - - let notificationEndpoint: String - - let notificationPrivateKey: P256.KeyAgreement.PrivateKey! - let notificationPublicKey: P256.KeyAgreement.PublicKey! - let notificationAuth: Data - - static let `default`: AppSecret = { - return AppSecret() - }() - - init() { - let keys = MastodonKeys() - - #if DEBUG - self.notificationEndpoint = keys.notification_endpoint_debug - let nonce = keys.notification_key_nonce_debug - let auth = keys.notification_key_auth_debug - #else - self.notificationEndpoint = keys.notification_endpoint - let nonce = keys.notification_key_nonce - let auth = keys.notification_key_auth - #endif - - notificationPrivateKey = try! P256.KeyAgreement.PrivateKey(rawRepresentation: Data(base64Encoded: nonce)!) - notificationPublicKey = notificationPrivateKey!.publicKey - notificationAuth = Data(base64Encoded: auth)! - } - - var uncompressionNotificationPublicKeyData: Data { - var data = notificationPublicKey.rawRepresentation - if data.count == 64 { - let prefix: [UInt8] = [0x04] - data = Data(prefix) + data - } - return data - } - -} diff --git a/Mastodon/Supporting Files/AppSharedName.swift b/Mastodon/Supporting Files/AppSharedName.swift deleted file mode 100644 index 3570c68d..00000000 --- a/Mastodon/Supporting Files/AppSharedName.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// AppSharedName.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-4-26. -// - -import Foundation - -enum AppSharedName { - static let groupID = "group.org.joinmastodon.mastodon-temp" -} diff --git a/NotificationService/Extension/String.swift b/NotificationService/Extension/String.swift deleted file mode 100644 index edb16242..00000000 --- a/NotificationService/Extension/String.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// String.swift -// NotificationService -// -// Created by MainasuK Cirno on 2021-4-25. -// - -import Foundation - -extension String { - static func normalize(base64String: String) -> String { - let base64 = base64String - .replacingOccurrences(of: "-", with: "+") - .replacingOccurrences(of: "_", with: "/") - .padding() - return base64 - } - - private func padding() -> String { - let remainder = self.count % 4 - if remainder > 0 { - return self.padding( - toLength: self.count + 4 - remainder, - withPad: "=", - startingAt: 0 - ) - } - return self - } -} - diff --git a/NotificationService/MastodonNotification.swift b/NotificationService/MastodonNotification.swift new file mode 100644 index 00000000..f3941b12 --- /dev/null +++ b/NotificationService/MastodonNotification.swift @@ -0,0 +1,35 @@ +// +// MastodonNotification.swift +// NotificationService +// +// Created by MainasuK Cirno on 2021-4-26. +// + +import Foundation + +struct MastodonPushNotification: Codable { + + private let _accessToken: String + var accessToken: String { + return String.normalize(base64String: _accessToken) + } + + let notificationID: Int + let notificationType: String + + let preferredLocale: String? + let icon: String? + let title: String + let body: String + + enum CodingKeys: String, CodingKey { + case _accessToken = "access_token" + case notificationID = "notification_id" + case notificationType = "notification_type" + case preferredLocale = "preferred_locale" + case icon + case title + case body + } + +} diff --git a/NotificationService/NotificationService+Decrypt.swift b/NotificationService/NotificationService+Decrypt.swift index 065863fd..858e7c2c 100644 --- a/NotificationService/NotificationService+Decrypt.swift +++ b/NotificationService/NotificationService+Decrypt.swift @@ -32,7 +32,13 @@ extension NotificationService { return nil } - guard let plaintext = try? AES.GCM.open(sealedBox, using: key) else { + var _plaintext: Data? + do { + _plaintext = try AES.GCM.open(sealedBox, using: key) + } catch { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sealedBox open fail %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) + } + guard let plaintext = _plaintext else { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: failed to open sealedBox", ((#file as NSString).lastPathComponent), #line, #function) return nil } @@ -65,32 +71,3 @@ extension NotificationService { return info } } - -extension NotificationService { - struct MastodonNotification: Codable { - - private let _accessToken: String - var accessToken: String { - return String.normalize(base64String: _accessToken) - } - - let notificationID: Int - let notificationType: String - - let preferredLocale: String? - let icon: String? - let title: String - let body: String - - enum CodingKeys: String, CodingKey { - case _accessToken = "access_token" - case notificationID = "notification_id" - case notificationType = "notification_type" - case preferredLocale = "preferred_locale" - case icon - case title - case body - } - - } -} diff --git a/NotificationService/NotificationService.entitlements b/NotificationService/NotificationService.entitlements new file mode 100644 index 00000000..d334a5e6 --- /dev/null +++ b/NotificationService/NotificationService.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.org.joinmastodon.mastodon-temp + + + diff --git a/NotificationService/NotificationService.swift b/NotificationService/NotificationService.swift index e20fc23b..f40f84ce 100644 --- a/NotificationService/NotificationService.swift +++ b/NotificationService/NotificationService.swift @@ -10,7 +10,7 @@ import CommonOSLog import CryptoKit import AlamofireImage import Base85 -import Keys +import AppShared class NotificationService: UNNotificationServiceExtension { @@ -25,7 +25,8 @@ class NotificationService: UNNotificationServiceExtension { // Modify the notification content here... os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - let privateKey = AppSecret.default.notificationPrivateKey! + let privateKey = AppSecret.default.notificationPrivateKey + let auth = AppSecret.default.notificationAuth guard let encodedPayload = bestAttemptContent.userInfo["p"] as? String, let payload = Data(base85Encoded: encodedPayload, options: [], encoding: .z85) else { @@ -48,9 +49,8 @@ class NotificationService: UNNotificationServiceExtension { return } - let auth = AppSecret.default.notificationAuth guard let plaintextData = NotificationService.decrypt(payload: payload, salt: salt, auth: auth, privateKey: privateKey, publicKey: publicKey), - let notification = try? JSONDecoder().decode(MastodonNotification.self, from: plaintextData) else { + let notification = try? JSONDecoder().decode(MastodonPushNotification.self, from: plaintextData) else { contentHandler(bestAttemptContent) return } @@ -58,6 +58,7 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent.title = notification.title bestAttemptContent.subtitle = "" bestAttemptContent.body = notification.body + bestAttemptContent.userInfo["plaintext"] = plaintextData UserDefaults.shared.notificationBadgeCount += 1 bestAttemptContent.badge = NSNumber(integerLiteral: UserDefaults.shared.notificationBadgeCount) diff --git a/Podfile b/Podfile index bb929277..ea7075ea 100644 --- a/Podfile +++ b/Podfile @@ -27,16 +27,16 @@ target 'Mastodon' do end + target 'AppShared' do + + end + end plugin 'cocoapods-keys', { :project => "Mastodon", :keys => [ "notification_endpoint", - "notification_endpoint_debug", - "notification_key_nonce", - "notification_key_nonce_debug", - "notification_key_auth", - "notification_key_auth_debug" + "notification_endpoint_debug" ] } \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock index d34a2ada..e341a242 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -30,6 +30,6 @@ SPEC CHECKSUMS: SwiftGen: 67860cc7c3cfc2ed25b9b74cfd55495fc89f9108 "UITextField+Shake": 298ac5a0f239d731bdab999b19b628c956ca0ac3 -PODFILE CHECKSUM: b99204f58cb11d471cfad7269bbf0abb853dc953 +PODFILE CHECKSUM: a8dbae22e6e0bfb84f7db59aef1aa1716793d287 COCOAPODS: 1.10.1 diff --git a/README.md b/README.md index d3cddb07..71194684 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ arch -x86_64 pod install - [CryptoSwift](https://github.com/krzyzanowskim/CryptoSwift) - [DateToolSwift](https://github.com/MatthewYork/DateTools) - [Kanna](https://github.com/tid-kijyun/Kanna) +- [KeychainAccess](https://github.com/kishikawakatsumi/KeychainAccess.git) - [Kingfisher](https://github.com/onevcat/Kingfisher) - [SwiftGen](https://github.com/SwiftGen/SwiftGen) - [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)