Tweaks to notification extension

• Fixed an issue where the notification extension could end up in an invalid state if you delete and create a new account in rapid succession
• Fixed an issue where notification processing errors weren't getting handled correctly resulting in the notification extension timing out
• Stopped the notification extension from trying to download profile images
• Added commit hash to version info
• Tweaked the notification extension logic flow to be more straight forward
pull/986/head
Morgan Pretty
parent dc85c69993
commit db796896f4

@ -8042,7 +8042,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 452; CURRENT_PROJECT_VERSION = 453;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
@ -8079,7 +8079,7 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ""; HEADER_SEARCH_PATHS = "";
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.6.2; MARKETING_VERSION = 2.6.3;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-fobjc-arc-exceptions", "-fobjc-arc-exceptions",
@ -8120,7 +8120,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_IDENTITY = "iPhone Distribution";
CURRENT_PROJECT_VERSION = 452; CURRENT_PROJECT_VERSION = 453;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@ -8152,7 +8152,7 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ""; HEADER_SEARCH_PATHS = "";
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.6.2; MARKETING_VERSION = 2.6.3;
ONLY_ACTIVE_ARCH = NO; ONLY_ACTIVE_ARCH = NO;
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DNS_BLOCK_ASSERTIONS=1", "-DNS_BLOCK_ASSERTIONS=1",

@ -17,11 +17,7 @@ public struct SessionApp {
.defaulting(to: "") .defaulting(to: "")
let appVersion: String? = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) let appVersion: String? = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)
.map { "App: \($0)\(buildNumber)" } .map { "App: \($0)\(buildNumber)" }
#if DEBUG
let commitInfo: String? = (Bundle.main.infoDictionary?["GitCommitHash"] as? String).map { "Commit: \($0)" } let commitInfo: String? = (Bundle.main.infoDictionary?["GitCommitHash"] as? String).map { "Commit: \($0)" }
#else
let commitInfo: String? = nil
#endif
let versionInfo: [String] = [ let versionInfo: [String] = [
"iOS \(UIDevice.current.systemVersion)", "iOS \(UIDevice.current.systemVersion)",
@ -119,9 +115,11 @@ public struct SessionApp {
ProfileManager.resetProfileStorage() ProfileManager.resetProfileStorage()
Attachment.resetAttachmentStorage() Attachment.resetAttachmentStorage()
AppEnvironment.shared.notificationPresenter.clearAllNotifications() AppEnvironment.shared.notificationPresenter.clearAllNotifications()
onReset?()
Log.info("Data Reset Complete.")
Log.flush() Log.flush()
onReset?()
exit(0) exit(0)
} }

@ -33,12 +33,19 @@ class VersionFooterView: UIView {
result.lineBreakMode = .byCharWrapping result.lineBreakMode = .byCharWrapping
result.numberOfLines = 0 result.numberOfLines = 0
if let infoDict = Bundle.main.infoDictionary
let version: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, let version: String = ((infoDict?["CFBundleShortVersionString"] as? String) ?? "0.0.0") // stringlint:disable
let buildNumber: String = Bundle.main.infoDictionary?["CFBundleVersion"] as? String let buildNumber: String? = (infoDict?["CFBundleShortVersionString"] as? String) // stringlint:disable
{ let commitInfo: String? = (infoDict?["GitCommitHash"] as? String) // stringlint:disable
result.text = "Version \(version) (\(buildNumber))" let buildInfo: String = [buildNumber, commitInfo]
} .compactMap { $0 }
.joined(separator: " - ")
result.text = [
"Version \(version)",
(!buildInfo.isEmpty ? " (" : ""),
buildInfo,
(!buildInfo.isEmpty ? ")" : ""),
].joined()
return result return result
}() }()

