Add HealthChat API and extended profile services (#9)

Expose OpenID tokens and homeserver credentials from the client proxy, and
add fork-only services for the HealthChat member directory backend
(api.healthchat.ch) plus a service that reads MSC4133 extended profile
fields directly from the homeserver. Groundwork for the members tab and
the profile screens.

Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
This commit is contained in:
2026-09-22 16:56:20 +02:00
committed by Sedat Qaja
parent ea07f76c60
commit e5bfc3a26d
19 changed files with 1807 additions and 0 deletions
@@ -871,6 +871,28 @@ class ClientProxy: ClientProxyProtocol {
}
}
func requestOpenIDToken() async -> Result<OpenIDToken, ClientProxyError> {
do {
return try await .success(.init(rustValue: client.requestOpenidToken()))
} catch {
MXLog.error("Failed requesting OpenID token with error: \(error)")
return .failure(.sdkError(error))
}
}
func homeserverCredentials() -> Result<HomeserverCredentials, ClientProxyError> {
do {
let session = try client.session()
guard let homeserverURL = URL(string: session.homeserverUrl) else {
return .failure(.invalidServerName)
}
return .success(.init(homeserverURL: homeserverURL, accessToken: session.accessToken))
} catch {
MXLog.error("Failed retrieving the session's credentials with error: \(error)")
return .failure(.sdkError(error))
}
}
func roomDirectorySearchProxy() -> RoomDirectorySearchProxyProtocol {
RoomDirectorySearchProxy(roomDirectorySearch: client.roomDirectorySearch())
}
@@ -261,6 +261,14 @@ protocol ClientProxyProtocol: AnyObject {
func profile(for userID: String) async -> Result<UserProfile, ClientProxyError>
/// An OpenID token for authenticating against services that trust the homeserver (MSC1961).
/// Short-lived — cache per call site.
func requestOpenIDToken() async -> Result<OpenIDToken, ClientProxyError>
/// The session's REST credentials for client-server API calls the SDK doesn't cover.
/// The access token can rotate after a refresh, so re-read for every request.
func homeserverCredentials() -> Result<HomeserverCredentials, ClientProxyError>
func roomDirectorySearchProxy() -> RoomDirectorySearchProxyProtocol
func resolveRoomAlias(_ alias: String) async -> Result<ResolvedRoomAlias, ClientProxyError>
@@ -0,0 +1,21 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Foundation
/// The session's REST credentials for direct client-server API calls that the SDK doesn't cover.
/// The access token can rotate after a refresh, so re-read these for every request and never persist them.
nonisolated struct HomeserverCredentials: Equatable {
let homeserverURL: URL
let accessToken: String
}
extension HomeserverCredentials: CustomStringConvertible {
var description: String {
"HomeserverCredentials(homeserverURL: \(homeserverURL), accessToken: <redacted>)"
}
}
@@ -0,0 +1,36 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
/// An OpenID token that third-party services can use to verify the user's identity
/// against the homeserver (MSC1961). Short-lived — cache per call site.
nonisolated struct OpenIDToken: Equatable {
let accessToken: String
let matrixServerName: String
/// The interval after which the token expires, relative to when it was requested.
let expiresIn: TimeInterval
init(accessToken: String, matrixServerName: String, expiresIn: TimeInterval) {
self.accessToken = accessToken
self.matrixServerName = matrixServerName
self.expiresIn = expiresIn
}
init(rustValue: MatrixRustSDK.OpenIdToken) {
accessToken = rustValue.accessToken
matrixServerName = rustValue.matrixServerName
expiresIn = TimeInterval(rustValue.expiresInSeconds)
}
}
extension OpenIDToken: CustomStringConvertible {
var description: String {
"OpenIDToken(matrixServerName: \(matrixServerName), accessToken: <redacted>)"
}
}