mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'fix/tweaks-to-config-message-generation' of https://github.com/mpretty-cyro/session-ios into bug-fix-for-call
This commit is contained in:
commit
fdde7e4fe0
9 changed files with 250 additions and 203 deletions
|
@ -177,6 +177,7 @@
|
||||||
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; };
|
7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; };
|
||||||
7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; };
|
7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; };
|
||||||
|
7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD477A727EC39F5004E2822 /* Atomic.swift */; };
|
||||||
7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; };
|
7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; };
|
||||||
7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
|
7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
|
||||||
7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; };
|
7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD1A892745C4F000FB91B9 /* Permissions.swift */; };
|
||||||
|
@ -1198,6 +1199,7 @@
|
||||||
7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
7BC707F127290ACB002817AD /* SessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallManager.swift; sourceTree = "<group>"; };
|
7BC707F127290ACB002817AD /* SessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallManager.swift; sourceTree = "<group>"; };
|
||||||
7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = "<group>"; };
|
7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = "<group>"; };
|
||||||
|
7BD477A727EC39F5004E2822 /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
|
||||||
7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = "<group>"; };
|
7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = "<group>"; };
|
||||||
7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = "<group>"; };
|
7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = "<group>"; };
|
||||||
7BFD1A892745C4F000FB91B9 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
|
7BFD1A892745C4F000FB91B9 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2433,6 +2435,7 @@
|
||||||
B8A582B0258C66C900AFD84C /* General */ = {
|
B8A582B0258C66C900AFD84C /* General */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7BD477A727EC39F5004E2822 /* Atomic.swift */,
|
||||||
7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */,
|
7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */,
|
||||||
7BAF54D627ACD0E3003D12F8 /* String+Localization.swift */,
|
7BAF54D627ACD0E3003D12F8 /* String+Localization.swift */,
|
||||||
7BAF54D727ACD0E3003D12F8 /* UITableView+ReusableView.swift */,
|
7BAF54D727ACD0E3003D12F8 /* UITableView+ReusableView.swift */,
|
||||||
|
@ -4796,6 +4799,7 @@
|
||||||
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
|
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
|
||||||
B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */,
|
B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */,
|
||||||
C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */,
|
C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */,
|
||||||
|
7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */,
|
||||||
B8BC00C0257D90E30032E807 /* General.swift in Sources */,
|
B8BC00C0257D90E30032E807 /* General.swift in Sources */,
|
||||||
C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */,
|
C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */,
|
||||||
C3D9E41F25676C870040E4F3 /* OWSPrimaryStorageProtocol.swift in Sources */,
|
C3D9E41F25676C870040E4F3 /* OWSPrimaryStorageProtocol.swift in Sources */,
|
||||||
|
|
|
@ -283,49 +283,46 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
||||||
let linkPreviewDraft = snInputView.linkPreviewInfo?.draft
|
let linkPreviewDraft = snInputView.linkPreviewInfo?.draft
|
||||||
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
|
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
|
||||||
|
|
||||||
Storage.write(with: { transaction in
|
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
for: self.thread,
|
||||||
for: self.thread,
|
isNewThread: !oldThreadShouldBeVisible,
|
||||||
with: transaction,
|
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
|
||||||
isNewThread: !oldThreadShouldBeVisible,
|
)
|
||||||
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
|
.map { [weak self] _ in
|
||||||
)
|
self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage)
|
||||||
.map { [weak self] _ in
|
|
||||||
self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage)
|
|
||||||
|
|
||||||
Storage.write(with: { transaction in
|
Storage.write(with: { transaction in
|
||||||
message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction)
|
message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction)
|
||||||
}, completion: { [weak self] in
|
}, completion: { [weak self] in
|
||||||
tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
|
tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
|
||||||
|
|
||||||
Storage.shared.write(
|
Storage.shared.write(
|
||||||
with: { transaction in
|
with: { transaction in
|
||||||
tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction)
|
tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction)
|
||||||
},
|
},
|
||||||
completion: { [weak self] in
|
completion: { [weak self] in
|
||||||
// At this point the TSOutgoingMessage should have its link preview set, so we can scroll to the bottom knowing
|
// At this point the TSOutgoingMessage should have its link preview set, so we can scroll to the bottom knowing
|
||||||
// the height of the new message cell
|
// the height of the new message cell
|
||||||
self?.scrollToBottom(isAnimated: false)
|
self?.scrollToBottom(isAnimated: false)
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Storage.shared.write { transaction in
|
|
||||||
MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self?.handleMessageSent()
|
Storage.shared.write { transaction in
|
||||||
})
|
MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show an error indicating that approving the thread failed
|
self?.handleMessageSent()
|
||||||
promise.catch(on: DispatchQueue.main) { [weak self] _ in
|
})
|
||||||
let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert)
|
}
|
||||||
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
|
|
||||||
self?.present(alert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
promise.retainUntilComplete()
|
// Show an error indicating that approving the thread failed
|
||||||
})
|
promise.catch(on: DispatchQueue.main) { [weak self] _ in
|
||||||
|
let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
|
||||||
|
self?.present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.retainUntilComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) {
|
func sendAttachments(_ attachments: [SignalAttachment], with text: String, onComplete: (() -> ())? = nil) {
|
||||||
|
@ -349,44 +346,41 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
||||||
let oldThreadShouldBeVisible: Bool = thread.shouldBeVisible
|
let oldThreadShouldBeVisible: Bool = thread.shouldBeVisible
|
||||||
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
|
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
|
||||||
|
|
||||||
Storage.write(with: { transaction in
|
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
for: self.thread,
|
||||||
for: self.thread,
|
isNewThread: !oldThreadShouldBeVisible,
|
||||||
with: transaction,
|
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
|
||||||
isNewThread: !oldThreadShouldBeVisible,
|
)
|
||||||
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
|
.map { [weak self] _ in
|
||||||
|
Storage.write(
|
||||||
|
with: { transaction in
|
||||||
|
tsMessage.save(with: transaction)
|
||||||
|
// The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet
|
||||||
|
},
|
||||||
|
completion: { [weak self] in
|
||||||
|
Storage.write(with: { transaction in
|
||||||
|
MessageSender.send(message, with: attachments, in: thread, using: transaction)
|
||||||
|
}, completion: { [weak self] in
|
||||||
|
// At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing
|
||||||
|
// the height of the new message cell
|
||||||
|
self?.scrollToBottom(isAnimated: false)
|
||||||
|
})
|
||||||
|
self?.handleMessageSent()
|
||||||
|
|
||||||
|
// Attachment successfully sent - dismiss the screen
|
||||||
|
onComplete?()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.map { [weak self] _ in
|
}
|
||||||
Storage.write(
|
|
||||||
with: { transaction in
|
|
||||||
tsMessage.save(with: transaction)
|
|
||||||
// The new message cell is inserted at this point, but the TSOutgoingMessage doesn't have its attachment yet
|
|
||||||
},
|
|
||||||
completion: { [weak self] in
|
|
||||||
Storage.write(with: { transaction in
|
|
||||||
MessageSender.send(message, with: attachments, in: thread, using: transaction)
|
|
||||||
}, completion: { [weak self] in
|
|
||||||
// At this point the TSOutgoingMessage should have its attachments set, so we can scroll to the bottom knowing
|
|
||||||
// the height of the new message cell
|
|
||||||
self?.scrollToBottom(isAnimated: false)
|
|
||||||
})
|
|
||||||
self?.handleMessageSent()
|
|
||||||
|
|
||||||
// Attachment successfully sent - dismiss the screen
|
// Show an error indicating that approving the thread failed
|
||||||
onComplete?()
|
promise.catch(on: DispatchQueue.main) { [weak self] _ in
|
||||||
}
|
let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert)
|
||||||
)
|
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
|
||||||
}
|
self?.present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Show an error indicating that approving the thread failed
|
promise.retainUntilComplete()
|
||||||
promise.catch(on: DispatchQueue.main) { [weak self] _ in
|
|
||||||
let alert = UIAlertController(title: "Session", message: "An error occurred when trying to accept this message request", preferredStyle: .alert)
|
|
||||||
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
|
|
||||||
self?.present(alert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
promise.retainUntilComplete()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMessageSent() {
|
func handleMessageSent() {
|
||||||
|
@ -1128,7 +1122,7 @@ extension ConversationVC: UIDocumentInteractionControllerDelegate {
|
||||||
|
|
||||||
extension ConversationVC {
|
extension ConversationVC {
|
||||||
|
|
||||||
fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, with transaction: YapDatabaseReadWriteTransaction, isNewThread: Bool, timestamp: UInt64) -> Promise<Void> {
|
fileprivate func approveMessageRequestIfNeeded(for thread: TSThread?, isNewThread: Bool, timestamp: UInt64) -> Promise<Void> {
|
||||||
guard let contactThread: TSContactThread = thread as? TSContactThread else { return Promise.value(()) }
|
guard let contactThread: TSContactThread = thread as? TSContactThread else { return Promise.value(()) }
|
||||||
|
|
||||||
// If the contact doesn't exist then we should create it so we can store the 'isApproved' state
|
// If the contact doesn't exist then we should create it so we can store the 'isApproved' state
|
||||||
|
@ -1159,7 +1153,17 @@ extension ConversationVC {
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
.then { MessageSender.sendNonDurably(messageRequestResponse, in: contactThread, using: transaction) }
|
.then { _ -> Promise<Void> in
|
||||||
|
let (promise, seal) = Promise<Void>.pending()
|
||||||
|
Storage.writeSync { transaction in
|
||||||
|
MessageSender.sendNonDurably(messageRequestResponse, in: contactThread, using: transaction)
|
||||||
|
.done { seal.fulfill(()) }
|
||||||
|
.catch { _ in seal.fulfill(()) } // Fulfill even if this failed; the configuration in the swarm should be at most 2 days old
|
||||||
|
.retainUntilComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
}
|
||||||
.map { _ in
|
.map { _ in
|
||||||
if self?.presentedViewController is ModalActivityIndicatorViewController {
|
if self?.presentedViewController is ModalActivityIndicatorViewController {
|
||||||
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
||||||
|
@ -1168,9 +1172,11 @@ extension ConversationVC {
|
||||||
}
|
}
|
||||||
.map { _ in
|
.map { _ in
|
||||||
// Default 'didApproveMe' to true for the person approving the message request
|
// Default 'didApproveMe' to true for the person approving the message request
|
||||||
contact.isApproved = true
|
Storage.write { transaction in
|
||||||
contact.didApproveMe = (contact.didApproveMe || !isNewThread)
|
contact.isApproved = true
|
||||||
Storage.shared.setContact(contact, using: transaction)
|
contact.didApproveMe = (contact.didApproveMe || !isNewThread)
|
||||||
|
Storage.shared.setContact(contact, using: transaction)
|
||||||
|
}
|
||||||
|
|
||||||
// Hide the 'messageRequestView' since the request has been approved and force a config
|
// Hide the 'messageRequestView' since the request has been approved and force a config
|
||||||
// sync to propagate the contact approval state (both must run on the main thread)
|
// sync to propagate the contact approval state (both must run on the main thread)
|
||||||
|
@ -1208,30 +1214,27 @@ extension ConversationVC {
|
||||||
|
|
||||||
// Send a sync message with the details of the contact
|
// Send a sync message with the details of the contact
|
||||||
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
|
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
|
||||||
appDelegate.forceSyncConfigurationNowIfNeeded(with: transaction).retainUntilComplete()
|
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func acceptMessageRequest() {
|
@objc func acceptMessageRequest() {
|
||||||
Storage.write { transaction in
|
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
for: self.thread,
|
||||||
for: self.thread,
|
isNewThread: false,
|
||||||
with: transaction,
|
timestamp: NSDate.millisecondTimestamp()
|
||||||
isNewThread: false,
|
)
|
||||||
timestamp: NSDate.millisecondTimestamp()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Show an error indicating that approving the thread failed
|
// Show an error indicating that approving the thread failed
|
||||||
promise.catch(on: DispatchQueue.main) { [weak self] _ in
|
promise.catch(on: DispatchQueue.main) { [weak self] _ in
|
||||||
let alert = UIAlertController(title: "Session", message: NSLocalizedString("MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE", comment: ""), preferredStyle: .alert)
|
let alert = UIAlertController(title: "Session", message: NSLocalizedString("MESSAGE_REQUESTS_APPROVAL_ERROR_MESSAGE", comment: ""), preferredStyle: .alert)
|
||||||
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
|
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
|
||||||
self?.present(alert, animated: true, completion: nil)
|
self?.present(alert, animated: true, completion: nil)
|
||||||
}
|
|
||||||
|
|
||||||
promise.retainUntilComplete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
promise.retainUntilComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func deleteMessageRequest() {
|
@objc func deleteMessageRequest() {
|
||||||
|
|
|
@ -144,10 +144,11 @@ extension AppDelegate {
|
||||||
guard Storage.shared.getUser()?.name != nil else { return }
|
guard Storage.shared.getUser()?.name != nil else { return }
|
||||||
let userDefaults = UserDefaults.standard
|
let userDefaults = UserDefaults.standard
|
||||||
let lastSync = userDefaults[.lastConfigurationSync] ?? .distantPast
|
let lastSync = userDefaults[.lastConfigurationSync] ?? .distantPast
|
||||||
guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60,
|
guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60 else { return } // Sync every 2 days
|
||||||
let configurationMessage = ConfigurationMessage.getCurrent() else { return } // Sync every 2 days
|
|
||||||
let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
|
let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
|
||||||
Storage.shared.write { transaction in
|
Storage.write { transaction in
|
||||||
|
guard let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else { return }
|
||||||
|
|
||||||
let job = MessageSendJob(message: configurationMessage, destination: destination)
|
let job = MessageSendJob(message: configurationMessage, destination: destination)
|
||||||
JobQueue.shared.add(job, using: transaction)
|
JobQueue.shared.add(job, using: transaction)
|
||||||
}
|
}
|
||||||
|
@ -159,14 +160,17 @@ extension AppDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func forceSyncConfigurationNowIfNeeded(with transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise<Void> {
|
func forceSyncConfigurationNowIfNeeded() -> Promise<Void> {
|
||||||
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
|
|
||||||
return Promise.value(())
|
|
||||||
}
|
|
||||||
|
|
||||||
let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
|
let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
|
||||||
let (promise, seal) = Promise<Void>.pending()
|
let (promise, seal) = Promise<Void>.pending()
|
||||||
|
|
||||||
|
// Note: SQLite only supports a single write thread so we can be sure this will retrieve the most up-to-date data
|
||||||
Storage.writeSync { transaction in
|
Storage.writeSync { transaction in
|
||||||
|
guard Storage.shared.getUser(using: transaction)?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
|
||||||
|
seal.fulfill(())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
MessageSender.send(configurationMessage, to: destination, using: transaction).done {
|
MessageSender.send(configurationMessage, to: destination, using: transaction).done {
|
||||||
seal.fulfill(())
|
seal.fulfill(())
|
||||||
}.catch { _ in
|
}.catch { _ in
|
||||||
|
|
|
@ -135,7 +135,7 @@ final class NukeDataModal : Modal {
|
||||||
appDelegate.forceSyncConfigurationNowIfNeeded().ensure(on: DispatchQueue.main) {
|
appDelegate.forceSyncConfigurationNowIfNeeded().ensure(on: DispatchQueue.main) {
|
||||||
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
||||||
UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
|
UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
|
||||||
General.Cache.cachedEncodedPublicKey = nil // Remove the cached key so it gets re-cached on next access
|
General.Cache.cachedEncodedPublicKey.mutate { $0 = nil } // Remove the cached key so it gets re-cached on next access
|
||||||
NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
|
NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
|
||||||
}.retainUntilComplete()
|
}.retainUntilComplete()
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ final class NukeDataModal : Modal {
|
||||||
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
||||||
let potentiallyMaliciousSnodes = confirmations.compactMap { $0.value == false ? $0.key : nil }
|
let potentiallyMaliciousSnodes = confirmations.compactMap { $0.value == false ? $0.key : nil }
|
||||||
if potentiallyMaliciousSnodes.isEmpty {
|
if potentiallyMaliciousSnodes.isEmpty {
|
||||||
General.Cache.cachedEncodedPublicKey = nil // Remove the cached key so it gets re-cached on next access
|
General.Cache.cachedEncodedPublicKey.mutate { $0 = nil } // Remove the cached key so it gets re-cached on next access
|
||||||
UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
|
UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later
|
||||||
NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
|
NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -40,7 +40,7 @@ extension Storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact? {
|
public func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact? {
|
||||||
guard let userPublicKey = getUserPublicKey() else { return nil }
|
let userPublicKey = getUserHexEncodedPublicKey()
|
||||||
var result: Contact?
|
var result: Contact?
|
||||||
|
|
||||||
if let transaction = transaction {
|
if let transaction = transaction {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import SessionUtilitiesKit
|
||||||
|
|
||||||
extension ConfigurationMessage {
|
extension ConfigurationMessage {
|
||||||
|
|
||||||
public static func getCurrent(with transaction: YapDatabaseReadWriteTransaction? = nil) -> ConfigurationMessage? {
|
public static func getCurrent(with transaction: YapDatabaseReadTransaction) -> ConfigurationMessage? {
|
||||||
let storage = Storage.shared
|
let storage = Storage.shared
|
||||||
guard let user = storage.getUser(using: transaction) else { return nil }
|
guard let user = storage.getUser(using: transaction) else { return nil }
|
||||||
|
|
||||||
|
@ -13,94 +13,84 @@ extension ConfigurationMessage {
|
||||||
var openGroups: Set<String> = []
|
var openGroups: Set<String> = []
|
||||||
var contacts: Set<ConfigurationMessage.Contact> = []
|
var contacts: Set<ConfigurationMessage.Contact> = []
|
||||||
|
|
||||||
let populateDataClosure: (YapDatabaseReadTransaction) -> () = { transaction in
|
TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in
|
||||||
TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in
|
guard let thread = object as? TSGroupThread else { return }
|
||||||
guard let thread = object as? TSGroupThread else { return }
|
|
||||||
|
|
||||||
switch thread.groupModel.groupType {
|
switch thread.groupModel.groupType {
|
||||||
case .closedGroup:
|
case .closedGroup:
|
||||||
guard thread.isCurrentUserMemberInGroup() else { return }
|
guard thread.isCurrentUserMemberInGroup() else { return }
|
||||||
|
|
||||||
let groupID = thread.groupModel.groupId
|
let groupID = thread.groupModel.groupId
|
||||||
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
|
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
|
||||||
|
|
||||||
guard
|
guard
|
||||||
storage.isClosedGroup(groupPublicKey, using: transaction),
|
storage.isClosedGroup(groupPublicKey, using: transaction),
|
||||||
let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey, using: transaction)
|
let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey, using: transaction)
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let closedGroup = ClosedGroup(
|
let closedGroup = ClosedGroup(
|
||||||
publicKey: groupPublicKey,
|
publicKey: groupPublicKey,
|
||||||
name: (thread.groupModel.groupName ?? ""),
|
name: (thread.groupModel.groupName ?? ""),
|
||||||
encryptionKeyPair: encryptionKeyPair,
|
encryptionKeyPair: encryptionKeyPair,
|
||||||
members: Set(thread.groupModel.groupMemberIds),
|
members: Set(thread.groupModel.groupMemberIds),
|
||||||
admins: Set(thread.groupModel.groupAdminIds),
|
admins: Set(thread.groupModel.groupAdminIds),
|
||||||
expirationTimer: thread.disappearingMessagesDuration(with: transaction)
|
expirationTimer: thread.disappearingMessagesDuration(with: transaction)
|
||||||
)
|
|
||||||
closedGroups.insert(closedGroup)
|
|
||||||
|
|
||||||
case .openGroup:
|
|
||||||
if let threadId: String = thread.uniqueId, let v2OpenGroup = storage.getV2OpenGroup(for: threadId) {
|
|
||||||
openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)")
|
|
||||||
}
|
|
||||||
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey()
|
|
||||||
|
|
||||||
contacts = storage.getAllContacts(with: transaction)
|
|
||||||
.filter { contact -> Bool in
|
|
||||||
let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID)
|
|
||||||
|
|
||||||
return (
|
|
||||||
// Skip the current user
|
|
||||||
contact.sessionID != currentUserPublicKey &&
|
|
||||||
// Contacts which have visible threads
|
|
||||||
TSContactThread.fetch(uniqueId: threadID, transaction: transaction)?.shouldBeVisible == true && (
|
|
||||||
|
|
||||||
// Include already approved contacts
|
|
||||||
contact.isApproved ||
|
|
||||||
contact.didApproveMe ||
|
|
||||||
|
|
||||||
// Sync blocked contacts
|
|
||||||
SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(contact.sessionID)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
closedGroups.insert(closedGroup)
|
||||||
.map { contact -> ConfigurationMessage.Contact in
|
|
||||||
// Can just default the 'hasX' values to true as they will be set to this
|
|
||||||
// when converting to proto anyway
|
|
||||||
let profilePictureURL = contact.profilePictureURL
|
|
||||||
let profileKey = contact.profileEncryptionKey?.keyData
|
|
||||||
|
|
||||||
return ConfigurationMessage.Contact(
|
case .openGroup:
|
||||||
publicKey: contact.sessionID,
|
if let threadId: String = thread.uniqueId, let v2OpenGroup = storage.getV2OpenGroup(for: threadId) {
|
||||||
displayName: (contact.name ?? contact.sessionID),
|
openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)")
|
||||||
profilePictureURL: profilePictureURL,
|
}
|
||||||
profileKey: profileKey,
|
|
||||||
hasIsApproved: true,
|
default: break
|
||||||
isApproved: contact.isApproved,
|
|
||||||
hasIsBlocked: true,
|
|
||||||
isBlocked: SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(contact.sessionID),
|
|
||||||
hasDidApproveMe: true,
|
|
||||||
didApproveMe: contact.didApproveMe
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.asSet()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we are provided with a transaction then read the data based on the state of the database
|
let currentUserPublicKey: String = getUserHexEncodedPublicKey()
|
||||||
// from within the transaction rather than the state in disk
|
|
||||||
if let transaction: YapDatabaseReadWriteTransaction = transaction {
|
contacts = storage.getAllContacts(with: transaction)
|
||||||
populateDataClosure(transaction)
|
.compactMap { contact -> ConfigurationMessage.Contact? in
|
||||||
}
|
let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID)
|
||||||
else {
|
|
||||||
Storage.read { transaction in populateDataClosure(transaction) }
|
guard
|
||||||
}
|
// Skip the current user
|
||||||
|
contact.sessionID != currentUserPublicKey &&
|
||||||
|
// Contacts which have visible threads
|
||||||
|
TSContactThread.fetch(uniqueId: threadID, transaction: transaction)?.shouldBeVisible == true && (
|
||||||
|
|
||||||
|
// Include already approved contacts
|
||||||
|
contact.isApproved ||
|
||||||
|
contact.didApproveMe ||
|
||||||
|
|
||||||
|
// Sync blocked contacts
|
||||||
|
SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(contact.sessionID)
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can just default the 'hasX' values to true as they will be set to this
|
||||||
|
// when converting to proto anyway
|
||||||
|
let profilePictureURL = contact.profilePictureURL
|
||||||
|
let profileKey = contact.profileEncryptionKey?.keyData
|
||||||
|
|
||||||
|
return ConfigurationMessage.Contact(
|
||||||
|
publicKey: contact.sessionID,
|
||||||
|
displayName: (contact.name ?? contact.sessionID),
|
||||||
|
profilePictureURL: profilePictureURL,
|
||||||
|
profileKey: profileKey,
|
||||||
|
hasIsApproved: true,
|
||||||
|
isApproved: contact.isApproved,
|
||||||
|
hasIsBlocked: true,
|
||||||
|
isBlocked: SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(contact.sessionID),
|
||||||
|
hasDidApproveMe: true,
|
||||||
|
didApproveMe: contact.didApproveMe
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.asSet()
|
||||||
|
|
||||||
return ConfigurationMessage(
|
return ConfigurationMessage(
|
||||||
displayName: displayName,
|
displayName: displayName,
|
||||||
|
|
|
@ -869,12 +869,14 @@ extension MessageReceiver {
|
||||||
// a new configuration message (otherwise the `contact` will be loaded direct from the database and the
|
// a new configuration message (otherwise the `contact` will be loaded direct from the database and the
|
||||||
// `didApproveMe` value won't have been updated)
|
// `didApproveMe` value won't have been updated)
|
||||||
DispatchQueue.global(qos: .background).async {
|
DispatchQueue.global(qos: .background).async {
|
||||||
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent() else {
|
Storage.write { transaction in
|
||||||
return
|
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey)
|
let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey)
|
||||||
MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete()
|
MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
||||||
|
|
||||||
public enum General {
|
public enum General {
|
||||||
public enum Cache {
|
public enum Cache {
|
||||||
public static var cachedEncodedPublicKey: String? = nil
|
public static var cachedEncodedPublicKey: Atomic<String?> = Atomic(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +14,10 @@ public class GeneralUtilities: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getUserHexEncodedPublicKey() -> String {
|
public func getUserHexEncodedPublicKey() -> String {
|
||||||
if let cachedKey: String = General.Cache.cachedEncodedPublicKey { return cachedKey }
|
if let cachedKey: String = General.Cache.cachedEncodedPublicKey.wrappedValue { return cachedKey }
|
||||||
|
|
||||||
if let keyPair = OWSIdentityManager.shared().identityKeyPair() { // Can be nil under some circumstances
|
if let keyPair = OWSIdentityManager.shared().identityKeyPair() { // Can be nil under some circumstances
|
||||||
General.Cache.cachedEncodedPublicKey = keyPair.hexEncodedPublicKey
|
General.Cache.cachedEncodedPublicKey.mutate { $0 = keyPair.hexEncodedPublicKey }
|
||||||
return keyPair.hexEncodedPublicKey
|
return keyPair.hexEncodedPublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
44
SessionUtilitiesKit/General/Atomic.swift
Normal file
44
SessionUtilitiesKit/General/Atomic.swift
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// MARK: - Atomic<Value>
|
||||||
|
/// The `Atomic<Value>` wrapper is a generic wrapper providing a thread-safe way to get and set a value
|
||||||
|
///
|
||||||
|
/// A write-up on the need for this class and it's approach can be found here:
|
||||||
|
/// https://www.vadimbulavin.com/swift-atomic-properties-with-property-wrappers/
|
||||||
|
/// there is also another approach which can be taken but it requires separate types for collections and results in
|
||||||
|
/// a somewhat inconsistent interface between different `Atomic` wrappers
|
||||||
|
@propertyWrapper
|
||||||
|
public class Atomic<Value> {
|
||||||
|
private let queue: DispatchQueue = DispatchQueue(label: "io.oxen.\(UUID().uuidString)")
|
||||||
|
private var value: Value
|
||||||
|
|
||||||
|
/// In order to change the value you **must** use the `mutate` function
|
||||||
|
public var wrappedValue: Value {
|
||||||
|
return queue.sync { return value }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For more information see https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#projections
|
||||||
|
public var projectedValue: Atomic<Value> {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
public init(_ initialValue: Value) {
|
||||||
|
self.value = initialValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Functions
|
||||||
|
|
||||||
|
public func mutate(_ mutation: (inout Value) -> Void) {
|
||||||
|
return queue.sync {
|
||||||
|
mutation(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Atomic where Value: CustomDebugStringConvertible {
|
||||||
|
var debugDescription: String {
|
||||||
|
return value.debugDescription
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue