libQuotient
libQuotient copied to clipboard
Batteries-included Account / Connection management
Problem
Currently, handling connections in libquotient-based clients is complicated.
To log in a client, the flow is roughly:
- Create a
Connection
object - run
Connection::resolveServer(mxId)
to get the server's url and query the login flows - wait until the login flows in the connection change
- call
Connection::loginWithPassword(mxId, password, ...)
- wait until
Connection::connected
- Save the account to local state config (
AccountSettings
) - Save the access token to the keychain
- Add the connection to the client's list of connections (e.g.,
Quotient::AccountRegistry
) - Start syncing
- Save the state cache after each sync iteration
To load an existing connection:
- Load list of connections from state config; for each one do:
- Load settings through
AccountSettings
- Load access token from keychain
- Create a
Connection
object - Run
Connection::assumeIdentity(mxId, accessToken)
- Wait until
Connection::connected
- Run
Connection::loadState
- Add the connection to the client's list of connections (e.g.,
Quotient::AccountRegistry
) - start syncing
- Save state cache after each sync
(I'm skipping over SSO login and account registration here. From libQuotient's perspective, account registration doesn't really exist; the relevant API here is the same as for login. For SSO login, SsoSession
exists)
There are some further complications here, e.g., that a client might want to wait until after the initial sync / load from cache before showing the connection (or switching away from a loading screen) to make the UX nicer.
With sliding sync, OIDC login, and my work on making libquotient work offline (#777), this would not get simpler.
Overall, there are too many things that are hard to understand, making the developer expierence not amazing.
There's also the - somewhat failed - attempt to improve this by providing the account loading functionality in Quotient::AccountRegistry
.
Goals
- There's easy to use API for logging in accounts through password, traditional sso, and oidc, registering accounts, and loading existing accounts
- The
Connection
type is always ready to be used by a client, in the sense that it has the necessary userid, deviceid, access-/refreshtoken, crypto state, etc. that is required for operation. Notably, this does not mean that the homeserver must be reachable / was already reached (see #777) - Connections are loaded in a "batteries-included" way, e.g., the automatically load/save the state cache, run a sync loop, are saved to state config to be restored when the client starts next time, have their access token saved to keychain, are added to an accountregistry, etc. These behaviors can be overwritten as needed.
Proposal
-
Connection
uses its public Constructors -
The following functions are removed (at least from public API, might be kept as internal API as needed. we should look into removing functions that clients don't need from public API, but that's a different topic):
-
Connection::isLoggedIn
(needs some further thought. should connections be automatically be destroyed when logging out? At least, it should not be relevant for checking whether a connection is "loaded" yet) -
Connection::loadState
,Connection::saveState
: Handled automatically, depending on configuration -
Connection::prepareForSso
,Connection::resolveServer
,Connection::setHomeserver
,Connection::assumeIdentity
: Implementation details that the client shouldn't need to care about -
Connection::sync
: maybe, might be useful for situations where we want to selectively sync, -
Connection::syncLoop
,Connection::stopSync
: Handled automatically depending on configuration
-
-
Connection signals are removed / changed as needed:
-
Connection::resolveError
needs is moved toPendingConnection
-
Connection::homeserverChanged
is moved (or removed entirely, as needed) -
Connection::loginFlowsChanged
is removed -
Connection::connected
is removed (probably. See #777) -
Connection::loggedOut
is removed if we end up deleting connections automatically on logout -
Connection::stateChanged
is removed -
Connection::loginError
is moved toPendingConnection
-
-
A new
Homeserver
class is added (name up for debate). It's intended usage is:- Resolving the server's url from an mxid or url
- Querying the server's login flows
- Exposing well-known info to client
- Exposing password, sso, oidc login capability to client
-
AccountRegistry
is kept roughly as-is: -
AccountRegistry::add
andAccountRegistry::drop
are dropped from public API (maybe?) -
new functions
-
PendingConnection AccountRegistry::loginWithPassword(mxId, password)
-
PendingConnection AccountRegistry::restoreConnection(mxId)
-
PendingConnection AccountRegistry::loginWithSso(homeserverUrl)
-
PendingConnection AccountRegistry::loginWithOidc(url)
-
PendingConnection AccountRegistry::registerAccount()
(maybe we need a different name forAccountRegistry
to make it clear that this is not about adding a name to the registry)
-
-
A function that lists the mxIDs of all connections that the are stored in state cache, i.e., that can be loaded
-
Maybe a convenience function to automatically load all connections
-
PendingConnection
- Future/Promise to get a Connection
- Subclasses for the situations that need specific user interaction to get a connection (SSO login, OIDC login, registration)
-
PendingSsoConnection
replacesSsoSession
-
The functions for getting connections (
AccountRegistry::loginWithPassword
, etc.) take an optionalConnectionSettings
parameter, which configures- Whether a sync loop is started automatically (default: yes)
- Whether cache is saved / restored (default: yes)
- Whether the connection is added to the account registry (default: yes)
- Whether the connection is remembered (i.e., stored to state config) (default: yes)
-
Semi-related:
- There's some kind of mechanism by which the client can provide the backend for the state config used to store accounts. This would clean reduce the number of individual config files that e.g. NeoChat produces