Include profile in configuration sync message

This commit is contained in:
Niels Andriesse 2021-02-22 16:34:27 +11:00
parent d532badd09
commit e945a6779f
13 changed files with 190 additions and 30 deletions

View File

@ -126,7 +126,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
view.addSubview(messagesTableView)
messagesTableView.pin(to: view)
view.addSubview(scrollButton)
scrollButton.pin(.right, to: .right, of: view, withInset: -22)
scrollButton.pin(.right, to: .right, of: view, withInset: -16)
// Blocked banner
addOrRemoveBlockedBanner()
// Notifications
@ -207,7 +207,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return }
if !didConstrainScrollButton {
// Bit of a hack to do this here, but it works out.
scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -(newHeight + 22))
scrollButton.pin(.bottom, to: .bottom, of: view, withInset: -(newHeight + 16))
didConstrainScrollButton = true
}
UIView.animate(withDuration: 0.25) {

View File

@ -104,7 +104,7 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate,
mainStackView.axis = .vertical
mainStackView.isLayoutMarginsRelativeArrangement = true
let adjustment = (InputViewButton.expandedSize - InputViewButton.size) / 2
mainStackView.layoutMargins = UIEdgeInsets(top: Values.smallSpacing, leading: Values.largeSpacing, bottom: Values.smallSpacing, trailing: Values.largeSpacing - adjustment)
mainStackView.layoutMargins = UIEdgeInsets(top: 2, leading: Values.mediumSpacing, bottom: 2, trailing: Values.mediumSpacing - adjustment)
addSubview(mainStackView)
mainStackView.pin(.top, to: .bottom, of: separator)
mainStackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: self)

View File

@ -46,6 +46,9 @@ final class InputViewButton : UIView {
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
addSubview(blurView)
blurView.pin(to: self)
layer.borderWidth = Values.separatorThickness
let borderColor = (isLightMode ? UIColor.black : UIColor.white).withAlphaComponent(Values.veryLowOpacity)
layer.borderColor = borderColor.cgColor
}
backgroundView.backgroundColor = isSendButton ? Colors.accent : Colors.text.withAlphaComponent(0.05)
addSubview(backgroundView)

View File

