Simplify ClassicAppAccount loading (only load what we need).
This commit is contained in:
@@ -8,38 +8,23 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ClassicAppAccountManager {
|
class ClassicAppAccountManager {
|
||||||
static let matrixKitFolder = "MatrixKit"
|
private let cacheFolder: URL
|
||||||
static let kMXKAccountsKey = "accountsV2"
|
private let aesKey: Data
|
||||||
static let kMXFileStoreFolder = "MXFileStore"
|
private let iv: Data
|
||||||
static let kMXFileStoreUsersFolder = "users"
|
private let cryptoStorePassphrase: Data
|
||||||
static let cryptoStoreFolder = "MXCryptoStore"
|
|
||||||
|
|
||||||
let cacheFolder: URL
|
private(set) var accounts: [ClassicAppAccount] = []
|
||||||
let iv: Data
|
|
||||||
let aesKey: Data
|
|
||||||
|
|
||||||
private(set) var accounts: [ClassicAppMXAccount] = []
|
init(cacheFolder: URL, aesKey: Data, iv: Data, cryptoStorePassphrase: Data) {
|
||||||
private var users: [String: ClassicAppMXUser] = [:]
|
|
||||||
|
|
||||||
var activeAccounts: [ClassicAppAccount] {
|
|
||||||
accounts
|
|
||||||
.filter { !$0.isDisabled && !$0.isSoftLogout }
|
|
||||||
.compactMap(activeAccount)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(cacheFolder: URL, iv: Data, aesKey: Data) {
|
|
||||||
self.cacheFolder = cacheFolder
|
self.cacheFolder = cacheFolder
|
||||||
self.iv = iv
|
|
||||||
self.aesKey = aesKey
|
self.aesKey = aesKey
|
||||||
}
|
self.iv = iv
|
||||||
|
self.cryptoStorePassphrase = cryptoStorePassphrase
|
||||||
/// Return the path of the file containing stored MXAccounts array
|
|
||||||
func accountFile() -> URL {
|
|
||||||
cacheFolder.appending(component: Self.matrixKitFolder).appending(component: Self.kMXKAccountsKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAccounts() {
|
func loadAccounts() {
|
||||||
MXLog.info("Loading accounts from Classic app.")
|
MXLog.info("Loading accounts from Classic app.")
|
||||||
|
|
||||||
let accountFile = accountFile()
|
let accountFile = accountFile()
|
||||||
if FileManager.default.fileExists(atPath: accountFile.path(percentEncoded: false)) {
|
if FileManager.default.fileExists(atPath: accountFile.path(percentEncoded: false)) {
|
||||||
let startDate = Date()
|
let startDate = Date()
|
||||||
@@ -51,103 +36,58 @@ class ClassicAppAccountManager {
|
|||||||
let unciphered = try ClassicAppAES.decrypt(fileContent, aesKey: aesKey, iv: iv)
|
let unciphered = try ClassicAppAES.decrypt(fileContent, aesKey: aesKey, iv: iv)
|
||||||
let decoder = NSKeyedUnarchiver(forReadingWith: unciphered)
|
let decoder = NSKeyedUnarchiver(forReadingWith: unciphered)
|
||||||
decoder.setClass(ClassicAppMXAccount.self, forClassName: "MXKAccount")
|
decoder.setClass(ClassicAppMXAccount.self, forClassName: "MXKAccount")
|
||||||
decoder.setClass(ClassicAppMXThirdPartyIdentifier.self, forClassName: "MXThirdPartyIdentifier")
|
|
||||||
decoder.setClass(ClassicAppMXDevice.self, forClassName: "MXDevice")
|
|
||||||
|
|
||||||
guard let accounts = decoder.decodeObject(forKey: "mxAccounts") as? [ClassicAppMXAccount] else {
|
guard let mxAccounts = decoder.decodeObject(forKey: "mxAccounts") as? [ClassicAppMXAccount] else {
|
||||||
MXLog.error("Failed to decode accounts.")
|
MXLog.error("Failed to decode accounts.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.accounts = accounts
|
MXLog.info("\(mxAccounts.count) accounts loaded in \(Date().timeIntervalSince(startDate) * 1000)ms.")
|
||||||
|
|
||||||
MXLog.info("[MXKAccountManager] loadAccounts. \(accounts.count) accounts loaded in \(Date().timeIntervalSince(startDate) * 1000)ms")
|
accounts = mxAccounts
|
||||||
|
.filter(\.isActive)
|
||||||
|
.compactMap(makeAccount)
|
||||||
|
|
||||||
|
MXLog.info("\(mxAccounts.count) active accounts available.")
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed to load account file: \(error)")
|
MXLog.error("Failed to load account file: \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
for account in activeAccounts {
|
|
||||||
if let user = loadUsers([account.userID], forAccount: account.userID).first {
|
|
||||||
users[user.userID] = user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if accounts.isEmpty {
|
|
||||||
MXLog.info("[MXKAccountManager] loadAccounts. No accounts")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// From `MXCryptoMachineStore`
|
// MARK: - Private
|
||||||
func cryptoStoreURL(for userID: String) -> URL {
|
|
||||||
cacheFolder.appending(component: Self.cryptoStoreFolder).appending(component: userID)
|
private func makeAccount(for mxAccount: ClassicAppMXAccount) -> ClassicAppAccount? {
|
||||||
|
let userID = mxAccount.userID
|
||||||
|
let user = loadUser(for: mxAccount)
|
||||||
|
|
||||||
|
guard let serverName = serverName(for: userID) else { return nil }
|
||||||
|
|
||||||
|
return ClassicAppAccount(userID: userID,
|
||||||
|
displayName: user?.displayName,
|
||||||
|
avatarURL: user?.avatarURL.flatMap(URL.init(string:)),
|
||||||
|
serverName: serverName,
|
||||||
|
cryptoStoreURL: cryptoStoreURL(for: userID),
|
||||||
|
cryptoStorePassphrase: cryptoStorePassphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileStorePath: URL {
|
private func loadUser(for mxAccount: ClassicAppMXAccount) -> ClassicAppMXUser? {
|
||||||
cacheFolder.appending(component: Self.kMXFileStoreFolder)
|
let userID = mxAccount.userID
|
||||||
}
|
|
||||||
|
|
||||||
func storePath(for userID: String) -> URL {
|
|
||||||
fileStorePath.appending(component: userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This store contains all of the users known to the specific user ID.
|
|
||||||
func storeUsersPath(for userID: String) -> URL {
|
|
||||||
storePath(for: userID).appending(component: Self.kMXFileStoreUsersFolder)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadUsers(_ userIDs: [String], forAccount accountUserID: String) -> [ClassicAppMXUser] {
|
|
||||||
// Determine which groups to load based on userIds
|
|
||||||
var groups: [String: [String]] = [:]
|
|
||||||
for userID in userIDs {
|
|
||||||
let groupID = String(userID.hash % 100)
|
let groupID = String(userID.hash % 100)
|
||||||
|
let groupFile = storeUsersPath(for: userID).appendingPathComponent(groupID)
|
||||||
|
|
||||||
if groups[groupID] != nil {
|
|
||||||
groups[groupID]?.append(userID)
|
|
||||||
} else {
|
|
||||||
groups[groupID] = [userID]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let usersFolder = storeUsersPath(for: accountUserID)
|
|
||||||
|
|
||||||
var loadedUsers: [ClassicAppMXUser] = []
|
|
||||||
for group in groups.keys {
|
|
||||||
autoreleasepool {
|
|
||||||
let groupFile = usersFolder.appendingPathComponent(group)
|
|
||||||
|
|
||||||
// Load stored users in this group
|
|
||||||
do {
|
do {
|
||||||
let fileContent = try Data(contentsOf: groupFile)
|
let fileContent = try Data(contentsOf: groupFile)
|
||||||
|
|
||||||
let decoder = NSKeyedUnarchiver(forReadingWith: fileContent)
|
let decoder = NSKeyedUnarchiver(forReadingWith: fileContent)
|
||||||
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXUser")
|
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXUser")
|
||||||
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXMyUser")
|
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXMyUser")
|
||||||
|
|
||||||
if let groupUsers = decoder.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? [String: ClassicAppMXUser] {
|
let groupUsers = decoder.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? [String: ClassicAppMXUser]
|
||||||
let usersToLoad = Set(groups[group] ?? [])
|
return groupUsers?[userID]
|
||||||
for user in groupUsers.values where usersToLoad.contains(user.userID) {
|
|
||||||
loadedUsers.append(user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.warning("[MXFileStore] Warning: MXFileRoomStore file for users group \(group) has been corrupted")
|
MXLog.warning("Users group \(groupID) file for \(mxAccount.userID) has been corrupted.")
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return loadedUsers
|
|
||||||
}
|
|
||||||
|
|
||||||
private func activeAccount(mxAccount: ClassicAppMXAccount) -> ClassicAppAccount? {
|
|
||||||
guard let userID = mxAccount.credentials.userID, let serverName = serverName(for: userID) else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClassicAppAccount(userID: userID,
|
|
||||||
displayName: users[userID]?.displayName,
|
|
||||||
serverName: serverName,
|
|
||||||
cryptoStoreURL: cryptoStoreURL(for: userID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The server name extracted from the user's ID.
|
/// The server name extracted from the user's ID.
|
||||||
@@ -157,43 +97,35 @@ class ClassicAppAccountManager {
|
|||||||
guard components.count > 1 else { return nil }
|
guard components.count > 1 else { return nil }
|
||||||
return components[1] // Directly use [1] as .last may be the port number.
|
return components[1] // Directly use [1] as .last may be the port number.
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Probably not needed
|
// MARK: - File URLs
|
||||||
|
|
||||||
private extension ClassicAppAccountManager {
|
private static let matrixKitFolder = "MatrixKit"
|
||||||
func loadUsers(forAccount accountUserID: String) {
|
private static let cryptoStoreFolder = "MXCryptoStore"
|
||||||
let startDate = Date()
|
private static let kMXKAccountsKey = "accountsV2"
|
||||||
var users: [String: ClassicAppMXUser] = [:]
|
private static let kMXFileStoreFolder = "MXFileStore"
|
||||||
|
private static let kMXFileStoreUsersFolder = "users"
|
||||||
|
|
||||||
// Load all users which are distributed in several files
|
/// The file URL that contains the app's `MXAccounts` array.
|
||||||
let storeUsersPath = storeUsersPath(for: accountUserID)
|
private func accountFile() -> URL {
|
||||||
let groups = try? FileManager.default.contentsOfDirectory(atPath: storeUsersPath.path(percentEncoded: false))
|
cacheFolder.appending(component: Self.matrixKitFolder).appending(component: Self.kMXKAccountsKey)
|
||||||
|
|
||||||
if let groups {
|
|
||||||
for group in groups {
|
|
||||||
let groupFile = storeUsersPath.appending(path: group)
|
|
||||||
|
|
||||||
// Load stored users in this group
|
|
||||||
do {
|
|
||||||
let fileContent = try Data(contentsOf: groupFile)
|
|
||||||
|
|
||||||
let decoder = NSKeyedUnarchiver(forReadingWith: fileContent)
|
|
||||||
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXUser")
|
|
||||||
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXMyUser")
|
|
||||||
|
|
||||||
if let groupUsers = decoder.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? [String: ClassicAppMXUser] {
|
|
||||||
// Append them
|
|
||||||
users.merge(groupUsers) { _, new in new }
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed to load users from group \(group): \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MXLog.debug("[MXFileStore] Loaded \(users.count) MXUsers in \(Date().timeIntervalSince(startDate) * 1000)ms")
|
/// The database file URL as defined in `MXCryptoMachineStore`.
|
||||||
|
private func cryptoStoreURL(for userID: String) -> URL {
|
||||||
|
cacheFolder.appending(component: Self.cryptoStoreFolder).appending(component: userID)
|
||||||
|
}
|
||||||
|
|
||||||
self.users = users
|
/// This store contains all of the users known to the specific user ID.
|
||||||
|
private func storeUsersPath(for userID: String) -> URL {
|
||||||
|
storePath(for: userID).appending(component: Self.kMXFileStoreUsersFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func storePath(for userID: String) -> URL {
|
||||||
|
fileStorePath.appending(component: userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var fileStorePath: URL {
|
||||||
|
cacheFolder.appending(component: Self.kMXFileStoreFolder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,56 +10,32 @@ import Foundation
|
|||||||
struct ClassicAppAccount {
|
struct ClassicAppAccount {
|
||||||
let userID: String
|
let userID: String
|
||||||
let displayName: String?
|
let displayName: String?
|
||||||
|
let avatarURL: URL?
|
||||||
let serverName: String
|
let serverName: String
|
||||||
let cryptoStoreURL: URL
|
let cryptoStoreURL: URL
|
||||||
|
let cryptoStorePassphrase: Data
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: NSCoding Types
|
// MARK: NSCoding Types
|
||||||
|
|
||||||
class ClassicAppMXAccount: NSObject, NSCoding {
|
class ClassicAppMXAccount: NSObject, NSCoding {
|
||||||
/// The account's credentials: homeserver, access token, user ID.
|
/// The obtained user ID.
|
||||||
private(set) var credentials: Credentials
|
var userID: String
|
||||||
/// The identity server URL.
|
/// The homeserver url (ex: "https://matrix.org").
|
||||||
var identityServerURL: String
|
var homeserver: String?
|
||||||
/// The antivirus server URL, if any (nil by default).
|
|
||||||
/// Set a non-null url to configure the antivirus scanner use.
|
|
||||||
var antivirusServerURL: String?
|
|
||||||
/// The Push Gateway URL used to send event notifications to (nil by default).
|
|
||||||
/// This URL should be over HTTPS and never over HTTP.
|
|
||||||
var pushGatewayURL: String?
|
|
||||||
/// The 3PIDs linked to this account.
|
|
||||||
/// [self load3PIDs] must be called to update the property.
|
|
||||||
private(set) var threePIDs: [ClassicAppMXThirdPartyIdentifier]?
|
|
||||||
/// The account user's device.
|
|
||||||
/// [self loadDeviceInformation] must be called to update the property.
|
|
||||||
private(set) var device: ClassicAppMXDevice?
|
|
||||||
/// Transient information storage.
|
|
||||||
private(set) var others = NSMutableDictionary()
|
|
||||||
/// Flag to indicate that an APNS pusher has been set on the homeserver for this device.
|
|
||||||
private(set) var hasPusherForPushNotifications = false
|
|
||||||
|
|
||||||
/// The Push notification activity (based on PushKit) for this account.
|
|
||||||
/// YES when Push is turned on (locally available and enabled homeserver side).
|
|
||||||
var isPushKitNotificationActive: Bool {
|
|
||||||
// This would typically have custom getter logic
|
|
||||||
hasPusherForPushKitNotifications
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flag to indicate that a PushKit pusher has been set on the homeserver for this device.
|
|
||||||
private(set) var hasPusherForPushKitNotifications = false
|
|
||||||
/// Enable In-App notifications based on Remote notifications rules.
|
|
||||||
/// NO by default.
|
|
||||||
var enableInAppNotifications = false
|
|
||||||
/// Disable the account without logging out (NO by default).
|
/// Disable the account without logging out (NO by default).
|
||||||
///
|
///
|
||||||
/// A matrix session is automatically opened for the account when this property is toggled from YES to NO.
|
/// A matrix session is automatically opened for the account when this property is toggled from YES to NO.
|
||||||
/// The session is closed when this property is set to YES.
|
/// The session is closed when this property is set to YES.
|
||||||
var isDisabled = false
|
let isDisabled: Bool
|
||||||
/// Flag indicating if the end user has been warned about encryption and its limitations.
|
|
||||||
var isWarnedAboutEncryption = false
|
|
||||||
|
|
||||||
/// Flag to indicate if the account has been logged out by the homeserver admin.
|
/// Flag to indicate if the account has been logged out by the homeserver admin.
|
||||||
private(set) var isSoftLogout = false
|
let isSoftLogout: Bool
|
||||||
|
|
||||||
|
/// Whether or not the account is considered active.
|
||||||
|
var isActive: Bool {
|
||||||
|
!isDisabled && !isSoftLogout
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: NSCoding
|
// MARK: NSCoding
|
||||||
|
|
||||||
@@ -87,173 +63,18 @@ class ClassicAppMXAccount: NSObject, NSCoding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
let homeserverURL = coder.decodeObject(forKey: Keys.homeserverURL) as? String
|
guard let userID = coder.decodeObject(forKey: Keys.userID) as? String,
|
||||||
let userID = coder.decodeObject(forKey: Keys.userID) as? String
|
let homeserver = coder.decodeObject(forKey: Keys.homeserverURL) as? String else {
|
||||||
let accessToken = coder.decodeObject(forKey: Keys.accessToken) as? String
|
return nil
|
||||||
|
|
||||||
credentials = Credentials(homeserver: homeserverURL,
|
|
||||||
userID: userID,
|
|
||||||
accessToken: accessToken)
|
|
||||||
|
|
||||||
credentials.accessTokenExpiresAt = UInt64(coder.decodeInt64(forKey: Keys.accessTokenExpiresAt))
|
|
||||||
credentials.refreshToken = coder.decodeObject(forKey: Keys.refreshToken) as? String
|
|
||||||
credentials.identityServer = coder.decodeObject(forKey: Keys.identityServerURL) as? String
|
|
||||||
credentials.identityServerAccessToken = coder.decodeObject(forKey: Keys.identityServerAccessToken) as? String
|
|
||||||
credentials.deviceID = coder.decodeObject(forKey: Keys.deviceID) as? String
|
|
||||||
credentials.allowedCertificate = coder.decodeObject(forKey: Keys.allowedCertificate) as? Data
|
|
||||||
|
|
||||||
identityServerURL = credentials.identityServer ?? ""
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
if let threePIDs = coder.decodeObject(forKey: Keys.threePIDs) as? [ClassicAppMXThirdPartyIdentifier] {
|
|
||||||
self.threePIDs = threePIDs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let device = coder.decodeObject(forKey: Keys.device) as? ClassicAppMXDevice {
|
self.userID = userID
|
||||||
self.device = device
|
self.homeserver = homeserver
|
||||||
}
|
|
||||||
|
|
||||||
if let antivirusServerURL = coder.decodeObject(forKey: Keys.antivirusServerURL) as? String {
|
|
||||||
self.antivirusServerURL = antivirusServerURL
|
|
||||||
}
|
|
||||||
|
|
||||||
if let pushGatewayURL = coder.decodeObject(forKey: Keys.pushGatewayURL) as? String {
|
|
||||||
self.pushGatewayURL = pushGatewayURL
|
|
||||||
}
|
|
||||||
|
|
||||||
hasPusherForPushNotifications = coder.decodeBool(forKey: Keys.hasPusherForPushNotifications)
|
|
||||||
hasPusherForPushKitNotifications = coder.decodeBool(forKey: Keys.hasPusherForPushKitNotifications)
|
|
||||||
enableInAppNotifications = coder.decodeBool(forKey: Keys.enableInAppNotifications)
|
|
||||||
|
|
||||||
isDisabled = coder.decodeBool(forKey: Keys.isDisabled)
|
isDisabled = coder.decodeBool(forKey: Keys.isDisabled)
|
||||||
isSoftLogout = coder.decodeBool(forKey: Keys.isSoftLogout)
|
isSoftLogout = coder.decodeBool(forKey: Keys.isSoftLogout)
|
||||||
|
|
||||||
isWarnedAboutEncryption = coder.decodeBool(forKey: Keys.isWarnedAboutEncryption)
|
super.init()
|
||||||
|
|
||||||
if let others = coder.decodeObject(forKey: Keys.others) as? NSMutableDictionary {
|
|
||||||
self.others = others
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(with coder: NSCoder) {
|
|
||||||
fatalError("Not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `MXCredentials` struct contains credentials to communicate with the Matrix
|
|
||||||
/// Client-Server API.
|
|
||||||
struct Credentials {
|
|
||||||
/// The homeserver url (ex: "https://matrix.org").
|
|
||||||
var homeserver: String?
|
|
||||||
/// The identity server url (ex: "https://vector.im").
|
|
||||||
var identityServer: String?
|
|
||||||
/// The obtained user ID.
|
|
||||||
var userID: String?
|
|
||||||
/// The access token to create a MXRestClient
|
|
||||||
var accessToken: String?
|
|
||||||
/// The timestamp in milliseconds for when the access token will expire
|
|
||||||
var accessTokenExpiresAt: UInt64 = 0
|
|
||||||
/// The refresh token, which can be used to obtain new access tokens. (optional)
|
|
||||||
var refreshToken: String?
|
|
||||||
/// The access token to create a MXIdentityServerRestClient
|
|
||||||
var identityServerAccessToken: String?
|
|
||||||
/// The device ID.
|
|
||||||
var deviceID: String?
|
|
||||||
/// The server certificate trusted by the user (nil when the server is trusted by the device).
|
|
||||||
var allowedCertificate: Data?
|
|
||||||
/// The ignored server certificate (set when the user ignores a certificate change).
|
|
||||||
var ignoredCertificate: Data?
|
|
||||||
/// Additional data received during login process
|
|
||||||
var loginOthers: [String: Any]?
|
|
||||||
|
|
||||||
init(homeserver: String?, userID: String?, accessToken: String?) {
|
|
||||||
self.homeserver = homeserver
|
|
||||||
self.userID = userID
|
|
||||||
self.accessToken = accessToken
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `MXThirdPartyIdentifier` represents the response to /account/3pid GET request.
|
|
||||||
class ClassicAppMXThirdPartyIdentifier: NSObject, NSCoding {
|
|
||||||
/// The medium of the third party identifier.
|
|
||||||
var medium: String
|
|
||||||
/// The third party identifier address.
|
|
||||||
var address: String
|
|
||||||
/// The timestamp in milliseconds when this 3PID has been validated.
|
|
||||||
var validatedAt: UInt64
|
|
||||||
/// The timestamp in milliseconds when this 3PID has been added to the user account.
|
|
||||||
var addedAt: UInt64
|
|
||||||
|
|
||||||
// MARK: NSCoding
|
|
||||||
|
|
||||||
enum Keys {
|
|
||||||
static let medium = "medium" // String
|
|
||||||
static let address = "address" // String
|
|
||||||
static let validatedAt = "validatedAt" // NSNumber?.uint64Value
|
|
||||||
static let addedAt = "addedAt" // NSNumber?.uint64Value
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
guard let medium = aDecoder.decodeObject(forKey: Keys.medium) as? String,
|
|
||||||
let address = aDecoder.decodeObject(forKey: Keys.address) as? String else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
self.medium = medium
|
|
||||||
self.address = address
|
|
||||||
|
|
||||||
if let validatedAtNumber = aDecoder.decodeObject(forKey: Keys.validatedAt) as? NSNumber {
|
|
||||||
validatedAt = validatedAtNumber.uint64Value
|
|
||||||
} else {
|
|
||||||
validatedAt = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if let addedAtNumber = aDecoder.decodeObject(forKey: Keys.addedAt) as? NSNumber {
|
|
||||||
addedAt = addedAtNumber.uint64Value
|
|
||||||
} else {
|
|
||||||
addedAt = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(with coder: NSCoder) {
|
|
||||||
fatalError("Not available")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `MXDevice` represents a device of the current user.
|
|
||||||
class ClassicAppMXDevice: NSObject, NSCoding {
|
|
||||||
/// A unique identifier of the device.
|
|
||||||
var deviceID: String
|
|
||||||
/// The display name set by the user for this device. Absent if no name has been set.
|
|
||||||
var displayName: String?
|
|
||||||
/// The IP address where this device was last seen. (May be a few minutes out of date, for efficiency reasons).
|
|
||||||
var lastSeenIP: String?
|
|
||||||
/// The timestamp (in milliseconds since the unix epoch) when this devices was last seen. (May be a few minutes out of date, for efficiency reasons).
|
|
||||||
var lastSeenTimestamp: UInt64
|
|
||||||
/// The latest recorded user agent for the device.
|
|
||||||
var lastSeenUserAgent: String?
|
|
||||||
|
|
||||||
// MARK: NSCoding
|
|
||||||
|
|
||||||
enum Keys {
|
|
||||||
static let deviceID = "device_id" // String
|
|
||||||
static let displayName = "display_name" // String?
|
|
||||||
static let lastSeenIP = "last_seen_ip" // String?
|
|
||||||
static let lastSeenTimestamp = "last_seen_ts" // NSNumber?.uint64Value
|
|
||||||
static let lastSeenUserAgent = "org.matrix.msc3852.last_seen_user_agent" // String?
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
guard let deviceID = aDecoder.decodeObject(forKey: Keys.deviceID) as? String else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
self.deviceID = deviceID
|
|
||||||
displayName = aDecoder.decodeObject(forKey: Keys.displayName) as? String
|
|
||||||
lastSeenIP = aDecoder.decodeObject(forKey: Keys.lastSeenIP) as? String
|
|
||||||
lastSeenTimestamp = (aDecoder.decodeObject(forKey: Keys.lastSeenTimestamp) as? NSNumber)?.uint64Value ?? 0
|
|
||||||
lastSeenUserAgent = aDecoder.decodeObject(forKey: Keys.lastSeenUserAgent) as? String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode(with coder: NSCoder) {
|
func encode(with coder: NSCoder) {
|
||||||
@@ -264,22 +85,11 @@ class ClassicAppMXDevice: NSObject, NSCoding {
|
|||||||
/// `MXUser` represents a user in Matrix.
|
/// `MXUser` represents a user in Matrix.
|
||||||
class ClassicAppMXUser: NSObject, NSCoding {
|
class ClassicAppMXUser: NSObject, NSCoding {
|
||||||
/// The user id.
|
/// The user id.
|
||||||
private(set) var userID: String
|
let userID: String
|
||||||
/// The user display name.
|
/// The user display name.
|
||||||
var displayName: String?
|
let displayName: String?
|
||||||
/// The url of the user of the avatar.
|
/// The url of the user of the avatar.
|
||||||
var avatarURL: String?
|
let avatarURL: String?
|
||||||
/// The user status.
|
|
||||||
var statusMessage: String?
|
|
||||||
|
|
||||||
/// Whether the user is currently active.
|
|
||||||
/// If YES, lastActiveAgo is an approximation and "Now" should be shown instead.
|
|
||||||
private(set) var currentlyActive = false
|
|
||||||
/// The time in milliseconds since epoch the last activity by the user has
|
|
||||||
/// been tracked by the home server.
|
|
||||||
var lastActiveLocalTimestamp: UInt64 = 0
|
|
||||||
/// Only when event.originServerTs > latestUpdateTS, we change displayname and avatarUrl.
|
|
||||||
var latestUpdateTimestamp: UInt64 = 0
|
|
||||||
|
|
||||||
// MARK: NSCoding
|
// MARK: NSCoding
|
||||||
|
|
||||||
@@ -301,10 +111,6 @@ class ClassicAppMXUser: NSObject, NSCoding {
|
|||||||
self.userID = userID
|
self.userID = userID
|
||||||
displayName = aDecoder.decodeObject(forKey: Keys.displayName) as? String
|
displayName = aDecoder.decodeObject(forKey: Keys.displayName) as? String
|
||||||
avatarURL = aDecoder.decodeObject(forKey: Keys.avatarURL) as? String
|
avatarURL = aDecoder.decodeObject(forKey: Keys.avatarURL) as? String
|
||||||
statusMessage = aDecoder.decodeObject(forKey: Keys.statusMessage) as? String
|
|
||||||
currentlyActive = aDecoder.decodeBool(forKey: Keys.currentlyActive)
|
|
||||||
// lastActiveLocalTimestamp = UInt64(aDecoder.decodeInt64(forKey: Keys.lastActiveLocalTimestamp))
|
|
||||||
// latestUpdateTimestamp = UInt64(aDecoder.decodeInt64(forKey: Keys.latestUpdateTimestamp))
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,15 +48,11 @@ final class ClassicAppManager: ClassicAppManagerProtocol {
|
|||||||
throw ClassicAppManagerError.missingCryptoStorePassphrase
|
throw ClassicAppManagerError.missingCryptoStorePassphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
let accountManager = ClassicAppAccountManager(cacheFolder: url, iv: accountIV, aesKey: accountAESKey)
|
let accountManager = ClassicAppAccountManager(cacheFolder: url,
|
||||||
|
aesKey: accountAESKey,
|
||||||
|
iv: accountIV,
|
||||||
|
cryptoStorePassphrase: cryptoStorePassphrase)
|
||||||
accountManager.loadAccounts()
|
accountManager.loadAccounts()
|
||||||
let activeAccounts = accountManager.activeAccounts
|
return accountManager.accounts
|
||||||
|
|
||||||
MXLog.info("Loaded \(accountManager.accounts.count) accounts")
|
|
||||||
MXLog.verbose("Loaded accounts: \(accountManager.accounts.compactMap(\.credentials.userID).formatted(.list(type: .and)))")
|
|
||||||
MXLog.info("Found \(activeAccounts.count) active accounts")
|
|
||||||
MXLog.verbose("Active accounts: \(activeAccounts.compactMap(\.userID).formatted(.list(type: .and)))")
|
|
||||||
|
|
||||||
return activeAccounts
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user