Skip to content

Commit 50d5d17

Browse files
committed
add MatrixStore storage interface
1 parent 85b2018 commit 50d5d17

File tree

4 files changed

+264
-98
lines changed

4 files changed

+264
-98
lines changed

Sources/MatrixClient/UserIdentifier.swift

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77

88
import Foundation
99

10+
public protocol MatrixUserIdentifierProtocol: Equatable, Comparable {
11+
var localpart: String { get set }
12+
13+
init?(string: String)
14+
}
15+
1016
/// Users within Matrix are uniquely identified by their Matrix user ID.
1117
///
1218
/// The user ID is namespaced to the homeserver which allocated the account and has the form:
@@ -55,7 +61,11 @@ import Foundation
5561
/// The length restriction is derived from the limit on the length of the `sender` key on events; since the user ID
5662
/// appears in every event sent by the user, it is limited to ensure that the user ID does not dominate over the actual
5763
/// content of the events.
58-
public struct MatrixUserIdentifier: RawRepresentable, Equatable {
64+
public struct MatrixUserIdentifier: RawRepresentable, MatrixUserIdentifierProtocol {
65+
public static func < (lhs: MatrixUserIdentifier, rhs: MatrixUserIdentifier) -> Bool {
66+
lhs.rawValue < rhs.rawValue
67+
}
68+
5969
public var localpart: String
6070
public var domain: String?
6171

@@ -161,3 +171,65 @@ extension MatrixUserIdentifier: Codable {
161171
try container.encode(rawValue)
162172
}
163173
}
174+
175+
public struct MatrixFullUserIdentifier: RawRepresentable, MatrixUserIdentifierProtocol {
176+
public init(localpart: String, domain: String) {
177+
self.localpart = localpart
178+
self.domain = domain
179+
}
180+
181+
public var localpart: String
182+
public var domain: String
183+
184+
public init?(rawValue: MatrixUserIdentifier) {
185+
guard let domain = rawValue.domain else {
186+
return nil
187+
}
188+
self.domain = domain
189+
localpart = rawValue.localpart
190+
}
191+
192+
public init?(string: String) {
193+
guard let rawValue = MatrixUserIdentifier(string: string)
194+
else {
195+
return nil
196+
}
197+
guard let domain = rawValue.domain else {
198+
return nil
199+
}
200+
self.domain = domain
201+
localpart = rawValue.localpart
202+
}
203+
204+
public var rawValue: MatrixUserIdentifier {
205+
.init(locapart: localpart, domain: domain)
206+
}
207+
208+
public var FQMXID: String {
209+
"@\(localpart):\(domain)"
210+
}
211+
212+
public typealias RawValue = MatrixUserIdentifier
213+
214+
public static func < (lhs: MatrixFullUserIdentifier, rhs: MatrixFullUserIdentifier) -> Bool {
215+
lhs.FQMXID < rhs.FQMXID
216+
}
217+
}
218+
219+
extension MatrixFullUserIdentifier: Codable {
220+
public init(from decoder: Decoder) throws {
221+
let container = try decoder.singleValueContainer()
222+
let rawValue = try container.decode(String.self)
223+
let id = MatrixFullUserIdentifier(string: rawValue)
224+
if let id = id {
225+
self = id
226+
} else {
227+
throw MatrixError.BadJSON
228+
}
229+
}
230+
231+
public func encode(to encoder: Encoder) throws {
232+
var container = encoder.singleValueContainer()
233+
try container.encode(rawValue)
234+
}
235+
}

Sources/MatrixCore/MatrixCore.swift

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,84 @@
11

2-
import CoreData
32
import Foundation
43
import MatrixClient
54
import OSLog
65

6+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
7+
internal struct MatrixCoreLogger {
8+
internal static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "MatrixCore")
9+
}
10+
11+
@available(swift, introduced: 5.5)
12+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
13+
@MainActor
14+
public class MatrixCore<T: MatrixStore> {
15+
public let store: T
16+
public var info: T.AccountInfo
17+
18+
public var client: MatrixClient
19+
20+
// MARK: - computed variables
21+
22+
public var id: T.AccountInfo.AccountIdentifier {
23+
info.id
24+
}
25+
26+
public var accessToken: String? {
27+
get {
28+
info.accessToken
29+
}
30+
set {
31+
info.accessToken = newValue
32+
client.accessToken = newValue
33+
}
34+
}
35+
36+
public var mxID: MatrixFullUserIdentifier {
37+
info.mxID
38+
}
39+
40+
public var FQMXID: String {
41+
info.FQMXID
42+
}
43+
44+
public convenience init(store: T, accountID: T.AccountInfo.AccountIdentifier) async throws {
45+
let info = try await store.getAccountInfo(accountID: accountID)
46+
self.init(store: store, account: info)
47+
}
48+
49+
public init(store: T, account: T.AccountInfo) {
50+
self.store = store
51+
info = account
52+
client = MatrixClient(
53+
homeserver: account.homeServer,
54+
urlSession: URLSession(configuration: .default),
55+
accessToken: account.accessToken
56+
)
57+
}
58+
59+
// MARK: auth management
60+
61+
/// Issue loggout request to Homeserver and remove account info from store.
62+
public func logout() async throws {
63+
do {
64+
try await client.logout()
65+
} catch let error as MatrixServerError {
66+
if error.errcode == .UnknownToken {
67+
MatrixCoreLogger.logger.warning("Token already unknown at homeserver. deleting account info.")
68+
} else {
69+
throw error
70+
}
71+
}
72+
73+
try await store.deleteAccountInfo(account: info)
74+
}
75+
}
76+
777
/*
878
@available(swift, introduced: 5.5)
979
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
1080
@MainActor
1181
public class MatrixCore {
12-
public let context: NSManagedObjectContext
13-
14-
// TODO: internal?
15-
public var coreDataMatrixAccount: MatrixAccount
16-
17-
public internal(set) var client: MatrixClient
18-
public internal(set) var userID: MatrixUserIdentifier
19-
20-
internal static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "MatrixCore")
21-
22-
// MARK: - Dynamic variables
23-
2482
public var displayName: String? {
2583
coreDataMatrixAccount.displayName
2684
}

Sources/MatrixCore/MatrixStore.swift

Lines changed: 4 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -21,93 +21,13 @@ public protocol MatrixStore {
2121

2222
@available(swift, introduced: 5.5)
2323
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
24-
func getAccountInfo(accountID: MatrixUserIdentifier) async throws -> AccountInfo
24+
func getAccountInfo(accountID: AccountInfo.AccountIdentifier) async throws -> AccountInfo
2525

2626
@available(swift, introduced: 5.5)
2727
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
2828
func getAccountInfos() async throws -> [AccountInfo]
29-
}
30-
31-
public protocol MatrixStoreAccountInfo {
32-
var name: String { get }
33-
var displayName: String? { get }
34-
var mxID: MatrixUserIdentifier { get }
35-
var homeServer: MatrixHomeserver { get }
36-
var accessToken: String? { get }
37-
}
38-
39-
// TODO: only on Darwin platforms
40-
public extension MatrixStoreAccountInfo {
41-
/// Load the ``accessToken`` from Keychain.
42-
static func getFromKeychain(account: MatrixUserIdentifier,
43-
extraKeychainArguments: [String: Any] = [:]) throws -> String
44-
{
45-
guard let userID = account.FQMXID,
46-
let domain = account.domain
47-
else {
48-
throw MatrixError.NotFound
49-
}
50-
51-
var keychainQuery = extraKeychainArguments
52-
keychainQuery[kSecClass as String] = kSecClassInternetPassword
53-
keychainQuery[kSecMatchLimit as String] = kSecMatchLimitOne
54-
keychainQuery[kSecAttrAccount as String] = userID
55-
keychainQuery[kSecAttrServer as String] = domain
56-
keychainQuery[kSecReturnAttributes as String] = true
57-
keychainQuery[kSecReturnData as String] = true
58-
59-
var item: CFTypeRef?
60-
let status = SecItemCopyMatching(keychainQuery as CFDictionary, &item)
61-
guard status == errSecSuccess else {
62-
throw MatrixCoreError.keychainError(status)
63-
}
64-
65-
guard let existingItem = item as? [String: Any],
66-
let tokenData = existingItem[kSecValueData as String] as? Data,
67-
let token = String(data: tokenData, encoding: .utf8)
68-
else {
69-
throw MatrixCoreError.keychainError(errSecInvalidData)
70-
}
71-
72-
return token
73-
}
7429

75-
/// Save the ``accessToken`` to keychain, using the accountID data as identifier.
76-
func saveToKeychain(extraKeychainArguments: [String: Any] = [:]) throws {
77-
guard let userID = mxID.FQMXID,
78-
let domain = mxID.domain,
79-
let accessToken = self.accessToken?.data(using: .utf8)
80-
else {
81-
throw MatrixError.NotFound
82-
}
83-
84-
var keychainInsertQuery = extraKeychainArguments
85-
keychainInsertQuery[kSecClass as String] = kSecClassInternetPassword
86-
keychainInsertQuery[kSecAttrAccount as String] = userID
87-
keychainInsertQuery[kSecAttrServer as String] = domain
88-
keychainInsertQuery[kSecValueData as String] = accessToken
89-
90-
let status = SecItemAdd(keychainInsertQuery as CFDictionary, nil)
91-
guard status == errSecSuccess else {
92-
throw MatrixCoreError.keychainError(status)
93-
}
94-
}
95-
96-
func deleteFromKeychain(extraKeychainArguments: [String: Any] = [:]) throws {
97-
guard let userID = mxID.FQMXID,
98-
let domain = mxID.domain
99-
else {
100-
throw MatrixError.NotFound
101-
}
102-
103-
var keychainQuery = extraKeychainArguments
104-
keychainQuery[kSecClass as String] = kSecClassInternetPassword
105-
keychainQuery[kSecAttrAccount as String] = userID
106-
keychainQuery[kSecAttrServer as String] = domain
107-
108-
let status = SecItemDelete(keychainQuery as CFDictionary)
109-
guard status == errSecSuccess else {
110-
throw MatrixCoreError.keychainError(status)
111-
}
112-
}
30+
@available(swift, introduced: 5.5)
31+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
32+
func deleteAccountInfo(account: AccountInfo) async throws
11333
}

0 commit comments

Comments
 (0)