commit
cc49eca233
|
@ -774,6 +774,8 @@
|
|||
F5765D284BC6ECAC0C1D33F0 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; };
|
||||
FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; };
|
||||
FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; };
|
||||
FD3C907327E8387300CD579F /* OpenGroupServerIdLookupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */; };
|
||||
FD3C907527E83AC200CD579F /* OpenGroupServerIdLookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */; };
|
||||
FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */; };
|
||||
FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */; };
|
||||
FD659AC027A7649600F12C02 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */; };
|
||||
|
@ -787,6 +789,7 @@
|
|||
FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; };
|
||||
FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; };
|
||||
FDC4389E27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
|
||||
FDFD645827EC1F4000808CA1 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645727EC1F4000808CA1 /* Atomic.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -1816,6 +1819,8 @@
|
|||
F9BBF530D71905BA9007675F /* Pods-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionShareExtension/Pods-SessionShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
|
||||
FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
|
||||
FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookupMigration.swift; sourceTree = "<group>"; };
|
||||
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = "<group>"; };
|
||||
FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = "<group>"; };
|
||||
FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
|
||||
FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1829,6 +1834,7 @@
|
|||
FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
|
||||
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; };
|
||||
FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FDFD645727EC1F4000808CA1 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
|
||||
FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -2339,6 +2345,7 @@
|
|||
C33FDB8A255A581200E217F9 /* AppContext.h */,
|
||||
C33FDB85255A581100E217F9 /* AppContext.m */,
|
||||
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
|
||||
FDFD645727EC1F4000808CA1 /* Atomic.swift */,
|
||||
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
|
||||
B8F5F58225EC94A6003BF8D4 /* Collection+Subscripting.swift */,
|
||||
B8AE75A325A6C6A6001A84D2 /* Data+Trimming.swift */,
|
||||
|
@ -3062,6 +3069,7 @@
|
|||
children = (
|
||||
B8B32044258C117C0020074B /* ContactsMigration.swift */,
|
||||
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */,
|
||||
FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */,
|
||||
C38EF271255B6D79007E1867 /* OWSDatabaseMigration.h */,
|
||||
C38EF270255B6D79007E1867 /* OWSDatabaseMigration.m */,
|
||||
C38EF26F255B6D79007E1867 /* OWSDatabaseMigrationRunner.h */,
|
||||
|
@ -3191,6 +3199,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
C3DB6694260AC923001EFC55 /* OpenGroupV2.swift */,
|
||||
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */,
|
||||
B88FA7B726045D100049422F /* OpenGroupAPIV2.swift */,
|
||||
C3DB66CB260AF1F3001EFC55 /* OpenGroupAPIV2+ObjC.swift */,
|
||||
C3DB66AB260ACA42001EFC55 /* OpenGroupManagerV2.swift */,
|
||||
|
@ -4530,6 +4539,7 @@
|
|||
C38EF407255B6DF7007E1867 /* Toast.swift in Sources */,
|
||||
C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */,
|
||||
C38EF409255B6DF7007E1867 /* ContactTableViewCell.m in Sources */,
|
||||
FD3C907327E8387300CD579F /* OpenGroupServerIdLookupMigration.swift in Sources */,
|
||||
C38EF32A255B6DBF007E1867 /* UIUtil.m in Sources */,
|
||||
C38EF335255B6DBF007E1867 /* BlockListCache.swift in Sources */,
|
||||
C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */,
|
||||
|
@ -4680,6 +4690,7 @@
|
|||
C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */,
|
||||
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
|
||||
B8F5F58325EC94A6003BF8D4 /* Collection+Subscripting.swift in Sources */,
|
||||
FDFD645827EC1F4000808CA1 /* Atomic.swift in Sources */,
|
||||
C33FDEF8255A656D00E217F9 /* Promise+Delaying.swift in Sources */,
|
||||
B8BC00C0257D90E30032E807 /* General.swift in Sources */,
|
||||
C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */,
|
||||
|
@ -4766,6 +4777,7 @@
|
|||
B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */,
|
||||
C3C2A7712553A41E00C340D1 /* ControlMessage.swift in Sources */,
|
||||
C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */,
|
||||
FD3C907527E83AC200CD579F /* OpenGroupServerIdLookup.swift in Sources */,
|
||||
C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */,
|
||||
C300A5BD2554B00D00555489 /* ReadReceipt.swift in Sources */,
|
||||
C32C5AB5256DBE8F003C73A2 /* TSOutgoingMessage+Conversion.swift in Sources */,
|
||||
|
@ -5173,7 +5185,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 329;
|
||||
CURRENT_PROJECT_VERSION = 333;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -5198,7 +5210,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.11.23;
|
||||
MARKETING_VERSION = 1.11.24;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -5246,7 +5258,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 329;
|
||||
CURRENT_PROJECT_VERSION = 333;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -5276,7 +5288,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.11.23;
|
||||
MARKETING_VERSION = 1.11.24;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -5312,7 +5324,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 329;
|
||||
CURRENT_PROJECT_VERSION = 333;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||
|
@ -5335,7 +5347,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.11.23;
|
||||
MARKETING_VERSION = 1.11.24;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||
|
@ -5386,7 +5398,7 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 329;
|
||||
CURRENT_PROJECT_VERSION = 333;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
|
@ -5414,7 +5426,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.11.23;
|
||||
MARKETING_VERSION = 1.11.24;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
|
||||
|
@ -6322,7 +6334,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 329;
|
||||
CURRENT_PROJECT_VERSION = 333;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -6361,7 +6373,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 1.11.23;
|
||||
MARKETING_VERSION = 1.11.24;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
|
@ -6393,7 +6405,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 329;
|
||||
CURRENT_PROJECT_VERSION = 333;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -6432,7 +6444,7 @@
|
|||
"$(SRCROOT)",
|
||||
);
|
||||
LLVM_LTO = NO;
|
||||
MARKETING_VERSION = 1.11.23;
|
||||
MARKETING_VERSION = 1.11.24;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||
PRODUCT_NAME = Session;
|
||||
|
|
|
@ -263,49 +263,46 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
|||
let linkPreviewDraft = snInputView.linkPreviewInfo?.draft
|
||||
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
|
||||
|
||||
Storage.write(with: { transaction in
|
||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||
for: self.thread,
|
||||
with: transaction,
|
||||
isNewThread: !oldThreadShouldBeVisible,
|
||||
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
|
||||
)
|
||||
.map { [weak self] _ in
|
||||
self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage)
|
||||
|
||||
Storage.write(with: { transaction in
|
||||
message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction)
|
||||
}, completion: { [weak self] in
|
||||
tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
|
||||
|
||||
Storage.shared.write(
|
||||
with: { transaction in
|
||||
tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction)
|
||||
},
|
||||
completion: { [weak self] in
|
||||
// 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
|
||||
self?.scrollToBottom(isAnimated: false)
|
||||
}
|
||||
)
|
||||
|
||||
Storage.shared.write { transaction in
|
||||
MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction)
|
||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||
for: self.thread,
|
||||
isNewThread: !oldThreadShouldBeVisible,
|
||||
timestamp: (sentTimestamp - 1) // Set 1ms earlier as this is used for sorting
|
||||
)
|
||||
.map { [weak self] _ in
|
||||
self?.viewModel.appendUnsavedOutgoingTextMessage(tsMessage)
|
||||
|
||||
Storage.write(with: { transaction in
|
||||
message.linkPreview = VisibleMessage.LinkPreview.from(linkPreviewDraft, using: transaction)
|
||||
}, completion: { [weak self] in
|
||||
tsMessage.linkPreview = OWSLinkPreview.from(message.linkPreview)
|
||||
|
||||
Storage.shared.write(
|
||||
with: { transaction in
|
||||
tsMessage.save(with: transaction as! YapDatabaseReadWriteTransaction)
|
||||
},
|
||||
completion: { [weak self] in
|
||||
// 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
|
||||
self?.scrollToBottom(isAnimated: false)
|
||||
}
|
||||
|
||||
self?.handleMessageSent()
|
||||
})
|
||||
}
|
||||
|
||||
// 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()
|
||||
})
|
||||
)
|
||||
|
||||
Storage.shared.write { transaction in
|
||||
MessageSender.send(message, with: [], in: thread, using: transaction as! YapDatabaseReadWriteTransaction)
|
||||
}
|
||||
|
||||
self?.handleMessageSent()
|
||||
})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -329,44 +326,41 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
|||
let oldThreadShouldBeVisible: Bool = thread.shouldBeVisible
|
||||
let tsMessage = TSOutgoingMessage.from(message, associatedWith: thread)
|
||||
|
||||
Storage.write(with: { transaction in
|
||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||
for: self.thread,
|
||||
with: transaction,
|
||||
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?()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||
for: self.thread,
|
||||
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()
|
||||
|
||||
promise.retainUntilComplete()
|
||||
})
|
||||
// Attachment successfully sent - dismiss the screen
|
||||
onComplete?()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 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 handleMessageSent() {
|
||||
|
@ -1103,7 +1097,7 @@ extension ConversationVC: UIDocumentInteractionControllerDelegate {
|
|||
|
||||
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(()) }
|
||||
|
||||
// If the contact doesn't exist then we should create it so we can store the 'isApproved' state
|
||||
|
@ -1134,7 +1128,17 @@ extension ConversationVC {
|
|||
}
|
||||
|
||||
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
|
||||
if self?.presentedViewController is ModalActivityIndicatorViewController {
|
||||
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
||||
|
@ -1143,9 +1147,11 @@ extension ConversationVC {
|
|||
}
|
||||
.map { _ in
|
||||
// Default 'didApproveMe' to true for the person approving the message request
|
||||
contact.isApproved = true
|
||||
contact.didApproveMe = (contact.didApproveMe || !isNewThread)
|
||||
Storage.shared.setContact(contact, using: transaction)
|
||||
Storage.write { transaction in
|
||||
contact.isApproved = true
|
||||
contact.didApproveMe = (contact.didApproveMe || !isNewThread)
|
||||
Storage.shared.setContact(contact, using: transaction)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -1183,30 +1189,27 @@ extension ConversationVC {
|
|||
|
||||
// Send a sync message with the details of the contact
|
||||
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
|
||||
appDelegate.forceSyncConfigurationNowIfNeeded(with: transaction).retainUntilComplete()
|
||||
appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func acceptMessageRequest() {
|
||||
Storage.write { transaction in
|
||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||
for: self.thread,
|
||||
with: transaction,
|
||||
isNewThread: false,
|
||||
timestamp: NSDate.millisecondTimestamp()
|
||||
)
|
||||
|
||||
// Show an error indicating that approving the thread failed
|
||||
promise.catch(on: DispatchQueue.main) { [weak self] _ in
|
||||
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))
|
||||
self?.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
promise.retainUntilComplete()
|
||||
let promise: Promise<Void> = self.approveMessageRequestIfNeeded(
|
||||
for: self.thread,
|
||||
isNewThread: false,
|
||||
timestamp: NSDate.millisecondTimestamp()
|
||||
)
|
||||
|
||||
// Show an error indicating that approving the thread failed
|
||||
promise.catch(on: DispatchQueue.main) { [weak self] _ in
|
||||
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))
|
||||
self?.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
promise.retainUntilComplete()
|
||||
}
|
||||
|
||||
@objc func deleteMessageRequest() {
|
||||
|
|
|
@ -9,7 +9,7 @@ final class LinkPreviewView : UIView {
|
|||
private lazy var imageViewContainerHeightConstraint = imageView.set(.height, to: 100)
|
||||
|
||||
private lazy var sentLinkPreviewTextColor: UIColor = {
|
||||
let isOutgoing = (viewItem!.interaction.interactionType() == .outgoingMessage)
|
||||
let isOutgoing = (viewItem?.interaction.interactionType() == .outgoingMessage)
|
||||
switch (isOutgoing, AppModeManager.shared.currentAppMode) {
|
||||
case (true, .dark), (false, .light): return .black
|
||||
case (true, .light): return Colors.grey
|
||||
|
|
|
@ -7,7 +7,18 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
|
|||
private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel
|
||||
private var tableViewTopConstraint: NSLayoutConstraint!
|
||||
private var unreadMessageRequestCount: UInt {
|
||||
OWSMessageUtils.sharedManager().unreadMessageRequestCount()
|
||||
var count: UInt = 0
|
||||
|
||||
dbConnection.read { transaction in
|
||||
let ext = transaction.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction
|
||||
ext.enumerateRows(inGroup: TSMessageRequestGroup) { _, _, object, _, _, _ in
|
||||
if ((object as? TSThread)?.unreadMessageCount(transaction: transaction) ?? 0) > 0 {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
private var threadCount: UInt {
|
||||
|
@ -84,6 +95,12 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
|
|||
// MARK: Lifecycle
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Note: This is a hack to ensure `isRTL` is initially gets run on the main thread so the value is cached (it gets
|
||||
// called on background threads and if it hasn't cached the value then it can cause odd performance issues since
|
||||
// it accesses UIKit)
|
||||
_ = CurrentAppContext().isRTL
|
||||
|
||||
// Threads (part 1)
|
||||
dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to)
|
||||
// Preparation
|
||||
|
|
|
@ -7,10 +7,11 @@ extension AppDelegate {
|
|||
guard Storage.shared.getUser()?.name != nil else { return }
|
||||
let userDefaults = UserDefaults.standard
|
||||
let lastSync = userDefaults[.lastConfigurationSync] ?? .distantPast
|
||||
guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60,
|
||||
let configurationMessage = ConfigurationMessage.getCurrent() else { return } // Sync every 2 days
|
||||
guard Date().timeIntervalSince(lastSync) > 7 * 24 * 60 * 60 else { return } // Sync every 2 days
|
||||
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)
|
||||
JobQueue.shared.add(job, using: transaction)
|
||||
}
|
||||
|
@ -22,14 +23,17 @@ extension AppDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func forceSyncConfigurationNowIfNeeded(with transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise<Void> {
|
||||
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
|
||||
return Promise.value(())
|
||||
}
|
||||
|
||||
func forceSyncConfigurationNowIfNeeded() -> Promise<Void> {
|
||||
let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
|
||||
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
|
||||
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 {
|
||||
seal.fulfill(())
|
||||
}.catch { _ in
|
||||
|
|
|
@ -166,6 +166,7 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
|
|||
|
||||
- (BOOL)isRTL
|
||||
{
|
||||
// FIXME: We should try to remove this as we've had to add a hack to ensure the first call to this runs on the main thread
|
||||
static BOOL isRTL = NO;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
|
|
@ -128,7 +128,7 @@ final class NukeDataModal : Modal {
|
|||
appDelegate.forceSyncConfigurationNowIfNeeded().ensure(on: DispatchQueue.main) {
|
||||
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
|
||||
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)
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ final class NukeDataModal : Modal {
|
|||
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
|
||||
let potentiallyMaliciousSnodes = confirmations.compactMap { $0.value == false ? $0.key : nil }
|
||||
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
|
||||
NotificationCenter.default.post(name: .dataNukeRequested, object: nil)
|
||||
} else {
|
||||
|
|
|
@ -42,7 +42,7 @@ public final class BackgroundPoller : NSObject {
|
|||
guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic }
|
||||
return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) {
|
||||
return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise<Void> in
|
||||
let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey)
|
||||
let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey)
|
||||
let promises = messages.compactMap { json -> Promise<Void>? in
|
||||
// Use a best attempt approach here; we don't want to fail the entire process if one of the
|
||||
// messages failed to parse.
|
||||
|
@ -51,6 +51,10 @@ public final class BackgroundPoller : NSObject {
|
|||
let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true)
|
||||
return job.execute()
|
||||
}
|
||||
|
||||
// Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value
|
||||
SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: lastRawMessage)
|
||||
|
||||
return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,19 @@ extension Storage {
|
|||
private static let closedGroupZombieMembersCollection = "SNClosedGroupZombieMembersCollection"
|
||||
|
||||
public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [ECKeyPair] {
|
||||
var result: [ECKeyPair] = []
|
||||
Storage.read { transaction in
|
||||
result = self.getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String, using transaction: YapDatabaseReadTransaction) -> [ECKeyPair] {
|
||||
let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey)
|
||||
var timestampsAndKeyPairs: [(timestamp: Double, keyPair: ECKeyPair)] = []
|
||||
Storage.read { transaction in
|
||||
transaction.enumerateKeysAndObjects(inCollection: collection) { key, object, _ in
|
||||
guard let timestamp = Double(key), let keyPair = object as? ECKeyPair else { return }
|
||||
timestampsAndKeyPairs.append((timestamp, keyPair))
|
||||
}
|
||||
transaction.enumerateKeysAndObjects(inCollection: collection) { key, object, _ in
|
||||
guard let timestamp = Double(key), let keyPair = object as? ECKeyPair else { return }
|
||||
timestampsAndKeyPairs.append((timestamp, keyPair))
|
||||
}
|
||||
return timestampsAndKeyPairs.sorted { $0.timestamp < $1.timestamp }.map { $0.keyPair }
|
||||
}
|
||||
|
@ -24,6 +30,10 @@ extension Storage {
|
|||
public func getLatestClosedGroupEncryptionKeyPair(for groupPublicKey: String) -> ECKeyPair? {
|
||||
return getClosedGroupEncryptionKeyPairs(for: groupPublicKey).last
|
||||
}
|
||||
|
||||
public func getLatestClosedGroupEncryptionKeyPair(for groupPublicKey: String, using transaction: YapDatabaseReadTransaction) -> ECKeyPair? {
|
||||
return getClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction).last
|
||||
}
|
||||
|
||||
public func addClosedGroupEncryptionKeyPair(_ keyPair: ECKeyPair, for groupPublicKey: String, using transaction: Any) {
|
||||
let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey)
|
||||
|
@ -39,10 +49,14 @@ extension Storage {
|
|||
public func getUserClosedGroupPublicKeys() -> Set<String> {
|
||||
var result: Set<String> = []
|
||||
Storage.read { transaction in
|
||||
result = Set(transaction.allKeys(inCollection: Storage.closedGroupPublicKeyCollection))
|
||||
result = self.getUserClosedGroupPublicKeys(using: transaction)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func getUserClosedGroupPublicKeys(using transaction: YapDatabaseReadTransaction) -> Set<String> {
|
||||
return Set(transaction.allKeys(inCollection: Storage.closedGroupPublicKeyCollection))
|
||||
}
|
||||
|
||||
public func addClosedGroupPublicKey(_ groupPublicKey: String, using transaction: Any) {
|
||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(groupPublicKey, forKey: groupPublicKey, inCollection: Storage.closedGroupPublicKeyCollection)
|
||||
|
@ -81,4 +95,8 @@ extension Storage {
|
|||
public func isClosedGroup(_ publicKey: String) -> Bool {
|
||||
getUserClosedGroupPublicKeys().contains(publicKey)
|
||||
}
|
||||
|
||||
public func isClosedGroup(_ publicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool {
|
||||
getUserClosedGroupPublicKeys(using: transaction).contains(publicKey)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,8 +152,31 @@ extension Storage {
|
|||
let key = "\(server).\(room)"
|
||||
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: collection)
|
||||
}
|
||||
|
||||
// MARK: - OpenGroupServerIdToUniqueIdLookup
|
||||
|
||||
public static let openGroupServerIdToUniqueIdLookupCollection = "SNOpenGroupServerIdToUniqueIdLookup"
|
||||
|
||||
public func getOpenGroupServerIdLookup(_ serverId: UInt64, in room: String, on server: String, using transaction: YapDatabaseReadTransaction) -> OpenGroupServerIdLookup? {
|
||||
let key: String = OpenGroupServerIdLookup.id(serverId: serverId, in: room, on: server)
|
||||
return transaction.object(forKey: key, inCollection: Storage.openGroupServerIdToUniqueIdLookupCollection) as? OpenGroupServerIdLookup
|
||||
}
|
||||
|
||||
public func addOpenGroupServerIdLookup(_ serverId: UInt64?, tsMessageId: String?, in room: String, on server: String, using transaction: YapDatabaseReadWriteTransaction) {
|
||||
guard let serverId: UInt64 = serverId, let tsMessageId: String = tsMessageId else { return }
|
||||
|
||||
let lookup: OpenGroupServerIdLookup = OpenGroupServerIdLookup(server: server, room: room, serverId: serverId, tsMessageId: tsMessageId)
|
||||
addOpenGroupServerIdLookup(lookup, using: transaction)
|
||||
}
|
||||
|
||||
public func addOpenGroupServerIdLookup(_ lookup: OpenGroupServerIdLookup, using transaction: YapDatabaseReadWriteTransaction) {
|
||||
transaction.setObject(lookup, forKey: lookup.id, inCollection: Storage.openGroupServerIdToUniqueIdLookupCollection)
|
||||
}
|
||||
|
||||
public func removeOpenGroupServerIdLookup(_ serverId: UInt64, in room: String, on server: String, using transaction: YapDatabaseReadWriteTransaction) {
|
||||
let key: String = OpenGroupServerIdLookup.id(serverId: serverId, in: room, on: server)
|
||||
transaction.removeObject(forKey: key, inCollection: Storage.openGroupServerIdToUniqueIdLookupCollection)
|
||||
}
|
||||
|
||||
// MARK: - Metadata
|
||||
|
||||
|
|
|
@ -36,11 +36,21 @@ extension Storage {
|
|||
}
|
||||
|
||||
@objc public func getUser() -> Contact? {
|
||||
guard let userPublicKey = getUserPublicKey() else { return nil }
|
||||
return getUser(using: nil)
|
||||
}
|
||||
|
||||
public func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact? {
|
||||
let userPublicKey = getUserHexEncodedPublicKey()
|
||||
var result: Contact?
|
||||
Storage.read { transaction in
|
||||
|
||||
if let transaction = transaction {
|
||||
result = Storage.shared.getContact(with: userPublicKey, using: transaction)
|
||||
}
|
||||
else {
|
||||
Storage.read { transaction in
|
||||
result = Storage.shared.getContact(with: userPublicKey, using: transaction)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ import SessionUtilitiesKit
|
|||
|
||||
extension ConfigurationMessage {
|
||||
|
||||
public static func getCurrent(with transaction: YapDatabaseReadWriteTransaction? = nil) -> ConfigurationMessage? {
|
||||
public static func getCurrent(with transaction: YapDatabaseReadTransaction) -> ConfigurationMessage? {
|
||||
let storage = Storage.shared
|
||||
guard let user = storage.getUser() else { return nil }
|
||||
guard let user = storage.getUser(using: transaction) else { return nil }
|
||||
|
||||
let displayName = user.name
|
||||
let profilePictureURL = user.profilePictureURL
|
||||
|
@ -13,91 +13,84 @@ extension ConfigurationMessage {
|
|||
var openGroups: Set<String> = []
|
||||
var contacts: Set<ConfigurationMessage.Contact> = []
|
||||
|
||||
let populateDataClosure: (YapDatabaseReadTransaction) -> () = { transaction in
|
||||
TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in
|
||||
guard let thread = object as? TSGroupThread else { return }
|
||||
|
||||
switch thread.groupModel.groupType {
|
||||
case .closedGroup:
|
||||
guard thread.isCurrentUserMemberInGroup() else { return }
|
||||
|
||||
let groupID = thread.groupModel.groupId
|
||||
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
|
||||
|
||||
guard storage.isClosedGroup(groupPublicKey), let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else {
|
||||
return
|
||||
}
|
||||
|
||||
let closedGroup = ClosedGroup(
|
||||
publicKey: groupPublicKey,
|
||||
name: thread.groupModel.groupName!,
|
||||
encryptionKeyPair: encryptionKeyPair,
|
||||
members: Set(thread.groupModel.groupMemberIds),
|
||||
admins: Set(thread.groupModel.groupAdminIds),
|
||||
expirationTimer: thread.disappearingMessagesDuration(with: transaction)
|
||||
)
|
||||
closedGroups.insert(closedGroup)
|
||||
|
||||
case .openGroup:
|
||||
if let v2OpenGroup = storage.getV2OpenGroup(for: thread.uniqueId!) {
|
||||
openGroups.insert("\(v2OpenGroup.server)/\(v2OpenGroup.room)?public_key=\(v2OpenGroup.publicKey)")
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in
|
||||
guard let thread = object as? TSGroupThread else { return }
|
||||
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey()
|
||||
|
||||
contacts = storage.getAllContacts(with: transaction)
|
||||
.filter { contact -> Bool in
|
||||
let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID)
|
||||
switch thread.groupModel.groupType {
|
||||
case .closedGroup:
|
||||
guard thread.isCurrentUserMemberInGroup() else { return }
|
||||
|
||||
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)
|
||||
)
|
||||
)
|
||||
}
|
||||
.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
|
||||
let groupID = thread.groupModel.groupId
|
||||
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
|
||||
|
||||
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
|
||||
guard
|
||||
storage.isClosedGroup(groupPublicKey, using: transaction),
|
||||
let encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey, using: transaction)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let closedGroup = ClosedGroup(
|
||||
publicKey: groupPublicKey,
|
||||
name: (thread.groupModel.groupName ?? ""),
|
||||
encryptionKeyPair: encryptionKeyPair,
|
||||
members: Set(thread.groupModel.groupMemberIds),
|
||||
admins: Set(thread.groupModel.groupAdminIds),
|
||||
expirationTimer: thread.disappearingMessagesDuration(with: transaction)
|
||||
)
|
||||
}
|
||||
.asSet()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// If we are provided with a transaction then read the data based on the state of the database
|
||||
// from within the transaction rather than the state in disk
|
||||
if let transaction: YapDatabaseReadWriteTransaction = transaction {
|
||||
populateDataClosure(transaction)
|
||||
}
|
||||
else {
|
||||
Storage.read { transaction in populateDataClosure(transaction) }
|
||||
}
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey()
|
||||
|
||||
contacts = storage.getAllContacts(with: transaction)
|
||||
.compactMap { contact -> ConfigurationMessage.Contact? in
|
||||
let threadID = TSContactThread.threadID(fromContactSessionID: contact.sessionID)
|
||||
|
||||
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(
|
||||
displayName: displayName,
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc(SNOpenGroupServerIdLookup)
|
||||
public final class OpenGroupServerIdLookup: NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility
|
||||
@objc public let id: String
|
||||
@objc public let serverId: UInt64
|
||||
@objc public let tsMessageId: String
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
@objc public init(server: String, room: String, serverId: UInt64, tsMessageId: String) {
|
||||
self.id = OpenGroupServerIdLookup.id(serverId: serverId, in: room, on: server)
|
||||
self.serverId = serverId
|
||||
self.tsMessageId = tsMessageId
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
private override init() { preconditionFailure("Use init(blindedId:sessionId:) instead.") }
|
||||
|
||||
// MARK: - Coding
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
guard let id: String = coder.decodeObject(forKey: "id") as! String? else { return nil }
|
||||
guard let serverId: UInt64 = coder.decodeObject(forKey: "serverId") as! UInt64? else { return nil }
|
||||
guard let tsMessageId: String = coder.decodeObject(forKey: "tsMessageId") as! String? else { return nil }
|
||||
|
||||
self.id = id
|
||||
self.serverId = serverId
|
||||
self.tsMessageId = tsMessageId
|
||||
}
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(id, forKey: "id")
|
||||
coder.encode(serverId, forKey: "serverId")
|
||||
coder.encode(tsMessageId, forKey: "tsMessageId")
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
static func id(serverId: UInt64, in room: String, on server: String) -> String {
|
||||
return "\(server).\(room).\(serverId)"
|
||||
}
|
||||
}
|
|
@ -219,9 +219,15 @@ extension MessageReceiver {
|
|||
// Note: We only update these values if the proto actually has values for them (this is to
|
||||
// prevent an edge case where an old client could override the values with default values
|
||||
// since they aren't included)
|
||||
if contactInfo.hasIsApproved { contact.isApproved = contactInfo.isApproved }
|
||||
//
|
||||
// Note: Since message requests has no reverse, the only case we need to process is a
|
||||
// config message setting *isApproved* and *didApproveMe* to true. This may prevent some
|
||||
// weird edge cases where a config message swapping *isApproved* and *didApproveMe* to
|
||||
// false.
|
||||
if contactInfo.hasIsApproved && contactInfo.isApproved { contact.isApproved = true }
|
||||
if contactInfo.hasDidApproveMe && contactInfo.didApproveMe { contact.didApproveMe = true }
|
||||
|
||||
if contactInfo.hasIsBlocked { contact.isBlocked = contactInfo.isBlocked }
|
||||
if contactInfo.hasDidApproveMe { contact.didApproveMe = contactInfo.didApproveMe }
|
||||
|
||||
Storage.shared.setContact(contact, using: transaction)
|
||||
|
||||
|
@ -385,7 +391,15 @@ extension MessageReceiver {
|
|||
}
|
||||
if let tsMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) {
|
||||
// Keep track of the open group server message ID ↔ message ID relationship
|
||||
if let serverID = message.openGroupServerMessageID { tsMessage.openGroupServerMessageID = serverID }
|
||||
if let serverID = message.openGroupServerMessageID {
|
||||
tsMessage.openGroupServerMessageID = serverID
|
||||
|
||||
// Create a lookup between the openGroupServerMessageId and the tsMessage id for easy lookup
|
||||
if let openGroup: OpenGroupV2 = storage.getV2OpenGroup(for: threadID) {
|
||||
storage.addOpenGroupServerIdLookup(serverID, tsMessageId: tsMessageID, in: openGroup.room, on: openGroup.server, using: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of server hash
|
||||
if let serverHash = message.serverHash { tsMessage.serverHash = serverHash }
|
||||
tsMessage.save(with: transaction)
|
||||
|
@ -827,12 +841,14 @@ extension MessageReceiver {
|
|||
// a new configuration message (otherwise the `contact` will be loaded direct from the database and the
|
||||
// `didApproveMe` value won't have been updated)
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent() else {
|
||||
return
|
||||
Storage.write { transaction in
|
||||
guard Storage.shared.getUser()?.name != nil, let configurationMessage = ConfigurationMessage.getCurrent(with: transaction) else {
|
||||
return
|
||||
}
|
||||
|
||||
let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey)
|
||||
MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete()
|
||||
}
|
||||
|
||||
let destination: Message.Destination = Message.Destination.contact(publicKey: userPublicKey)
|
||||
MessageSender.send(configurationMessage, to: destination, using: transaction).retainUntilComplete()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -313,6 +313,7 @@ public final class MessageSender : NSObject {
|
|||
base64EncodedData: plaintext.base64EncodedString(), base64EncodedSignature: nil)
|
||||
OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in
|
||||
message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) }
|
||||
|
||||
storage.write(with: { transaction in
|
||||
MessageSender.handleSuccessfulMessageSend(message, to: destination, serverTimestamp: openGroupMessage.sentTimestamp, using: transaction)
|
||||
seal.fulfill(())
|
||||
|
@ -341,6 +342,20 @@ public final class MessageSender : NSObject {
|
|||
// Otherwise the quote messages may not be able
|
||||
// to be found by the timestamp on other devices
|
||||
tsMessage.updateOpenGroupServerID(openGroupServerMessageID, serverTimeStamp: timestamp)
|
||||
|
||||
// Create a lookup between the openGroupServerMessageId and the tsMessage id for easy lookup
|
||||
switch destination {
|
||||
case .openGroupV2(let room, let server):
|
||||
Storage.shared.addOpenGroupServerIdLookup(
|
||||
openGroupServerMessageID,
|
||||
tsMessageId: tsMessage.uniqueId,
|
||||
in: room,
|
||||
on: server,
|
||||
using: transaction
|
||||
)
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
// Mark the message as sent
|
||||
var recipients = [ message.recipient! ]
|
||||
|
|
|
@ -100,15 +100,17 @@ public final class ClosedGroupPoller : NSObject {
|
|||
|
||||
private func poll(_ groupPublicKey: String) -> Promise<Void> {
|
||||
guard isPolling(for: groupPublicKey) else { return Promise.value(()) }
|
||||
let promise = SnodeAPI.getSwarm(for: groupPublicKey).then2 { [weak self] swarm -> Promise<[JSON]> in
|
||||
let promise = SnodeAPI.getSwarm(for: groupPublicKey).then2 { [weak self] swarm -> Promise<(Snode, [JSON], JSON?)> in
|
||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||
guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) }
|
||||
guard let self = self, self.isPolling(for: groupPublicKey) else { return Promise(error: Error.pollingCanceled) }
|
||||
return SnodeAPI.getRawMessages(from: snode, associatedWith: groupPublicKey).map2 {
|
||||
SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey)
|
||||
let (rawMessages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey)
|
||||
|
||||
return (snode, rawMessages, lastRawMessage)
|
||||
}
|
||||
}
|
||||
promise.done2 { [weak self] rawMessages in
|
||||
promise.done2 { [weak self] snode, rawMessages, lastRawMessage in
|
||||
guard let self = self, self.isPolling(for: groupPublicKey) else { return }
|
||||
if !rawMessages.isEmpty {
|
||||
SNLog("Received \(rawMessages.count) new message(s) in closed group with public key: \(groupPublicKey).")
|
||||
|
@ -125,6 +127,9 @@ public final class ClosedGroupPoller : NSObject {
|
|||
SNLog("Failed to deserialize envelope due to error: \(error).")
|
||||
}
|
||||
}
|
||||
|
||||
// Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value
|
||||
SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: groupPublicKey, from: lastRawMessage)
|
||||
}
|
||||
promise.catch2 { error in
|
||||
SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).")
|
||||
|
|
|
@ -82,6 +82,7 @@ public final class OpenGroupPollerV2 : NSObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - Moderators
|
||||
if var x = OpenGroupAPIV2.moderators[server] {
|
||||
x[body.room] = Set(body.moderators)
|
||||
|
@ -89,18 +90,23 @@ public final class OpenGroupPollerV2 : NSObject {
|
|||
} else {
|
||||
OpenGroupAPIV2.moderators[server] = [ body.room : Set(body.moderators) ]
|
||||
}
|
||||
|
||||
// - Deletions
|
||||
guard !body.deletions.isEmpty else { return }
|
||||
|
||||
let deletedMessageServerIDs = Set(body.deletions.map { UInt64($0.deletedMessageID) })
|
||||
storage.write { transaction in
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
guard let threadID = storage.v2GetThreadID(for: openGroupID),
|
||||
let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { return }
|
||||
var messagesToRemove: [TSMessage] = []
|
||||
thread.enumerateInteractions(with: transaction) { interaction, stop in
|
||||
guard let message = interaction as? TSMessage, deletedMessageServerIDs.contains(message.openGroupServerMessageID) else { return }
|
||||
messagesToRemove.append(message)
|
||||
guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { return }
|
||||
|
||||
deletedMessageServerIDs.forEach { openGroupServerMessageId in
|
||||
guard let messageLookup: OpenGroupServerIdLookup = storage.getOpenGroupServerIdLookup(openGroupServerMessageId, in: body.room, on: self.server, using: transaction) else {
|
||||
return
|
||||
}
|
||||
guard let tsMessage: TSMessage = TSMessage.fetch(uniqueId: messageLookup.tsMessageId, transaction: transaction) else { return }
|
||||
|
||||
tsMessage.remove(with: transaction)
|
||||
storage.removeOpenGroupServerIdLookup(openGroupServerMessageId, in: body.room, on: self.server, using: transaction)
|
||||
}
|
||||
messagesToRemove.forEach { $0.remove(with: transaction) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ public final class Poller : NSObject {
|
|||
let userPublicKey = getUserHexEncodedPublicKey()
|
||||
return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey).then(on: Threading.pollerQueue) { [weak self] rawResponse -> Promise<Void> in
|
||||
guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } }
|
||||
let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: userPublicKey)
|
||||
let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: userPublicKey)
|
||||
if !messages.isEmpty {
|
||||
SNLog("Received \(messages.count) new message(s).")
|
||||
}
|
||||
|
@ -110,6 +110,10 @@ public final class Poller : NSObject {
|
|||
SNLog("Failed to deserialize envelope due to error: \(error).")
|
||||
}
|
||||
}
|
||||
|
||||
// Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value
|
||||
SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: userPublicKey, from: lastRawMessage)
|
||||
|
||||
strongSelf.pollCount += 1
|
||||
if strongSelf.pollCount == Poller.maxPollCount {
|
||||
throw Error.pollLimitReached
|
||||
|
|
|
@ -17,15 +17,18 @@ public protocol SessionMessagingKitStorageProtocol {
|
|||
func getUserKeyPair() -> ECKeyPair?
|
||||
func getUserED25519KeyPair() -> Box.KeyPair?
|
||||
func getUser() -> Contact?
|
||||
func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact?
|
||||
func getAllContacts() -> Set<Contact>
|
||||
func getAllContacts(with transaction: YapDatabaseReadTransaction) -> Set<Contact>
|
||||
|
||||
// MARK: - Closed Groups
|
||||
|
||||
func getUserClosedGroupPublicKeys() -> Set<String>
|
||||
func getUserClosedGroupPublicKeys(using transaction: YapDatabaseReadTransaction) -> Set<String>
|
||||
func getZombieMembers(for groupPublicKey: String) -> Set<String>
|
||||
func setZombieMembers(for groupPublicKey: String, to zombies: Set<String>, using transaction: Any)
|
||||
func isClosedGroup(_ publicKey: String) -> Bool
|
||||
func isClosedGroup(_ publicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool
|
||||
|
||||
// MARK: - Jobs
|
||||
|
||||
|
@ -67,6 +70,13 @@ public protocol SessionMessagingKitStorageProtocol {
|
|||
func getLastDeletionServerID(for room: String, on server: String) -> Int64?
|
||||
func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any)
|
||||
func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any)
|
||||
|
||||
// MARK: - OpenGroupServerIdToUniqueIdLookup
|
||||
|
||||
func getOpenGroupServerIdLookup(_ serverId: UInt64, in room: String, on server: String, using transaction: YapDatabaseReadTransaction) -> OpenGroupServerIdLookup?
|
||||
func addOpenGroupServerIdLookup(_ serverId: UInt64?, tsMessageId: String?, in room: String, on server: String, using transaction: YapDatabaseReadWriteTransaction)
|
||||
func addOpenGroupServerIdLookup(_ lookup: OpenGroupServerIdLookup, using transaction: YapDatabaseReadWriteTransaction)
|
||||
func removeOpenGroupServerIdLookup(_ serverId: UInt64, in room: String, on server: String, using transaction: YapDatabaseReadWriteTransaction)
|
||||
|
||||
// MARK: - Open Group Metadata
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
|||
|
||||
public enum General {
|
||||
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 {
|
||||
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
|
||||
General.Cache.cachedEncodedPublicKey = keyPair.hexEncodedPublicKey
|
||||
General.Cache.cachedEncodedPublicKey.mutate { $0 = keyPair.hexEncodedPublicKey }
|
||||
return keyPair.hexEncodedPublicKey
|
||||
}
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
|||
message.sentTimestamp = NSDate.millisecondTimestamp()
|
||||
message.text = (isSharingUrl && (messageText?.isEmpty == true || attachments[0].linkPreviewDraft == nil) ?
|
||||
(
|
||||
(messageText?.isEmpty == true ?
|
||||
(messageText?.isEmpty == true || (attachments[0].text() == messageText) ?
|
||||
attachments[0].text() :
|
||||
"\(attachments[0].text() ?? "")\n\n\(messageText ?? "")"
|
||||
)
|
||||
|
|
|
@ -398,20 +398,6 @@ public final class SnodeAPI : NSObject {
|
|||
return promise
|
||||
}
|
||||
|
||||
public static func getMessages(for publicKey: String) -> Promise<Set<MessageListPromise>> {
|
||||
let (promise, seal) = Promise<Set<MessageListPromise>>.pending()
|
||||
Threading.workQueue.async {
|
||||
attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||
getTargetSnodes(for: publicKey).mapValues2 { targetSnode in
|
||||
return getMessagesInternal(from: targetSnode, associatedWith: publicKey).map2 { rawResponse in
|
||||
parseRawMessagesResponse(rawResponse, from: targetSnode, associatedWith: publicKey)
|
||||
}
|
||||
}.map2 { Set($0) }
|
||||
}.done2 { seal.fulfill($0) }.catch2 { seal.reject($0) }
|
||||
}
|
||||
return promise
|
||||
}
|
||||
|
||||
private static func getMessagesInternal(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise {
|
||||
let storage = SNSnodeKitConfiguration.shared.storage
|
||||
|
||||
|
@ -573,20 +559,23 @@ public final class SnodeAPI : NSObject {
|
|||
})
|
||||
}
|
||||
|
||||
public static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> [JSON] {
|
||||
guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] }
|
||||
updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: rawMessages)
|
||||
return removeDuplicates(from: rawMessages, associatedWith: publicKey)
|
||||
public static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> (messages: [JSON], lastRawMessage: JSON?) {
|
||||
guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return ([], nil) }
|
||||
|
||||
return (
|
||||
removeDuplicates(from: rawMessages, associatedWith: publicKey),
|
||||
rawMessages.last
|
||||
)
|
||||
}
|
||||
|
||||
private static func updateLastMessageHashValueIfPossible(for snode: Snode, associatedWith publicKey: String, from rawMessages: [JSON]) {
|
||||
if let lastMessage = rawMessages.last, let lastHash = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? UInt64 {
|
||||
public static func updateLastMessageHashValueIfPossible(for snode: Snode, associatedWith publicKey: String, from lastRawMessage: JSON?) {
|
||||
if let lastMessage = lastRawMessage, let lastHash = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? UInt64 {
|
||||
SNSnodeKitConfiguration.shared.storage.writeSync { transaction in
|
||||
SNSnodeKitConfiguration.shared.storage.setLastMessageHashInfo(for: snode, associatedWith: publicKey,
|
||||
to: [ "hash" : lastHash, "expirationDate" : NSNumber(value: expirationDate) ], using: transaction)
|
||||
}
|
||||
} else if (!rawMessages.isEmpty) {
|
||||
SNLog("Failed to update last message hash value from: \(rawMessages).")
|
||||
} else if (lastRawMessage != nil) {
|
||||
SNLog("Failed to update last message hash value from: \(String(describing: lastRawMessage)).")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (NSArray<OWSDatabaseMigration *> *)allMigrations
|
||||
{
|
||||
return @[
|
||||
[SNOpenGroupServerIdLookupMigration new],
|
||||
[SNMessageRequestsMigration new],
|
||||
[SNContactsMigration new]
|
||||
];
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc(SNOpenGroupServerIdLookupMigration)
|
||||
public class OpenGroupServerIdLookupMigration: OWSDatabaseMigration {
|
||||
@objc
|
||||
class func migrationId() -> String {
|
||||
return "003"
|
||||
}
|
||||
|
||||
override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
self.doMigrationAsync(completion: completion)
|
||||
}
|
||||
|
||||
private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) {
|
||||
var lookups: [OpenGroupServerIdLookup] = []
|
||||
|
||||
Storage.write(with: { transaction in
|
||||
TSGroupThread.enumerateCollectionObjects(with: transaction) { object, _ in
|
||||
guard let thread: TSGroupThread = object as? TSGroupThread else { return }
|
||||
guard let threadId: String = thread.uniqueId else { return }
|
||||
guard let openGroup: OpenGroupV2 = Storage.shared.getV2OpenGroup(for: threadId) else { return }
|
||||
|
||||
thread.enumerateInteractions(with: transaction) { interaction, _ in
|
||||
guard let tsMessage: TSMessage = interaction as? TSMessage else { return }
|
||||
guard let tsMessageId: String = tsMessage.uniqueId else { return }
|
||||
|
||||
lookups.append(
|
||||
OpenGroupServerIdLookup(
|
||||
server: openGroup.server,
|
||||
room: openGroup.room,
|
||||
serverId: tsMessage.openGroupServerMessageID,
|
||||
tsMessageId: tsMessageId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
lookups.forEach { lookup in
|
||||
Storage.shared.addOpenGroupServerIdLookup(lookup, using: transaction)
|
||||
}
|
||||
self.save(with: transaction) // Intentionally capture self
|
||||
}, completion: {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
+ (instancetype)sharedManager;
|
||||
|
||||
- (NSUInteger)unreadMessagesCount;
|
||||
- (NSUInteger)unreadMessageRequestCount;
|
||||
- (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread;
|
||||
|
||||
- (void)updateApplicationBadgeCount;
|
||||
|
|
|
@ -93,27 +93,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)unreadMessageRequestCount {
|
||||
__block NSUInteger count = 0;
|
||||
|
||||
[LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName];
|
||||
NSArray<NSString *> *allGroups = [unreadMessages allGroups];
|
||||
// FIXME: Confusingly, `allGroups` includes contact threads as well
|
||||
for (NSString *groupID in allGroups) {
|
||||
TSThread *thread = [TSThread fetchObjectWithUniqueID:groupID transaction:transaction];
|
||||
|
||||
// Only increase the count for message requests
|
||||
if (![thread isMessageRequestUsingTransaction:transaction]) { continue; }
|
||||
if ([unreadMessages numberOfItemsInGroup:groupID] > 0) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread
|
||||
{
|
||||
__block NSUInteger numberOfItems;
|
||||
|
|
Loading…
Reference in New Issue