Page MenuHomePhorge

No OneTemporary

Size
63 KB
Referenced Files
None
Subscribers
None
diff --git a/src/client/client.cpp b/src/client/client.cpp
index 2210c5d..fa951fe 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -1,757 +1,772 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <filesystem>
#include <algorithm>
#include <chrono>
#include <lager/constant.hpp>
#include "client.hpp"
#include "client-model.hpp"
#include "alias.hpp"
#include "immer-utils.hpp"
namespace Kazv
{
static Timestamp tsNow()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count();
}
static Client::PromiseT sendMultiVerificationEvents(Client::ContextT ctx, VerificationTracker::PendingEvents pendingEvents)
{
std::vector<Client::PromiseT> ps;
for (auto ed : pendingEvents) {
ps.push_back(ctx.dispatch(SendToDeviceMessageAction{
ed.event,
{{ed.toUserId, {ed.toDeviceId}}}
}));
}
return ctx.promiseInterface().all(ps);
}
static Client::PromiseT maybeMarkDevicesVerified(Client::ContextT ctx, VerificationTracker::PendingEvents pendingEvents)
{
immer::map<std::string, immer::map<std::string, DeviceTrustLevel>> trustLevelMap;
for (auto ed : pendingEvents) {
if (VerificationUtils::typeOf(ed.event) == VerificationEventTypes::tDone) {
// a process asking us to send an outbound done event means that we have verified them
trustLevelMap = setIn(trustLevelMap, DeviceTrustLevel::Verified, ed.toUserId, ed.toDeviceId);
kzo.client.dbg() << "maybeMarkDevicesVerified: verified " << ed.toUserId << "/" << ed.toDeviceId << std::endl;
}
}
if (!trustLevelMap.empty()) {
return ctx.dispatch(SetDevicesTrustLevelsAction{trustLevelMap});
}
return ctx.createResolvedPromise({});
}
static Client::PromiseT verificationChangePostProcess(Client::ContextT ctx, VerificationTracker::PendingEvents es)
{
return ctx.dispatch(NotifyVerificationTrackerModelAction{})
.then([ctx, es](auto &&) {
return maybeMarkDevicesVerified(ctx, es);
})
.then([ctx, es](auto &&) {
return sendMultiVerificationEvents(ctx, es);
});
}
Client::Client(lager::reader<SdkModel> sdk,
ContextT ctx, std::nullopt_t)
: m_sdk(sdk)
, m_client(sdk.map(&SdkModel::c))
, m_ctx(std::move(ctx))
{
}
Client::Client(lager::reader<SdkModel> sdk,
ContextWithDepsT ctx)
: m_sdk(sdk)
, m_client(sdk.map(&SdkModel::c))
, m_ctx(ctx)
, m_deps(std::move(ctx))
{
}
Client::Client(InEventLoopTag,
ContextWithDepsT ctx)
: m_sdk(std::nullopt)
, m_client(std::nullopt)
, m_ctx(ctx)
, m_deps(std::move(ctx))
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
, KAZV_ON_EVENT_LOOP_VAR(true)
#endif
{
}
Client::Client(InEventLoopTag,
ContextT ctx, DepsT deps)
: m_sdk(std::nullopt)
, m_client(std::nullopt)
, m_ctx(std::move(ctx))
, m_deps(std::move(deps))
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
, KAZV_ON_EVENT_LOOP_VAR(true)
#endif
{
}
Client Client::toEventLoop() const
{
return Client(InEventLoopTag{}, m_ctx, m_deps.value());
}
Room Client::room(std::string id) const
{
if (m_deps.has_value()) {
return Room(sdkCursor(), lager::make_constant(id), m_ctx, m_deps.value());
} else {
return Room(sdkCursor(), lager::make_constant(id), m_ctx);
}
}
Room Client::roomByCursor(lager::reader<std::string> id) const
{
if (m_deps.has_value()) {
return Room(sdkCursor(), id, m_ctx, m_deps.value());
} else {
return Room(sdkCursor(), id, m_ctx);
}
}
auto Client::passwordLogin(std::string homeserver, std::string username,
std::string password, std::string deviceName) const
-> PromiseT
{
auto p1 = m_ctx.dispatch(LoginAction{
homeserver, username, password, deviceName});
p1
.then([that=toEventLoop()](auto stat) {
if (! stat.success()) {
return;
}
// It is meaningless to wait for it in a Promise
// that is never exposed to the user.
that.startSyncing();
});
return p1;
}
auto Client::mLoginTokenLogin(
std::string homeserver,
std::string loginToken,
std::optional<std::string> deviceName
) const -> PromiseT
{
auto p1 = m_ctx.dispatch(MLoginTokenLoginAction{
homeserver, loginToken, deviceName});
p1
.then([that=toEventLoop()](auto stat) {
if (! stat.success()) {
return;
}
that.startSyncing();
});
return p1;
}
auto Client::tokenLogin(std::string homeserver, std::string username,
std::string token, std::string deviceId) const
-> PromiseT
{
auto p1 = m_ctx.dispatch(TokenLoginAction{
homeserver, username, token, deviceId});
p1
.then([that=toEventLoop()](auto stat) {
if (! stat.success()) {
return;
}
that.startSyncing();
});
return p1;
}
auto Client::shouldSync() const -> lager::reader<bool> {
return this->clientCursor()[&ClientModel::shouldSync];
}
auto Client::logout() const
-> PromiseT
{
return stopSyncing().then([ctx=m_ctx] (auto stat) {
return ctx.dispatch(HardLogoutAction{});
});
}
auto Client::autoDiscover(std::string userId) const
-> PromiseT
{
return m_ctx.dispatch(GetWellknownAction{userId})
.then([that=toEventLoop()](auto stat) {
if (!stat.success()) {
return that.m_ctx.createResolvedPromise(stat);
}
return that.m_ctx.dispatch(GetVersionsAction{stat.dataStr("homeserverUrl")})
.then([that, stat](auto stat2) {
if (!stat2.success()) {
return stat2;
} else {
return stat;
}
});
});
}
auto Client::createRoom(
RoomVisibility v,
std::optional<std::string> name,
std::optional<std::string> alias,
immer::array<std::string> invite,
std::optional<bool> isDirect,
bool allowFederate,
std::optional<std::string> topic,
JsonWrap powerLevelContentOverride,
std::optional<CreateRoomPreset> preset,
immer::array<Event> initialState
) const
-> PromiseT
{
CreateRoomAction a;
a.visibility = v;
a.name = name;
a.roomAliasName = alias;
a.invite = invite;
a.isDirect = isDirect;
a.topic = topic;
a.powerLevelContentOverride = powerLevelContentOverride;
// Synapse won't buy it if we do not provide
// a creationContent object.
a.creationContent = json{
{"m.federate", allowFederate}
};
a.preset = preset;
a.initialState = initialState;
return m_ctx.dispatch(std::move(a));
}
auto Client::joinRoomById(std::string roomId) const -> PromiseT
{
return m_ctx.dispatch(JoinRoomByIdAction{roomId});
}
auto Client::joinRoom(std::string roomId, immer::array<std::string> serverName) const
-> PromiseT
{
return m_ctx.dispatch(JoinRoomAction{roomId, serverName});
}
auto Client::uploadContent(immer::box<Bytes> content,
std::string uploadId,
std::optional<std::string> filename,
std::optional<std::string> contentType) const
-> PromiseT
{
return m_ctx.dispatch(UploadContentAction{
FileDesc(FileContent{content.get().begin(), content.get().end()}),
filename, contentType, uploadId});
}
auto Client::uploadContent(FileDesc file) const
-> PromiseT
{
auto basename = file.name()
? std::optional(std::filesystem::path(file.name().value()).filename().string())
: std::nullopt;
return m_ctx.dispatch(UploadContentAction{
file,
// use only basename to prevent path info being leaked
basename,
file.contentType(),
// uploadId unused
std::string{}});
}
std::string Client::mxcUriToHttpV1(std::string mxcUri) const {
using namespace CursorOp;
auto [serverName, mediaId] = mxcUriToMediaDesc(mxcUri);
return (+clientCursor())
.template job<GetContentJobV1>()
.make(serverName, mediaId).url();
}
auto Client::downloadContent(std::string mxcUri, std::optional<FileDesc> downloadTo) const
-> PromiseT
{
return m_ctx.dispatch(DownloadContentAction{mxcUri, downloadTo});
}
auto Client::downloadThumbnail(
std::string mxcUri,
int width,
int height,
std::optional<ThumbnailResizingMethod> method,
std::optional<FileDesc> downloadTo) const
-> PromiseT
{
return m_ctx.dispatch(DownloadThumbnailAction{mxcUri, width, height, method, std::nullopt, downloadTo});
}
auto Client::startSyncing() const -> PromiseT
{
KAZV_VERIFY_THREAD_ID();
using namespace Kazv::CursorOp;
if (+syncing()) {
return m_ctx.createResolvedPromise(true);
}
- auto p1 = m_ctx.createResolvedPromise(true)
- .then([that=toEventLoop()](auto) {
- // post filters, if filters are incomplete
- if ((+that.clientCursor()[&ClientModel::initialSyncFilterId]).empty()
- || (+that.clientCursor()[&ClientModel::incrementalSyncFilterId]).empty()) {
- return that.m_ctx.dispatch(PostInitialFiltersAction{});
- }
- return that.m_ctx.createResolvedPromise(true);
- })
- .then([that=toEventLoop()](auto stat) {
- if (! stat.success()) {
- return that.m_ctx.createResolvedPromise(stat);
- }
- // Upload identity keys if we need to
- if (+that.clientCursor()[&ClientModel::crypto]
- && ! +that.clientCursor()[&ClientModel::identityKeysUploaded]) {
- return that.m_ctx.dispatch(UploadIdentityKeysAction{});
- } else {
- return that.m_ctx.createResolvedPromise(true);
- }
- });
+ auto p1 = ensureReadyForSyncing();
p1
.then([m_ctx=m_ctx](auto stat) {
m_ctx.dispatch(SetShouldSyncAction{true});
return stat;
})
.then([that=toEventLoop()](auto stat) {
if (stat.success()) {
that.syncForever();
}
});
return p1;
}
auto Client::processVerificationEventsFromSync(EventList toDeviceEvents) const -> PromiseT
{
return ensureInitVerificationTracker()
.then([that=toEventLoop(), ves=toDeviceEvents](auto &&stat) {
if (!stat.success()) {
return that.m_ctx.createResolvedPromise(stat);
}
auto &vt = lager::get<VerificationTracker>(that.m_deps.value());
auto now = tsNow();
auto es = vt.processIncoming(now, ves);
auto p = maybeMarkDevicesVerified(that.m_ctx, es);
for (auto ed : es) {
// We don't actually need to wait for these events to
// be sent before we proceed into next sync cycle.
that.m_ctx.dispatch(SendToDeviceMessageAction{
ed.event,
{{ed.toUserId, {ed.toDeviceId}}}
});
}
return p.then([ctx=that.m_ctx](const auto &) {
return ctx.dispatch(NotifyVerificationTrackerModelAction{});
});
});
}
auto Client::requestOutgoingToDeviceVerification(std::string userId, std::string deviceId) const -> PromiseT
{
return ensureInitVerificationTracker()
.then([that=toEventLoop(), userId, deviceId](auto &&stat) {
if (!stat.success()) {
return that.m_ctx.createResolvedPromise(stat);
}
return that.m_ctx.dispatch(EnsureKeysFromDevicesAction{
{{userId, {deviceId}}},
});
})
.then([that=toEventLoop(), userId, deviceId](auto &&stat) {
if (!stat.success()) {
return that.m_ctx.createResolvedPromise(stat);
}
auto &vt = lager::get<VerificationTracker>(that.m_deps.value());
auto deviceOpt = that.clientCursor().map([userId, deviceId](const ClientModel &client) {
return client.deviceLists.get(userId, deviceId);
}).make().get();
if (!deviceOpt) {
return that.m_ctx.createResolvedPromise({ /* succ = */ false, json::object({
{"errorCode", "MOE.KAZV.MXC.NO_DEVICE_KEYS"},
{"error", "Cannot obtain device keys"},
})});
}
auto device = deviceOpt.value();
auto es = vt.requestOutgoingToDevice(VerificationUtils::DeviceIdentity{
userId,
deviceId,
device.ed25519Key,
}, tsNow());
return verificationChangePostProcess(that.m_ctx, es);
});
}
auto Client::readyForVerification(std::string userId, std::string deviceId) const -> PromiseT
{
return ensureInitVerificationTracker()
.then([that=toEventLoop(), userId, deviceId](auto &&stat) {
if (!stat.success()) {
return that.m_ctx.createResolvedPromise(stat);
}
return that.m_ctx.dispatch(EnsureKeysFromDevicesAction{
{{userId, {deviceId}}},
});
})
.then([that=toEventLoop(), userId, deviceId](auto &&stat) {
if (!stat.success()) {
return that.m_ctx.createResolvedPromise(stat);
}
auto &vt = lager::get<VerificationTracker>(that.m_deps.value());
auto deviceOpt = that.clientCursor().map([userId, deviceId](const ClientModel &client) {
return client.deviceLists.get(userId, deviceId);
}).make().get();
if (!deviceOpt) {
return that.m_ctx.createResolvedPromise({ /* succ = */ false, json::object({
{"errorCode", "MOE.KAZV.MXC.NO_DEVICE_KEYS"},
{"error", "Cannot obtain device keys"},
})});
}
auto device = deviceOpt.value();
vt.setTheirIdentity(VerificationUtils::DeviceIdentity{
userId,
deviceId,
device.ed25519Key,
});
auto es = vt.userReady(userId, deviceId);
return verificationChangePostProcess(that.m_ctx, es);
});
}
auto Client::cancelVerification(std::string userId, std::string deviceId) const -> PromiseT
{
return ensureInitVerificationTracker()
.then([that=toEventLoop(), userId, deviceId](auto &&) {
auto &vt = lager::get<VerificationTracker>(that.m_deps.value());
auto es = vt.userCancel(userId, deviceId);
return verificationChangePostProcess(that.m_ctx, es);
});
}
auto Client::confirmVerificationSasMatch(std::string userId, std::string deviceId) const -> PromiseT
{
return ensureInitVerificationTracker()
.then([that=toEventLoop(), userId, deviceId](auto &&) {
auto &vt = lager::get<VerificationTracker>(that.m_deps.value());
auto es = vt.userConfirmMatch(userId, deviceId);
return verificationChangePostProcess(that.m_ctx, es);
});
}
auto Client::denyVerificationSasMatch(std::string userId, std::string deviceId) const -> PromiseT
{
return ensureInitVerificationTracker()
.then([that=toEventLoop(), userId, deviceId](auto &&) {
auto &vt = lager::get<VerificationTracker>(that.m_deps.value());
auto es = vt.userDenyMatch(userId, deviceId);
return verificationChangePostProcess(that.m_ctx, es);
});
}
auto Client::ensureInitVerificationTracker() const -> PromiseT
{
if (!m_deps) {
return m_ctx.createResolvedPromise(false);
}
bool hasCrypto{clientCursor().map([](const auto &c) {
return c.crypto.has_value();
}).make().get()};
if (!hasCrypto) {
return m_ctx.createResolvedPromise(false);
}
return m_ctx.createResolvedPromise({})
.then([that=toEventLoop()](auto) {
auto &vt = lager::get<VerificationTracker>(that.m_deps.value());
if (vt.identity.userId.empty()) {
auto client = that.clientCursor().get();
const auto &crypto = client.constCrypto();
vt.identity = {
client.userId,
client.deviceId,
crypto.ed25519IdentityKey(),
};
}
return EffectStatus{/* succ = */ true};
});
}
auto Client::syncForever(std::optional<int> retryTime) const -> void
{
KAZV_VERIFY_THREAD_ID();
// assert (m_deps);
using namespace CursorOp;
- bool isInitialSync = ! (+clientCursor()[&ClientModel::syncToken]).has_value();
-
bool shouldSync = +clientCursor()[&ClientModel::shouldSync];
if (! shouldSync) {
return;
}
- //
-
- auto syncRes = m_ctx.dispatch(SyncAction{});
-
- auto uploadOneTimeKeysRes = syncRes
- .then([that=toEventLoop()](auto stat) {
- if (! stat.success()) {
- return that.m_ctx.createResolvedPromise(stat);
- }
- auto &rg = lager::get<RandomInterface &>(that.m_deps.value());
- bool hasCrypto{+that.clientCursor()[&ClientModel::crypto]};
- if (! hasCrypto) {
- return that.m_ctx.createResolvedPromise(true);
- }
- auto numKeysToGenerate = (+that.clientCursor()).numOneTimeKeysNeeded();
- return that.m_ctx.dispatch(GenerateAndUploadOneTimeKeysAction{
- numKeysToGenerate,
- rg.generateRange<RandomData>(GenerateAndUploadOneTimeKeysAction::randomSize(numKeysToGenerate))
- });
- });
-
- auto queryKeysRes = syncRes
- .then([that=toEventLoop(), isInitialSync](auto stat) {
- if (! stat.success()) {
- return that.m_ctx.createResolvedPromise(stat);
- }
- bool hasCrypto{+that.clientCursor()[&ClientModel::crypto]};
- return hasCrypto
- ? that.m_ctx.dispatch(QueryKeysAction{isInitialSync})
- : that.m_ctx.createResolvedPromise(true);
- });
- auto processVerificationEventsRes = syncRes
- .then([that=toEventLoop()](EffectStatus stat) {
- if (!stat.success() || !that.m_deps ||
- !that.clientCursor().map([](const auto &c) {
- return c.crypto.has_value();
- }).make().get()) {
- return that.m_ctx.createResolvedPromise(stat);
- }
- kzo.client.dbg() << "processVerificationEvents: " << stat.data().get().dump() << std::endl;
- EventList ves = stat.data().get().at("verificationEvents").at("toDevice").template get<EventList>();
- return that.processVerificationEventsFromSync(ves);
- });
-
- m_ctx.promiseInterface()
- .all(std::vector<PromiseT>{uploadOneTimeKeysRes, queryKeysRes, processVerificationEventsRes})
+ doOneSyncCycle()
.then([that=toEventLoop(), retryTime](auto stat) {
if (stat.success()) {
that.syncForever(); // reset retry time
} else {
auto firstRetryTime = +that.clientCursor()[&ClientModel::firstRetryMs];
auto retryTimeFactor = +that.clientCursor()[&ClientModel::retryTimeFactor];
auto maxRetryTime = +that.clientCursor()[&ClientModel::maxRetryMs];
auto curRetryTime = retryTime ? retryTime.value() : firstRetryTime;
if (curRetryTime > maxRetryTime) { curRetryTime = maxRetryTime; }
auto nextRetryTime = curRetryTime * retryTimeFactor;
kzo.client.warn() << "Sync failed, retrying in " << curRetryTime << "ms" << std::endl;
auto &jh = getJobHandler(that.m_deps.value());
jh.setTimeout([that=that.toEventLoop(), nextRetryTime]() { that.syncForever(nextRetryTime); },
curRetryTime);
}
});
}
auto Client::stopSyncing() const -> PromiseT
{
return m_ctx.dispatch(SetShouldSyncAction{false});
}
+ auto Client::ensureReadyForSyncing() const -> PromiseT
+ {
+ KAZV_VERIFY_THREAD_ID();
+ using namespace Kazv::CursorOp;
+
+ return m_ctx.createResolvedPromise(true)
+ .then([that=toEventLoop()](auto) {
+ // post filters, if filters are incomplete
+ if ((+that.clientCursor()[&ClientModel::initialSyncFilterId]).empty()
+ || (+that.clientCursor()[&ClientModel::incrementalSyncFilterId]).empty()) {
+ return that.m_ctx.dispatch(PostInitialFiltersAction{});
+ }
+ return that.m_ctx.createResolvedPromise(true);
+ })
+ .then([that=toEventLoop()](auto stat) {
+ if (! stat.success()) {
+ return that.m_ctx.createResolvedPromise(stat);
+ }
+ // Upload identity keys if we need to
+ if (+that.clientCursor()[&ClientModel::crypto]
+ && ! +that.clientCursor()[&ClientModel::identityKeysUploaded]) {
+ return that.m_ctx.dispatch(UploadIdentityKeysAction{});
+ } else {
+ return that.m_ctx.createResolvedPromise(true);
+ }
+ });
+ }
+
+ auto Client::doOneSyncCycle() const -> PromiseT
+ {
+ KAZV_VERIFY_THREAD_ID();
+ using namespace CursorOp;
+
+ bool isInitialSync = !(+clientCursor()[&ClientModel::syncToken]).has_value();
+
+ auto syncRes = m_ctx.dispatch(SyncAction{});
+
+ auto uploadOneTimeKeysRes = syncRes
+ .then([that=toEventLoop()](auto stat) {
+ if (! stat.success()) {
+ return that.m_ctx.createResolvedPromise(stat);
+ }
+ auto &rg = lager::get<RandomInterface &>(that.m_deps.value());
+ bool hasCrypto{+that.clientCursor()[&ClientModel::crypto]};
+ if (! hasCrypto) {
+ return that.m_ctx.createResolvedPromise(true);
+ }
+ auto numKeysToGenerate = (+that.clientCursor()).numOneTimeKeysNeeded();
+ return that.m_ctx.dispatch(GenerateAndUploadOneTimeKeysAction{
+ numKeysToGenerate,
+ rg.generateRange<RandomData>(GenerateAndUploadOneTimeKeysAction::randomSize(numKeysToGenerate))
+ });
+ });
+
+ auto queryKeysRes = syncRes
+ .then([that=toEventLoop(), isInitialSync](auto stat) {
+ if (! stat.success()) {
+ return that.m_ctx.createResolvedPromise(stat);
+ }
+ bool hasCrypto{+that.clientCursor()[&ClientModel::crypto]};
+ return hasCrypto
+ ? that.m_ctx.dispatch(QueryKeysAction{isInitialSync})
+ : that.m_ctx.createResolvedPromise(true);
+ });
+
+ auto processVerificationEventsRes = syncRes
+ .then([that=toEventLoop()](EffectStatus stat) {
+ if (!stat.success() || !that.m_deps ||
+ !that.clientCursor().map([](const auto &c) {
+ return c.crypto.has_value();
+ }).make().get()) {
+ return that.m_ctx.createResolvedPromise(stat);
+ }
+ kzo.client.dbg() << "processVerificationEvents: " << stat.data().get().dump() << std::endl;
+ EventList ves = stat.data().get().at("verificationEvents").at("toDevice").template get<EventList>();
+ return that.processVerificationEventsFromSync(ves);
+ });
+
+ return m_ctx.promiseInterface()
+ .all(std::vector<PromiseT>{uploadOneTimeKeysRes, queryKeysRes, processVerificationEventsRes});
+ }
+
lager::reader<ClientModel> Client::clientCursor() const
{
KAZV_VERIFY_THREAD_ID();
if (m_client.has_value()) {
return m_client.value();
} else {
assert(m_deps.has_value());
return lager::get<SdkModelCursorKey>(m_deps.value())->map(&SdkModel::c);
}
}
const lager::reader<SdkModel> &Client::sdkCursor() const
{
KAZV_VERIFY_THREAD_ID();
if (m_sdk.has_value()) {
return m_sdk.value();
} else {
assert(m_deps.has_value());
return *(lager::get<SdkModelCursorKey>(m_deps.value()));
}
}
auto Client::getProfile(std::string userId) const -> PromiseT
{
return m_ctx.dispatch(GetUserProfileAction{userId});
}
auto Client::setAvatarUrl(std::optional<std::string> avatarUrl) const -> PromiseT
{
return m_ctx.dispatch(SetAvatarUrlAction{avatarUrl});
}
auto Client::setDisplayName(std::optional<std::string> displayName) const -> PromiseT
{
return m_ctx.dispatch(SetDisplayNameAction{displayName});
}
auto Client::devicesOfUser(std::string userId) const -> lager::reader<immer::flex_vector<DeviceKeyInfo>>
{
return clientCursor()
[&ClientModel::deviceLists]
[&DeviceListTracker::deviceLists]
[userId]
[lager::lenses::or_default]
.xform(containerMap(immer::flex_vector<DeviceKeyInfo>{}, zug::map([](const auto &pair) {
const auto &[deviceId, info] = pair;
(void)deviceId;
return info;
})));
}
auto Client::setDeviceTrustLevel(std::string userId, std::string deviceId, DeviceTrustLevel trustLevel) const -> PromiseT
{
return m_ctx.dispatch(SetDeviceTrustLevelAction{userId, deviceId, trustLevel});
}
auto Client::trustLevelNeededToSendKeys() const -> lager::reader<DeviceTrustLevel>
{
return clientCursor()[&ClientModel::trustLevelNeededToSendKeys];
}
auto Client::setTrustLevelNeededToSendKeys(DeviceTrustLevel trustLevel) const -> PromiseT
{
return m_ctx.dispatch(SetTrustLevelNeededToSendKeysAction{trustLevel});
}
auto Client::directRoomMap() const -> lager::reader<immer::map<std::string, std::string>>
{
return clientCursor().map(&ClientModel::directRoomMap);
}
auto Client::roomIdsUnderTag(std::string tagId) const -> lager::reader<immer::map<std::string, double>>
{
return clientCursor().map([tagId](const auto &c) {
return c.roomIdsUnderTag(tagId);
});
}
auto Client::roomIdsByTagId() const -> lager::reader<immer::map<std::string, immer::map<std::string, double>>>
{
return clientCursor().map(&ClientModel::roomIdsByTagId);
}
auto Client::accountData() const -> lager::reader<immer::map<std::string, Event>>
{
return clientCursor()[&ClientModel::accountData];
}
auto Client::setAccountData(Event accountDataEvent) const -> PromiseT
{
return m_ctx.dispatch(SetAccountDataAction{accountDataEvent});
}
NotificationHandler Client::notificationHandler() const
{
return NotificationHandler(clientCursor());
}
auto Client::getVersions(std::string homeserver) const -> PromiseT
{
return m_ctx.dispatch(GetVersionsAction{homeserver});
}
auto Client::supportVersions() const -> lager::reader<immer::array<std::string>>
{
return clientCursor()[&ClientModel::versions];
}
auto Client::addDirectRoom(std::string userId, std::string roomId) const -> PromiseT
{
auto content = this->accountData().get()["m.direct"].content().get();
if (content.contains(userId)) {
auto& rooms = content[userId];
if (rooms.is_array()) {
if (std::find(rooms.begin(), rooms.end(), roomId) != rooms.end()) {
// The roomId is already in the m.direct, do nothing
return m_ctx.createResolvedPromise(true);
}
} else {
rooms = json::array({});
}
} else {
content.emplace(userId, json::array({}));
}
content[userId].push_back(roomId);
return Client::setAccountData(json{
{"type", "m.direct"},
{"content", std::move(content)}
});
}
auto Client::getRoomIdByAliasJob(std::string roomAlias) const -> BaseJob
{
return Kazv::getRoomIdByAliasJob(clientCursor().get(), roomAlias);
}
auto Client::purgeRoomEvents(immer::map<std::string, std::size_t> roomIdToMaxToKeepMap) const -> PromiseT
{
return m_ctx.dispatch(PurgeRoomTimelineAction{roomIdToMaxToKeepMap});
}
auto Client::loadEventsFromStorage(immer::map<std::string, EventList> timelineEvents, immer::map<std::string, EventList> relatedEvents) const -> PromiseT
{
return m_ctx.dispatch(LoadEventsFromStorageAction{
std::move(timelineEvents),
std::move(relatedEvents),
});
}
auto Client::importFromKeyBackupFile(std::string fileContent, std::string password) const -> PromiseT
{
return m_ctx.dispatch(ImportFromKeyBackupFileAction{
std::move(fileContent),
std::move(password),
});
}
}
diff --git a/src/client/client.hpp b/src/client/client.hpp
index eccb9f2..a1d7605 100644
--- a/src/client/client.hpp
+++ b/src/client/client.hpp
@@ -1,764 +1,788 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <lager/reader.hpp>
#include <immer/box.hpp>
#include <immer/map.hpp>
#include <immer/flex_vector.hpp>
#include <immer/flex_vector_transient.hpp>
#include "sdk-model.hpp"
#include "client/client-model.hpp"
#include "client/actions/content.hpp"
#include "sdk-model-cursor-tag.hpp"
#include "get-content-job-v1.hpp"
#include "room/room.hpp"
#include "notification-handler.hpp"
#include <verification-tracker.hpp>
namespace Kazv
{
/**
* Represent a Matrix client.
*
* If the Client is constructed from a cursor originated from
* a root whose event loop is on thread A, then we say that
* the Client belongs to thread A. If the Client is not constructed
* from a cursor, then we say that the Client belongs to the thread
* where the event loop of the context runs.
*
* All methods in this class that take a cursor only take a cursor
* on the same thread as the Client. All methods in this class that
* return a cursor will return a cursor on the same thread as the Client.
*
* All methods in this class must be run on the same thread as the
* the Client. If the Client is not constructed from a cursor,
* copy-constructing another Client from this is safe from any thread.
* If the Client is constructed from a cursor, copy-constructing another
* Client is safe only from the same thread as this Client.
*
* ## Device verification integration
*
* The `startSyncing()` function will automatically feed verification events
* received from sync into the VerificationTracker, and send outbound events
* according to the result returned from the VerificationTracker.
*
* The verification processing functions in this class (
* requestOutgoingToDeviceVerification(),
* readyForVerification(),
* cancelVerification(), confirmVerificationSasMatch(),
* denyVerificationSasMatch()) will also automatically send outbound
* events according to the result returned from the VerificationTracker.
* They will also ensure the device keys of the devices to be verified
* is available during the verification process.
*
* Additionally, this class will cause the VerificationTrackerModelChanged
* trigger to be emitted when appropriate.
* If you use a `lager::sensor` to observe the `VerificationTracker::model`,
* you should call `lager::commit()` on the `lager::sensor` **in the event loop
* thread** after you received VerificationTrackerModelChanged.
*
* You must always access VerificationTracker::model from the event loop thread,
* because the modifications to VerificationTracker always happen in the event
* loop thread. However, once you have a copy of the VerificationTrackerModel,
* you are free to copy it and pass it onto other threads.
*
* ## Error handling
*
* A lot of functions in Client and Room are asynchronous actions.
* These actions return the result via a Promise.
* If an API request has failed, the Promise p will satisfy the following:
* - `!p.success()`
* - `p.dataStr("error")` will contain the error message from the response.
* - `p.dataStr("errorCode")` will contain the matrix error code, if available,
* or the HTTP status code otherwise.
*
* What information is resolved if the API request has succeeded is defined
* by individual functions.
*/
class Client
{
public:
using ActionT = ClientAction;
using DepsT = lager::deps<JobInterface &, EventInterface &, SdkModelCursorKey, RandomInterface &, VerificationTracker &
#ifdef KAZV_USE_THREAD_SAFETY_HELPER
, EventLoopThreadIdKeeper &
#endif
>;
using ContextT = Context<ActionT>;
using ContextWithDepsT = Context<ActionT, DepsT>;
using PromiseT = SingleTypePromise<DefaultRetType>;
struct InEventLoopTag {};
/**
* Constructor.
*
* Construct the client. Without Deps support.
*
* @warning You should not use this directly. Use
* Sdk::client() instead.
*/
Client(lager::reader<SdkModel> sdk,
ContextT ctx, std::nullopt_t);
/**
* Constructor.
*
* Construct the client, with Deps support.
*
* This enables startSyncing() to work properly.
*
* @warning You should not use this directly. Use
* Sdk::client() instead.
*/
Client(lager::reader<SdkModel> sdk,
ContextWithDepsT ctx);
/**
* Constructor.
*
* Construct the client, with Deps support.
*
* The constructed Client belongs to the thread of event loop.
*
* @warning You should not use this directly. Use
* Sdk::client() instead.
*/
Client(InEventLoopTag,
ContextWithDepsT ctx);
/**
* Constructor.
*
* Construct the client, with Deps support.
*
* The constructed Client belongs to the thread of event loop.
*
* @warning You should not use this directly. Use
* Sdk::client() instead.
*/
Client(InEventLoopTag, ContextT ctx, DepsT deps);
/**
* Create a Client that is not constructed from a cursor.
*
* The returned Client belongs to the thread of event loop.
*
* This function is thread-safe if every thread calls it
* using different objects.
*
* @return A Client not constructed from a cursor.
*/
Client toEventLoop() const;
/* lager::reader<immer::map<std::string, Room>> */
inline auto rooms() const {
return clientCursor()
[&ClientModel::roomList]
[&RoomListModel::rooms];
}
/* lager::reader<RangeT<std::string>> */
inline auto roomIds() const {
return rooms().xform(
zug::map([](auto m) {
return intoImmer(
immer::flex_vector<std::string>{},
zug::map([](auto val) { return val.first; }),
m);
}));
}
auto roomIdsUnderTag(std::string tagId) const -> lager::reader<immer::map<std::string, double>>;
/**
* Get the room ids under all tags.
*
* @return A lager::reader containing the map from tag id to a map from room id to order.
* Rooms without a tag will be under the tag id of the empty string.
*/
auto roomIdsByTagId() const -> lager::reader<immer::map<std::string, immer::map<std::string, double>>>;
KAZV_WRAP_ATTR(ClientModel, clientCursor(), serverUrl)
KAZV_WRAP_ATTR(ClientModel, clientCursor(), loggedIn)
KAZV_WRAP_ATTR(ClientModel, clientCursor(), userId)
KAZV_WRAP_ATTR(ClientModel, clientCursor(), token)
KAZV_WRAP_ATTR(ClientModel, clientCursor(), deviceId)
KAZV_WRAP_ATTR(ClientModel, clientCursor(), toDevice)
/**
* Get the room with @c id .
*
* This is equivalent to `roomByCursor(lager::make_constant(id))`.
*
* @param id The room id.
* @return A Room representing the room with `id`.
*/
Room room(std::string id) const;
/**
* Get the room with `id`.
*
* The Room returned will change as the content in `id` changes.
*
* For example, you can have the Room that is always the first
* alphabetically in all rooms by:
*
* \code{.cpp}
* auto someProcessing =
* zug::map([=](auto ids) {
* std::sort(ids.begin(), ids.end(), [=](auto id1, auto id2) {
* using namespace Kazv::CursorOp;
* return (+client.room(id1).name()) < (+client.room(id2).name());
* });
* return ids;
* });
* auto room =
* client.roomByCursor(
* client.roomIds().xform(someProcessing)[0]);
* \endcode
*
* @param id A lager::reader<std::string> containing the room id.
* @return A Room representing the room with `id`.
*/
Room roomByCursor(lager::reader<std::string> id) const;
/**
* Login using the password.
*
* This will create a new session on the homeserver.
*
* If the returned Promise resolves successfully, this will
* call `startSyncing()`.
*
* @param homeserver The base url of the homeserver. E.g. `https://tusooa.xyz`.
* @param username The username. This can be the full user id or
* just the local part. E.g. `tusooa`, `@tusooa:tusooa.xyz`.
* @param password The password.
* @param deviceName Optionally, a custom device name. If empty, `libkazv`
* will be used.
* @return A Promise that resolves when logging in successfully, or
* when there is an error.
*/
PromiseT passwordLogin(std::string homeserver, std::string username,
std::string password, std::string deviceName) const;
/**
* Login using `token` and `deviceId`.
*
* This will not make a request. Library users should make sure
* the information is correct and the token and the device id are valid.
*
* If the returned Promise resolves successfully, this will
* call `startSyncing()`.
*
* @param homeserver The base url of the homeserver. E.g. `https://tusooa.xyz`.
* @param username The full user id. E.g. `@tusooa:tusooa.xyz`.
* @param token The access token.
* @param deviceId The device id that is paired with `token`.
* @return A Promise that resolves when the account information is filled in.
*/
PromiseT tokenLogin(std::string homeserver, std::string username,
std::string token, std::string deviceId) const;
/**
* Login using a login token.
*
* This will create a new session on the homeserver.
*
* If the returned Promise resolves successfully, this will
* call `startSyncing()`.
*
* @param homeserver The base url of the homeserver. E.g. `https://tusooa.xyz`.
* @param loginToken The login token.
* @param deviceName Optionally, a custom device name. If empty, `libkazv`
* will be used.
* @return A Promise that resolves when logging in successfully, or
* when there is an error.
*/
PromiseT mLoginTokenLogin(
std::string homeserver,
std::string loginToken,
std::optional<std::string> deviceName
) const;
/**
* Get the `shouldSync` field of current ClientModel.
*
* @return A lager::reader of bool of the `shouldSync` field of the ClientModel
*/
auto shouldSync() const -> lager::reader<bool>;
/**
* Stop syncing and then logout current session.
*
* Meanwhile, clear the current token and set loggedIn to false.
*
* @return A promise that resolves when the syncing is stopped.
*/
PromiseT logout() const;
/**
* Automatically discover the homeserver for `userId`.
*
* If the operation succeeds, `r.dataStr("homeserverUrl")` will contain
* the url suitable to pass to `tokenLogin()` and `passwordLogin()`.
*
* If there is no well-known file (i.e. server responds with 404),
* `r.dataStr("homeserverUrl")` will contain the domain part of the user
* id (`https://example.org` for `@foo:example.org`).
*
* @param userId The full user id. E.g. `@foo:example.org`.
* @return A Promise that resolves when the auto-discovery finishes.
*/
PromiseT autoDiscover(std::string userId) const;
/**
* Create a room.
*
* @param v The visibility of the room.
* @param name The name of the room.
* @param alias The alias of the room.
* @param invite User ids to invite to this room.
* @param isDirect Whether this room is a direct chat.
* @param allowFederate Whether to allow users from other homeservers
* to join this room.
* @param topic The topic of the room.
* @param powerLevelContentOverride The content of the m.room.power_levels
* state event to override the default.
* @param preset The preset to create the room with.
* @return A Promise that resolves when the room is created,
* or when there is an error.
*/
PromiseT createRoom(
RoomVisibility v,
std::optional<std::string> name = {},
std::optional<std::string> alias = {},
immer::array<std::string> invite = {},
std::optional<bool> isDirect = {},
bool allowFederate = true,
std::optional<std::string> topic = {},
JsonWrap powerLevelContentOverride = json::object(),
std::optional<CreateRoomPreset> preset = std::nullopt,
immer::array<Event> initialState = immer::array<Event>()
) const;
/**
* Join a room by its id.
*
* @param roomId The id of the room to join.
* @return A Promise that resolves when the room is joined,
* or when there is an error.
*/
PromiseT joinRoomById(std::string roomId) const;
/**
* Join a room by its id or alias.
*
* @param roomId The id *or alias* of the room to join.
* @param serverName A list of servers to use when joining the room.
* This corresponds to the `via` parameter in a matrix.to url.
* @return A Promise that resolves when the room is joined,
* or when there is an error.
*/
PromiseT joinRoom(std::string roomId, immer::array<std::string> serverName) const;
/**
* Upload content to the content repository.
*
* @param content The content to upload.
* @param uploadId
* @param filename The name of the file.
* @param contentType The content type of the file.
* @return A Promise that resolves when the upload is successful,
* or when there is an error. If it successfully resolves to `r`,
* `r.dataStr("mxcUri")` will be the MXC URI of the uploaded
* content.
*/
PromiseT uploadContent(immer::box<Bytes> content,
std::string uploadId,
std::optional<std::string> filename = std::nullopt,
std::optional<std::string> contentType = std::nullopt) const;
/**
* Upload content to the content repository.
*
* @param file The file to upload.
* @return A Promise that resolves when the upload is successful,
* or when there is an error. If it successfully resolves to `r`,
* `r.dataStr("mxcUri")` will be the MXC URI of the uploaded
* content.
*/
PromiseT uploadContent(FileDesc file) const;
/**
* Convert a MXC URI to an HTTP(s) URI.
*
* The converted URI will be using the homeserver of
* this Client.
*
* @param mxcUri The MXC URI to convert.
* @return The HTTP(s) URI that has the content indicated
* by `mxcUri`.
*/
inline std::string mxcUriToHttp(std::string mxcUri) const {
using namespace CursorOp;
auto [serverName, mediaId] = mxcUriToMediaDesc(mxcUri);
return (+clientCursor())
.template job<GetContentJob>()
.make(serverName, mediaId).url();
}
/**
* Convert a MXC URI to an HTTP(s) URI that needs Authorization.
*
* The converted URI will be using the homeserver of
* this Client.
*
* @param mxcUri The MXC URI to convert.
* @return The HTTP(s) URI that has the content indicated
* by `mxcUri`.
*/
std::string mxcUriToHttpV1(std::string mxcUri) const;
/**
* Download content from the content repository
*
* After the returned Promise resolves successfully,
* if @c downloadTo is provided, the content will be available
* in that file; if it is not provided, `r.dataStr("content")`
* will contain the content of the downloaded file.
*
* @param mxcUri The MXC URI of the content.
* @param downloadTo The file to write the content to. Must not be
* an in-memory file.
* @return A Promise that is resolved after the content
* is downloaded, or when there is an error.
*/
PromiseT downloadContent(std::string mxcUri,
std::optional<FileDesc> downloadTo = std::nullopt) const;
/**
* Download a thumbnail from the content repository
*
* After the returned Promise resolves successfully,
* if @c downloadTo is provided, the content will be available
* in that file; if it is not provided, `r.dataStr("content")`
* will contain the content of the downloaded file.
*
* @param mxcUri The MXC URI of the content.
* @param width,height The dimension wanted for the thumbnail
* @param method The method to generate the thumbnail. Either `Crop`
* or `Scale`.
* @param downloadTo The file to write the content to. Must not be
* an in-memory file.
* @return A Promise that is resolved after the thumbnail
* is downloaded, or when there is an error.
*/
PromiseT downloadThumbnail(std::string mxcUri,
int width,
int height,
std::optional<ThumbnailResizingMethod> method = std::nullopt,
std::optional<FileDesc> downloadTo = std::nullopt) const;
/**
* Fetch the profile of a user.
*
* @param userId The id of the user to fetch.
* @return A Promise that resolves when the fetch is completed.
* If successful, `r.dataStr("avatarUrl")` will contain the
* avatar url of that user, and `r.dataStr("displayName")` will
* contain the display name of that user.
*/
PromiseT getProfile(std::string userId) const;
/**
* Change the avatar url of the current user.
*
* @param avatarUrl The url of the new avatar. Should be an MXC URI.
* If it is std::nullopt, remove the user avatar.
* @return A Promise that resolves when the request is completed.
*/
PromiseT setAvatarUrl(std::optional<std::string> avatarUrl) const;
/**
* Change the display name of the current user.
*
* @param displayName The new display name. If it is std::nullopt,
* remove the user avatar.
* @return A Promise that resolves when the request is completed.
*/
PromiseT setDisplayName(std::optional<std::string> displayName) const;
// lager::reader<bool>
inline auto syncing() const {
return clientCursor()[&ClientModel::syncing];
}
/**
* Start syncing if the Client is not syncing.
*
* Syncing will continue indefinitely, if the preparation of
* the sync (posting filters and uploading identity keys,
* if needed) is successful, or until stopSyncing() is called.
*
+ * This function internally calls ensureReadyForSyncing() and
+ * doOneSyncCycle().
+ *
* @return A Promise that resolves when the Client is syncing
* (more exactly, when syncing() contains true), or when there
* is an error in the preparation of the sync.
*/
PromiseT startSyncing() const;
/**
* Stop the indefinite syncing.
*
* After this, no more syncing actions will be dispatched.
*
* @return A Promise that resolves when syncing is stopped.
*/
PromiseT stopSyncing() const;
+ /**
+ * Ensure the Client can start syncing using doOneSyncCycle().
+ *
+ * @return A Promise that resolves when the application can
+ * call doOneSyncCycle() or when there is an error. When there is
+ * an error, the Promise contains the standard error format.
+ */
+ PromiseT ensureReadyForSyncing() const;
+
+ /**
+ * Call the sync endpoint once and process the response.
+ *
+ * @return A Promise that resolves when the sync cycle is finished,
+ * or when there is an error. If the Promise is successful, the
+ * application can call doOneSyncCycle() again. If the Promise is
+ * not successful, the application should wait some time before calling
+ * doOneSyncCycle() again, and exponentially increase the wait time
+ * between retries (or retry when the user manually requests it).
+ */
+ PromiseT doOneSyncCycle() const;
+
/**
* Get the info of all devices of user `userId` that supports encryption.
*
* @param userId The id of the user to get the devices of.
*
* @return a lager::reader of a RangeT of DeviceKeyInfo representing the devices of that user.
*/
auto devicesOfUser(std::string userId) const -> lager::reader<immer::flex_vector<DeviceKeyInfo>>;
/**
* Set the trust level of a device.
*
* @param userId The id of the user to whom the device belongs.
* @param deviceId The id of the device.
*
* @return a Promise that resolves when the setting is changed.
*/
PromiseT setDeviceTrustLevel(std::string userId, std::string deviceId, DeviceTrustLevel trustLevel) const;
/**
* Get the trust level needed to send keys to a device.
*
* @return a lager::reader of the trust level threshold.
*/
auto trustLevelNeededToSendKeys() const -> lager::reader<DeviceTrustLevel>;
/**
* Set the trust level needed to send keys to a device.
*
* @param trustLevel The trust level threshold.
*
* @return a Promise that resolves when the setting is changed.
*/
PromiseT setTrustLevelNeededToSendKeys(DeviceTrustLevel trustLevel) const;
/**
* Get the map from direct messaging room ids to user ids.
*
* @return a lager::reader of such mapping.
*/
auto directRoomMap() const -> lager::reader<immer::map<std::string, std::string>>;
/**
* Get the account data that is not associated with any room.
*
* @return A lager::reader of a map from the type to the account data event.
*/
auto accountData() const -> lager::reader<immer::map<std::string, Event>>;
/**
* Set the account data that is not associated with any room.
*
* @return A Promise that resolves when the account data
* has been set, or when there is an error.
*/
PromiseT setAccountData(Event accountDataEvent) const;
/**
* Get a notification handler that works on this Client.
*
* @return A notification handler that works on this Client.
*/
NotificationHandler notificationHandler() const;
/**
* Serialize the model to a Boost.Serialization archive.
*
* @param ar A Boost.Serialization output archive.
*
* This function can be used to save the model. For loading,
* you should use the makeSdk function. For example:
*
* ```c++
* client.serializeTo(outputAr);
*
* SdkModel m;
* inputAr >> m;
* auto newSdk = makeSdk(m, ...);
* ```
*/
template<class Archive>
void serializeTo(Archive &ar) const {
ar << sdkCursor().get();
}
/**
* Get all supported versions.
*
* @param homeserver The base url of the homeserver. E.g. `https://tusooa.xyz`.
* @return A Promise that resolves when the versions has been set,
* or when there is an error.
*/
PromiseT getVersions(std::string homeserver) const;
/**
* Get all supported versions.
*
* @return A lager::reader of a array contains all supported versions.
*
* See https://spec.matrix.org/v1.14/#specification-versions
*/
auto supportVersions() const -> lager::reader<immer::array<std::string>>;
/**
* Mark a room as a direct chat by send the m.direct account data.
*
* @param userId The user id that direct to.
* @param roomId The direct chat room id.
* @return A Promise that resolves when the account data
* has been set, or when there is an error.
*/
PromiseT addDirectRoom(std::string userId, std::string roomId) const;
/**
* Get a GetRoomIdByAliasJob.
* Use Kazv::parseGetRoomIdByAliasResponse to parse its response.
*
* @param roomAlias The room alias.
* @return A GetRoomIdByAliasJob.
*/
BaseJob getRoomIdByAliasJob(std::string roomAlias) const;
/**
* Purge events in room, keeping the latest `numToKeep` events.
*
* The events are removed from the lager store. The timeline will
* contain at most `numToKeep` events, but the `messages` property
* may contain more in order to maintain the room invariants.
* @sa RoomModel
*
* @param roomIdToMaxToKeepMap A map from "room id" to "max number of timeline events to keep."
*/
PromiseT purgeRoomEvents(immer::map<std::string, std::size_t> roomIdToMaxToKeepMap) const;
/**
* Load events from storage into the model.
*
* @param timelineEvents Map from room id to a list of message events that
* should be put into the timeline.
* @param relatedEvents Map from room id to a list of message events that should
* not be put into the timeline (for example, because the storage does not
* know or care where it should go in the timeline).
* @return A Promise that resolves when the events are loaded into the store.
*/
PromiseT loadEventsFromStorage(immer::map<std::string, EventList> timelineEvents, immer::map<std::string, EventList> relatedEvents) const;
/**
* Import keys from a key backup file.
*
* @param fileContent The raw content of the file.
* @param password The password to decrypt the file.
* @return A Promise that resolves when the keys are imported or when there is an error. Assume the Promise resolves to `r`, if it is successful, `r.dataJson("imported")` contains the number of keys imported. Otherwise, `r` contains the standard error structure.
*/
PromiseT importFromKeyBackupFile(std::string fileContent, std::string password) const;
/**
* Process verification events from a sync result.
*
* This will be called automatically after a sync.
*
* @param toDeviceEvents The list of to-device verification events received from sync.
* @return A Promise that resolves when the processing is done. After it resolves,
* actions will be dispatched to send any pending to-device events in the
* VerificationTracker's model.
*/
PromiseT processVerificationEventsFromSync(EventList toDeviceEvents) const;
/**
* Request an outgoing verification using to-device message.
*
* This will automatically fetch the device keys if we do not yet have
* them.
*
* @return A Promise that resolves when the outgoing request is sent,
* or when there is an error.
*/
PromiseT requestOutgoingToDeviceVerification(std::string userId, std::string deviceId) const;
/**
* Signal that the user is ready for an incoming verification request.
*
* This will automatically fetch the device keys if we do not yet have
* them.
*
* @return A Promise that resolves when the ready event is sent,
* or when there is an error.
*/
PromiseT readyForVerification(std::string userId, std::string deviceId) const;
/**
* Cancel a verification process.
*
* @return A Promise that resolves when the cancel event is sent,
* or when there is an error.
*/
PromiseT cancelVerification(std::string userId, std::string deviceId) const;
/**
* Confirm an sas match for a verification process.
*
* @return A Promise that resolves when the next event is sent,
* or when there is an error.
*/
PromiseT confirmVerificationSasMatch(std::string userId, std::string deviceId) const;
/**
* Deny an sas match for a verification process.
*
* @return A Promise that resolves when the next event is sent,
* or when there is an error.
*/
PromiseT denyVerificationSasMatch(std::string userId, std::string deviceId) const;
/**
* Ensure the VerificationTracker is initialized.
*/
PromiseT ensureInitVerificationTracker() const;
private:
void syncForever(std::optional<int> retryTime = std::nullopt) const;
const lager::reader<SdkModel> &sdkCursor() const;
lager::reader<ClientModel> clientCursor() const;
std::optional<lager::reader<SdkModel>> m_sdk;
std::optional<lager::reader<ClientModel>> m_client;
ContextT m_ctx;
std::optional<DepsT> m_deps;
KAZV_DECLARE_THREAD_ID();
KAZV_DECLARE_EVENT_LOOP_THREAD_ID_KEEPER(m_deps.has_value() ? &lager::get<EventLoopThreadIdKeeper &>(m_deps.value()) : 0);
};
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 8, 6:12 PM (3 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784179
Default Alt Text
(63 KB)

Event Timeline