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.
188 lines
10 KiB
Swift
188 lines
10 KiB
Swift
import PromiseKit
|
|
|
|
@objc(SNOpenGroupManagerV2)
|
|
public final class OpenGroupManagerV2 : NSObject {
|
|
private var pollers: [String:OpenGroupPollerV2] = [:] // One for each server
|
|
private var isPolling = false
|
|
|
|
// MARK: Initialization
|
|
@objc public static let shared = OpenGroupManagerV2()
|
|
|
|
private override init() { }
|
|
|
|
// MARK: Polling
|
|
@objc public func startPolling() {
|
|
guard !isPolling else { return }
|
|
isPolling = true
|
|
let servers = Set(Storage.shared.getAllV2OpenGroups().values.map { $0.server })
|
|
servers.forEach { server in
|
|
if let poller = pollers[server] { poller.stop() } // Should never occur
|
|
let poller = OpenGroupPollerV2(for: server)
|
|
poller.startIfNeeded()
|
|
pollers[server] = poller
|
|
}
|
|
}
|
|
|
|
@objc public func stopPolling() {
|
|
pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
|
|
pollers.removeAll()
|
|
}
|
|
|
|
// MARK: Adding & Removing
|
|
|
|
public func hasExistingOpenGroup(room: String, server: String, publicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> Bool {
|
|
guard let serverUrl: URL = URL(string: server) else { return false }
|
|
|
|
let serverHost: String = (serverUrl.host ?? server)
|
|
let serverPort: String = (serverUrl.port.map { ":\($0)" } ?? "")
|
|
let defaultServerHost: String = OpenGroupAPIV2.defaultServer.substring(from: "http://".count)
|
|
var serverOptions: Set<String> = Set([
|
|
server,
|
|
"\(serverHost)\(serverPort)",
|
|
"http://\(serverHost)\(serverPort)",
|
|
"https://\(serverHost)\(serverPort)"
|
|
])
|
|
|
|
if serverHost == OpenGroupAPIV2.legacyDefaultServerDNS {
|
|
let defaultServerOptions: Set<String> = Set([
|
|
defaultServerHost,
|
|
OpenGroupAPIV2.defaultServer,
|
|
"https://\(defaultServerHost)"
|
|
])
|
|
serverOptions = serverOptions.union(defaultServerOptions)
|
|
}
|
|
else if serverHost == defaultServerHost {
|
|
let legacyServerOptions: Set<String> = Set([
|
|
OpenGroupAPIV2.legacyDefaultServerDNS,
|
|
"http://\(OpenGroupAPIV2.legacyDefaultServerDNS)",
|
|
"https://\(OpenGroupAPIV2.legacyDefaultServerDNS)"
|
|
])
|
|
serverOptions = serverOptions.union(legacyServerOptions)
|
|
}
|
|
|
|
// First check if there is no poller for the specified server
|
|
if serverOptions.first(where: { OpenGroupManagerV2.shared.pollers[$0] != nil }) == nil {
|
|
return false
|
|
}
|
|
|
|
// Then check if there is an existing open group thread
|
|
let hasExistingThread: Bool = serverOptions.contains(where: { serverName in
|
|
let groupId: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData("\(serverName).\(room)")
|
|
|
|
return (TSGroupThread.fetch(groupId: groupId, transaction: transaction) != nil)
|
|
})
|
|
|
|
return hasExistingThread
|
|
}
|
|
|
|
public func add(room: String, server: String, publicKey: String, using transaction: Any) -> Promise<Void> {
|
|
// If we are currently polling for this server and already have a TSGroupThread for this room the do nothing
|
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
|
|
|
if hasExistingOpenGroup(room: room, server: server, publicKey: publicKey, using: transaction) {
|
|
SNLog("Ignoring join open group attempt (already joined)")
|
|
return Promise.value(())
|
|
}
|
|
|
|
let storage = Storage.shared
|
|
// Clear any existing data if needed
|
|
storage.removeLastMessageServerID(for: room, on: server, using: transaction)
|
|
storage.removeLastDeletionServerID(for: room, on: server, using: transaction)
|
|
storage.removeAuthToken(for: room, on: server, using: transaction)
|
|
// Store the public key
|
|
storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction)
|
|
let (promise, seal) = Promise<Void>.pending()
|
|
|
|
transaction.addCompletionQueue(DispatchQueue.global(qos: .userInitiated)) {
|
|
// Get the group info
|
|
OpenGroupAPIV2.getInfo(for: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { info in
|
|
// Create the open group model and the thread
|
|
let openGroup = OpenGroupV2(server: server, room: room, name: info.name, publicKey: publicKey, imageID: info.imageID)
|
|
let groupID = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
|
|
let model = TSGroupModel(title: openGroup.name, memberIds: [ getUserHexEncodedPublicKey() ], image: nil, groupId: groupID, groupType: .openGroup, adminIds: [])
|
|
// Store everything
|
|
storage.write(with: { transaction in
|
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
|
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
|
|
thread.shouldBeVisible = true
|
|
thread.save(with: transaction)
|
|
storage.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
|
|
}, completion: {
|
|
// Start the poller if needed
|
|
if OpenGroupManagerV2.shared.pollers[server] == nil {
|
|
let poller = OpenGroupPollerV2(for: server)
|
|
poller.startIfNeeded()
|
|
OpenGroupManagerV2.shared.pollers[server] = poller
|
|
}
|
|
// Fetch the group image
|
|
OpenGroupAPIV2.getGroupImage(for: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { data in
|
|
storage.write { transaction in
|
|
// Update the thread
|
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
|
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
|
|
thread.groupModel.groupImage = UIImage(data: data)
|
|
thread.save(with: transaction)
|
|
}
|
|
}.retainUntilComplete()
|
|
// Finish
|
|
seal.fulfill(())
|
|
})
|
|
}.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
|
seal.reject(error)
|
|
}
|
|
}
|
|
return promise
|
|
}
|
|
|
|
public func delete(_ openGroup: OpenGroupV2, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
|
|
let storage = SNMessagingKitConfiguration.shared.storage
|
|
// Stop the poller if needed
|
|
let openGroups = storage.getAllV2OpenGroups().values.filter { $0.server == openGroup.server }
|
|
if openGroups.count == 1 && openGroups.last == openGroup {
|
|
let poller = pollers[openGroup.server]
|
|
poller?.stop()
|
|
pollers[openGroup.server] = nil
|
|
}
|
|
// Remove all data
|
|
var messageIDs: Set<String> = []
|
|
var messageTimestamps: Set<UInt64> = []
|
|
thread.enumerateInteractions(with: transaction) { interaction, _ in
|
|
messageIDs.insert(interaction.uniqueId!)
|
|
messageTimestamps.insert(interaction.timestamp)
|
|
}
|
|
storage.updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, using: transaction)
|
|
Storage.shared.removeReceivedMessageTimestamps(messageTimestamps, using: transaction)
|
|
Storage.shared.removeLastMessageServerID(for: openGroup.room, on: openGroup.server, using: transaction)
|
|
Storage.shared.removeLastDeletionServerID(for: openGroup.room, on: openGroup.server, using: transaction)
|
|
let _ = OpenGroupAPIV2.deleteAuthToken(for: openGroup.room, on: openGroup.server)
|
|
Storage.shared.removeOpenGroupPublicKey(for: openGroup.server, using: transaction)
|
|
thread.removeAllThreadInteractions(with: transaction)
|
|
thread.remove(with: transaction)
|
|
Storage.shared.removeV2OpenGroup(for: thread.uniqueId!, using: transaction)
|
|
}
|
|
|
|
// MARK: Convenience
|
|
public static func parseV2OpenGroup(from string: String) -> (room: String, server: String, publicKey: String)? {
|
|
guard let url = URL(string: string), let host = url.host ?? given(string.split(separator: "/").first, { String($0) }), let query = url.query else { return nil }
|
|
// Inputs that should work:
|
|
// https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
|
// http://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
|
// sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c (does NOT go to HTTPS)
|
|
// https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
|
// 143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
|
let useTLS = (url.scheme == "https")
|
|
|
|
// If there is no scheme then the host is included in the path (so handle that case)
|
|
let hostFreePath = (url.host != nil || !url.path.starts(with: host) ? url.path : url.path.substring(from: host.count))
|
|
let updatedPath = (hostFreePath.starts(with: "/r/") ? hostFreePath.substring(from: 2) : hostFreePath)
|
|
let room = String(updatedPath.dropFirst()) // Drop the leading slash
|
|
let queryParts = query.split(separator: "=")
|
|
guard !room.isEmpty && !room.contains("/"), queryParts.count == 2, queryParts[0] == "public_key" else { return nil }
|
|
let publicKey = String(queryParts[1])
|
|
guard publicKey.count == 64 && Hex.isValid(publicKey) else { return nil }
|
|
var server = (useTLS ? "https://" : "http://") + host
|
|
if let port = url.port { server += ":\(port)" }
|
|
return (room: room, server: server, publicKey: publicKey)
|
|
}
|
|
}
|