@ -331,9 +331,15 @@ internal extension LibSession {
// Download the profile picture if needed (this can be triggered within // Download the profile picture if needed (this can be triggered within
// database reads/writes so dispatch the download to a separate queue to // database reads/writes so dispatch the download to a separate queue to
// prevent blocking) // prevent blocking)
//
// Note: Only trigger the avatar download if we are in the main app (don't
// want the extensions to trigger this as it can clog up their networking)
if if
oldAvatarUrl != (updatedProfile.profilePictureUrl ?? "") || Singleton.hasAppContext &&
oldAvatarKey != (updatedProfile.profileEncryptionKey ?? Data(repeating: 0, count: ProfileManager.avatarAES256KeyByteLength)) Singleton.appContext.isMainApp && (
oldAvatarUrl != (updatedProfile.profilePictureUrl ?? "") ||
oldAvatarKey != (updatedProfile.profileEncryptionKey ?? Data(repeating: 0, count: ProfileManager.avatarAES256KeyByteLength))
)
{ {
DispatchQueue.global(qos: .background).async { DispatchQueue.global(qos: .background).async {
ProfileManager.downloadAvatar(for: updatedProfile) ProfileManager.downloadAvatar(for: updatedProfile)

@ -371,7 +371,7 @@ public extension Message {
_ db: Database, _ db: Database,
data: Data, data: Data,
metadata: PushNotificationAPI.NotificationMetadata, metadata: PushNotificationAPI.NotificationMetadata,
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies
) throws -> ProcessedMessage? { ) throws -> ProcessedMessage? {
return try processRawReceivedMessage( return try processRawReceivedMessage(
db, db,

@ -382,7 +382,7 @@ public enum PushNotificationAPI {
public static func processNotification( public static func processNotification(
notificationContent: UNNotificationContent, notificationContent: UNNotificationContent,
dependencies: Dependencies = Dependencies() using dependencies: Dependencies
) -> (data: Data?, metadata: NotificationMetadata, result: ProcessResult) { ) -> (data: Data?, metadata: NotificationMetadata, result: ProcessResult) {
// Make sure the notification is from the updated push server // Make sure the notification is from the updated push server
guard notificationContent.userInfo["spns"] != nil else { guard notificationContent.userInfo["spns"] != nil else {

@ -578,7 +578,8 @@ public struct ProfileManager {
} }
// Download the profile picture if needed // Download the profile picture if needed
guard avatarNeedsDownload else { return } // FIXME: We don't want to trigger the download within the notification extension, as part of the groups rebuild this has been moved into a Job which won't be run so this logic can be removed
guard avatarNeedsDownload && Singleton.hasAppContext && Singleton.appContext.isMainApp else { return }
let dedupeIdentifier: String = "AvatarDownload-\(publicKey)-\(targetAvatarUrl ?? "remove")" // stringlint:disable let dedupeIdentifier: String = "AvatarDownload-\(publicKey)-\(targetAvatarUrl ?? "remove")" // stringlint:disable

@ -12,6 +12,7 @@ import SignalCoreKit
import SessionUtilitiesKit import SessionUtilitiesKit
public final class NotificationServiceExtension: UNNotificationServiceExtension { public final class NotificationServiceExtension: UNNotificationServiceExtension {
private let dependencies: Dependencies = Dependencies()
private var didPerformSetup = false private var didPerformSetup = false
private var contentHandler: ((UNNotificationContent) -> Void)? private var contentHandler: ((UNNotificationContent) -> Void)?
private var request: UNNotificationRequest? private var request: UNNotificationRequest?
@ -28,7 +29,10 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
override public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { override public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler self.contentHandler = contentHandler
self.request = request self.request = request
// It's technically possible for 'completeSilently' to be called twice due to the NSE timeout so
self.hasCompleted.mutate { $0 = false }
// Abort if the main app is running // Abort if the main app is running
guard !(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { guard !(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
Log.info("didReceive called while main app running.") Log.info("didReceive called while main app running.")
@ -47,180 +51,196 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
Singleton.setup(appContext: NotificationServiceExtensionContext()) Singleton.setup(appContext: NotificationServiceExtensionContext())
} }
let isCallOngoing: Bool = (UserDefaults.sharedLokiProject?[.isCallOngoing])
.defaulting(to: false)
// Perform main setup // Perform main setup
Storage.resumeDatabaseAccess() Storage.resumeDatabaseAccess()
DispatchQueue.main.sync { self.setUpIfNecessary() { } } DispatchQueue.main.sync {
self.setUpIfNecessary() { [weak self] in
// Handle the push notification self?.handleNotification(notificationContent, isPerformingResetup: false)
Singleton.appReadiness.runNowOrWhenAppDidBecomeReady { }
let (maybeData, metadata, result) = PushNotificationAPI.processNotification( }
notificationContent: notificationContent }
)
private func handleNotification(_ notificationContent: UNMutableNotificationContent, isPerformingResetup: Bool) {
guard let (maybeData, metadata, result) = PushNotificationAPI.processNotification(
(result == .success || result == .legacySuccess), notificationContent: notificationContent,
let data: Data = maybeData using: dependencies
else { )
switch result {
// If we got an explicit failure, or we got a success but no content then show guard metadata.accountId == getUserHexEncodedPublicKey(using: dependencies) else {
// the fallback notification guard !isPerformingResetup else {
case .success, .legacySuccess, .failure, .legacyFailure: Log.error("Received notification for an accountId that isn't the current user, resetup failed.")
return self.handleFailure(for: notificationContent, error: .processing(result)) return self.completeSilenty()
// Just log if the notification was too long (a ~2k message should be able to fit so
// these will most commonly be call or config messages)
case .successTooLong:
Log.info("Received too long notification for namespace: \(metadata.namespace).")
return self.completeSilenty()
case .legacyForceSilent, .failureNoContent: return self.completeSilenty()
}
} }
// HACK: It is important to use write synchronously here to avoid a race condition Log.warn("Received notification for an accountId that isn't the current user, attempting to resetup.")
// where the completeSilenty() is called before the local notification request return self.forceResetup(notificationContent)
// is added to notification center }
Storage.shared.write { [weak self] db in
do { guard
guard let processedMessage: ProcessedMessage = try Message.processRawReceivedMessageAsNotification(db, data: data, metadata: metadata) else { (result == .success || result == .legacySuccess),
throw NotificationError.messageProcessing let data: Data = maybeData
} else {
switch result {
// If we got an explicit failure, or we got a success but no content then show
// the fallback notification
case .success, .legacySuccess, .failure, .legacyFailure:
return self.handleFailure(for: notificationContent, error: .processing(result))
// Just log if the notification was too long (a ~2k message should be able to fit so
// these will most commonly be call or config messages)
case .successTooLong:
Log.info("Received too long notification for namespace: \(metadata.namespace), dataLength: \(metadata.dataLength).")
return self.completeSilenty()
switch processedMessage { case .legacyForceSilent, .failureNoContent: return self.completeSilenty()
/// Custom handle config messages (as they don't get handled by the normal `MessageReceiver.handle` call }
case .config(let publicKey, let namespace, let serverHash, let serverTimestampMs, let data): }
try LibSession.handleConfigMessages(
db, let isCallOngoing: Bool = (UserDefaults.sharedLokiProject?[.isCallOngoing])
messages: [ .defaulting(to: false)
ConfigMessageReceiveJob.Details.MessageInfo(
namespace: namespace, // HACK: It is important to use write synchronously here to avoid a race condition
serverHash: serverHash, // where the completeSilenty() is called before the local notification request
serverTimestampMs: serverTimestampMs, // is added to notification center
data: data dependencies.storage.write { [weak self, dependencies] db in
do {
guard let processedMessage: ProcessedMessage = try Message.processRawReceivedMessageAsNotification(db, data: data, metadata: metadata, using: dependencies) else {
throw NotificationError.messageProcessing
}
switch processedMessage {
/// Custom handle config messages (as they don't get handled by the normal `MessageReceiver.handle` call
case .config(let publicKey, let namespace, let serverHash, let serverTimestampMs, let data):
try LibSession.handleConfigMessages(
db,
messages: [
ConfigMessageReceiveJob.Details.MessageInfo(
namespace: namespace,
serverHash: serverHash,
serverTimestampMs: serverTimestampMs,
data: data
)
],
publicKey: publicKey
)
/// Due to the way the `CallMessage` works we need to custom handle it's behaviour within the notification
/// extension, for all other message types we want to just use the standard `MessageReceiver.handle` call
case .standard(let threadId, let threadVariant, _, let messageInfo) where messageInfo.message is CallMessage:
guard let callMessage = messageInfo.message as? CallMessage else {
throw NotificationError.ignorableMessage
}
// Throw if the message is outdated and shouldn't be processed
try MessageReceiver.throwIfMessageOutdated(
db,
message: messageInfo.message,
threadId: threadId,
threadVariant: threadVariant,
using: dependencies
)
try MessageReceiver.handleCallMessage(
db,
threadId: threadId,
threadVariant: threadVariant,
message: callMessage
)
guard case .preOffer = callMessage.kind else {
throw NotificationError.ignorableMessage
}
switch (db[.areCallsEnabled], isCallOngoing) {
case (false, _):
if
let sender: String = callMessage.sender,
let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(
db,
for: callMessage,
state: .permissionDenied
) )
], {
publicKey: publicKey let thread: SessionThread = try SessionThread
) .fetchOrCreate(
/// Due to the way the `CallMessage` works we need to custom handle it's behaviour within the notification
/// extension, for all other message types we want to just use the standard `MessageReceiver.handle` call
case .standard(let threadId, let threadVariant, _, let messageInfo) where messageInfo.message is CallMessage:
guard let callMessage = messageInfo.message as? CallMessage else {
throw NotificationError.ignorableMessage
}
// Throw if the message is outdated and shouldn't be processed
try MessageReceiver.throwIfMessageOutdated(
db,
message: messageInfo.message,
threadId: threadId,
threadVariant: threadVariant
)
try MessageReceiver.handleCallMessage(
db,
threadId: threadId,
threadVariant: threadVariant,
message: callMessage
)
guard case .preOffer = callMessage.kind else {
throw NotificationError.ignorableMessage
}
switch (db[.areCallsEnabled], isCallOngoing) {
case (false, _):
if
let sender: String = callMessage.sender,
let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(
db, db,
for: callMessage, id: sender,
state: .permissionDenied variant: .contact,
shouldBeVisible: nil
) )
{
let thread: SessionThread = try SessionThread // Notify the user if the call message wasn't already read
.fetchOrCreate( if !interaction.wasRead {
SessionEnvironment.shared?.notificationsManager.wrappedValue?
.notifyUser(
db, db,
id: sender, forIncomingCall: interaction,
variant: .contact, in: thread,
shouldBeVisible: nil applicationState: .background
) )
// Notify the user if the call message wasn't already read
if !interaction.wasRead {
SessionEnvironment.shared?.notificationsManager.wrappedValue?
.notifyUser(
db,
forIncomingCall: interaction,
in: thread,
applicationState: .background
)
}
} }
}
case (true, true):
try MessageReceiver.handleIncomingCallOfferInBusyState(
db,
message: callMessage
)
case (true, false):
try MessageReceiver.insertCallInfoMessage(db, for: callMessage)
return self?.handleSuccessForIncomingCall(db, for: callMessage)
}
// Perform any required post-handling logic
try MessageReceiver.postHandleMessage(
db,
threadId: threadId,
threadVariant: threadVariant,
message: messageInfo.message
)
case .standard(let threadId, let threadVariant, let proto, let messageInfo):
try MessageReceiver.handle(
db,
threadId: threadId,
threadVariant: threadVariant,
message: messageInfo.message,
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
associatedWithProto: proto
)
}
db.afterNextTransaction(
onCommit: { _ in self?.completeSilenty() },
onRollback: { _ in self?.completeSilenty() }
)
}
catch {
// If an error occurred we want to rollback the transaction (by throwing) and then handle
// the error outside of the database
let handleError = {
switch error {
case MessageReceiverError.invalidGroupPublicKey, MessageReceiverError.noGroupKeyPair,
MessageReceiverError.outdatedMessage, NotificationError.ignorableMessage:
self?.completeSilenty()
case NotificationError.messageProcessing:
self?.handleFailure(for: notificationContent, error: .messageProcessing)
case let msgError as MessageReceiverError: case (true, true):
self?.handleFailure(for: notificationContent, error: .messageHandling(msgError)) try MessageReceiver.handleIncomingCallOfferInBusyState(
db,
message: callMessage
)
default: self?.handleFailure(for: notificationContent, error: .other(error)) case (true, false):
try MessageReceiver.insertCallInfoMessage(db, for: callMessage)
return self?.handleSuccessForIncomingCall(db, for: callMessage)
} }
// Perform any required post-handling logic
try MessageReceiver.postHandleMessage(
db,
threadId: threadId,
threadVariant: threadVariant,
message: messageInfo.message
)
case .standard(let threadId, let threadVariant, let proto, let messageInfo):
try MessageReceiver.handle(
db,
threadId: threadId,
threadVariant: threadVariant,
message: messageInfo.message,
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
associatedWithProto: proto,
using: dependencies
)
}
db.afterNextTransaction(
onCommit: { _ in self?.completeSilenty() },
onRollback: { _ in self?.completeSilenty() }
)
}
catch {
// If an error occurred we want to rollback the transaction (by throwing) and then handle
// the error outside of the database
let handleError = {
switch error {
case MessageReceiverError.invalidGroupPublicKey, MessageReceiverError.noGroupKeyPair,
MessageReceiverError.outdatedMessage, NotificationError.ignorableMessage:
self?.completeSilenty()
case NotificationError.messageProcessing:
self?.handleFailure(for: notificationContent, error: .messageProcessing)
case let msgError as MessageReceiverError:
self?.handleFailure(for: notificationContent, error: .messageHandling(msgError))
default: self?.handleFailure(for: notificationContent, error: .other(error))
} }
db.afterNextTransactionNested(
onCommit: { _ in handleError() },
onRollback: { _ in handleError() }
)
throw error
} }
db.afterNextTransaction(
onCommit: { _ in handleError() },
onRollback: { _ in handleError() }
)
throw error
} }
} }
} }
@ -233,7 +253,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
// The NSE will often re-use the same process, so if we're // The NSE will often re-use the same process, so if we're
// already set up we want to do nothing; we're already ready // already set up we want to do nothing; we're already ready
// to process new messages. // to process new messages.
guard !didPerformSetup else { return } guard !didPerformSetup else { return completion() }
Log.info("Performing setup.") Log.info("Performing setup.")
didPerformSetup = true didPerformSetup = true
@ -257,7 +277,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
// Setup LibSession // Setup LibSession
LibSession.addLogger() LibSession.addLogger()
LibSession.createNetworkIfNeeded()
}, },
migrationsCompletion: { [weak self] result, needsConfigSync in migrationsCompletion: { [weak self] result, needsConfigSync in
switch result { switch result {
@ -278,45 +297,64 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
} }
DispatchQueue.main.async { DispatchQueue.main.async {
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync) self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync, completion: completion)
} }
} }
completion()
} }
) )
} }
private func versionMigrationsDidComplete(needsConfigSync: Bool) { private func versionMigrationsDidComplete(needsConfigSync: Bool, completion: @escaping () -> Void) {
AssertIsOnMainThread() AssertIsOnMainThread()
// If we need a config sync then trigger it now // If we need a config sync then trigger it now
if needsConfigSync { if needsConfigSync {
Storage.shared.write { db in Storage.shared.write { db in
ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db)) ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db))
} }
} }
checkIsAppReady(migrationsCompleted: true)
}
private func checkIsAppReady(migrationsCompleted: Bool) {
AssertIsOnMainThread()
// Only mark the app as ready once.
guard !Singleton.appReadiness.isAppReady else { return }
// App isn't ready until storage is ready AND all version migrations are complete. // App isn't ready until storage is ready AND all version migrations are complete.
guard Storage.shared.isValid && migrationsCompleted else { guard Storage.shared.isValid else {
Log.error("Storage invalid.") Log.error("Storage invalid.")
self.completeSilenty() return self.completeSilenty()
return }
// If the app wasn't ready then mark it as ready now
if !Singleton.appReadiness.isAppReady {
// Note that this does much more than set a flag; it will also run all deferred blocks.
Singleton.appReadiness.setAppReady()
}
completion()
}
/// It's possible for the NotificationExtension to still have some kind of cached data from the old database after it's been deleted
/// when a new account is created shortly after, this results in weird errors when receiving PNs for the new account
///
/// In order to avoid this situation we check to see whether the received PN is targetting the current user and, if not, we call this
/// method to force a resetup of the notification extension
///
/// **Note:** We need to reconfigure the database here because if the database was deleted it's possible for the NotificationExtension
/// to somehow still have some form of access to the old one
private func forceResetup(_ notificationContent: UNMutableNotificationContent) {
Storage.reconfigureDatabase()
LibSession.clearMemoryState()
dependencies.caches.mutate(cache: .general) { $0.clearCachedUserPublicKey() }
self.setUpIfNecessary() { [weak self] in
// If we had already done a setup then `libSession` won't have been re-setup so
// we need to do so now (this ensures it has the correct user keys as well)
Storage.shared.read { db in
LibSession.loadState(
db,
userPublicKey: getUserHexEncodedPublicKey(db),
ed25519SecretKey: Identity.fetchUserEd25519KeyPair(db)?.secretKey
)
}
self?.handleNotification(notificationContent, isPerformingResetup: true)
} }
SignalUtilitiesKit.Configuration.performMainSetup()
// Note that this does much more than set a flag; it will also run all deferred blocks.
Singleton.appReadiness.setAppReady()
} }
// MARK: Handle completion // MARK: Handle completion
@ -434,5 +472,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
let userInfo: [String: Any] = [ NotificationServiceExtension.isFromRemoteKey: true ] let userInfo: [String: Any] = [ NotificationServiceExtension.isFromRemoteKey: true ]
content.userInfo = userInfo content.userInfo = userInfo
contentHandler!(content) contentHandler!(content)
hasCompleted.mutate { $0 = true }
} }
} }

