Skip to content

DBR - 5 - Config message handling #451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Session/Conversations/ConversationViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate, NavigatableStateHold
// Generate the optimistic data
let optimisticMessageId: UUID = UUID()
let threadData: SessionThreadViewModel = self.internalThreadData
let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser(using: dependencies)
let currentUserProfile: Profile = dependencies.mutate(cache: .libSession) { $0.profile }
let interaction: Interaction = Interaction(
threadId: threadData.threadId,
threadVariant: threadData.threadVariant,
Expand Down
2 changes: 1 addition & 1 deletion Session/Home/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class HomeViewModel: NavigatableStateHolder {
showViewedSeedBanner: (initialState?.showViewedSeedBanner ?? true),
hasHiddenMessageRequests: (initialState?.hasHiddenMessageRequests ?? false),
unreadMessageRequestThreadCount: 0,
userProfile: (initialState?.userProfile ?? Profile.fetchOrCreateCurrentUser(using: dependencies))
userProfile: (initialState?.userProfile ?? dependencies.mutate(cache: .libSession) { $0.profile })
)
self.pagedDataObserver = nil

Expand Down
5 changes: 4 additions & 1 deletion Session/Media Viewing & Editing/MessageInfoScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,10 @@ struct MessageInfoView_Previews: PreviewProvider {
expiresInSeconds: nil,
state: .failed,
isSenderModeratorOrAdmin: false,
currentUserProfile: Profile.fetchOrCreateCurrentUser(using: dependencies),
currentUserProfile: Profile(
id: "0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b",
name: "TestUser"
),
quote: nil,
quoteAttachment: nil,
linkPreview: nil,
Expand Down
84 changes: 46 additions & 38 deletions Session/Onboarding/Onboarding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ extension Onboarding {

public var displayName: String
public var _displayNamePublisher: AnyPublisher<String?, Error>?
private var hasInitialDisplayName: Bool
private var userProfileConfigMessage: ProcessedMessage?
private var disposables: Set<AnyCancellable> = Set()

Expand All @@ -102,35 +103,46 @@ extension Onboarding {
self.dependencies = dependencies
self.initialFlow = flow

/// Determine the current state based on what's in the database
typealias StoredData = (
state: State,
displayName: String,
ed25519KeyPair: KeyPair,
x25519KeyPair: KeyPair
)
let storedData: StoredData = dependencies[singleton: .storage].read { db -> StoredData in
// If we have no ed25519KeyPair then the user doesn't have an account
/// Try to load the users `ed25519KeyPair` from the database
let ed25519KeyPair: KeyPair = dependencies[singleton: .storage]
.read { db -> KeyPair? in Identity.fetchUserEd25519KeyPair(db) }
.defaulting(to: .empty)

/// Retrieve the users `displayName` from `libSession` (the source of truth)
let displayName: String = dependencies.mutate(cache: .libSession) { $0.profile }.name
let hasInitialDisplayName: Bool = dependencies.mutate(cache: .libSession) {
$0.displayName?.nullIfEmpty != nil
}

self.ed25519KeyPair = ed25519KeyPair
self.displayName = displayName
self.hasInitialDisplayName = hasInitialDisplayName
self.x25519KeyPair = {
guard
let x25519KeyPair: KeyPair = Identity.fetchUserKeyPair(db),
let ed25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db)
else { return (.noUser, "", KeyPair.empty, KeyPair.empty) }
ed25519KeyPair != .empty,
let x25519PublicKey: [UInt8] = dependencies[singleton: .crypto].generate(
.x25519(ed25519Pubkey: ed25519KeyPair.publicKey)
),
let x25519SecretKey: [UInt8] = dependencies[singleton: .crypto].generate(
.x25519(ed25519Seckey: ed25519KeyPair.secretKey)
)
else { return .empty }

// If we have no display name then collect one (this can happen if the
// app crashed during onboarding which would leave the user in an invalid
// state with no display name)
let displayName: String = Profile.fetchOrCreateCurrentUser(db, using: dependencies).name
guard !displayName.isEmpty else { return (.missingName, "anonymous".localized(), x25519KeyPair, ed25519KeyPair) }
return KeyPair(publicKey: x25519PublicKey, secretKey: x25519SecretKey)
}()
self.userSessionId = (x25519KeyPair != .empty ?
SessionId(.standard, publicKey: x25519KeyPair.publicKey) :
.invalid
)
self.state = {
guard ed25519KeyPair != .empty else { return .noUser }
guard hasInitialDisplayName else { return .missingName }

// Otherwise we have enough for a full user and can start the app
return (.completed, displayName, x25519KeyPair, ed25519KeyPair)
}.defaulting(to: (.noUser, "", KeyPair.empty, KeyPair.empty))

/// Store the initial `displayName` value in case we need it
self.displayName = storedData.displayName
return .completed
}()

/// Update the cached values depending on the `initialState`
switch storedData.state {
switch state {
case .noUser, .noUserFailedIdentity:
/// Remove the `LibSession.Cache` just in case (to ensure no previous state remains)
dependencies.remove(cache: .libSession)
Expand All @@ -147,8 +159,8 @@ extension Onboarding {
/// recover somehow
self.state = .noUserFailedIdentity
self.seed = Data()
self.ed25519KeyPair = KeyPair(publicKey: [], secretKey: [])
self.x25519KeyPair = KeyPair(publicKey: [], secretKey: [])
self.ed25519KeyPair = .empty
self.x25519KeyPair = .empty
self.userSessionId = .invalid
self.useAPNS = false
return
Expand All @@ -157,17 +169,10 @@ extension Onboarding {
/// The identity data was successfully generated so store it for the onboarding process
self.state = .noUser
self.seed = finalSeedData
self.ed25519KeyPair = identity.ed25519KeyPair
self.x25519KeyPair = identity.x25519KeyPair
self.userSessionId = SessionId(.standard, publicKey: x25519KeyPair.publicKey)
self.useAPNS = false

case .missingName, .completed:
self.state = storedData.state
self.seed = Data()
self.ed25519KeyPair = storedData.ed25519KeyPair
self.x25519KeyPair = storedData.x25519KeyPair
self.userSessionId = dependencies[cache: .general].sessionId
self.useAPNS = dependencies[defaults: .standard, key: .isUsingFullAPNs]

/// If we are already in a completed state then updated the completion subject accordingly
Expand All @@ -193,6 +198,7 @@ extension Onboarding {
self.userSessionId = SessionId(.standard, publicKey: x25519KeyPair.publicKey)
self.useAPNS = dependencies[defaults: .standard, key: .isUsingFullAPNs]
self.displayName = displayName
self.hasInitialDisplayName = !displayName.isEmpty
self._displayNamePublisher = nil
}

Expand Down Expand Up @@ -230,7 +236,7 @@ extension Onboarding {
using: dependencies
)

typealias PollResult = (configMessage: ProcessedMessage, displayName: String)
typealias PollResult = (configMessage: ProcessedMessage, displayName: String?)
let publisher: AnyPublisher<String?, Error> = poller
.poll(forceSynchronousProcessing: true)
.tryMap { [userSessionId, dependencies] messages, _, _, _ -> PollResult? in
Expand All @@ -245,7 +251,7 @@ extension Onboarding {
cache.loadDefaultStateFor(
variant: .userProfile,
sessionId: userSessionId,
userEd25519KeyPair: identity.ed25519KeyPair,
userEd25519SecretKey: identity.ed25519KeyPair.secretKey,
groupEd25519SecretKey: nil
)
try cache.unsafeDirectMergeConfigMessage(
Expand All @@ -260,7 +266,7 @@ extension Onboarding {
]
)

return (targetMessage, cache.userProfileDisplayName)
return (targetMessage, cache.displayName)
}
.handleEvents(
receiveOutput: { [weak self] result in
Expand All @@ -269,8 +275,8 @@ extension Onboarding {
/// Only store the `displayName` returned from the swarm if the user hasn't provided one in the display
/// name step (otherwise the user could enter a display name and have it immediately overwritten due to the
/// config request running slow)
if self?.displayName.isEmpty == true {
self?.displayName = result.displayName
if self?.hasInitialDisplayName != true, let displayName = result.displayName {
self?.displayName = displayName
}

self?.userProfileConfigMessage = result.configMessage
Expand Down Expand Up @@ -366,6 +372,8 @@ extension Onboarding {
.messages
)
}

try? $0.updateProfile(displayName: displayName)
}

/// Clear the `lastNameUpdate` timestamp and forcibly set the `displayName` provided during the onboarding
Expand Down
3 changes: 2 additions & 1 deletion Session/Settings/DeveloperSettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,8 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
Onboarding.Cache(
ed25519KeyPair: identityData.ed25519KeyPair,
x25519KeyPair: identityData.x25519KeyPair,
displayName: Profile.fetchOrCreateCurrentUser(using: dependencies)
displayName: dependencies
.mutate(cache: .libSession) { $0.profile }
.name
.nullIfEmpty
.defaulting(to: "Anonymous"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
groupEd25519SecretKey: nil,
cachedData: nil
)
cache.setConfig(for: .userProfile, sessionId: userSessionId, to: userProfileConfig)

let userProfile: Row? = try? Row.fetchOne(
db,
sql: """
Expand All @@ -62,13 +64,10 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
""",
arguments: [userSessionId.hexString]
)
try LibSession.update(
profileInfo: LibSession.ProfileInfo(
name: (userProfile?["name"] ?? ""),
profilePictureUrl: userProfile?["profilePictureUrl"],
profileEncryptionKey: userProfile?["profileEncryptionKey"]
),
in: userProfileConfig
try cache.updateProfile(
displayName: (userProfile?["name"] ?? ""),
profilePictureUrl: userProfile?["profilePictureUrl"],
profileEncryptionKey: userProfile?["profileEncryptionKey"]
)

try LibSession.updateNoteToSelf(
Expand Down Expand Up @@ -106,6 +105,8 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
groupEd25519SecretKey: nil,
cachedData: nil
)
cache.setConfig(for: .contacts, sessionId: userSessionId, to: contactsConfig)

let validContactIds: [String] = allThreads
.values
.filter { thread in
Expand Down Expand Up @@ -199,6 +200,8 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
groupEd25519SecretKey: nil,
cachedData: nil
)
cache.setConfig(for: .convoInfoVolatile, sessionId: userSessionId, to: convoInfoVolatileConfig)

let volatileThreadInfo: [Row] = try Row.fetchAll(db, sql: """
SELECT
thread.id,
Expand Down Expand Up @@ -287,6 +290,8 @@ enum _014_GenerateInitialUserConfigDumps: Migration {
groupEd25519SecretKey: nil,
cachedData: nil
)
cache.setConfig(for: .userGroups, sessionId: userSessionId, to: userGroupsConfig)

let legacyGroupInfo: [Row] = try Row.fetchAll(db, sql: """
SELECT
closedGroup.threadId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,16 @@ enum _015_BlockCommunityMessageRequests: Migration {
""",
arguments: [userSessionId.hexString]
)
let userProfileConfig: LibSession.Config = try cache.loadState(
for: .userProfile,
sessionId: userSessionId,
userEd25519SecretKey: Array(userEd25519SecretKey),
groupEd25519SecretKey: nil,
cachedData: configDump
)

let rawBlindedMessageRequestValue: Int32 = try LibSession.rawBlindedMessageRequestValue(in: userProfileConfig)

// Use the value in the config if we happen to have one, otherwise use the default
try db.execute(sql: """
DELETE FROM setting
WHERE key = 'checkForCommunityMessageRequests'
""")

var targetValue: Bool = (rawBlindedMessageRequestValue < 0 ?
var targetValue: Bool = (!cache.has(.checkForCommunityMessageRequests) ?
true :
(rawBlindedMessageRequestValue > 0)
cache.get(.checkForCommunityMessageRequests)
)
let boolAsData: Data = withUnsafeBytes(of: &targetValue) { Data($0) }
try db.execute(
Expand Down
12 changes: 0 additions & 12 deletions SessionMessagingKit/Database/Models/Profile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,18 +285,6 @@ public extension Profile {
)
}

/// Fetches or creates a Profile for the current user
///
/// **Note:** This method intentionally does **not** save the newly created Profile,
/// it will need to be explicitly saved after calling
static func fetchOrCreateCurrentUser(using dependencies: Dependencies) -> Profile {
let userSessionId: SessionId = dependencies[cache: .general].sessionId

return dependencies[singleton: .storage]
.read { db in fetchOrCreateCurrentUser(db, using: dependencies) }
.defaulting(to: defaultFor(userSessionId.hexString))
}

/// Fetches or creates a Profile for the current user
///
/// **Note:** This method intentionally does **not** save the newly created Profile,
Expand Down
18 changes: 8 additions & 10 deletions SessionMessagingKit/Jobs/GroupInviteMemberJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,18 @@ public enum GroupInviteMemberJob: JobExecutor {
guard
let threadId: String = job.threadId,
let detailsData: Data = job.details,
let currentInfo: (groupName: String, adminProfile: Profile) = dependencies[singleton: .storage].read({ db in
let maybeGroupName: String? = try ClosedGroup
let groupName: String = dependencies[singleton: .storage].read({ db in
try ClosedGroup
.filter(id: threadId)
.select(.name)
.asRequest(of: String.self)
.fetchOne(db)

guard let groupName: String = maybeGroupName else { throw StorageError.objectNotFound }

return (groupName, Profile.fetchOrCreateCurrentUser(db, using: dependencies))
}),
let details: Details = try? JSONDecoder(using: dependencies).decode(Details.self, from: detailsData)
else { return failure(job, JobRunnerError.missingRequiredDetails, true) }

let sentTimestampMs: Int64 = dependencies[cache: .snodeAPI].currentOffsetTimestampMs()
let adminProfile: Profile = dependencies.mutate(cache: .libSession) { $0.profile }

/// Perform the actual message sending
dependencies[singleton: .storage]
Expand All @@ -65,11 +62,12 @@ public enum GroupInviteMemberJob: JobExecutor {
message: try GroupUpdateInviteMessage(
inviteeSessionIdHexString: details.memberSessionIdHexString,
groupSessionId: SessionId(.group, hex: threadId),
groupName: currentInfo.groupName,
groupName: groupName,
memberAuthData: details.memberAuthData,
profile: VisibleMessage.VMProfile.init(
profile: currentInfo.adminProfile,
blocksCommunityMessageRequests: nil
profile: VisibleMessage.VMProfile(
displayName: adminProfile.name,
profileKey: adminProfile.profileEncryptionKey,
profilePictureUrl: adminProfile.profilePictureUrl
),
sentTimestampMs: UInt64(sentTimestampMs),
authMethod: try Authentication.with(
Expand Down
10 changes: 4 additions & 6 deletions SessionMessagingKit/Jobs/GroupLeavingJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,10 @@ public enum GroupLeavingJob: JobExecutor {
.defaulting(to: 0)
let finalBehaviour: GroupLeavingJob.Details.Behaviour = {
guard
(
dependencies.mutate(cache: .libSession) { cache in
cache.wasKickedFromGroup(groupSessionId: SessionId(.group, hex: threadId)) ||
cache.groupIsDestroyed(groupSessionId: SessionId(.group, hex: threadId))
}
)
dependencies.mutate(cache: .libSession, { cache in
cache.wasKickedFromGroup(groupSessionId: SessionId(.group, hex: threadId)) ||
cache.groupIsDestroyed(groupSessionId: SessionId(.group, hex: threadId))
})
else { return details.behaviour }

return .delete
Expand Down
2 changes: 1 addition & 1 deletion SessionMessagingKit/Jobs/UpdateProfilePictureJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public enum UpdateProfilePictureJob: JobExecutor {
}

// Note: The user defaults flag is updated in DisplayPictureManager
let profile: Profile = Profile.fetchOrCreateCurrentUser(using: dependencies)
let profile: Profile = dependencies.mutate(cache: .libSession) { $0.profile }
let displayPictureUpdate: DisplayPictureManager.Update = profile.profilePictureFileName
.map { dependencies[singleton: .displayPictureManager].loadDisplayPictureFromDisk(for: $0) }
.map { .currentUserUploadImageData($0) }
Expand Down
Loading