@ -38,7 +38,7 @@ final class ScrollToBottomButton : UIView {
layer.cornerRadius = size / 2
layer.masksToBounds = true
// Border
layer.borderWidth = 1
layer.borderWidth = Values.separatorThickness
let borderColor = (isLightMode ? UIColor.black : UIColor.white).withAlphaComponent(Values.veryLowOpacity)
layer.borderColor = borderColor.cgColor
// Icon

View File

@ -35,7 +35,7 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon
}()
private lazy var scanQRCodeWrapperVC: ScanQRCodeWrapperVC = {
let message = "Bla bla foo bar"
let message = "Navigate to Settings → Recovery Phrase on your other device to show your QR code."
let result = ScanQRCodeWrapperVC(message: message)
result.delegate = self
return result
@ -117,7 +117,8 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon
}
func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) {
print(string)
guard let seed = string.data(using: .utf8) else { return }
continueWithSeed(seed)
}
func continueWithSeed(_ seed: Data) {
@ -126,11 +127,7 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519KeyPair.hexEncodedPublicKey
OWSPrimaryStorage.shared().setRestorationTime(Date().timeIntervalSince1970)
UserDefaults.standard[.hasViewedSeed] = true
UserDefaults.standard[.isUsingFullAPNs] = true // TODO: Get this from the sync message or show the PN mode screen
TSAccountManager.sharedInstance().didRegister()
let syncTokensJob = SyncPushTokensJob(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences)
syncTokensJob.uploadOnlyIfStale = false
let _: Promise<Void> = syncTokensJob.run()
NotificationCenter.default.addObserver(self, selector: #selector(handleConfigurationMessageReceived), name: .configurationMessageReceived, object: nil)
ModalActivityIndicatorViewController.present(fromViewController: navigationController!) { [weak self] modal in
self?.activityIndicatorModal = modal
@ -138,7 +135,12 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon
}
@objc private func handleConfigurationMessageReceived() {
DispatchQueue.main.async {
self.navigationController!.dismiss(animated: true) {
let pnModeVC = PNModeVC()
self.navigationController!.setViewControllers([ pnModeVC ], animated: true)
}
}
}
}

View File

@ -4,6 +4,7 @@ extension AppDelegate {
@objc(syncConfigurationIfNeeded)
func syncConfigurationIfNeeded() {
guard Storage.shared.getUserDisplayName() != nil else { return }
let userDefaults = UserDefaults.standard
let lastSync = userDefaults[.lastConfigurationSync] ?? .distantPast
guard Date().timeIntervalSince(lastSync) > 2 * 24 * 60 * 60 else { return } // Sync every 2 days
@ -17,6 +18,7 @@ extension AppDelegate {
}
func forceSyncConfigurationNowIfNeeded() -> Promise<Void> {
guard Storage.shared.getUserDisplayName() != nil else { return Promise.value(()) }
let configurationMessage = ConfigurationMessage.getCurrent()
let destination = Message.Destination.contact(publicKey: getUserHexEncodedPublicKey())
let (promise, seal) = Promise<Void>.pending()

View File

@ -4,16 +4,28 @@ import SessionUtilitiesKit
public final class ConfigurationMessage : ControlMessage {
public var closedGroups: Set<ClosedGroup> = []
public var openGroups: Set<String> = []
public var displayName: String?
public var profilePictureURL: String?
public var profileKey: Data?
public override var ttl: UInt64 { 4 * 24 * 60 * 60 * 1000 }
public override var isSelfSendValid: Bool { true }
// MARK: Validation
public override var isValid: Bool {
guard displayName != nil else { return false }
return true
}
// MARK: Initialization
public override init() { super.init() }
public init(closedGroups: Set<ClosedGroup>, openGroups: Set<String>) {
public init(displayName: String, profilePictureURL: String?, profileKey: Data?, closedGroups: Set<ClosedGroup>, openGroups: Set<String>) {
super.init()
self.displayName = displayName
self.profilePictureURL = profilePictureURL
self.profileKey = profileKey
self.closedGroups = closedGroups
self.openGroups = openGroups
}
@ -23,24 +35,36 @@ public final class ConfigurationMessage : ControlMessage {
super.init(coder: coder)
if let closedGroups = coder.decodeObject(forKey: "closedGroups") as! Set<ClosedGroup>? { self.closedGroups = closedGroups }
if let openGroups = coder.decodeObject(forKey: "openGroups") as! Set<String>? { self.openGroups = openGroups }
if let displayName = coder.decodeObject(forKey: "displayName") as! String? { self.displayName = displayName }
if let profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? { self.profilePictureURL = profilePictureURL }
if let profileKey = coder.decodeObject(forKey: "profileKey") as! Data? { self.profileKey = profileKey }
}
public override func encode(with coder: NSCoder) {
super.encode(with: coder)
coder.encode(closedGroups, forKey: "closedGroups")
coder.encode(openGroups, forKey: "openGroups")
coder.encode(displayName, forKey: "displayName")
coder.encode(profilePictureURL, forKey: "profilePictureURL")
coder.encode(profileKey, forKey: "profileKey")
}
// MARK: Proto Conversion
public override class func fromProto(_ proto: SNProtoContent) -> ConfigurationMessage? {
guard let configurationProto = proto.configurationMessage else { return nil }
let displayName = configurationProto.displayName
let profilePictureURL = configurationProto.profilePicture
let profileKey = configurationProto.profileKey
let closedGroups = Set(configurationProto.closedGroups.compactMap { ClosedGroup.fromProto($0) })
let openGroups = Set(configurationProto.openGroups)
return ConfigurationMessage(closedGroups: closedGroups, openGroups: openGroups)
return ConfigurationMessage(displayName: displayName, profilePictureURL: profilePictureURL, profileKey: profileKey, closedGroups: closedGroups, openGroups: openGroups)
}
public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? {
let configurationProto = SNProtoConfigurationMessage.builder()
guard let displayName = displayName else { return nil }
let configurationProto = SNProtoConfigurationMessage.builder(displayName: displayName)
if let profilePictureURL = profilePictureURL { configurationProto.setProfilePicture(profilePictureURL) }
if let profileKey = profileKey { configurationProto.setProfileKey(profileKey) }
configurationProto.setClosedGroups(closedGroups.compactMap { $0.toProto() })
configurationProto.setOpenGroups([String](openGroups))
let contentProto = SNProtoContent.builder()

View File

@ -3126,15 +3126,21 @@ extension SNProtoConfigurationMessageClosedGroup.SNProtoConfigurationMessageClos
// MARK: - SNProtoConfigurationMessageBuilder
@objc public class func builder() -> SNProtoConfigurationMessageBuilder {
return SNProtoConfigurationMessageBuilder()
@objc public class func builder(displayName: String) -> SNProtoConfigurationMessageBuilder {
return SNProtoConfigurationMessageBuilder(displayName: displayName)
}
// asBuilder() constructs a builder that reflects the proto's contents.
@objc public func asBuilder() -> SNProtoConfigurationMessageBuilder {
let builder = SNProtoConfigurationMessageBuilder()
let builder = SNProtoConfigurationMessageBuilder(displayName: displayName)
builder.setClosedGroups(closedGroups)
builder.setOpenGroups(openGroups)
if let _value = profilePicture {
builder.setProfilePicture(_value)
}
if let _value = profileKey {
builder.setProfileKey(_value)
}
return builder
}
@ -3144,6 +3150,12 @@ extension SNProtoConfigurationMessageClosedGroup.SNProtoConfigurationMessageClos
@objc fileprivate override init() {}
@objc fileprivate init(displayName: String) {
super.init()
setDisplayName(displayName)
}
@objc public func addClosedGroups(_ valueParam: SNProtoConfigurationMessageClosedGroup) {
var items = proto.closedGroups
items.append(valueParam.proto)
@ -3164,6 +3176,18 @@ extension SNProtoConfigurationMessageClosedGroup.SNProtoConfigurationMessageClos
proto.openGroups = wrappedItems
}
@objc public func setDisplayName(_ valueParam: String) {
proto.displayName = valueParam
}
@objc public func setProfilePicture(_ valueParam: String) {
proto.profilePicture = valueParam
}
@objc public func setProfileKey(_ valueParam: Data) {
proto.profileKey = valueParam
}
@objc public func build() throws -> SNProtoConfigurationMessage {
return try SNProtoConfigurationMessage.parseProto(proto)
}
@ -3177,14 +3201,38 @@ extension SNProtoConfigurationMessageClosedGroup.SNProtoConfigurationMessageClos
@objc public let closedGroups: [SNProtoConfigurationMessageClosedGroup]
@objc public let displayName: String
@objc public var openGroups: [String] {
return proto.openGroups
}
@objc public var profilePicture: String? {
guard proto.hasProfilePicture else {
return nil
}
return proto.profilePicture
}
@objc public var hasProfilePicture: Bool {
return proto.hasProfilePicture
}
@objc public var profileKey: Data? {
guard proto.hasProfileKey else {
return nil
}
return proto.profileKey
}
@objc public var hasProfileKey: Bool {
return proto.hasProfileKey
}
private init(proto: SessionProtos_ConfigurationMessage,
closedGroups: [SNProtoConfigurationMessageClosedGroup]) {
closedGroups: [SNProtoConfigurationMessageClosedGroup],
displayName: String) {
self.proto = proto
self.closedGroups = closedGroups
self.displayName = displayName
}
@objc
@ -3201,12 +3249,18 @@ extension SNProtoConfigurationMessageClosedGroup.SNProtoConfigurationMessageClos
var closedGroups: [SNProtoConfigurationMessageClosedGroup] = []
closedGroups = try proto.closedGroups.map { try SNProtoConfigurationMessageClosedGroup.parseProto($0) }
guard proto.hasDisplayName else {
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: displayName")
}
let displayName = proto.displayName
// MARK: - Begin Validation Logic for SNProtoConfigurationMessage -
// MARK: - End Validation Logic for SNProtoConfigurationMessage -
let result = SNProtoConfigurationMessage(proto: proto,
closedGroups: closedGroups)
closedGroups: closedGroups,
displayName: displayName)
return result
}

View File

@ -1139,7 +1139,7 @@ struct SessionProtos_DataMessage {
/// name, members
case update // = 2
/// wrappers
/// publicKey, wrappers
case encryptionKeyPair // = 3
/// name
@ -1151,8 +1151,6 @@ struct SessionProtos_DataMessage {
/// members
case membersRemoved // = 6
case memberLeft // = 7
/// wrappers
case encryptionKeyPairRequest // = 8
init() {
@ -1248,6 +1246,34 @@ struct SessionProtos_ConfigurationMessage {
var openGroups: [String] = []
/// @required
var displayName: String {
get {return _displayName ?? String()}
set {_displayName = newValue}
}
/// Returns true if `displayName` has been explicitly set.
var hasDisplayName: Bool {return self._displayName != nil}
/// Clears the value of `displayName`. Subsequent reads from it will return its default value.
mutating func clearDisplayName() {self._displayName = nil}
var profilePicture: String {
get {return _profilePicture ?? String()}
set {_profilePicture = newValue}
}
/// Returns true if `profilePicture` has been explicitly set.
var hasProfilePicture: Bool {return self._profilePicture != nil}
/// Clears the value of `profilePicture`. Subsequent reads from it will return its default value.
mutating func clearProfilePicture() {self._profilePicture = nil}
var profileKey: Data {
get {return _profileKey ?? SwiftProtobuf.Internal.emptyData}
set {_profileKey = newValue}
}
/// Returns true if `profileKey` has been explicitly set.
var hasProfileKey: Bool {return self._profileKey != nil}
/// Clears the value of `profileKey`. Subsequent reads from it will return its default value.
mutating func clearProfileKey() {self._profileKey = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
struct ClosedGroup {
@ -1300,6 +1326,10 @@ struct SessionProtos_ConfigurationMessage {
}
init() {}
fileprivate var _displayName: String? = nil
fileprivate var _profilePicture: String? = nil
fileprivate var _profileKey: Data? = nil
}
struct SessionProtos_ReceiptMessage {
@ -3186,9 +3216,13 @@ extension SessionProtos_ConfigurationMessage: SwiftProtobuf.Message, SwiftProtob
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "closedGroups"),
2: .same(proto: "openGroups"),
3: .same(proto: "displayName"),
4: .same(proto: "profilePicture"),
5: .same(proto: "profileKey"),
]
public var isInitialized: Bool {
if self._displayName == nil {return false}
if !SwiftProtobuf.Internal.areAllInitialized(self.closedGroups) {return false}
return true
}
@ -3198,6 +3232,9 @@ extension SessionProtos_ConfigurationMessage: SwiftProtobuf.Message, SwiftProtob
switch fieldNumber {
case 1: try decoder.decodeRepeatedMessageField(value: &self.closedGroups)
case 2: try decoder.decodeRepeatedStringField(value: &self.openGroups)
case 3: try decoder.decodeSingularStringField(value: &self._displayName)
case 4: try decoder.decodeSingularStringField(value: &self._profilePicture)
case 5: try decoder.decodeSingularBytesField(value: &self._profileKey)
default: break
}
}
@ -3210,12 +3247,24 @@ extension SessionProtos_ConfigurationMessage: SwiftProtobuf.Message, SwiftProtob
if !self.openGroups.isEmpty {
try visitor.visitRepeatedStringField(value: self.openGroups, fieldNumber: 2)
}
if let v = self._displayName {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
if let v = self._profilePicture {
try visitor.visitSingularStringField(value: v, fieldNumber: 4)
}
if let v = self._profileKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 5)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: SessionProtos_ConfigurationMessage, rhs: SessionProtos_ConfigurationMessage) -> Bool {
if lhs.closedGroups != rhs.closedGroups {return false}
if lhs.openGroups != rhs.openGroups {return false}
if lhs._displayName != rhs._displayName {return false}
if lhs._profilePicture != rhs._profilePicture {return false}
if lhs._profileKey != rhs._profileKey {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

View File

@ -222,8 +222,12 @@ message ConfigurationMessage {
repeated bytes admins = 5;
}
repeated ClosedGroup closedGroups = 1;
repeated string openGroups = 2;
repeated ClosedGroup closedGroups = 1;
repeated string openGroups = 2;
// @required
required string displayName = 3;
optional string profilePicture = 4;
optional bytes profileKey = 5;
}
message ReceiptMessage {

View File

@ -145,6 +145,22 @@ extension MessageReceiver {
private static func handleConfigurationMessage(_ message: ConfigurationMessage, using transaction: Any) {
guard message.sender == getUserHexEncodedPublicKey(), !UserDefaults.standard[.hasSyncedConfiguration] else { return }
let storage = SNMessagingKitConfiguration.shared.storage
let profileManager = SSKEnvironment.shared.profileManager
// Profile
let sessionID = getUserHexEncodedPublicKey()
let contact = Storage.shared.getContact(with: sessionID) ?? Contact(sessionID: sessionID)
if let displayName = message.displayName {
profileManager.updateProfileForContact(withID: sessionID, displayName: displayName, with: transaction as! YapDatabaseReadWriteTransaction)
contact.displayName = displayName
}
if let profileKey = message.profileKey, let profilePictureURL = message.profilePictureURL, profileKey.count == kAES256_KeyByteLength {
profileManager.setProfileKeyData(profileKey, forRecipientId: sessionID, avatarURL: profilePictureURL)
contact.profilePictureURL = profilePictureURL
contact.profilePictureEncryptionKey = OWSAES256Key(data: profileKey)
}
// Notification
UserDefaults.standard[.hasSyncedConfiguration] = true
// Closed groups
let allClosedGroupPublicKeys = storage.getUserClosedGroupPublicKeys()
NotificationCenter.default.post(name: .configurationMessageReceived, object: nil)
for closedGroup in message.closedGroups {
@ -152,12 +168,12 @@ extension MessageReceiver {
handleNewClosedGroup(groupPublicKey: closedGroup.publicKey, name: closedGroup.name, encryptionKeyPair: closedGroup.encryptionKeyPair,
members: [String](closedGroup.members), admins: [String](closedGroup.admins), messageSentTimestamp: message.sentTimestamp!, using: transaction)
}
// Open groups
let allOpenGroups = Set(storage.getAllUserOpenGroups().keys)
for openGroupURL in message.openGroups {
guard !allOpenGroups.contains(openGroupURL) else { continue }
OpenGroupManager.shared.add(with: openGroupURL, using: transaction).retainUntilComplete()
}
UserDefaults.standard[.hasSyncedConfiguration] = true
}
@discardableResult

View File

@ -152,7 +152,9 @@ public enum MessageReceiver {
if message is VisibleMessage && !isValid && proto.dataMessage?.attachments.isEmpty == false {
isValid = true
}
guard isValid else { throw Error.invalidMessage }
guard isValid else {
throw Error.invalidMessage
}
// Return
return (message, proto)
} else {

View File

@ -2,6 +2,10 @@
extension ConfigurationMessage {
public static func getCurrent() -> ConfigurationMessage {
let storage = Storage.shared
let displayName = storage.getUserDisplayName()!
let profilePictureURL = storage.getUserProfilePictureURL()
let profileKey = storage.getUserProfileKey()
var closedGroups: Set<ClosedGroup> = []
var openGroups: Set<String> = []
Storage.read { transaction in
@ -12,18 +16,18 @@ extension ConfigurationMessage {
guard thread.isCurrentUserMemberInGroup() else { return }
let groupID = thread.groupModel.groupId
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
guard Storage.shared.isClosedGroup(groupPublicKey),
let encryptionKeyPair = Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { return }
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))
closedGroups.insert(closedGroup)
case .openGroup:
guard let openGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!) else { return }
guard let openGroup = storage.getOpenGroup(for: thread.uniqueId!) else { return }
openGroups.insert(openGroup.server)
default: break
}
}
}
return ConfigurationMessage(closedGroups: closedGroups, openGroups: openGroups)
return ConfigurationMessage(displayName: displayName, profilePictureURL: profilePictureURL, profileKey: profileKey, closedGroups: closedGroups, openGroups: openGroups)
}
}