@ -428,7 +428,7 @@ open class Storage {
Storage.shared.dbWriter = nil Storage.shared.dbWriter = nil
deleteDatabaseFiles() deleteDatabaseFiles()
try? deleteDbKeys() do { try deleteDbKeys() } catch { Log.warn("Failed to delete database keys.") }
} }
public static func reconfigureDatabase() { public static func reconfigureDatabase() {
@ -444,9 +444,9 @@ open class Storage {
} }
private static func deleteDatabaseFiles() { private static func deleteDatabaseFiles() {
OWSFileSystem.deleteFile(databasePath) if !OWSFileSystem.deleteFile(databasePath) { Log.warn("Failed to delete database.") }
OWSFileSystem.deleteFile(databasePathShm) if !OWSFileSystem.deleteFile(databasePathShm) { Log.warn("Failed to delete database-shm.") }
OWSFileSystem.deleteFile(databasePathWal) if !OWSFileSystem.deleteFile(databasePathWal) { Log.warn("Failed to delete database-wal.") }
} }
private static func deleteDbKeys() throws { private static func deleteDbKeys() throws {

@ -135,21 +135,12 @@ fileprivate class TransactionHandler: TransactionObserver {
} }
} }
catch { catch {
SNLog("[Database] afterNextTransactionNested onCommit failed") Log.warn("[Database] afterNextTransactionNested onCommit failed")
} }
} }
func databaseDidRollback(_ db: Database) { func databaseDidRollback(_ db: Database) {
TransactionHandler.registeredHandlers.mutate { $0.remove(identifier) } TransactionHandler.registeredHandlers.mutate { $0.remove(identifier) }
onRollback(db)
do {
try db.inTransaction {
onRollback(db)
return .commit
}
}
catch {
SNLog("[Database] afterNextTransactionNested onRollback failed")
}
} }
} }

@ -9,6 +9,10 @@ public enum General {
public class Cache: GeneralCacheType { public class Cache: GeneralCacheType {
public var encodedPublicKey: String? = nil public var encodedPublicKey: String? = nil
public var recentReactionTimestamps: [Int64] = [] public var recentReactionTimestamps: [Int64] = []
public func clearCachedUserPublicKey() {
encodedPublicKey = nil
}
} }
} }
@ -55,4 +59,6 @@ public protocol ImmutableGeneralCacheType: ImmutableCacheType {
public protocol GeneralCacheType: ImmutableGeneralCacheType, MutableCacheType { public protocol GeneralCacheType: ImmutableGeneralCacheType, MutableCacheType {
var encodedPublicKey: String? { get set } var encodedPublicKey: String? { get set }
var recentReactionTimestamps: [Int64] { get set } var recentReactionTimestamps: [Int64] { get set }
func clearCachedUserPublicKey()
} }

Loading…
Cancel
Save