2022-06-08 06:29:51 +02:00
|
|
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import GRDB
|
2021-03-24 02:37:33 +01:00
|
|
|
import PromiseKit
|
2022-02-24 00:39:22 +01:00
|
|
|
import Sodium
|
|
|
|
import SessionUtilitiesKit
|
2022-03-03 07:46:35 +01:00
|
|
|
import SessionSnodeKit
|
2021-03-24 02:37:33 +01:00
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
// MARK: - OGMCacheType
|
|
|
|
|
|
|
|
public protocol OGMCacheType {
|
|
|
|
var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>? { get set }
|
|
|
|
var groupImagePromises: [String: Promise<Data>] { get set }
|
|
|
|
|
|
|
|
var pollers: [String: OpenGroupAPI.Poller] { get set }
|
|
|
|
var isPolling: Bool { get set }
|
|
|
|
|
|
|
|
var hasPerformedInitialPoll: [String: Bool] { get set }
|
|
|
|
var timeSinceLastPoll: [String: TimeInterval] { get set }
|
|
|
|
|
2022-08-25 09:24:43 +02:00
|
|
|
var pendingChanges: [OpenGroupAPI.PendingChange] { get set }
|
2022-08-30 01:45:40 +02:00
|
|
|
|
|
|
|
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval
|
2022-03-16 05:55:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - OpenGroupManager
|
|
|
|
|
2022-02-15 06:44:10 +01:00
|
|
|
@objc(SNOpenGroupManager)
|
|
|
|
public final class OpenGroupManager: NSObject {
|
2022-03-16 05:55:56 +01:00
|
|
|
// MARK: - Cache
|
|
|
|
|
|
|
|
public class Cache: OGMCacheType {
|
2022-03-07 07:43:30 +01:00
|
|
|
public var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>?
|
2022-03-16 05:55:56 +01:00
|
|
|
public var groupImagePromises: [String: Promise<Data>] = [:]
|
2022-03-07 07:43:30 +01:00
|
|
|
|
2022-03-15 05:19:23 +01:00
|
|
|
public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
|
|
|
|
public var isPolling: Bool = false
|
|
|
|
|
2022-03-07 07:43:30 +01:00
|
|
|
/// Server URL to value
|
|
|
|
public var hasPerformedInitialPoll: [String: Bool] = [:]
|
|
|
|
public var timeSinceLastPoll: [String: TimeInterval] = [:]
|
|
|
|
|
|
|
|
fileprivate var _timeSinceLastOpen: TimeInterval?
|
2022-03-18 06:39:25 +01:00
|
|
|
public func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
|
2022-03-07 07:43:30 +01:00
|
|
|
if let storedTimeSinceLastOpen: TimeInterval = _timeSinceLastOpen {
|
|
|
|
return storedTimeSinceLastOpen
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let lastOpen: Date = dependencies.standardUserDefaults[.lastOpen] else {
|
|
|
|
_timeSinceLastOpen = .greatestFiniteMagnitude
|
|
|
|
return .greatestFiniteMagnitude
|
|
|
|
}
|
|
|
|
|
|
|
|
_timeSinceLastOpen = dependencies.date.timeIntervalSince(lastOpen)
|
|
|
|
return dependencies.date.timeIntervalSince(lastOpen)
|
|
|
|
}
|
2022-08-25 09:24:43 +02:00
|
|
|
|
|
|
|
public var pendingChanges: [OpenGroupAPI.PendingChange] = []
|
2022-03-07 07:43:30 +01:00
|
|
|
}
|
2022-02-15 06:44:10 +01:00
|
|
|
|
2022-03-07 07:43:30 +01:00
|
|
|
// MARK: - Variables
|
2022-02-16 00:31:08 +01:00
|
|
|
|
2022-03-07 07:43:30 +01:00
|
|
|
@objc public static let shared: OpenGroupManager = OpenGroupManager()
|
2022-02-16 00:31:08 +01:00
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
/// Note: This should not be accessed directly but rather via the 'OGMDependencies' type
|
|
|
|
fileprivate let mutableCache: Atomic<OGMCacheType> = Atomic(Cache())
|
2022-03-04 08:02:38 +01:00
|
|
|
|
2022-02-15 06:44:10 +01:00
|
|
|
// MARK: - Polling
|
2022-03-15 05:19:23 +01:00
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
public func startPolling(using dependencies: OGMDependencies = OGMDependencies()) {
|
|
|
|
guard !dependencies.cache.isPolling else { return }
|
2022-03-15 05:19:23 +01:00
|
|
|
|
2022-06-22 06:27:34 +02:00
|
|
|
let servers: Set<String> = dependencies.storage
|
2022-06-09 10:37:44 +02:00
|
|
|
.read { db in
|
|
|
|
// The default room promise creates an OpenGroup with an empty `roomToken` value,
|
|
|
|
// we don't want to start a poller for this as the user hasn't actually joined a room
|
|
|
|
try OpenGroup
|
|
|
|
.select(.server)
|
2022-06-24 10:29:45 +02:00
|
|
|
.filter(OpenGroup.Columns.isActive == true)
|
2022-06-09 10:37:44 +02:00
|
|
|
.filter(OpenGroup.Columns.roomToken != "")
|
|
|
|
.distinct()
|
|
|
|
.asRequest(of: String.self)
|
|
|
|
.fetchSet(db)
|
|
|
|
}
|
|
|
|
.defaulting(to: [])
|
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
dependencies.mutableCache.mutate { cache in
|
2022-03-15 05:19:23 +01:00
|
|
|
cache.isPolling = true
|
2022-06-08 06:29:51 +02:00
|
|
|
cache.pollers = servers
|
2022-06-09 10:37:44 +02:00
|
|
|
.reduce(into: [:]) { result, server in
|
2022-06-22 06:27:34 +02:00
|
|
|
result[server.lowercased()]?.stop() // Should never occur
|
|
|
|
result[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased())
|
2022-03-15 05:19:23 +01:00
|
|
|
}
|
2022-06-22 06:27:34 +02:00
|
|
|
|
|
|
|
// Note: We loop separately here because when the cache is mocked-out for tests it
|
|
|
|
// doesn't actually store the value (meaning the pollers won't be started), but if
|
|
|
|
// we do it in the 'reduce' function, the 'reduce' result will actually store the
|
|
|
|
// poller value resulting in a bunch of OpenGroup pollers running in a way that can't
|
|
|
|
// be stopped during unit tests
|
|
|
|
cache.pollers.forEach { _, poller in poller.startIfNeeded(using: dependencies) }
|
2022-03-15 05:19:23 +01:00
|
|
|
}
|
2021-03-24 02:37:33 +01:00
|
|
|
}
|
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
public func stopPolling(using dependencies: OGMDependencies = OGMDependencies()) {
|
|
|
|
dependencies.mutableCache.mutate {
|
2022-03-15 05:19:23 +01:00
|
|
|
$0.pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
|
|
|
|
$0.pollers.removeAll()
|
|
|
|
$0.isPolling = false
|
|
|
|
}
|
2021-03-24 02:37:33 +01:00
|
|
|
}
|
|
|
|
|
2022-02-15 06:44:10 +01:00
|
|
|
// MARK: - Adding & Removing
|
|
|
|
|
2022-07-04 09:36:48 +02:00
|
|
|
private static func port(for server: String, serverUrl: URL) -> String {
|
|
|
|
if let port: Int = serverUrl.port {
|
|
|
|
return ":\(port)"
|
|
|
|
}
|
|
|
|
|
|
|
|
let components: [String] = server.components(separatedBy: ":")
|
|
|
|
|
|
|
|
guard
|
|
|
|
let port: String = components.last,
|
|
|
|
(
|
|
|
|
port != components.first &&
|
|
|
|
!port.starts(with: "//")
|
|
|
|
)
|
|
|
|
else { return "" }
|
|
|
|
|
|
|
|
return ":\(port)"
|
|
|
|
}
|
|
|
|
|
|
|
|
public static func isSessionRunOpenGroup(server: String) -> Bool {
|
|
|
|
guard let serverUrl: URL = URL(string: server.lowercased()) else { return false }
|
|
|
|
|
|
|
|
let serverPort: String = OpenGroupManager.port(for: server, serverUrl: serverUrl)
|
|
|
|
let serverHost: String = serverUrl.host
|
|
|
|
.defaulting(
|
|
|
|
to: server
|
|
|
|
.lowercased()
|
|
|
|
.replacingOccurrences(of: serverPort, with: "")
|
|
|
|
)
|
|
|
|
let options: Set<String> = Set([
|
|
|
|
OpenGroupAPI.legacyDefaultServerIP,
|
|
|
|
OpenGroupAPI.defaultServer
|
|
|
|
.replacingOccurrences(of: "http://", with: "")
|
|
|
|
.replacingOccurrences(of: "https://", with: "")
|
|
|
|
])
|
|
|
|
|
|
|
|
return options.contains(serverHost)
|
|
|
|
}
|
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
public func hasExistingOpenGroup(_ db: Database, roomToken: String, server: String, publicKey: String, dependencies: OGMDependencies = OGMDependencies()) -> Bool {
|
2022-06-22 06:27:34 +02:00
|
|
|
guard let serverUrl: URL = URL(string: server.lowercased()) else { return false }
|
2022-03-21 04:29:46 +01:00
|
|
|
|
2022-07-04 09:36:48 +02:00
|
|
|
let serverPort: String = OpenGroupManager.port(for: server, serverUrl: serverUrl)
|
|
|
|
let serverHost: String = serverUrl.host
|
|
|
|
.defaulting(
|
|
|
|
to: server
|
|
|
|
.lowercased()
|
|
|
|
.replacingOccurrences(of: serverPort, with: "")
|
|
|
|
)
|
|
|
|
let defaultServerHost: String = OpenGroupAPI.defaultServer
|
|
|
|
.replacingOccurrences(of: "http://", with: "")
|
|
|
|
.replacingOccurrences(of: "https://", with: "")
|
2022-03-21 04:29:46 +01:00
|
|
|
var serverOptions: Set<String> = Set([
|
2022-06-22 06:27:34 +02:00
|
|
|
server.lowercased(),
|
2022-03-21 04:45:33 +01:00
|
|
|
"\(serverHost)\(serverPort)",
|
|
|
|
"http://\(serverHost)\(serverPort)",
|
|
|
|
"https://\(serverHost)\(serverPort)"
|
2022-03-21 04:29:46 +01:00
|
|
|
])
|
|
|
|
|
2022-07-04 09:36:48 +02:00
|
|
|
// If the server is run by Session then include all configurations in case one of the alternate configurations
|
|
|
|
// was used
|
|
|
|
if OpenGroupManager.isSessionRunOpenGroup(server: server) {
|
2022-06-28 09:53:03 +02:00
|
|
|
serverOptions.insert(defaultServerHost)
|
|
|
|
serverOptions.insert("http://\(defaultServerHost)")
|
2022-07-04 09:36:48 +02:00
|
|
|
serverOptions.insert("https://\(defaultServerHost)")
|
2022-06-28 09:53:03 +02:00
|
|
|
serverOptions.insert(OpenGroupAPI.legacyDefaultServerIP)
|
|
|
|
serverOptions.insert("http://\(OpenGroupAPI.legacyDefaultServerIP)")
|
|
|
|
serverOptions.insert("https://\(OpenGroupAPI.legacyDefaultServerIP)")
|
2022-03-21 04:29:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// First check if there is no poller for the specified server
|
|
|
|
if serverOptions.first(where: { dependencies.cache.pollers[$0] != nil }) == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then check if there is an existing open group thread
|
|
|
|
let hasExistingThread: Bool = serverOptions.contains(where: { serverName in
|
2022-06-09 10:37:44 +02:00
|
|
|
(try? SessionThread
|
|
|
|
.exists(
|
|
|
|
db,
|
|
|
|
id: OpenGroup.idFor(roomToken: roomToken, server: serverName)
|
|
|
|
))
|
|
|
|
.defaulting(to: false)
|
2022-03-21 04:29:46 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
return hasExistingThread
|
|
|
|
}
|
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
public func add(_ db: Database, roomToken: String, server: String, publicKey: String, isConfigMessage: Bool, dependencies: OGMDependencies = OGMDependencies()) -> Promise<Void> {
|
2022-03-03 07:46:35 +01:00
|
|
|
// If we are currently polling for this server and already have a TSGroupThread for this room the do nothing
|
2022-06-09 10:37:44 +02:00
|
|
|
if hasExistingOpenGroup(db, roomToken: roomToken, server: server, publicKey: publicKey, dependencies: dependencies) {
|
2022-03-04 03:33:06 +01:00
|
|
|
SNLog("Ignoring join open group attempt (already joined), user initiated: \(!isConfigMessage)")
|
2022-03-03 07:46:35 +01:00
|
|
|
return Promise.value(())
|
|
|
|
}
|
2022-02-15 06:44:10 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
// Store the open group information
|
2022-07-04 09:36:48 +02:00
|
|
|
let targetServer: String = {
|
|
|
|
guard OpenGroupManager.isSessionRunOpenGroup(server: server) else {
|
|
|
|
return server.lowercased()
|
|
|
|
}
|
|
|
|
|
|
|
|
return OpenGroupAPI.defaultServer
|
|
|
|
}()
|
|
|
|
let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: targetServer)
|
2022-06-09 10:37:44 +02:00
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
// Optionally try to insert a new version of the OpenGroup (it will fail if there is already an
|
|
|
|
// inactive one but that won't matter as we then activate it
|
2022-06-09 10:37:44 +02:00
|
|
|
_ = try? SessionThread.fetchOrCreate(db, id: threadId, variant: .openGroup)
|
2022-06-27 04:04:51 +02:00
|
|
|
_ = try? SessionThread.filter(id: threadId).updateAll(db, SessionThread.Columns.shouldBeVisible.set(to: true))
|
2022-06-24 10:29:45 +02:00
|
|
|
|
|
|
|
if (try? OpenGroup.exists(db, id: threadId)) == false {
|
|
|
|
try? OpenGroup
|
2022-07-04 09:36:48 +02:00
|
|
|
.fetchOrCreate(db, server: targetServer, roomToken: roomToken, publicKey: publicKey)
|
2022-06-24 10:29:45 +02:00
|
|
|
.save(db)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the group to active and reset the sequenceNumber (handle groups which have
|
|
|
|
// been deactivated)
|
2022-06-09 10:37:44 +02:00
|
|
|
_ = try? OpenGroup
|
2022-07-04 09:36:48 +02:00
|
|
|
.filter(id: OpenGroup.idFor(roomToken: roomToken, server: targetServer))
|
2022-06-24 10:29:45 +02:00
|
|
|
.updateAll(
|
|
|
|
db,
|
|
|
|
OpenGroup.Columns.isActive.set(to: true),
|
|
|
|
OpenGroup.Columns.sequenceNumber.set(to: 0)
|
2022-06-09 10:37:44 +02:00
|
|
|
)
|
2022-02-15 06:44:10 +01:00
|
|
|
|
2021-03-25 04:17:49 +01:00
|
|
|
let (promise, seal) = Promise<Void>.pending()
|
2022-02-15 06:44:10 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
// Note: We don't do this after the db commit as it can fail (resulting in endless loading)
|
|
|
|
OpenGroupAPI.workQueue.async {
|
|
|
|
dependencies.storage
|
2022-06-21 05:39:46 +02:00
|
|
|
.writeAsync { db in
|
2022-06-21 09:43:27 +02:00
|
|
|
// Note: The initial request for room info and it's capabilities should NOT be
|
|
|
|
// authenticated (this is because if the server requires blinding and the auth
|
|
|
|
// headers aren't blinded it will error - these endpoints do support unauthenticated
|
|
|
|
// retrieval so doing so prevents the error)
|
2022-06-09 10:37:44 +02:00
|
|
|
OpenGroupAPI
|
|
|
|
.capabilitiesAndRoom(
|
|
|
|
db,
|
|
|
|
for: roomToken,
|
2022-07-04 09:36:48 +02:00
|
|
|
on: targetServer,
|
2022-06-21 09:43:27 +02:00
|
|
|
authenticated: false,
|
2022-06-09 10:37:44 +02:00
|
|
|
using: dependencies
|
|
|
|
)
|
|
|
|
}
|
|
|
|
.done(on: OpenGroupAPI.workQueue) { response in
|
|
|
|
dependencies.storage.write { db in
|
2022-03-03 07:46:35 +01:00
|
|
|
// Store the capabilities first
|
|
|
|
OpenGroupManager.handleCapabilities(
|
2022-06-09 10:37:44 +02:00
|
|
|
db,
|
|
|
|
capabilities: response.capabilities.data,
|
2022-07-04 09:36:48 +02:00
|
|
|
on: targetServer
|
2022-03-03 07:46:35 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// Then the room
|
2022-06-09 10:37:44 +02:00
|
|
|
try OpenGroupManager.handlePollInfo(
|
|
|
|
db,
|
|
|
|
pollInfo: OpenGroupAPI.RoomPollInfo(room: response.room.data),
|
2022-03-03 07:46:35 +01:00
|
|
|
publicKey: publicKey,
|
|
|
|
for: roomToken,
|
2022-07-04 09:36:48 +02:00
|
|
|
on: targetServer,
|
2022-03-03 07:46:35 +01:00
|
|
|
dependencies: dependencies
|
|
|
|
) {
|
|
|
|
seal.fulfill(())
|
|
|
|
}
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
2022-02-11 06:48:16 +01:00
|
|
|
}
|
|
|
|
.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in
|
2022-03-15 05:19:23 +01:00
|
|
|
SNLog("Failed to join open group.")
|
2022-02-11 06:48:16 +01:00
|
|
|
seal.reject(error)
|
|
|
|
}
|
2022-06-09 10:37:44 +02:00
|
|
|
.retainUntilComplete()
|
2021-03-25 04:17:49 +01:00
|
|
|
}
|
2022-02-11 06:48:16 +01:00
|
|
|
|
2021-03-25 04:17:49 +01:00
|
|
|
return promise
|
2021-03-24 02:37:33 +01:00
|
|
|
}
|
|
|
|
|
2022-06-08 06:29:51 +02:00
|
|
|
public func delete(_ db: Database, openGroupId: String, dependencies: OGMDependencies = OGMDependencies()) {
|
|
|
|
let server: String? = try? OpenGroup
|
|
|
|
.select(.server)
|
|
|
|
.filter(id: openGroupId)
|
|
|
|
.asRequest(of: String.self)
|
|
|
|
.fetchOne(db)
|
|
|
|
|
2021-03-30 00:33:51 +02:00
|
|
|
// Stop the poller if needed
|
2022-06-09 10:37:44 +02:00
|
|
|
//
|
|
|
|
// Note: The default room promise creates an OpenGroup with an empty `roomToken` value,
|
|
|
|
// we don't want to start a poller for this as the user hasn't actually joined a room
|
|
|
|
let numActiveRooms: Int = (try? OpenGroup
|
2022-06-22 06:27:34 +02:00
|
|
|
.filter(OpenGroup.Columns.server == server?.lowercased())
|
2022-06-09 10:37:44 +02:00
|
|
|
.filter(OpenGroup.Columns.roomToken != "")
|
|
|
|
.filter(OpenGroup.Columns.isActive)
|
2022-06-08 06:29:51 +02:00
|
|
|
.fetchCount(db))
|
|
|
|
.defaulting(to: 1)
|
|
|
|
|
2022-06-22 06:27:34 +02:00
|
|
|
if numActiveRooms == 1, let server: String = server?.lowercased() {
|
2022-06-08 06:29:51 +02:00
|
|
|
let poller = dependencies.cache.pollers[server]
|
2021-04-19 06:44:27 +02:00
|
|
|
poller?.stop()
|
2022-06-08 06:29:51 +02:00
|
|
|
dependencies.mutableCache.mutate { $0.pollers[server] = nil }
|
2021-03-24 02:37:33 +01:00
|
|
|
}
|
2022-02-10 01:17:41 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
// Remove all the data (everything should cascade delete)
|
|
|
|
_ = try? SessionThread
|
|
|
|
.filter(id: openGroupId)
|
|
|
|
.deleteAll(db)
|
|
|
|
|
|
|
|
// Remove the open group (no foreign key to the thread so it won't auto-delete)
|
|
|
|
if server?.lowercased() != OpenGroupAPI.defaultServer.lowercased() {
|
|
|
|
_ = try? OpenGroup
|
|
|
|
.filter(id: openGroupId)
|
|
|
|
.deleteAll(db)
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// If it's a session-run room then just set it to inactive
|
|
|
|
_ = try? OpenGroup
|
|
|
|
.filter(id: openGroupId)
|
|
|
|
.updateAll(db, OpenGroup.Columns.isActive.set(to: false))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the thread and associated data
|
|
|
|
_ = try? SessionThread
|
2022-06-08 06:29:51 +02:00
|
|
|
.filter(id: openGroupId)
|
|
|
|
.deleteAll(db)
|
2021-03-24 02:37:33 +01:00
|
|
|
}
|
2021-03-29 02:49:59 +02:00
|
|
|
|
2022-02-16 00:31:08 +01:00
|
|
|
// MARK: - Response Processing
|
|
|
|
|
2022-02-17 08:33:23 +01:00
|
|
|
internal static func handleCapabilities(
|
2022-06-09 10:37:44 +02:00
|
|
|
_ db: Database,
|
|
|
|
capabilities: OpenGroupAPI.Capabilities,
|
|
|
|
on server: String
|
2022-02-16 00:31:08 +01:00
|
|
|
) {
|
2022-06-09 10:37:44 +02:00
|
|
|
// Remove old capabilities first
|
|
|
|
_ = try? Capability
|
2022-06-22 06:27:34 +02:00
|
|
|
.filter(Capability.Columns.openGroupServer == server.lowercased())
|
2022-06-09 10:37:44 +02:00
|
|
|
.deleteAll(db)
|
2022-02-16 00:31:08 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
// Then insert the new capabilities (both present and missing)
|
|
|
|
capabilities.capabilities.forEach { capability in
|
|
|
|
_ = try? Capability(
|
2022-06-22 06:27:34 +02:00
|
|
|
openGroupServer: server.lowercased(),
|
2022-08-05 09:10:01 +02:00
|
|
|
variant: capability,
|
2022-06-09 10:37:44 +02:00
|
|
|
isMissing: false
|
|
|
|
)
|
|
|
|
.saved(db)
|
|
|
|
}
|
|
|
|
capabilities.missing?.forEach { capability in
|
|
|
|
_ = try? Capability(
|
2022-06-22 06:27:34 +02:00
|
|
|
openGroupServer: server.lowercased(),
|
2022-08-05 09:10:01 +02:00
|
|
|
variant: capability,
|
2022-06-09 10:37:44 +02:00
|
|
|
isMissing: true
|
|
|
|
)
|
|
|
|
.saved(db)
|
|
|
|
}
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
internal static func handlePollInfo(
|
2022-06-09 10:37:44 +02:00
|
|
|
_ db: Database,
|
|
|
|
pollInfo: OpenGroupAPI.RoomPollInfo,
|
2022-02-16 00:31:08 +01:00
|
|
|
publicKey maybePublicKey: String?,
|
|
|
|
for roomToken: String,
|
|
|
|
on server: String,
|
2022-04-26 07:08:41 +02:00
|
|
|
waitForImageToComplete: Bool = false,
|
2022-03-16 05:55:56 +01:00
|
|
|
dependencies: OGMDependencies = OGMDependencies(),
|
2022-02-16 00:31:08 +01:00
|
|
|
completion: (() -> ())? = nil
|
2022-06-09 10:37:44 +02:00
|
|
|
) throws {
|
2022-02-16 00:31:08 +01:00
|
|
|
// Create the open group model and get or create the thread
|
2022-06-09 10:37:44 +02:00
|
|
|
let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: server)
|
|
|
|
|
|
|
|
guard let openGroup: OpenGroup = try OpenGroup.fetchOne(db, id: threadId) else { return }
|
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
// Only update the database columns which have changed (this is to prevent the UI from triggering
|
|
|
|
// updates due to changing database columns to the existing value)
|
2022-09-15 08:49:39 +02:00
|
|
|
let permissions = OpenGroup.Permissions.getPermissionsfromRoomInfo(pollInfo)
|
2022-09-15 07:50:15 +02:00
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
try OpenGroup
|
|
|
|
.filter(id: openGroup.id)
|
|
|
|
.updateAll(
|
|
|
|
db,
|
|
|
|
[
|
|
|
|
(openGroup.publicKey != maybePublicKey ?
|
|
|
|
maybePublicKey.map { OpenGroup.Columns.publicKey.set(to: $0) } :
|
|
|
|
nil
|
|
|
|
),
|
|
|
|
(openGroup.name != pollInfo.details?.name ?
|
|
|
|
(pollInfo.details?.name).map { OpenGroup.Columns.name.set(to: $0) } :
|
|
|
|
nil
|
|
|
|
),
|
|
|
|
(openGroup.roomDescription != pollInfo.details?.roomDescription ?
|
|
|
|
(pollInfo.details?.roomDescription).map { OpenGroup.Columns.roomDescription.set(to: $0) } :
|
|
|
|
nil
|
|
|
|
),
|
|
|
|
(openGroup.imageId != pollInfo.details?.imageId.map { "\($0)" } ?
|
2022-06-28 09:53:03 +02:00
|
|
|
(pollInfo.details?.imageId).map { OpenGroup.Columns.imageId.set(to: "\($0)") } :
|
2022-06-24 10:29:45 +02:00
|
|
|
nil
|
|
|
|
),
|
|
|
|
(openGroup.userCount != pollInfo.activeUsers ?
|
|
|
|
OpenGroup.Columns.userCount.set(to: pollInfo.activeUsers) :
|
|
|
|
nil
|
|
|
|
),
|
|
|
|
(openGroup.infoUpdates != pollInfo.details?.infoUpdates ?
|
|
|
|
(pollInfo.details?.infoUpdates).map { OpenGroup.Columns.infoUpdates.set(to: $0) } :
|
|
|
|
nil
|
2022-09-15 07:50:15 +02:00
|
|
|
),
|
|
|
|
(openGroup.permissions != permissions ?
|
|
|
|
OpenGroup.Columns.permissions.set(to: permissions) :
|
|
|
|
nil
|
2022-06-24 10:29:45 +02:00
|
|
|
)
|
|
|
|
].compactMap { $0 }
|
2022-06-09 10:37:44 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Update the admin/moderator group members
|
|
|
|
if let roomDetails: OpenGroupAPI.Room = pollInfo.details {
|
|
|
|
_ = try? GroupMember
|
|
|
|
.filter(GroupMember.Columns.groupId == threadId)
|
|
|
|
.deleteAll(db)
|
|
|
|
|
|
|
|
try roomDetails.admins.forEach { adminId in
|
|
|
|
_ = try GroupMember(
|
|
|
|
groupId: threadId,
|
|
|
|
profileId: adminId,
|
2022-08-16 05:56:40 +02:00
|
|
|
role: .admin,
|
|
|
|
isHidden: false
|
2022-06-09 10:37:44 +02:00
|
|
|
).saved(db)
|
|
|
|
}
|
|
|
|
|
2022-08-16 05:56:40 +02:00
|
|
|
try roomDetails.hiddenAdmins
|
|
|
|
.defaulting(to: [])
|
|
|
|
.forEach { adminId in
|
|
|
|
_ = try GroupMember(
|
|
|
|
groupId: threadId,
|
|
|
|
profileId: adminId,
|
|
|
|
role: .admin,
|
|
|
|
isHidden: true
|
|
|
|
).saved(db)
|
|
|
|
}
|
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
try roomDetails.moderators.forEach { moderatorId in
|
|
|
|
_ = try GroupMember(
|
|
|
|
groupId: threadId,
|
|
|
|
profileId: moderatorId,
|
2022-08-16 05:56:40 +02:00
|
|
|
role: .moderator,
|
|
|
|
isHidden: false
|
2022-06-09 10:37:44 +02:00
|
|
|
).saved(db)
|
|
|
|
}
|
2022-08-16 05:56:40 +02:00
|
|
|
|
|
|
|
try roomDetails.hiddenModerators
|
|
|
|
.defaulting(to: [])
|
|
|
|
.forEach { moderatorId in
|
|
|
|
_ = try GroupMember(
|
|
|
|
groupId: threadId,
|
|
|
|
profileId: moderatorId,
|
|
|
|
role: .moderator,
|
|
|
|
isHidden: true
|
|
|
|
).saved(db)
|
|
|
|
}
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
2022-06-09 10:37:44 +02:00
|
|
|
|
|
|
|
db.afterNextTransactionCommit { db in
|
2022-03-03 07:46:35 +01:00
|
|
|
// Start the poller if needed
|
2022-06-22 06:27:34 +02:00
|
|
|
if dependencies.cache.pollers[server.lowercased()] == nil {
|
2022-03-16 05:55:56 +01:00
|
|
|
dependencies.mutableCache.mutate {
|
2022-06-22 06:27:34 +02:00
|
|
|
$0.pollers[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased())
|
|
|
|
$0.pollers[server.lowercased()]?.startIfNeeded(using: dependencies)
|
2022-03-15 05:19:23 +01:00
|
|
|
}
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
/// Start downloading the room image (if we don't have one or it's been updated)
|
|
|
|
if
|
2022-07-12 09:43:52 +02:00
|
|
|
let imageId: String = pollInfo.details?.imageId,
|
2022-06-09 10:37:44 +02:00
|
|
|
(
|
2022-06-24 10:29:45 +02:00
|
|
|
openGroup.imageData == nil ||
|
2022-07-12 09:43:52 +02:00
|
|
|
openGroup.imageId != imageId
|
2022-06-09 10:37:44 +02:00
|
|
|
)
|
|
|
|
{
|
|
|
|
OpenGroupManager.roomImage(db, fileId: imageId, for: roomToken, on: server, using: dependencies)
|
2022-04-29 03:51:56 +02:00
|
|
|
.done { data in
|
2022-06-09 10:37:44 +02:00
|
|
|
dependencies.storage.write { db in
|
|
|
|
_ = try OpenGroup
|
|
|
|
.filter(id: threadId)
|
|
|
|
.updateAll(db, OpenGroup.Columns.imageData.set(to: data))
|
2022-04-26 07:08:41 +02:00
|
|
|
|
|
|
|
if waitForImageToComplete {
|
|
|
|
completion?()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.catch { _ in
|
|
|
|
if waitForImageToComplete {
|
|
|
|
completion?()
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
|
|
|
.retainUntilComplete()
|
|
|
|
}
|
2022-04-26 07:08:41 +02:00
|
|
|
else if waitForImageToComplete {
|
|
|
|
completion?()
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we want to wait for the image to complete then don't call the completion here
|
|
|
|
guard !waitForImageToComplete else { return }
|
2022-02-16 00:31:08 +01:00
|
|
|
|
2022-03-03 07:46:35 +01:00
|
|
|
// Finish
|
|
|
|
completion?()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal static func handleMessages(
|
2022-06-09 10:37:44 +02:00
|
|
|
_ db: Database,
|
|
|
|
messages: [OpenGroupAPI.Message],
|
2022-03-03 07:46:35 +01:00
|
|
|
for roomToken: String,
|
|
|
|
on server: String,
|
2022-03-16 05:55:56 +01:00
|
|
|
dependencies: OGMDependencies = OGMDependencies()
|
2022-03-03 07:46:35 +01:00
|
|
|
) {
|
|
|
|
// Sorting the messages by server ID before importing them fixes an issue where messages
|
|
|
|
// that quote older messages can't find those older messages
|
2022-06-09 10:37:44 +02:00
|
|
|
guard let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) else {
|
|
|
|
SNLog("Couldn't handle open group messages.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-08-15 08:04:07 +02:00
|
|
|
let seqNo: Int64? = messages.map { $0.seqNo }.max()
|
2022-03-03 07:46:35 +01:00
|
|
|
let sortedMessages: [OpenGroupAPI.Message] = messages
|
2022-08-05 09:10:01 +02:00
|
|
|
.filter { $0.deleted != true }
|
2022-03-03 07:46:35 +01:00
|
|
|
.sorted { lhs, rhs in lhs.id < rhs.id }
|
2022-08-08 05:56:11 +02:00
|
|
|
var messageServerIdsToRemove: [Int64] = messages
|
2022-08-05 09:10:01 +02:00
|
|
|
.filter { $0.deleted == true }
|
|
|
|
.map { $0.id }
|
2022-03-03 07:46:35 +01:00
|
|
|
|
|
|
|
if let seqNo: Int64 = seqNo {
|
2022-08-25 09:24:43 +02:00
|
|
|
// Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId')
|
2022-06-09 10:37:44 +02:00
|
|
|
_ = try? OpenGroup
|
|
|
|
.filter(id: openGroup.id)
|
|
|
|
.updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: seqNo))
|
2022-08-25 09:24:43 +02:00
|
|
|
|
|
|
|
// Update pendingChange cache
|
|
|
|
dependencies.mutableCache.mutate {
|
|
|
|
$0.pendingChanges = $0.pendingChanges
|
|
|
|
.filter { $0.seqNo == nil || $0.seqNo! > seqNo }
|
|
|
|
}
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process the messages
|
|
|
|
sortedMessages.forEach { message in
|
2022-08-11 03:51:15 +02:00
|
|
|
if message.base64EncodedData == nil && message.reactions == nil {
|
|
|
|
messageServerIdsToRemove.append(Int64(message.id))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-08-04 09:10:24 +02:00
|
|
|
// Handle messages
|
|
|
|
if let base64EncodedString: String = message.base64EncodedData,
|
|
|
|
let data = Data(base64Encoded: base64EncodedString)
|
|
|
|
{
|
|
|
|
do {
|
|
|
|
let processedMessage: ProcessedMessage? = try Message.processReceivedOpenGroupMessage(
|
2022-06-09 10:37:44 +02:00
|
|
|
db,
|
|
|
|
openGroupId: openGroup.id,
|
2022-08-04 09:10:24 +02:00
|
|
|
openGroupServerPublicKey: openGroup.publicKey,
|
|
|
|
message: message,
|
|
|
|
data: data,
|
2022-06-09 10:37:44 +02:00
|
|
|
dependencies: dependencies
|
|
|
|
)
|
2022-08-04 09:10:24 +02:00
|
|
|
|
|
|
|
if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo {
|
|
|
|
try MessageReceiver.handle(
|
|
|
|
db,
|
|
|
|
message: messageInfo.message,
|
|
|
|
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
|
|
|
openGroupId: openGroup.id,
|
|
|
|
dependencies: dependencies
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch {
|
|
|
|
switch error {
|
|
|
|
// Ignore duplicate & selfSend message errors (and don't bother logging
|
|
|
|
// them as there will be a lot since we each service node duplicates messages)
|
|
|
|
case DatabaseError.SQLITE_CONSTRAINT_UNIQUE,
|
|
|
|
MessageReceiverError.duplicateMessage,
|
|
|
|
MessageReceiverError.duplicateControlMessage,
|
|
|
|
MessageReceiverError.selfSend:
|
|
|
|
break
|
|
|
|
|
|
|
|
default: SNLog("Couldn't receive open group message due to error: \(error).")
|
|
|
|
}
|
2022-06-09 10:37:44 +02:00
|
|
|
}
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
2022-08-04 09:10:24 +02:00
|
|
|
|
|
|
|
// Handle reactions
|
2022-08-11 03:51:15 +02:00
|
|
|
if message.reactions != nil {
|
|
|
|
do {
|
|
|
|
let reactions: [Reaction] = Message.processRawReceivedReactions(
|
|
|
|
db,
|
|
|
|
openGroupId: openGroup.id,
|
|
|
|
message: message,
|
2022-08-25 09:24:43 +02:00
|
|
|
associatedPendingChanges: dependencies.cache.pendingChanges
|
|
|
|
.filter {
|
|
|
|
guard $0.server == server && $0.room == roomToken && $0.changeType == .reaction else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if case .reaction(let messageId, _, _) = $0.metadata {
|
|
|
|
return messageId == message.id
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
2022-08-11 03:51:15 +02:00
|
|
|
dependencies: dependencies
|
|
|
|
)
|
|
|
|
|
|
|
|
try MessageReceiver.handleOpenGroupReactions(
|
|
|
|
db,
|
2022-08-25 03:12:22 +02:00
|
|
|
threadId: openGroup.threadId,
|
2022-08-11 03:51:15 +02:00
|
|
|
openGroupMessageServerId: message.id,
|
|
|
|
openGroupReactions: reactions
|
|
|
|
)
|
|
|
|
}
|
|
|
|
catch {
|
|
|
|
SNLog("Couldn't handle open group reactions due to error: \(error).")
|
|
|
|
}
|
2022-08-08 05:07:29 +02:00
|
|
|
}
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle any deletions that are needed
|
2022-06-09 10:37:44 +02:00
|
|
|
guard !messageServerIdsToRemove.isEmpty else { return }
|
2022-03-03 07:46:35 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
_ = try? Interaction
|
2022-08-25 03:12:22 +02:00
|
|
|
.filter(Interaction.Columns.threadId == openGroup.threadId)
|
2022-06-09 10:37:44 +02:00
|
|
|
.filter(messageServerIdsToRemove.contains(Interaction.Columns.openGroupServerMessageId))
|
|
|
|
.deleteAll(db)
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
|
|
|
|
2022-03-02 04:44:56 +01:00
|
|
|
internal static func handleDirectMessages(
|
2022-06-09 10:37:44 +02:00
|
|
|
_ db: Database,
|
|
|
|
messages: [OpenGroupAPI.DirectMessage],
|
2022-03-02 04:44:56 +01:00
|
|
|
fromOutbox: Bool,
|
2022-02-25 01:59:29 +01:00
|
|
|
on server: String,
|
2022-03-16 05:55:56 +01:00
|
|
|
dependencies: OGMDependencies = OGMDependencies()
|
2022-02-25 01:59:29 +01:00
|
|
|
) {
|
2022-02-25 07:48:09 +01:00
|
|
|
// Don't need to do anything if we have no messages (it's a valid case)
|
|
|
|
guard !messages.isEmpty else { return }
|
2022-06-22 06:27:34 +02:00
|
|
|
guard let openGroup: OpenGroup = try? OpenGroup.filter(OpenGroup.Columns.server == server.lowercased()).fetchOne(db) else {
|
2022-02-25 01:59:29 +01:00
|
|
|
SNLog("Couldn't receive inbox message.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sorting the messages by server ID before importing them fixes an issue where messages
|
|
|
|
// that quote older messages can't find those older messages
|
|
|
|
let sortedMessages: [OpenGroupAPI.DirectMessage] = messages
|
|
|
|
.sorted { lhs, rhs in lhs.id < rhs.id }
|
2022-03-17 02:05:04 +01:00
|
|
|
let latestMessageId: Int64 = sortedMessages[sortedMessages.count - 1].id
|
2022-06-09 10:37:44 +02:00
|
|
|
var lookupCache: [String: BlindedIdLookup] = [:] // Only want this cache to exist for the current loop
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2022-03-03 07:46:35 +01:00
|
|
|
// Update the 'latestMessageId' value
|
|
|
|
if fromOutbox {
|
2022-06-09 10:37:44 +02:00
|
|
|
_ = try? OpenGroup
|
2022-06-22 06:27:34 +02:00
|
|
|
.filter(OpenGroup.Columns.server == server.lowercased())
|
2022-06-09 10:37:44 +02:00
|
|
|
.updateAll(db, OpenGroup.Columns.outboxLatestMessageId.set(to: latestMessageId))
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
|
|
|
else {
|
2022-06-09 10:37:44 +02:00
|
|
|
_ = try? OpenGroup
|
2022-06-22 06:27:34 +02:00
|
|
|
.filter(OpenGroup.Columns.server == server.lowercased())
|
2022-06-09 10:37:44 +02:00
|
|
|
.updateAll(db, OpenGroup.Columns.inboxLatestMessageId.set(to: latestMessageId))
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2022-03-03 07:46:35 +01:00
|
|
|
// Process the messages
|
|
|
|
sortedMessages.forEach { message in
|
|
|
|
guard let messageData = Data(base64Encoded: message.base64EncodedMessage) else {
|
|
|
|
SNLog("Couldn't receive inbox message.")
|
|
|
|
return
|
|
|
|
}
|
2022-02-25 01:59:29 +01:00
|
|
|
|
2022-03-03 07:46:35 +01:00
|
|
|
do {
|
2022-06-09 10:37:44 +02:00
|
|
|
let processedMessage: ProcessedMessage? = try Message.processReceivedOpenGroupDirectMessage(
|
|
|
|
db,
|
|
|
|
openGroupServerPublicKey: openGroup.publicKey,
|
|
|
|
message: message,
|
|
|
|
data: messageData,
|
2022-03-03 07:46:35 +01:00
|
|
|
isOutgoing: fromOutbox,
|
|
|
|
otherBlindedPublicKey: (fromOutbox ? message.recipient : message.sender),
|
2022-03-16 05:55:56 +01:00
|
|
|
dependencies: dependencies
|
2022-03-03 07:46:35 +01:00
|
|
|
)
|
|
|
|
|
2022-07-29 07:26:24 +02:00
|
|
|
// We want to update the BlindedIdLookup cache with the message info so we can avoid using the
|
|
|
|
// "expensive" lookup when possible
|
|
|
|
let lookup: BlindedIdLookup = try {
|
|
|
|
// Minor optimisation to avoid processing the same sender multiple times in the same
|
|
|
|
// 'handleMessages' call (since the 'mapping' call is done within a transaction we
|
|
|
|
// will never have a mapping come through part-way through processing these messages)
|
|
|
|
if let result: BlindedIdLookup = lookupCache[message.recipient] {
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
return try BlindedIdLookup.fetchOrCreate(
|
|
|
|
db,
|
|
|
|
blindedId: (fromOutbox ?
|
|
|
|
message.recipient :
|
|
|
|
message.sender
|
|
|
|
),
|
|
|
|
sessionId: (fromOutbox ?
|
|
|
|
nil :
|
|
|
|
processedMessage?.threadId
|
|
|
|
),
|
|
|
|
openGroupServer: server.lowercased(),
|
|
|
|
openGroupPublicKey: openGroup.publicKey,
|
|
|
|
isCheckingForOutbox: fromOutbox,
|
|
|
|
dependencies: dependencies
|
|
|
|
)
|
|
|
|
}()
|
|
|
|
lookupCache[message.recipient] = lookup
|
|
|
|
|
|
|
|
// We also need to set the 'syncTarget' for outgoing messages to be consistent with
|
|
|
|
// standard messages
|
2022-03-03 07:46:35 +01:00
|
|
|
if fromOutbox {
|
2022-06-09 10:37:44 +02:00
|
|
|
let syncTarget: String = (lookup.sessionId ?? message.recipient)
|
2022-03-02 04:44:56 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
switch processedMessage?.messageInfo.variant {
|
|
|
|
case .visibleMessage:
|
|
|
|
(processedMessage?.messageInfo.message as? VisibleMessage)?.syncTarget = syncTarget
|
|
|
|
|
|
|
|
case .expirationTimerUpdate:
|
|
|
|
(processedMessage?.messageInfo.message as? ExpirationTimerUpdate)?.syncTarget = syncTarget
|
|
|
|
|
2022-03-03 07:46:35 +01:00
|
|
|
default: break
|
|
|
|
}
|
2022-02-25 01:59:29 +01:00
|
|
|
}
|
2022-03-03 07:46:35 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo {
|
|
|
|
try MessageReceiver.handle(
|
|
|
|
db,
|
|
|
|
message: messageInfo.message,
|
|
|
|
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
|
|
|
openGroupId: nil, // Intentionally nil as they are technically not open group messages
|
|
|
|
dependencies: dependencies
|
|
|
|
)
|
2022-03-04 08:02:38 +01:00
|
|
|
}
|
2022-03-03 07:46:35 +01:00
|
|
|
}
|
2022-06-16 05:14:56 +02:00
|
|
|
catch {
|
|
|
|
switch error {
|
|
|
|
// Ignore duplicate and self-send errors (we will always receive a duplicate message back
|
|
|
|
// whenever we send a message so this ends up being spam otherwise)
|
|
|
|
case DatabaseError.SQLITE_CONSTRAINT_UNIQUE,
|
|
|
|
MessageReceiverError.duplicateMessage,
|
|
|
|
MessageReceiverError.duplicateControlMessage,
|
|
|
|
MessageReceiverError.selfSend:
|
|
|
|
break
|
|
|
|
|
|
|
|
default:
|
|
|
|
SNLog("Couldn't receive inbox message due to error: \(error).")
|
|
|
|
}
|
2022-02-25 01:59:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-16 00:31:08 +01:00
|
|
|
// MARK: - Convenience
|
|
|
|
|
2022-08-25 09:24:43 +02:00
|
|
|
public static func addPendingReaction(
|
|
|
|
emoji: String,
|
|
|
|
id: Int64,
|
|
|
|
in roomToken: String,
|
|
|
|
on server: String,
|
2022-08-29 07:58:49 +02:00
|
|
|
type: OpenGroupAPI.PendingChange.ReactAction,
|
2022-08-25 09:24:43 +02:00
|
|
|
using dependencies: OGMDependencies = OGMDependencies()
|
|
|
|
) -> OpenGroupAPI.PendingChange {
|
|
|
|
let pendingChange = OpenGroupAPI.PendingChange(
|
|
|
|
server: server,
|
|
|
|
room: roomToken,
|
|
|
|
changeType: .reaction,
|
|
|
|
metadata: .reaction(
|
|
|
|
messageId: id,
|
|
|
|
emoji: emoji,
|
|
|
|
action: type
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
dependencies.mutableCache.mutate {
|
|
|
|
$0.pendingChanges.append(pendingChange)
|
|
|
|
}
|
|
|
|
|
|
|
|
return pendingChange
|
|
|
|
}
|
|
|
|
|
|
|
|
public static func updatePendingChange(
|
|
|
|
_ pendingChange: OpenGroupAPI.PendingChange,
|
2022-08-31 08:38:25 +02:00
|
|
|
seqNo: Int64?,
|
2022-08-25 09:24:43 +02:00
|
|
|
using dependencies: OGMDependencies = OGMDependencies()
|
|
|
|
) {
|
|
|
|
dependencies.mutableCache.mutate {
|
|
|
|
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
|
|
|
|
$0.pendingChanges[index].seqNo = seqNo
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 06:58:31 +02:00
|
|
|
public static func removePendingChange(
|
|
|
|
_ pendingChange: OpenGroupAPI.PendingChange,
|
|
|
|
using dependencies: OGMDependencies = OGMDependencies()
|
|
|
|
) {
|
|
|
|
dependencies.mutableCache.mutate {
|
|
|
|
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
|
|
|
|
$0.pendingChanges.remove(at: index)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-18 08:13:20 +02:00
|
|
|
/// This method specifies if the given capability is supported on a specified Open Group
|
|
|
|
public static func isOpenGroupSupport(
|
|
|
|
_ capability: Capability.Variant,
|
|
|
|
on server: String?,
|
|
|
|
using dependencies: OGMDependencies = OGMDependencies()
|
|
|
|
) -> Bool {
|
|
|
|
guard let server: String = server else { return false }
|
|
|
|
|
|
|
|
return dependencies.storage
|
|
|
|
.read { db in
|
|
|
|
let capabilities: [Capability.Variant] = (try? Capability
|
|
|
|
.select(.variant)
|
|
|
|
.filter(Capability.Columns.openGroupServer == server)
|
|
|
|
.filter(Capability.Columns.isMissing == false)
|
|
|
|
.asRequest(of: Capability.Variant.self)
|
|
|
|
.fetchAll(db))
|
|
|
|
.defaulting(to: [])
|
|
|
|
|
|
|
|
return capabilities.contains(capability)
|
|
|
|
}
|
|
|
|
.defaulting(to: false)
|
|
|
|
}
|
|
|
|
|
2022-02-24 00:39:22 +01:00
|
|
|
/// This method specifies if the given publicKey is a moderator or an admin within a specified Open Group
|
2022-06-09 10:37:44 +02:00
|
|
|
public static func isUserModeratorOrAdmin(
|
|
|
|
_ publicKey: String,
|
|
|
|
for roomToken: String?,
|
|
|
|
on server: String?,
|
|
|
|
using dependencies: OGMDependencies = OGMDependencies()
|
|
|
|
) -> Bool {
|
|
|
|
guard let roomToken: String = roomToken, let server: String = server else { return false }
|
2022-03-04 08:02:38 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
let groupId: String = OpenGroup.idFor(roomToken: roomToken, server: server)
|
|
|
|
let targetRoles: [GroupMember.Role] = [.moderator, .admin]
|
|
|
|
|
|
|
|
return dependencies.storage
|
|
|
|
.read { db in
|
|
|
|
let isDirectModOrAdmin: Bool = (try? GroupMember
|
|
|
|
.filter(GroupMember.Columns.groupId == groupId)
|
|
|
|
.filter(GroupMember.Columns.profileId == publicKey)
|
|
|
|
.filter(targetRoles.contains(GroupMember.Columns.role))
|
|
|
|
.isNotEmpty(db))
|
|
|
|
.defaulting(to: false)
|
2022-03-04 08:02:38 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
// If the publicKey provided matches a mod or admin directly then just return immediately
|
|
|
|
if isDirectModOrAdmin { return true }
|
2022-03-04 08:02:38 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
// Otherwise we need to check if it's a variant of the current users key and if so we want
|
|
|
|
// to check if any of those have mod/admin entries
|
|
|
|
guard let sessionId: SessionId = SessionId(from: publicKey) else { return false }
|
2022-03-04 08:02:38 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
// Conveniently the logic for these different cases works in order so we can fallthrough each
|
|
|
|
// case with only minor efficiency losses
|
2022-06-22 06:27:34 +02:00
|
|
|
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
2022-03-04 08:02:38 +01:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
switch sessionId.prefix {
|
|
|
|
case .standard:
|
|
|
|
guard publicKey == userPublicKey else { return false }
|
|
|
|
fallthrough
|
|
|
|
|
|
|
|
case .unblinded:
|
|
|
|
guard let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
guard sessionId.prefix != .unblinded || publicKey == SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
|
|
|
|
case .blinded:
|
|
|
|
guard
|
|
|
|
let userEdKeyPair: Box.KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
|
|
|
let openGroupPublicKey: String = try? OpenGroup
|
|
|
|
.select(.publicKey)
|
|
|
|
.filter(id: groupId)
|
|
|
|
.asRequest(of: String.self)
|
|
|
|
.fetchOne(db),
|
|
|
|
let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(
|
|
|
|
serverPublicKey: openGroupPublicKey,
|
|
|
|
edKeyPair: userEdKeyPair,
|
|
|
|
genericHash: dependencies.genericHash
|
|
|
|
)
|
|
|
|
else { return false }
|
|
|
|
guard sessionId.prefix != .blinded || publicKey == SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we got to here that means that the 'publicKey' value matches one of the current
|
|
|
|
// users 'standard', 'unblinded' or 'blinded' keys and as such we should check if any
|
|
|
|
// of them exist in the `modsAndAminKeys` Set
|
|
|
|
let possibleKeys: Set<String> = Set([
|
|
|
|
userPublicKey,
|
|
|
|
SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString,
|
|
|
|
SessionId(.blinded, publicKey: blindedKeyPair.publicKey).hexString
|
|
|
|
])
|
|
|
|
|
|
|
|
return (try? GroupMember
|
|
|
|
.filter(GroupMember.Columns.groupId == groupId)
|
|
|
|
.filter(possibleKeys.contains(GroupMember.Columns.profileId))
|
|
|
|
.filter(targetRoles.contains(GroupMember.Columns.role))
|
|
|
|
.isNotEmpty(db))
|
|
|
|
.defaulting(to: false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.defaulting(to: false)
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
@discardableResult public static func getDefaultRoomsIfNeeded(using dependencies: OGMDependencies = OGMDependencies()) -> Promise<[OpenGroupAPI.Room]> {
|
2022-02-16 00:31:08 +01:00
|
|
|
// Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again
|
2022-03-16 05:55:56 +01:00
|
|
|
if let existingPromise: Promise<[OpenGroupAPI.Room]> = dependencies.cache.defaultRoomsPromise {
|
2022-03-11 07:15:55 +01:00
|
|
|
return existingPromise
|
|
|
|
}
|
2022-02-16 00:31:08 +01:00
|
|
|
|
2022-03-11 07:15:55 +01:00
|
|
|
let (promise, seal) = Promise<[OpenGroupAPI.Room]>.pending()
|
2022-07-26 03:36:32 +02:00
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
// Try to retrieve the default rooms 8 times
|
2022-06-24 10:29:45 +02:00
|
|
|
attempt(maxRetryCount: 8, recoveringOn: OpenGroupAPI.workQueue) {
|
2022-06-09 10:37:44 +02:00
|
|
|
dependencies.storage.read { db in
|
2022-07-26 03:36:32 +02:00
|
|
|
OpenGroupAPI.capabilitiesAndRooms(
|
|
|
|
db,
|
|
|
|
on: OpenGroupAPI.defaultServer,
|
|
|
|
authenticated: false,
|
|
|
|
using: dependencies
|
|
|
|
)
|
2022-06-09 10:37:44 +02:00
|
|
|
}
|
|
|
|
}
|
2022-07-26 03:36:32 +02:00
|
|
|
.done(on: OpenGroupAPI.workQueue) { response in
|
2022-06-09 10:37:44 +02:00
|
|
|
dependencies.storage.writeAsync { db in
|
2022-07-26 03:36:32 +02:00
|
|
|
// Store the capabilities first
|
|
|
|
OpenGroupManager.handleCapabilities(
|
|
|
|
db,
|
|
|
|
capabilities: response.capabilities.data,
|
|
|
|
on: OpenGroupAPI.defaultServer
|
|
|
|
)
|
|
|
|
|
|
|
|
// Then the rooms
|
|
|
|
response.rooms.data
|
2022-07-12 09:43:52 +02:00
|
|
|
.compactMap { room -> (String, String)? in
|
2022-06-09 10:37:44 +02:00
|
|
|
// Try to insert an inactive version of the OpenGroup (use 'insert' rather than 'save'
|
|
|
|
// as we want it to fail if the room already exists)
|
|
|
|
do {
|
|
|
|
_ = try OpenGroup(
|
|
|
|
server: OpenGroupAPI.defaultServer,
|
|
|
|
roomToken: room.token,
|
|
|
|
publicKey: OpenGroupAPI.defaultServerPublicKey,
|
|
|
|
isActive: false,
|
|
|
|
name: room.name,
|
|
|
|
roomDescription: room.roomDescription,
|
2022-07-12 09:43:52 +02:00
|
|
|
imageId: room.imageId,
|
2022-06-09 10:37:44 +02:00
|
|
|
imageData: nil,
|
|
|
|
userCount: room.activeUsers,
|
|
|
|
infoUpdates: room.infoUpdates,
|
|
|
|
sequenceNumber: 0,
|
|
|
|
inboxLatestMessageId: 0,
|
|
|
|
outboxLatestMessageId: 0
|
|
|
|
)
|
|
|
|
.inserted(db)
|
2022-03-07 07:43:30 +01:00
|
|
|
}
|
2022-06-09 10:37:44 +02:00
|
|
|
catch {}
|
|
|
|
|
2022-07-12 09:43:52 +02:00
|
|
|
guard let imageId: String = room.imageId else { return nil }
|
2022-06-09 10:37:44 +02:00
|
|
|
|
|
|
|
return (imageId, room.token)
|
|
|
|
}
|
|
|
|
.forEach { imageId, roomToken in
|
|
|
|
roomImage(
|
|
|
|
db,
|
|
|
|
fileId: imageId,
|
|
|
|
for: roomToken,
|
|
|
|
on: OpenGroupAPI.defaultServer,
|
|
|
|
using: dependencies
|
|
|
|
)
|
|
|
|
.retainUntilComplete()
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
|
|
|
}
|
2022-06-09 10:37:44 +02:00
|
|
|
|
2022-07-26 03:36:32 +02:00
|
|
|
seal.fulfill(response.rooms.data)
|
2022-06-09 10:37:44 +02:00
|
|
|
}
|
|
|
|
.catch(on: OpenGroupAPI.workQueue) { error in
|
|
|
|
dependencies.mutableCache.mutate { cache in
|
|
|
|
cache.defaultRoomsPromise = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
seal.reject(error)
|
|
|
|
}
|
|
|
|
.retainUntilComplete()
|
2022-03-11 07:15:55 +01:00
|
|
|
|
2022-03-17 07:12:19 +01:00
|
|
|
dependencies.mutableCache.mutate { cache in
|
2022-03-11 07:15:55 +01:00
|
|
|
cache.defaultRoomsPromise = promise
|
|
|
|
}
|
|
|
|
|
|
|
|
return promise
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static func roomImage(
|
2022-06-09 10:37:44 +02:00
|
|
|
_ db: Database,
|
2022-07-12 09:43:52 +02:00
|
|
|
fileId: String,
|
2022-02-16 00:31:08 +01:00
|
|
|
for roomToken: String,
|
|
|
|
on server: String,
|
2022-03-16 05:55:56 +01:00
|
|
|
using dependencies: OGMDependencies = OGMDependencies()
|
2022-02-16 00:31:08 +01:00
|
|
|
) -> Promise<Data> {
|
|
|
|
// Normally the image for a given group is stored with the group thread, so it's only
|
|
|
|
// fetched once. However, on the join open group screen we show images for groups the
|
|
|
|
// user * hasn't * joined yet. We don't want to re-fetch these images every time the
|
|
|
|
// user opens the app because that could slow the app down or be data-intensive. So
|
|
|
|
// instead we assume that these images don't change that often and just fetch them once
|
|
|
|
// a week. We also assume that they're all fetched at the same time as well, so that
|
|
|
|
// we only need to maintain one date in user defaults. On top of all of this we also
|
|
|
|
// don't double up on fetch requests by storing the existing request as a promise if
|
|
|
|
// there is one.
|
2022-06-09 10:37:44 +02:00
|
|
|
let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: server)
|
2022-03-16 05:55:56 +01:00
|
|
|
let lastOpenGroupImageUpdate: Date? = dependencies.standardUserDefaults[.lastOpenGroupImageUpdate]
|
2022-02-16 00:31:08 +01:00
|
|
|
let now: Date = dependencies.date
|
|
|
|
let timeSinceLastUpdate: TimeInterval = (lastOpenGroupImageUpdate.map { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
|
|
|
|
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
|
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
if
|
2022-06-22 06:27:34 +02:00
|
|
|
server.lowercased() == OpenGroupAPI.defaultServer,
|
2022-06-09 10:37:44 +02:00
|
|
|
timeSinceLastUpdate < updateInterval,
|
|
|
|
let data = try? OpenGroup
|
|
|
|
.select(.imageData)
|
|
|
|
.filter(id: threadId)
|
|
|
|
.asRequest(of: Data.self)
|
|
|
|
.fetchOne(db)
|
|
|
|
{ return Promise.value(data) }
|
2022-02-16 00:31:08 +01:00
|
|
|
|
2022-06-22 06:27:34 +02:00
|
|
|
if let promise = dependencies.cache.groupImagePromises[threadId] {
|
2022-02-16 00:31:08 +01:00
|
|
|
return promise
|
|
|
|
}
|
|
|
|
|
2022-06-09 10:37:44 +02:00
|
|
|
let (promise, seal) = Promise<Data>.pending()
|
|
|
|
|
|
|
|
// Trigger the download on a background queue
|
|
|
|
DispatchQueue.global(qos: .background).async {
|
|
|
|
dependencies.storage
|
2022-06-22 06:27:34 +02:00
|
|
|
.read { db in
|
2022-06-09 10:37:44 +02:00
|
|
|
OpenGroupAPI
|
|
|
|
.downloadFile(
|
|
|
|
db,
|
|
|
|
fileId: fileId,
|
|
|
|
from: roomToken,
|
|
|
|
on: server,
|
|
|
|
using: dependencies
|
|
|
|
)
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
2022-06-22 06:27:34 +02:00
|
|
|
.done { _, imageData in
|
|
|
|
if server.lowercased() == OpenGroupAPI.defaultServer {
|
2022-06-09 10:37:44 +02:00
|
|
|
dependencies.storage.write { db in
|
|
|
|
_ = try OpenGroup
|
|
|
|
.filter(id: threadId)
|
|
|
|
.updateAll(db, OpenGroup.Columns.imageData.set(to: imageData))
|
|
|
|
}
|
|
|
|
dependencies.standardUserDefaults[.lastOpenGroupImageUpdate] = now
|
|
|
|
}
|
|
|
|
|
|
|
|
seal.fulfill(imageData)
|
|
|
|
}
|
|
|
|
.catch { seal.reject($0) }
|
2022-06-21 05:39:46 +02:00
|
|
|
.retainUntilComplete()
|
2022-02-16 00:31:08 +01:00
|
|
|
}
|
2022-06-09 10:37:44 +02:00
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
dependencies.mutableCache.mutate { cache in
|
2022-06-22 06:27:34 +02:00
|
|
|
cache.groupImagePromises[threadId] = promise
|
2022-03-07 07:43:30 +01:00
|
|
|
}
|
2022-02-16 00:31:08 +01:00
|
|
|
|
|
|
|
return promise
|
|
|
|
}
|
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
public static func parseOpenGroup(from string: String) -> (room: String, server: String, publicKey: String)? {
|
2021-03-29 02:49:59 +02:00
|
|
|
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:
|
2022-03-16 05:55:56 +01:00
|
|
|
// https://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
2021-03-29 02:49:59 +02:00
|
|
|
// https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
2022-03-16 05:55:56 +01:00
|
|
|
// http://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
2021-03-29 02:49:59 +02:00
|
|
|
// http://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
|
|
|
// sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c (does NOT go to HTTPS)
|
2022-03-16 05:55:56 +01:00
|
|
|
// sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c (does NOT go to HTTPS)
|
|
|
|
// https://143.198.213.225:443/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
2021-03-29 02:49:59 +02:00
|
|
|
// https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
|
|
|
// 143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
2022-03-16 05:55:56 +01:00
|
|
|
// 143.198.213.255:80/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
|
2021-03-29 02:49:59 +02:00
|
|
|
let useTLS = (url.scheme == "https")
|
2022-03-18 00:15:57 +01:00
|
|
|
|
|
|
|
// If there is no scheme then the host is included in the path (so handle that case)
|
2022-03-18 00:20:08 +01:00
|
|
|
let hostFreePath = (url.host != nil || !url.path.starts(with: host) ? url.path : url.path.substring(from: host.count))
|
2022-03-18 00:15:57 +01:00
|
|
|
let updatedPath = (hostFreePath.starts(with: "/r/") ? hostFreePath.substring(from: 2) : hostFreePath)
|
2022-03-02 01:29:07 +01:00
|
|
|
let room = String(updatedPath.dropFirst()) // Drop the leading slash
|
2021-03-29 02:49:59 +02:00
|
|
|
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)
|
|
|
|
}
|
2021-03-24 02:37:33 +01:00
|
|
|
}
|
2022-02-24 00:39:22 +01:00
|
|
|
|
2022-03-16 05:55:56 +01:00
|
|
|
|
|
|
|
// MARK: - OGMDependencies
|
|
|
|
|
|
|
|
extension OpenGroupManager {
|
2022-06-22 06:27:34 +02:00
|
|
|
public class OGMDependencies: SMKDependencies {
|
2022-08-30 01:45:40 +02:00
|
|
|
internal var _mutableCache: Atomic<Atomic<OGMCacheType>?>
|
2022-03-16 05:55:56 +01:00
|
|
|
public var mutableCache: Atomic<OGMCacheType> {
|
|
|
|
get { Dependencies.getValueSettingIfNull(&_mutableCache) { OpenGroupManager.shared.mutableCache } }
|
2022-08-30 01:45:40 +02:00
|
|
|
set { _mutableCache.mutate { $0 = newValue } }
|
2022-03-16 05:55:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public var cache: OGMCacheType { return mutableCache.wrappedValue }
|
|
|
|
|
|
|
|
public init(
|
|
|
|
cache: Atomic<OGMCacheType>? = nil,
|
|
|
|
onionApi: OnionRequestAPIType.Type? = nil,
|
2022-03-29 01:15:22 +02:00
|
|
|
generalCache: Atomic<GeneralCacheType>? = nil,
|
2022-07-01 05:08:45 +02:00
|
|
|
storage: Storage? = nil,
|
2022-03-16 05:55:56 +01:00
|
|
|
sodium: SodiumType? = nil,
|
2022-03-18 06:39:25 +01:00
|
|
|
box: BoxType? = nil,
|
2022-03-16 05:55:56 +01:00
|
|
|
genericHash: GenericHashType? = nil,
|
2022-03-18 06:39:25 +01:00
|
|
|
sign: SignType? = nil,
|
|
|
|
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
2022-03-16 05:55:56 +01:00
|
|
|
ed25519: Ed25519Type? = nil,
|
|
|
|
nonceGenerator16: NonceGenerator16ByteType? = nil,
|
|
|
|
nonceGenerator24: NonceGenerator24ByteType? = nil,
|
|
|
|
standardUserDefaults: UserDefaultsType? = nil,
|
|
|
|
date: Date? = nil
|
|
|
|
) {
|
2022-08-30 01:45:40 +02:00
|
|
|
_mutableCache = Atomic(cache)
|
2022-03-16 05:55:56 +01:00
|
|
|
|
|
|
|
super.init(
|
|
|
|
onionApi: onionApi,
|
2022-03-29 01:15:22 +02:00
|
|
|
generalCache: generalCache,
|
2022-03-16 05:55:56 +01:00
|
|
|
storage: storage,
|
|
|
|
sodium: sodium,
|
2022-03-18 06:39:25 +01:00
|
|
|
box: box,
|
2022-03-16 05:55:56 +01:00
|
|
|
genericHash: genericHash,
|
2022-03-18 06:39:25 +01:00
|
|
|
sign: sign,
|
|
|
|
aeadXChaCha20Poly1305Ietf: aeadXChaCha20Poly1305Ietf,
|
2022-03-16 05:55:56 +01:00
|
|
|
ed25519: ed25519,
|
|
|
|
nonceGenerator16: nonceGenerator16,
|
|
|
|
nonceGenerator24: nonceGenerator24,
|
|
|
|
standardUserDefaults: standardUserDefaults,
|
|
|
|
date: date
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2022-02-24 00:39:22 +01:00
|
|
|
}
|