mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-23 19:45:05 +01:00
WIP
This commit is contained in:
parent
abb802b881
commit
faf4ea6434
@ -29,6 +29,7 @@ export class SessionLoadViewModel extends ViewModel {
|
||||
this._loading = false;
|
||||
this._error = null;
|
||||
this.backUrl = this.urlCreator.urlForSegment("session", true);
|
||||
this._dehydratedDevice = undefined;
|
||||
}
|
||||
|
||||
async start() {
|
||||
@ -110,6 +111,10 @@ export class SessionLoadViewModel extends ViewModel {
|
||||
// Statuses related to login are handled by respective login view models
|
||||
if (sc) {
|
||||
switch (sc.loadStatus.get()) {
|
||||
case LoadStatus.QueryAccount:
|
||||
return `Querying account encryption setup…`;
|
||||
case LoadStatus.SetupAccount:
|
||||
return `Please enter your password to restore your encryption setup`;
|
||||
case LoadStatus.SessionSetup:
|
||||
return `Setting up your encryption keys…`;
|
||||
case LoadStatus.Loading:
|
||||
@ -136,4 +141,27 @@ export class SessionLoadViewModel extends ViewModel {
|
||||
const logExport = await this.logger.export();
|
||||
this.platform.saveFileAs(logExport.asBlob(), `hydrogen-logs-${this.platform.clock.now()}.json`);
|
||||
}
|
||||
|
||||
get canSetupAccount() {
|
||||
return this._sessionContainer.loadStatus === LoadStatus.SetupAccount;
|
||||
}
|
||||
|
||||
get canDehydrateDevice() {
|
||||
return this.canSetupAccount && !!this._sessionContainer.accountSetup.encryptedDehydratedDevice;
|
||||
}
|
||||
|
||||
tryDecryptDehydratedDevice(password) {
|
||||
const {encryptedDehydratedDevice} = this._sessionContainer.accountSetup;
|
||||
if (encryptedDehydratedDevice) {
|
||||
this._dehydratedDevice = encryptedDehydratedDevice.decrypt(password);
|
||||
return !!this._dehydratedDevice;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
finishAccountSetup() {
|
||||
const dehydratedDevice = this._dehydratedDevice;
|
||||
this._dehydratedDevice = undefined;
|
||||
this._sessionContainer.accountSetup.finish(dehydratedDevice);
|
||||
}
|
||||
}
|
||||
|
@ -248,23 +248,77 @@ export class Session {
|
||||
async createIdentity(log) {
|
||||
if (this._olm) {
|
||||
if (!this._e2eeAccount) {
|
||||
this._e2eeAccount = await E2EEAccount.create({
|
||||
hsApi: this._hsApi,
|
||||
olm: this._olm,
|
||||
pickleKey: PICKLE_KEY,
|
||||
userId: this._sessionInfo.userId,
|
||||
deviceId: this._sessionInfo.deviceId,
|
||||
olmWorker: this._olmWorker,
|
||||
storage: this._storage,
|
||||
});
|
||||
this._e2eeAccount = this._createNewAccount(this._sessionInfo.deviceId, this._storage);
|
||||
log.set("keys", this._e2eeAccount.identityKeys);
|
||||
this._setupEncryption();
|
||||
}
|
||||
await this._e2eeAccount.generateOTKsIfNeeded(this._storage, log);
|
||||
await log.wrap("uploadKeys", log => this._e2eeAccount.uploadKeys(this._storage, log));
|
||||
await log.wrap("uploadKeys", log => this._e2eeAccount.uploadKeys(this._storage, undefined, log));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
dehydrateIdentity(dehydratedDevice, log = null) {
|
||||
this._platform.logger.wrapOrRun(log, "dehydrateIdentity", async log => {
|
||||
log.set("deviceId", dehydratedDevice.deviceId);
|
||||
if (!this._olm) {
|
||||
log.set("no_olm", true);
|
||||
return false;
|
||||
}
|
||||
if (dehydratedDevice.deviceId !== this.deviceId) {
|
||||
log.set("wrong_device", true);
|
||||
return false;
|
||||
}
|
||||
if (this._e2eeAccount) {
|
||||
log.set("account_already_setup", true);
|
||||
return false;
|
||||
}
|
||||
if (!await dehydratedDevice.claim(this._hsApi, log)) {
|
||||
log.set("already_claimed", true);
|
||||
return false;
|
||||
}
|
||||
this._e2eeAccount = await E2EEAccount.adoptDehydratedDevice({
|
||||
dehydratedDevice,
|
||||
hsApi: this._hsApi,
|
||||
olm: this._olm,
|
||||
pickleKey: PICKLE_KEY,
|
||||
userId: this._sessionInfo.userId,
|
||||
olmWorker: this._olmWorker,
|
||||
deviceId,
|
||||
storage,
|
||||
});
|
||||
log.set("keys", this._e2eeAccount.identityKeys);
|
||||
this._setupEncryption();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
_createNewAccount(deviceId, storage = undefined) {
|
||||
// storage is optional and if omitted the account won't be persisted (useful for dehydrating devices)
|
||||
return E2EEAccount.create({
|
||||
hsApi: this._hsApi,
|
||||
olm: this._olm,
|
||||
pickleKey: PICKLE_KEY,
|
||||
userId: this._sessionInfo.userId,
|
||||
olmWorker: this._olmWorker,
|
||||
deviceId,
|
||||
storage,
|
||||
});
|
||||
}
|
||||
|
||||
setupDehydratedDevice(key, log = null) {
|
||||
return this._platform.logger.wrapOrRun(log, "setupDehydratedDevice", async log => {
|
||||
const dehydrationAccount = await this._createNewAccount("temp-device-id");
|
||||
try {
|
||||
const deviceId = await uploadAccountAsDehydratedDevice(
|
||||
dehydrationAccount, this._hsApi, key, "Dehydrated device", log);
|
||||
return deviceId;
|
||||
} finally {
|
||||
dehydrationAccount.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async load(log) {
|
||||
const txn = await this._storage.readTxn([
|
||||
@ -323,6 +377,7 @@ export class Session {
|
||||
this._olmWorker?.dispose();
|
||||
this._sessionBackup?.dispose();
|
||||
this._megolmDecryption.dispose();
|
||||
this._e2eeAccount?.dispose();
|
||||
for (const room of this._rooms.values()) {
|
||||
room.dispose();
|
||||
}
|
||||
@ -517,7 +572,7 @@ export class Session {
|
||||
if (!isCatchupSync) {
|
||||
const needsToUploadOTKs = await this._e2eeAccount.generateOTKsIfNeeded(this._storage, log);
|
||||
if (needsToUploadOTKs) {
|
||||
await log.wrap("uploadKeys", log => this._e2eeAccount.uploadKeys(this._storage, log));
|
||||
await log.wrap("uploadKeys", log => this._e2eeAccount.uploadKeys(this._storage, undefined, log));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ export const LoadStatus = createEnum(
|
||||
"NotLoading",
|
||||
"Login",
|
||||
"LoginFailed",
|
||||
"QueryAccount", // check for dehydrated device after login
|
||||
"SetupAccount", // asked to restore from dehydrated device if present, call sc.accountSetup.finish() to progress to the next stage
|
||||
"Loading",
|
||||
"SessionSetup", // upload e2ee keys, ...
|
||||
"Migrating", //not used atm, but would fit here
|
||||
@ -85,7 +87,7 @@ export class SessionContainer {
|
||||
if (!sessionInfo) {
|
||||
throw new Error("Invalid session id: " + sessionId);
|
||||
}
|
||||
await this._loadSessionInfo(sessionInfo, false, log);
|
||||
await this._loadSessionInfo(sessionInfo, null, log);
|
||||
log.set("status", this._status.get());
|
||||
} catch (err) {
|
||||
log.catch(err);
|
||||
@ -154,7 +156,6 @@ export class SessionContainer {
|
||||
lastUsed: clock.now()
|
||||
};
|
||||
log.set("id", sessionId);
|
||||
await this._platform.sessionInfoStorage.add(sessionInfo);
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
if (err.name === "HomeServerError") {
|
||||
@ -173,11 +174,16 @@ export class SessionContainer {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const dehydratedDevice = await this._inspectAccountAfterLogin(sessionInfo);
|
||||
if (dehydratedDevice) {
|
||||
sessionInfo.deviceId = dehydratedDevice.deviceId;
|
||||
}
|
||||
await this._platform.sessionInfoStorage.add(sessionInfo);
|
||||
// loading the session can only lead to
|
||||
// LoadStatus.Error in case of an error,
|
||||
// so separate try/catch
|
||||
try {
|
||||
await this._loadSessionInfo(sessionInfo, true, log);
|
||||
await this._loadSessionInfo(sessionInfo, dehydratedDevice, log);
|
||||
log.set("status", this._status.get());
|
||||
} catch (err) {
|
||||
log.catch(err);
|
||||
@ -187,7 +193,7 @@ export class SessionContainer {
|
||||
});
|
||||
}
|
||||
|
||||
async _loadSessionInfo(sessionInfo, isNewLogin, log) {
|
||||
async _loadSessionInfo(sessionInfo, dehydratedDevice, log) {
|
||||
log.set("appVersion", this._platform.version);
|
||||
const clock = this._platform.clock;
|
||||
this._sessionStartedByReconnector = false;
|
||||
@ -233,7 +239,9 @@ export class SessionContainer {
|
||||
platform: this._platform,
|
||||
});
|
||||
await this._session.load(log);
|
||||
if (!this._session.hasIdentity) {
|
||||
if (dehydratedDevice) {
|
||||
await log.wrap("dehydrateIdentity", log => await this._session.dehydrateIdentity(dehydratedDevice, log));
|
||||
} else if (!this._session.hasIdentity) {
|
||||
this._status.set(LoadStatus.SessionSetup);
|
||||
await log.wrap("createIdentity", log => this._session.createIdentity(log));
|
||||
}
|
||||
@ -300,6 +308,30 @@ export class SessionContainer {
|
||||
}
|
||||
}
|
||||
|
||||
async _inspectAccountAfterLogin(sessionInfo) {
|
||||
this._status.set(LoadStatus.QueryAccount);
|
||||
const hsApi = new HomeServerApi({
|
||||
homeserver: sessionInfo.homeServer,
|
||||
accessToken: sessionInfo.accessToken,
|
||||
request: this._platform.request,
|
||||
});
|
||||
const olm = await this._olmPromise;
|
||||
const encryptedDehydratedDevice = await getDehydratedDevice(hsApi, olm);
|
||||
if (encryptedDehydratedDevice) {
|
||||
let resolveStageFinish;
|
||||
const promiseStageFinish = new Promise(r => resolveStageFinish = r);
|
||||
this._accountSetup = new AccountSetup(encryptedDehydratedDevice, resolveStageFinish);
|
||||
this._status.set(LoadStatus.SetupAccount);
|
||||
await promiseStageFinish;
|
||||
const dehydratedDevice = this._accountSetup?._dehydratedDevice;
|
||||
this._accountSetup = null;
|
||||
return dehydratedDevice;
|
||||
}
|
||||
}
|
||||
|
||||
get accountSetup() {
|
||||
return this._accountSetup;
|
||||
}
|
||||
|
||||
get loadStatus() {
|
||||
return this._status;
|
||||
@ -378,3 +410,20 @@ export class SessionContainer {
|
||||
this._loginFailure = null;
|
||||
}
|
||||
}
|
||||
|
||||
class AccountSetup {
|
||||
constructor(encryptedDehydratedDevice, finishStage) {
|
||||
this._encryptedDehydratedDevice = encryptedDehydratedDevice;
|
||||
this._dehydratedDevice = undefined;
|
||||
this._finishStage = finishStage;
|
||||
}
|
||||
|
||||
get encryptedDehydratedDevice() {
|
||||
return this._encryptedDehydratedDevice;
|
||||
}
|
||||
|
||||
finish(dehydratedDevice) {
|
||||
this._dehydratedDevice = dehydratedDevice;
|
||||
this._finishStage();
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,24 @@ const ACCOUNT_SESSION_KEY = SESSION_E2EE_KEY_PREFIX + "olmAccount";
|
||||
const DEVICE_KEY_FLAG_SESSION_KEY = SESSION_E2EE_KEY_PREFIX + "areDeviceKeysUploaded";
|
||||
const SERVER_OTK_COUNT_SESSION_KEY = SESSION_E2EE_KEY_PREFIX + "serverOTKCount";
|
||||
|
||||
async function initiallyStoreAccount(account, pickleKey, areDeviceKeysUploaded, serverOTKCount, storage) {
|
||||
const pickledAccount = account.pickle(pickleKey);
|
||||
const txn = await storage.readWriteTxn([
|
||||
storage.storeNames.session
|
||||
]);
|
||||
try {
|
||||
// add will throw if the key already exists
|
||||
// we would not want to overwrite olmAccount here
|
||||
txn.session.add(ACCOUNT_SESSION_KEY, pickledAccount);
|
||||
txn.session.add(DEVICE_KEY_FLAG_SESSION_KEY, areDeviceKeysUploaded);
|
||||
txn.session.add(SERVER_OTK_COUNT_SESSION_KEY, serverOTKCount);
|
||||
} catch (err) {
|
||||
txn.abort();
|
||||
throw err;
|
||||
}
|
||||
await txn.complete();
|
||||
}
|
||||
|
||||
export class Account {
|
||||
static async load({olm, pickleKey, hsApi, userId, deviceId, olmWorker, txn}) {
|
||||
const pickledAccount = await txn.session.get(ACCOUNT_SESSION_KEY);
|
||||
@ -34,6 +52,21 @@ export class Account {
|
||||
deviceId, areDeviceKeysUploaded, serverOTKCount, olm, olmWorker});
|
||||
}
|
||||
}
|
||||
|
||||
static async adoptDehydratedDevice({olm, dehydratedDevice, pickleKey, hsApi, userId, olmWorker, storage}) {
|
||||
const account = dehydratedDevice.adoptUnpickledOlmAccount();
|
||||
const areDeviceKeysUploaded = true;
|
||||
const oneTimeKeys = JSON.parse(this._account.one_time_keys());
|
||||
// only one algorithm supported by olm atm, so hardcode its name
|
||||
const oneTimeKeysEntries = Object.entries(oneTimeKeys.curve25519);
|
||||
const serverOTKCount = oneTimeKeysEntries.length;
|
||||
await initiallyStoreAccount(account, pickleKey, areDeviceKeysUploaded, serverOTKCount, storage);
|
||||
return new Account({
|
||||
pickleKey, hsApi, account, userId,
|
||||
deviceId: dehydratedDevice.deviceId,
|
||||
areDeviceKeysUploaded, serverOTKCount, olm, olmWorker
|
||||
});
|
||||
}
|
||||
|
||||
static async create({olm, pickleKey, hsApi, userId, deviceId, olmWorker, storage}) {
|
||||
const account = new olm.Account();
|
||||
@ -43,22 +76,9 @@ export class Account {
|
||||
account.create();
|
||||
account.generate_one_time_keys(account.max_number_of_one_time_keys());
|
||||
}
|
||||
const pickledAccount = account.pickle(pickleKey);
|
||||
const areDeviceKeysUploaded = false;
|
||||
const txn = await storage.readWriteTxn([
|
||||
storage.storeNames.session
|
||||
]);
|
||||
try {
|
||||
// add will throw if the key already exists
|
||||
// we would not want to overwrite olmAccount here
|
||||
txn.session.add(ACCOUNT_SESSION_KEY, pickledAccount);
|
||||
txn.session.add(DEVICE_KEY_FLAG_SESSION_KEY, areDeviceKeysUploaded);
|
||||
txn.session.add(SERVER_OTK_COUNT_SESSION_KEY, 0);
|
||||
} catch (err) {
|
||||
txn.abort();
|
||||
throw err;
|
||||
if (storage) {
|
||||
await initiallyStoreAccount(account, pickleKey, false, 0, storage);
|
||||
}
|
||||
await txn.complete();
|
||||
return new Account({pickleKey, hsApi, account, userId,
|
||||
deviceId, areDeviceKeysUploaded, serverOTKCount: 0, olm, olmWorker});
|
||||
}
|
||||
@ -80,7 +100,7 @@ export class Account {
|
||||
return this._identityKeys;
|
||||
}
|
||||
|
||||
async uploadKeys(storage, log) {
|
||||
async uploadKeys(storage, dehydratedDeviceId, log) {
|
||||
const oneTimeKeys = JSON.parse(this._account.one_time_keys());
|
||||
// only one algorithm supported by olm atm, so hardcode its name
|
||||
const oneTimeKeysEntries = Object.entries(oneTimeKeys.curve25519);
|
||||
@ -95,7 +115,7 @@ export class Account {
|
||||
log.set("otks", true);
|
||||
payload.one_time_keys = this._oneTimeKeysPayload(oneTimeKeysEntries);
|
||||
}
|
||||
const response = await this._hsApi.uploadKeys(payload, {log}).response();
|
||||
const response = await this._hsApi.uploadKeys(dehydratedDeviceId, payload, {log}).response();
|
||||
this._serverOTKCount = response?.one_time_key_counts?.signed_curve25519;
|
||||
log.set("serverOTKCount", this._serverOTKCount);
|
||||
// TODO: should we not modify this in the txn like we do elsewhere?
|
||||
@ -105,12 +125,12 @@ export class Account {
|
||||
await this._updateSessionStorage(storage, sessionStore => {
|
||||
if (oneTimeKeysEntries.length) {
|
||||
this._account.mark_keys_as_published();
|
||||
sessionStore.set(ACCOUNT_SESSION_KEY, this._account.pickle(this._pickleKey));
|
||||
sessionStore.set(SERVER_OTK_COUNT_SESSION_KEY, this._serverOTKCount);
|
||||
sessionStore?.set(ACCOUNT_SESSION_KEY, this._account.pickle(this._pickleKey));
|
||||
sessionStore?.set(SERVER_OTK_COUNT_SESSION_KEY, this._serverOTKCount);
|
||||
}
|
||||
if (!this._areDeviceKeysUploaded) {
|
||||
this._areDeviceKeysUploaded = true;
|
||||
sessionStore.set(DEVICE_KEY_FLAG_SESSION_KEY, this._areDeviceKeysUploaded);
|
||||
sessionStore?.set(DEVICE_KEY_FLAG_SESSION_KEY, this._areDeviceKeysUploaded);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -246,16 +266,20 @@ export class Account {
|
||||
}
|
||||
|
||||
async _updateSessionStorage(storage, callback) {
|
||||
const txn = await storage.readWriteTxn([
|
||||
storage.storeNames.session
|
||||
]);
|
||||
try {
|
||||
await callback(txn.session);
|
||||
} catch (err) {
|
||||
txn.abort();
|
||||
throw err;
|
||||
if (storage) {
|
||||
const txn = await storage.readWriteTxn([
|
||||
storage.storeNames.session
|
||||
]);
|
||||
try {
|
||||
await callback(txn.session);
|
||||
} catch (err) {
|
||||
txn.abort();
|
||||
throw err;
|
||||
}
|
||||
await txn.complete();
|
||||
} else {
|
||||
await callback(undefined);
|
||||
}
|
||||
await txn.complete();
|
||||
}
|
||||
|
||||
signObject(obj) {
|
||||
@ -273,4 +297,12 @@ export class Account {
|
||||
obj.unsigned = unsigned;
|
||||
}
|
||||
}
|
||||
|
||||
pickleWithKey(key) {
|
||||
return this._account.pickle(key);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._account.free();
|
||||
}
|
||||
}
|
||||
|
88
src/matrix/e2ee/Dehydration.js
Normal file
88
src/matrix/e2ee/Dehydration.js
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export const DEHYDRATION_LIBOLM_PICKLE_ALGORITHM = "org.matrix.msc2697.v1.olm.libolm_pickle";
|
||||
|
||||
async function getDehydratedDevice(hsApi, olm) {
|
||||
const response = await hsApi.getDehydratedDevice().response();
|
||||
if (response.device_data.algorithm === DEHYDRATION_LIBOLM_PICKLE_ALGORITHM) {
|
||||
return new DehydratedDevice(response, olm);
|
||||
}
|
||||
}
|
||||
|
||||
async function hasRemainingDevice(ownUserId, ownDeviceId, storage) {
|
||||
|
||||
}
|
||||
|
||||
async function uploadAccountAsDehydratedDevice(account, hsApi, key, deviceDisplayName, log) {
|
||||
const response = await hsApi.createDehydratedDevice({
|
||||
device_data: {
|
||||
algorithm: DEHYDRATION_LIBOLM_PICKLE_ALGORITHM,
|
||||
account: account.pickleWithKey(new Uint8Array(key)),
|
||||
passphrase: {}
|
||||
},
|
||||
initial_device_display_name: deviceDisplayName
|
||||
}).response();
|
||||
const deviceId = response.device_id;
|
||||
await account.uploadKeys(undefined, deviceId, log);
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
class EncryptedDehydratedDevice {
|
||||
constructor(dehydratedDevice, olm) {
|
||||
this._dehydratedDevice = dehydratedDevice;
|
||||
this._olm = olm;
|
||||
}
|
||||
|
||||
decrypt(key) {
|
||||
const account = new this._olm.Account();
|
||||
try {
|
||||
const pickledAccount = this._dehydratedDevice.device_data.account;
|
||||
account.unpickle(key, pickledAccount);
|
||||
return new DehydratedDevice(this._dehydratedDevice, account);
|
||||
} catch (err) {
|
||||
account.free();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DehydratedDevice {
|
||||
constructor(dehydratedDevice, account) {
|
||||
this._dehydratedDevice = dehydratedDevice;
|
||||
this._account = account;
|
||||
}
|
||||
|
||||
claim(hsApi, log) {
|
||||
try {
|
||||
const response = await hsApi.claimDehydratedDevice(this.deviceId, {log}).response();
|
||||
return response.success;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// make it clear that ownership is transfered upon calling this
|
||||
adoptUnpickledOlmAccount() {
|
||||
const account = this._account;
|
||||
this._account = null;
|
||||
return account;
|
||||
}
|
||||
|
||||
get deviceId() {
|
||||
this._dehydratedDevice.device_id;
|
||||
}
|
||||
}
|
@ -18,6 +18,9 @@ limitations under the License.
|
||||
import {encodeQueryParams, encodeBody} from "./common.js";
|
||||
import {HomeServerRequest} from "./HomeServerRequest.js";
|
||||
|
||||
const CS_R0_PREFIX = "/_matrix/client/r0";
|
||||
const DEHYDRATION_PREFIX = "/unstable/org.matrix.msc2697.v2";
|
||||
|
||||
export class HomeServerApi {
|
||||
constructor({homeserver, accessToken, request, reconnector}) {
|
||||
// store these both in a closure somehow so it's harder to get at in case of XSS?
|
||||
@ -28,8 +31,8 @@ export class HomeServerApi {
|
||||
this._reconnector = reconnector;
|
||||
}
|
||||
|
||||
_url(csPath) {
|
||||
return `${this._homeserver}/_matrix/client/r0${csPath}`;
|
||||
_url(csPath, prefix) {
|
||||
return this._homeserver + prefix + csPath;
|
||||
}
|
||||
|
||||
_baseRequest(method, url, queryParams, body, options, accessToken) {
|
||||
@ -92,15 +95,15 @@ export class HomeServerApi {
|
||||
}
|
||||
|
||||
_post(csPath, queryParams, body, options) {
|
||||
return this._authedRequest("POST", this._url(csPath), queryParams, body, options);
|
||||
return this._authedRequest("POST", this._url(csPath, options?.prefix || CS_R0_PREFIX), queryParams, body, options);
|
||||
}
|
||||
|
||||
_put(csPath, queryParams, body, options) {
|
||||
return this._authedRequest("PUT", this._url(csPath), queryParams, body, options);
|
||||
return this._authedRequest("PUT", this._url(csPath, options?.prefix || CS_R0_PREFIX), queryParams, body, options);
|
||||
}
|
||||
|
||||
_get(csPath, queryParams, body, options) {
|
||||
return this._authedRequest("GET", this._url(csPath), queryParams, body, options);
|
||||
return this._authedRequest("GET", this._url(csPath, options?.prefix || CS_R0_PREFIX), queryParams, body, options);
|
||||
}
|
||||
|
||||
sync(since, filter, timeout, options = null) {
|
||||
@ -170,8 +173,12 @@ export class HomeServerApi {
|
||||
return this._unauthedRequest("GET", `${this._homeserver}/_matrix/client/versions`, null, null, options);
|
||||
}
|
||||
|
||||
uploadKeys(payload, options = null) {
|
||||
return this._post("/keys/upload", null, payload, options);
|
||||
uploadKeys(dehydratedDeviceId, payload, options = null) {
|
||||
let path = "/keys/upload";
|
||||
if (dehydratedDeviceId) {
|
||||
path = path + `/${encodeURIComponent(dehydratedDeviceId)}`;
|
||||
}
|
||||
return this._post(path, null, payload, options);
|
||||
}
|
||||
|
||||
queryKeys(queryRequest, options = null) {
|
||||
@ -229,6 +236,21 @@ export class HomeServerApi {
|
||||
logout(options = null) {
|
||||
return this._post(`/logout`, null, null, options);
|
||||
}
|
||||
|
||||
getDehydratedDevice(options = {}) {
|
||||
options.prefix = DEHYDRATION_PREFIX;
|
||||
return this._get(`/dehydrated_device`, null, null, options);
|
||||
}
|
||||
|
||||
createDehydratedDevice(payload, options = null) {
|
||||
options.prefix = DEHYDRATION_PREFIX;
|
||||
return this._put(`/dehydrated_device`, null, payload, options);
|
||||
}
|
||||
|
||||
claimDehydratedDevice(deviceId) {
|
||||
options.prefix = DEHYDRATION_PREFIX;
|
||||
return this._post(`/dehydrated_device/claim`, null, {device_id: deviceId}, options);
|
||||
}
|
||||
}
|
||||
|
||||
import {Request as MockRequest} from "../../mocks/Request.js";
|
||||
|
Loading…
Reference in New Issue
Block a user