mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
7.9 KiB
Swift
164 lines
7.9 KiB
Swift
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import Foundation
|
|
import Combine
|
|
import GRDB
|
|
import Sodium
|
|
import SessionUtilitiesKit
|
|
import SessionSnodeKit
|
|
|
|
extension MessageSender {
|
|
private typealias PreparedGroupData = (
|
|
groupState: [ConfigDump.Variant: SessionUtil.Config],
|
|
thread: SessionThread,
|
|
group: ClosedGroup,
|
|
members: [GroupMember],
|
|
preparedNotificationsSubscription: HTTP.PreparedRequest<PushNotificationAPI.SubscribeResponse>?
|
|
)
|
|
public static func createGroup(
|
|
name: String,
|
|
displayPicture: SignalAttachment?,
|
|
members: [(String, Profile?)],
|
|
using dependencies: Dependencies = Dependencies()
|
|
) -> AnyPublisher<SessionThread, Error> {
|
|
return Just(())
|
|
.setFailureType(to: Error.self)
|
|
.flatMap { _ -> AnyPublisher<(url: String, filename: String, encryptionKey: Data)?, Error> in
|
|
guard let displayPicture: SignalAttachment = displayPicture else {
|
|
return Just(nil)
|
|
.setFailureType(to: Error.self)
|
|
.eraseToAnyPublisher()
|
|
}
|
|
// TODO: Upload group image first
|
|
return Just(nil)
|
|
.setFailureType(to: Error.self)
|
|
.eraseToAnyPublisher()
|
|
}
|
|
.flatMap { displayPictureInfo -> AnyPublisher<PreparedGroupData, Error> in
|
|
dependencies[singleton: .storage].writePublisher(using: dependencies) { db -> PreparedGroupData in
|
|
// Create and cache the libSession entries
|
|
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
|
let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser(db, using: dependencies)
|
|
let createdInfo: SessionUtil.CreatedGroupInfo = try SessionUtil.createGroup(
|
|
db,
|
|
name: name,
|
|
displayPictureUrl: displayPictureInfo?.url,
|
|
displayPictureFilename: displayPictureInfo?.filename,
|
|
displayPictureEncryptionKey: displayPictureInfo?.encryptionKey,
|
|
members: members,
|
|
admins: [(currentUserPublicKey, currentUserProfile)],
|
|
using: dependencies
|
|
)
|
|
|
|
// Save the relevant objects to the database
|
|
let thread: SessionThread = try SessionThread
|
|
.fetchOrCreate(
|
|
db,
|
|
id: createdInfo.group.id,
|
|
variant: .group,
|
|
shouldBeVisible: true,
|
|
using: dependencies
|
|
)
|
|
try createdInfo.group.insert(db)
|
|
try createdInfo.members.forEach { try $0.insert(db) }
|
|
|
|
// Prepare the notification subscription
|
|
let preparedNotificationSubscription = try? PushNotificationAPI
|
|
.preparedSubscribe(
|
|
publicKey: createdInfo.group.id,
|
|
subkey: nil,
|
|
ed25519KeyPair: createdInfo.identityKeyPair,
|
|
using: dependencies
|
|
)
|
|
|
|
return (
|
|
createdInfo.groupState,
|
|
thread,
|
|
createdInfo.group,
|
|
createdInfo.members,
|
|
preparedNotificationSubscription
|
|
)
|
|
}
|
|
}
|
|
.flatMap { preparedGroupData -> AnyPublisher<PreparedGroupData, Error> in
|
|
ConfigurationSyncJob
|
|
.run(publicKey: preparedGroupData.group.id, using: dependencies)
|
|
.flatMap { _ in
|
|
dependencies[singleton: .storage].writePublisher(using: dependencies) { db in
|
|
// Save the successfully created group and add to the user config
|
|
try SessionUtil.saveCreatedGroup(
|
|
db,
|
|
group: preparedGroupData.group,
|
|
groupState: preparedGroupData.groupState,
|
|
using: dependencies
|
|
)
|
|
|
|
return preparedGroupData
|
|
}
|
|
}
|
|
.handleEvents(
|
|
receiveCompletion: { result in
|
|
switch result {
|
|
case .finished: break
|
|
case .failure:
|
|
// Remove the config and database states
|
|
dependencies[singleton: .storage].writeAsync(using: dependencies) { db in
|
|
SessionUtil.removeGroupStateIfNeeded(
|
|
db,
|
|
groupIdentityPublicKey: preparedGroupData.group.id,
|
|
using: dependencies
|
|
)
|
|
|
|
_ = try? preparedGroupData.thread.delete(db)
|
|
_ = try? preparedGroupData.group.delete(db)
|
|
try? preparedGroupData.members.forEach { try $0.delete(db) }
|
|
}
|
|
}
|
|
}
|
|
)
|
|
.eraseToAnyPublisher()
|
|
}
|
|
.handleEvents(
|
|
receiveOutput: { _, thread, _, members, preparedNotificationSubscription in
|
|
// Start polling
|
|
dependencies[singleton: .closedGroupPoller].startIfNeeded(for: thread.id, using: dependencies)
|
|
|
|
// Subscribe for push notifications (if PNs are enabled)
|
|
preparedNotificationSubscription?
|
|
.send(using: dependencies)
|
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies)
|
|
.sinkUntilComplete()
|
|
|
|
// Save jobs for sending group member invitations
|
|
dependencies[singleton: .storage].write(using: dependencies) { db in
|
|
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
|
|
|
members
|
|
.filter { $0.profileId != currentUserPublicKey }
|
|
.forEach { member in
|
|
dependencies[singleton: .jobRunner].add(
|
|
db,
|
|
job: Job(
|
|
variant: .groupInviteMemberJob,
|
|
threadId: thread.id,
|
|
details: GroupInviteMemberJob.Details(
|
|
memberSubkey: Data(),
|
|
memberTag: Data()
|
|
)
|
|
),
|
|
canStartJob: true,
|
|
using: dependencies
|
|
)
|
|
|
|
// Send admin keys to any admins
|
|
guard member.role == .admin else { return }
|
|
|
|
}
|
|
}
|
|
}
|
|
)
|
|
.map { _, thread, _, _, _ in thread }
|
|
.eraseToAnyPublisher()
|
|
}
|
|
}
|