You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-12-01 04:43:29 +03:00
retry key backups when they fail
This commit is contained in:
@@ -98,7 +98,7 @@ describe("MegolmBackup", function() {
|
|||||||
mockCrypto = testUtils.mock(Crypto, 'Crypto');
|
mockCrypto = testUtils.mock(Crypto, 'Crypto');
|
||||||
mockCrypto.backupKey = new global.Olm.PkEncryption();
|
mockCrypto.backupKey = new global.Olm.PkEncryption();
|
||||||
mockCrypto.backupKey.set_recipient_key(
|
mockCrypto.backupKey.set_recipient_key(
|
||||||
"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK",
|
"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
|
||||||
);
|
);
|
||||||
mockCrypto.backupInfo = {
|
mockCrypto.backupInfo = {
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -134,7 +134,7 @@ describe("MegolmBackup", function() {
|
|||||||
megolmDecryption.olmlib = mockOlmLib;
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('automatically backs up keys', function() {
|
it('automatically calls the key back up', function() {
|
||||||
const groupSession = new global.Olm.OutboundGroupSession();
|
const groupSession = new global.Olm.OutboundGroupSession();
|
||||||
groupSession.create();
|
groupSession.create();
|
||||||
|
|
||||||
@@ -169,6 +169,194 @@ describe("MegolmBackup", function() {
|
|||||||
expect(mockCrypto.backupGroupSession).toHaveBeenCalled();
|
expect(mockCrypto.backupGroupSession).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sends backups to the server', function () {
|
||||||
|
const groupSession = new global.Olm.OutboundGroupSession();
|
||||||
|
groupSession.create();
|
||||||
|
const ibGroupSession = new global.Olm.InboundGroupSession();
|
||||||
|
ibGroupSession.create(groupSession.session_key());
|
||||||
|
|
||||||
|
const scheduler = [
|
||||||
|
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
||||||
|
"setProcessFunction",
|
||||||
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
||||||
|
const store = [
|
||||||
|
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
|
||||||
|
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom",
|
||||||
|
"storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter",
|
||||||
|
"storeFilter", "getSyncAccumulator", "startup", "deleteAllData",
|
||||||
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
||||||
|
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
const client = new MatrixClient({
|
||||||
|
baseUrl: "https://my.home.server",
|
||||||
|
idBaseUrl: "https://identity.server",
|
||||||
|
accessToken: "my.access.token",
|
||||||
|
request: function() {}, // NOP
|
||||||
|
store: store,
|
||||||
|
scheduler: scheduler,
|
||||||
|
userId: "@alice:bar",
|
||||||
|
deviceId: "device",
|
||||||
|
sessionStore: sessionStore,
|
||||||
|
cryptoStore: cryptoStore,
|
||||||
|
});
|
||||||
|
|
||||||
|
megolmDecryption = new MegolmDecryption({
|
||||||
|
userId: '@user:id',
|
||||||
|
crypto: mockCrypto,
|
||||||
|
olmDevice: olmDevice,
|
||||||
|
baseApis: client,
|
||||||
|
roomId: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
|
return client.initCrypto()
|
||||||
|
.then(() => {
|
||||||
|
return cryptoStore.doTxn("readwrite", [cryptoStore.STORE_SESSION], (txn) => {
|
||||||
|
cryptoStore.addEndToEndInboundGroupSession(
|
||||||
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
|
groupSession.session_id(),
|
||||||
|
{
|
||||||
|
forwardingCurve25519KeyChain: undefined,
|
||||||
|
keysClaimed: {
|
||||||
|
ed25519: "SENDER_ED25519",
|
||||||
|
},
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
session: ibGroupSession.pickle(olmDevice._pickleKey),
|
||||||
|
},
|
||||||
|
txn);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
client.enableKeyBackup({
|
||||||
|
algorithm: "foobar",
|
||||||
|
version: 1,
|
||||||
|
auth_data: {
|
||||||
|
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let numCalls = 0;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
client._http.authedRequest = function(callback, method, path, queryParams, data, opts) {
|
||||||
|
expect(++numCalls <= 1);
|
||||||
|
if (numCalls >= 2) {
|
||||||
|
// exit out of retry loop if there's something wrong
|
||||||
|
reject(new Error("authedRequest called too many timmes"));
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
expect(method).toBe("PUT");
|
||||||
|
expect(path).toBe("/room_keys/keys");
|
||||||
|
expect(queryParams.version).toBe(1);
|
||||||
|
expect(data.rooms[ROOM_ID].sessions).toExist();
|
||||||
|
expect(data.rooms[ROOM_ID].sessions).toIncludeKey(groupSession.session_id());
|
||||||
|
resolve();
|
||||||
|
return Promise.resolve({});
|
||||||
|
};
|
||||||
|
client._crypto.backupGroupSession("roomId", "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", [], groupSession.session_id(), groupSession.session_key());
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
expect(numCalls).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retries when a backup fails', function () {
|
||||||
|
const groupSession = new global.Olm.OutboundGroupSession();
|
||||||
|
groupSession.create();
|
||||||
|
const ibGroupSession = new global.Olm.InboundGroupSession();
|
||||||
|
ibGroupSession.create(groupSession.session_key());
|
||||||
|
|
||||||
|
const scheduler = [
|
||||||
|
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
||||||
|
"setProcessFunction",
|
||||||
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
||||||
|
const store = [
|
||||||
|
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
|
||||||
|
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom",
|
||||||
|
"storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter",
|
||||||
|
"storeFilter", "getSyncAccumulator", "startup", "deleteAllData",
|
||||||
|
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
|
||||||
|
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
|
||||||
|
const client = new MatrixClient({
|
||||||
|
baseUrl: "https://my.home.server",
|
||||||
|
idBaseUrl: "https://identity.server",
|
||||||
|
accessToken: "my.access.token",
|
||||||
|
request: function() {}, // NOP
|
||||||
|
store: store,
|
||||||
|
scheduler: scheduler,
|
||||||
|
userId: "@alice:bar",
|
||||||
|
deviceId: "device",
|
||||||
|
sessionStore: sessionStore,
|
||||||
|
cryptoStore: cryptoStore,
|
||||||
|
});
|
||||||
|
|
||||||
|
megolmDecryption = new MegolmDecryption({
|
||||||
|
userId: '@user:id',
|
||||||
|
crypto: mockCrypto,
|
||||||
|
olmDevice: olmDevice,
|
||||||
|
baseApis: client,
|
||||||
|
roomId: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
|
return client.initCrypto()
|
||||||
|
.then(() => {
|
||||||
|
return cryptoStore.doTxn("readwrite", [cryptoStore.STORE_SESSION], (txn) => {
|
||||||
|
cryptoStore.addEndToEndInboundGroupSession(
|
||||||
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
|
groupSession.session_id(),
|
||||||
|
{
|
||||||
|
forwardingCurve25519KeyChain: undefined,
|
||||||
|
keysClaimed: {
|
||||||
|
ed25519: "SENDER_ED25519",
|
||||||
|
},
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
session: ibGroupSession.pickle(olmDevice._pickleKey),
|
||||||
|
},
|
||||||
|
txn);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
client.enableKeyBackup({
|
||||||
|
algorithm: "foobar",
|
||||||
|
version: 1,
|
||||||
|
auth_data: {
|
||||||
|
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmoK"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let numCalls = 0;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
client._http.authedRequest = function(callback, method, path, queryParams, data, opts) {
|
||||||
|
expect(++numCalls <= 2);
|
||||||
|
if (numCalls >= 3) {
|
||||||
|
// exit out of retry loop if there's something wrong
|
||||||
|
reject(new Error("authedRequest called too many timmes"));
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
expect(method).toBe("PUT");
|
||||||
|
expect(path).toBe("/room_keys/keys");
|
||||||
|
expect(queryParams.version).toBe(1);
|
||||||
|
expect(data.rooms[ROOM_ID].sessions).toExist();
|
||||||
|
expect(data.rooms[ROOM_ID].sessions).toIncludeKey(groupSession.session_id());
|
||||||
|
if (numCalls > 1) {
|
||||||
|
resolve();
|
||||||
|
return Promise.resolve({});
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error("this is an expected failure"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
client._crypto.backupGroupSession("roomId", "F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI", [], groupSession.session_id(), groupSession.session_key());
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
expect(numCalls).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("restore", function() {
|
describe("restore", function() {
|
||||||
|
|||||||
@@ -848,6 +848,8 @@ MatrixClient.prototype.enableKeyBackup = function(info) {
|
|||||||
this._crypto.backupKey.set_recipient_key(info.auth_data.public_key);
|
this._crypto.backupKey.set_recipient_key(info.auth_data.public_key);
|
||||||
|
|
||||||
this.emit('keyBackupStatus', true);
|
this.emit('keyBackupStatus', true);
|
||||||
|
|
||||||
|
this._crypto._maybeSendKeyBackup();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ function Crypto(baseApis, sessionStore, userId, deviceId,
|
|||||||
this.backupInfo = null; // The info dict from /room_keys/version
|
this.backupInfo = null; // The info dict from /room_keys/version
|
||||||
this.backupKey = null; // The encryption key object
|
this.backupKey = null; // The encryption key object
|
||||||
this._checkedForBackup = false; // Have we checked the server for a backup we can use?
|
this._checkedForBackup = false; // Have we checked the server for a backup we can use?
|
||||||
|
this._sendingBackups = false; // Are we currently sending backups?
|
||||||
|
|
||||||
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||||
this._deviceList = new DeviceList(
|
this._deviceList = new DeviceList(
|
||||||
@@ -965,51 +966,62 @@ Crypto.prototype.importRoomKeys = function(keys) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Crypto.prototype._backupPayloadForSession = function(
|
Crypto.prototype._maybeSendKeyBackup = async function() {
|
||||||
senderKey, forwardingCurve25519KeyChain,
|
if (!this._sendingBackups) {
|
||||||
sessionId, sessionKey, keysClaimed,
|
this._sendingBackups = true;
|
||||||
exportFormat,
|
while (1) {
|
||||||
) {
|
if (!this.backupKey) {
|
||||||
// new session.
|
this._sendingBackups = false;
|
||||||
const session = new Olm.InboundGroupSession();
|
return;
|
||||||
try {
|
}
|
||||||
if (exportFormat) {
|
// FIXME: figure out what limit is reasonable
|
||||||
session.import_session(sessionKey);
|
const sessions = await this._cryptoStore.getSessionsNeedingBackup(10);
|
||||||
} else {
|
if (!sessions.length) {
|
||||||
session.create(sessionKey);
|
this._sendingBackups = false;
|
||||||
}
|
return;
|
||||||
if (sessionId != session.session_id()) {
|
}
|
||||||
throw new Error(
|
const data = {};
|
||||||
"Mismatched group session ID from senderKey: " +
|
for (const session of sessions) {
|
||||||
senderKey,
|
const room_id = session.sessionData.room_id;
|
||||||
);
|
if (data[room_id] === undefined)
|
||||||
}
|
data[room_id] = {sessions: {}};
|
||||||
|
|
||||||
if (!exportFormat) {
|
const sessionData = await this._olmDevice.exportInboundGroupSession(session.senderKey, session.sessionId, session.sessionData);
|
||||||
sessionKey = session.export_session();
|
sessionData.algorithm = olmlib.MEGOLM_ALGORITHM;
|
||||||
}
|
delete sessionData.session_id;
|
||||||
const firstKnownIndex = session.first_known_index();
|
delete sessionData.room_id;
|
||||||
|
const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData));
|
||||||
|
|
||||||
const sessionData = {
|
data[room_id]['sessions'][session.sessionId] = {
|
||||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
first_message_index: 1, // FIXME
|
||||||
sender_key: senderKey,
|
forwarded_count: (sessionData.forwardingCurve25519KeyChain || []).length,
|
||||||
sender_claimed_keys: keysClaimed,
|
is_verified: false, // FIXME: how do we determine this?
|
||||||
session_key: sessionKey,
|
session_data: encrypted,
|
||||||
forwarding_curve25519_key_chain: forwardingCurve25519KeyChain,
|
};
|
||||||
};
|
}
|
||||||
const encrypted = this.backupKey.encrypt(JSON.stringify(sessionData));
|
|
||||||
return {
|
let successful = false;
|
||||||
first_message_index: firstKnownIndex,
|
do {
|
||||||
forwarded_count: forwardingCurve25519KeyChain.length,
|
if (!this.backupKey) {
|
||||||
is_verified: false, // FIXME: how do we determine this?
|
this._sendingBackups = false;
|
||||||
session_data: encrypted,
|
return;
|
||||||
};
|
}
|
||||||
} finally {
|
try {
|
||||||
session.free();
|
await this._baseApis.sendKeyBackup(undefined, undefined, this.backupInfo.version, {rooms: data});
|
||||||
|
successful = true;
|
||||||
|
await this._cryptoStore.unmarkSessionsNeedingBackup(sessions);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log("send failed", e);
|
||||||
|
// FIXME: pause
|
||||||
|
}
|
||||||
|
} while (!successful);
|
||||||
|
// FIXME: pause between iterations?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
Crypto.prototype.backupGroupSession = function(
|
Crypto.prototype.backupGroupSession = async function(
|
||||||
roomId, senderKey, forwardingCurve25519KeyChain,
|
roomId, senderKey, forwardingCurve25519KeyChain,
|
||||||
sessionId, sessionKey, keysClaimed,
|
sessionId, sessionKey, keysClaimed,
|
||||||
exportFormat,
|
exportFormat,
|
||||||
@@ -1018,26 +1030,26 @@ Crypto.prototype.backupGroupSession = function(
|
|||||||
throw new Error("Key backups are not enabled");
|
throw new Error("Key backups are not enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this._backupPayloadForSession(
|
await this._cryptoStore.markSessionsNeedingBackup([{
|
||||||
senderKey, forwardingCurve25519KeyChain,
|
senderKey: senderKey,
|
||||||
sessionId, sessionKey, keysClaimed,
|
sessionId: sessionId,
|
||||||
exportFormat,
|
}]);
|
||||||
);
|
|
||||||
return this._baseApis.sendKeyBackup(roomId, sessionId, this.backupInfo.version, data);
|
this._maybeSendKeyBackup();
|
||||||
};
|
};
|
||||||
|
|
||||||
Crypto.prototype.backupAllGroupSessions = async function(version) {
|
Crypto.prototype.backupAllGroupSessions = async function(version) {
|
||||||
const keys = await this.exportRoomKeys();
|
await this._cryptoStore.doTxn(
|
||||||
const data = {};
|
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS, IndexedDBCryptoStore.STORE_BACKUP], (txn) => {
|
||||||
for (const key of keys) {
|
this._cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => {
|
||||||
if (data[key.room_id] === undefined) data[key.room_id] = {sessions: {}};
|
if (session !== null) {
|
||||||
|
this._cryptoStore.markSessionsNeedingBackup([session], txn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
data[key.room_id]['sessions'][key.session_id] = this._backupPayloadForSession(
|
this._maybeSendKeyBackup();
|
||||||
key.sender_key, key.forwarding_curve25519_key_chain,
|
|
||||||
key.session_id, key.session_key, key.sender_claimed_keys, true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this._baseApis.sendKeyBackup(undefined, undefined, version, {rooms: data});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307
|
/* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307
|
||||||
|
|||||||
@@ -460,6 +460,71 @@ export class Backend {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// session backups
|
||||||
|
|
||||||
|
getSessionsNeedingBackup(limit) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const sessions = [];
|
||||||
|
|
||||||
|
const txn = this._db.transaction(["sessions_needing_backup", "inbound_group_sessions"], "readonly");
|
||||||
|
txn.onerror = reject;
|
||||||
|
txn.oncomplete = function() {
|
||||||
|
resolve(sessions);
|
||||||
|
}
|
||||||
|
const objectStore = txn.objectStore("sessions_needing_backup");
|
||||||
|
const sessionStore = txn.objectStore("inbound_group_sessions");
|
||||||
|
const getReq = objectStore.openCursor();
|
||||||
|
getReq.onsuccess = function() {
|
||||||
|
const cursor = getReq.result;
|
||||||
|
if (cursor) {
|
||||||
|
const sessionGetReq = sessionStore.get(cursor.key)
|
||||||
|
sessionGetReq.onsuccess = function() {
|
||||||
|
sessions.push({
|
||||||
|
senderKey: sessionGetReq.result.senderCurve25519Key,
|
||||||
|
sessionId: sessionGetReq.result.sessionId,
|
||||||
|
sessionData: sessionGetReq.result.session
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//sessions.push(cursor.value);
|
||||||
|
if (!limit || sessions.length < limit) {
|
||||||
|
cursor.continue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarkSessionsNeedingBackup(sessions) {
|
||||||
|
const txn = this._db.transaction("sessions_needing_backup", "readwrite");
|
||||||
|
const objectStore = txn.objectStore("sessions_needing_backup");
|
||||||
|
return Promise.all(sessions.map((session) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(session);
|
||||||
|
const req = objectStore.delete([session.senderKey, session.sessionId]);
|
||||||
|
req.onsuccess = resolve;
|
||||||
|
req.onerror = reject;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
markSessionsNeedingBackup(sessions, txn) {
|
||||||
|
if (!txn) {
|
||||||
|
txn = this._db.transaction("sessions_needing_backup", "readwrite");
|
||||||
|
}
|
||||||
|
const objectStore = txn.objectStore("sessions_needing_backup");
|
||||||
|
return Promise.all(sessions.map((session) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = objectStore.put({
|
||||||
|
senderCurve25519Key: session.senderKey,
|
||||||
|
sessionId: session.sessionId
|
||||||
|
});
|
||||||
|
req.onsuccess = resolve;
|
||||||
|
req.onerror = reject;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
doTxn(mode, stores, func) {
|
doTxn(mode, stores, func) {
|
||||||
const txn = this._db.transaction(stores, mode);
|
const txn = this._db.transaction(stores, mode);
|
||||||
const promise = promiseifyTxn(txn);
|
const promise = promiseifyTxn(txn);
|
||||||
@@ -498,6 +563,11 @@ export function upgradeDatabase(db, oldVersion) {
|
|||||||
if (oldVersion < 6) {
|
if (oldVersion < 6) {
|
||||||
db.createObjectStore("rooms");
|
db.createObjectStore("rooms");
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 7) {
|
||||||
|
db.createObjectStore("sessions_needing_backup", {
|
||||||
|
keyPath: ["senderCurve25519Key", "sessionId"],
|
||||||
|
});
|
||||||
|
}
|
||||||
// Expand as needed.
|
// Expand as needed.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -407,6 +407,38 @@ export default class IndexedDBCryptoStore {
|
|||||||
this._backendPromise.value().getEndToEndRooms(txn, func);
|
this._backendPromise.value().getEndToEndRooms(txn, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the inbound group sessions that need to be backed up.
|
||||||
|
* @param {integer} limit The maximum number of sessions to retrieve. 0
|
||||||
|
* for no limit.
|
||||||
|
*/
|
||||||
|
getSessionsNeedingBackup(limit) {
|
||||||
|
return this._connect().then((backend) => {
|
||||||
|
return backend.getSessionsNeedingBackup(limit);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmark sessions as needing to be backed up.
|
||||||
|
* @param {[object]} The sessions that need to be backed up.
|
||||||
|
*/
|
||||||
|
unmarkSessionsNeedingBackup(sessions) {
|
||||||
|
return this._connect().then((backend) => {
|
||||||
|
return backend.unmarkSessionsNeedingBackup(sessions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark sessions as needing to be backed up.
|
||||||
|
* @param {[object]} The sessions that need to be backed up.
|
||||||
|
* @param {*} txn An active transaction. See doTxn(). (optional)
|
||||||
|
*/
|
||||||
|
markSessionsNeedingBackup(sessions, txn) {
|
||||||
|
return this._connect().then((backend) => {
|
||||||
|
return backend.markSessionsNeedingBackup(sessions, txn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a transaction on the crypto store. Any store methods
|
* Perform a transaction on the crypto store. Any store methods
|
||||||
* that require a transaction (txn) object to be passed in may
|
* that require a transaction (txn) object to be passed in may
|
||||||
@@ -440,3 +472,4 @@ IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
|
|||||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
|
||||||
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
|
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
|
||||||
IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
|
IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
|
||||||
|
IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup';
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export default class MemoryCryptoStore {
|
|||||||
this._deviceData = null;
|
this._deviceData = null;
|
||||||
// roomId -> Opaque roomInfo object
|
// roomId -> Opaque roomInfo object
|
||||||
this._rooms = {};
|
this._rooms = {};
|
||||||
|
// Set of {senderCurve25519Key+'/'+sessionId}
|
||||||
|
this._sessionsNeedingBackup = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -295,6 +297,39 @@ export default class MemoryCryptoStore {
|
|||||||
func(this._rooms);
|
func(this._rooms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSessionsNeedingBackup(limit) {
|
||||||
|
const sessions = [];
|
||||||
|
for (const session in this._sessionsNeedingBackup) {
|
||||||
|
if (this._inboundGroupSessions[session]) {
|
||||||
|
sessions.push({
|
||||||
|
senderKey: session.substr(0, 43),
|
||||||
|
sessionId: session.substr(44),
|
||||||
|
sessionData: this._inboundGroupSessions[session],
|
||||||
|
});
|
||||||
|
if (limit && session.length >= limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve(sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarkSessionsNeedingBackup(sessions) {
|
||||||
|
for(const session of sessions) {
|
||||||
|
delete this._sessionsNeedingBackup[session.senderKey + '/' + session.sessionId];
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
markSessionsNeedingBackup(sessions) {
|
||||||
|
for(const session of sessions) {
|
||||||
|
this._sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true;
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session key backups
|
||||||
|
|
||||||
doTxn(mode, stores, func) {
|
doTxn(mode, stores, func) {
|
||||||
return Promise.resolve(func(null));
|
return Promise.resolve(func(null));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user