You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
Merge pull request #1146 from uhoreg/reporting_olm_error
record, report, and notify about olm errors
This commit is contained in:
@@ -453,6 +453,99 @@ describe("MegolmDecryption", function() {
|
|||||||
bobClient2.stopClient();
|
bobClient2.stopClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("notifies devices when unable to create olm session", async function() {
|
||||||
|
const aliceClient = (new TestClient(
|
||||||
|
"@alice:example.com", "alicedevice",
|
||||||
|
)).client;
|
||||||
|
const bobClient = (new TestClient(
|
||||||
|
"@bob:example.com", "bobdevice",
|
||||||
|
)).client;
|
||||||
|
await Promise.all([
|
||||||
|
aliceClient.initCrypto(),
|
||||||
|
bobClient.initCrypto(),
|
||||||
|
]);
|
||||||
|
const aliceDevice = aliceClient._crypto._olmDevice;
|
||||||
|
const bobDevice = bobClient._crypto._olmDevice;
|
||||||
|
|
||||||
|
const encryptionCfg = {
|
||||||
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
|
};
|
||||||
|
const roomId = "!someroom";
|
||||||
|
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
|
||||||
|
const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
|
||||||
|
aliceClient.store.storeRoom(aliceRoom);
|
||||||
|
bobClient.store.storeRoom(bobRoom);
|
||||||
|
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
|
||||||
|
await bobClient.setRoomEncryption(roomId, encryptionCfg);
|
||||||
|
|
||||||
|
aliceRoom.getEncryptionTargetMembers = async () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
userId: "@alice:example.com",
|
||||||
|
membership: "join",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:example.com",
|
||||||
|
membership: "join",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
const BOB_DEVICES = {
|
||||||
|
bobdevice: {
|
||||||
|
user_id: "@bob:example.com",
|
||||||
|
device_id: "bobdevice",
|
||||||
|
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||||
|
keys: {
|
||||||
|
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
|
||||||
|
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
|
||||||
|
},
|
||||||
|
known: true,
|
||||||
|
verified: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
aliceClient._crypto._deviceList.storeDevicesForUser(
|
||||||
|
"@bob:example.com", BOB_DEVICES,
|
||||||
|
);
|
||||||
|
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
|
||||||
|
return this._getDevicesFromStore(userIds);
|
||||||
|
};
|
||||||
|
|
||||||
|
aliceClient.claimOneTimeKeys = async () => {
|
||||||
|
// Bob has no one-time keys
|
||||||
|
return {
|
||||||
|
one_time_keys: {},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let run = false;
|
||||||
|
aliceClient.sendToDevice = async (msgtype, contentMap) => {
|
||||||
|
run = true;
|
||||||
|
expect(msgtype).toBe("org.matrix.room_key.withheld");
|
||||||
|
expect(contentMap).toStrictEqual({
|
||||||
|
'@bob:example.com': {
|
||||||
|
bobdevice: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
code: 'm.no_olm',
|
||||||
|
reason: 'Unable to establish a secure channel.',
|
||||||
|
sender_key: aliceDevice.deviceCurve25519Key,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const event = new MatrixEvent({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: "@alice:example.com",
|
||||||
|
room_id: roomId,
|
||||||
|
event_id: "$event",
|
||||||
|
content: {},
|
||||||
|
});
|
||||||
|
await aliceClient._crypto.encryptEvent(event, aliceRoom);
|
||||||
|
|
||||||
|
expect(run).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("throws an error describing why it doesn't have a key", async function() {
|
it("throws an error describing why it doesn't have a key", async function() {
|
||||||
const aliceClient = (new TestClient(
|
const aliceClient = (new TestClient(
|
||||||
"@alice:example.com", "alicedevice",
|
"@alice:example.com", "alicedevice",
|
||||||
@@ -495,4 +588,103 @@ describe("MegolmDecryption", function() {
|
|||||||
},
|
},
|
||||||
}))).rejects.toThrow("The sender has blocked you.");
|
}))).rejects.toThrow("The sender has blocked you.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("throws an error describing the lack of an olm session", async function() {
|
||||||
|
const aliceClient = (new TestClient(
|
||||||
|
"@alice:example.com", "alicedevice",
|
||||||
|
)).client;
|
||||||
|
const bobClient = (new TestClient(
|
||||||
|
"@bob:example.com", "bobdevice",
|
||||||
|
)).client;
|
||||||
|
await Promise.all([
|
||||||
|
aliceClient.initCrypto(),
|
||||||
|
bobClient.initCrypto(),
|
||||||
|
]);
|
||||||
|
const bobDevice = bobClient._crypto._olmDevice;
|
||||||
|
|
||||||
|
const roomId = "!someroom";
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
|
||||||
|
type: "org.matrix.room_key.withheld",
|
||||||
|
sender: "@bob:example.com",
|
||||||
|
content: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
room_id: roomId,
|
||||||
|
session_id: "session_id",
|
||||||
|
sender_key: bobDevice.deviceCurve25519Key,
|
||||||
|
code: "m.no_olm",
|
||||||
|
reason: "Unable to establish a secure channel.",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
sender: "@bob:example.com",
|
||||||
|
event_id: "$event",
|
||||||
|
room_id: roomId,
|
||||||
|
content: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
ciphertext: "blablabla",
|
||||||
|
device_id: "bobdevice",
|
||||||
|
sender_key: bobDevice.deviceCurve25519Key,
|
||||||
|
session_id: "session_id",
|
||||||
|
},
|
||||||
|
origin_server_ts: now,
|
||||||
|
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error to indicate a wedged olm session", async function() {
|
||||||
|
const aliceClient = (new TestClient(
|
||||||
|
"@alice:example.com", "alicedevice",
|
||||||
|
)).client;
|
||||||
|
const bobClient = (new TestClient(
|
||||||
|
"@bob:example.com", "bobdevice",
|
||||||
|
)).client;
|
||||||
|
await Promise.all([
|
||||||
|
aliceClient.initCrypto(),
|
||||||
|
bobClient.initCrypto(),
|
||||||
|
]);
|
||||||
|
const bobDevice = bobClient._crypto._olmDevice;
|
||||||
|
|
||||||
|
const roomId = "!someroom";
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// pretend we got an event that we can't decrypt
|
||||||
|
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
sender: "@bob:example.com",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.bad.encrypted",
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
session_id: "session_id",
|
||||||
|
sender_key: bobDevice.deviceCurve25519Key,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
sender: "@bob:example.com",
|
||||||
|
event_id: "$event",
|
||||||
|
room_id: roomId,
|
||||||
|
content: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
ciphertext: "blablabla",
|
||||||
|
device_id: "bobdevice",
|
||||||
|
sender_key: bobDevice.deviceCurve25519Key,
|
||||||
|
session_id: "session_id",
|
||||||
|
},
|
||||||
|
origin_server_ts: now,
|
||||||
|
}))).rejects.toThrow("The secure channel with the sender was corrupted.");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -674,6 +674,18 @@ OlmDevice.prototype.matchesSession = async function(
|
|||||||
return matches;
|
return matches;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
OlmDevice.prototype.recordSessionProblem = async function(deviceKey, type, fixed) {
|
||||||
|
await this._cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||||
|
};
|
||||||
|
|
||||||
|
OlmDevice.prototype.sessionMayHaveProblems = async function(deviceKey, timestamp) {
|
||||||
|
return await this._cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
|
OlmDevice.prototype.filterOutNotifiedErrorDevices = async function(devices) {
|
||||||
|
return await this._cryptoStore.filterOutNotifiedErrorDevices(devices);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Outbound group session
|
// Outbound group session
|
||||||
// ======================
|
// ======================
|
||||||
|
|||||||
@@ -159,6 +159,16 @@ class DecryptionAlgorithm {
|
|||||||
shareKeysWithDevice(keyRequest) {
|
shareKeysWithDevice(keyRequest) {
|
||||||
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
|
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry decrypting all the events from a sender that haven't been
|
||||||
|
* decrypted yet.
|
||||||
|
*
|
||||||
|
* @param {string} senderKey the sender's key
|
||||||
|
*/
|
||||||
|
async retryDecryptionFromSender(senderKey) {
|
||||||
|
// ignore by default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
|
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
|
||||||
|
|
||||||
|
|||||||
@@ -253,8 +253,10 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorDevices = [];
|
||||||
|
|
||||||
await self._shareKeyWithDevices(
|
await self._shareKeyWithDevices(
|
||||||
session, shareMap,
|
session, shareMap, errorDevices,
|
||||||
);
|
);
|
||||||
|
|
||||||
// are there any new blocked devices that we need to notify?
|
// are there any new blocked devices that we need to notify?
|
||||||
@@ -281,6 +283,18 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const filteredErrorDevices =
|
||||||
|
await self._olmDevice.filterOutNotifiedErrorDevices(errorDevices);
|
||||||
|
for (const {userId, deviceInfo} of filteredErrorDevices) {
|
||||||
|
blockedMap[userId] = blockedMap[userId] || [];
|
||||||
|
blockedMap[userId].push({
|
||||||
|
code: "m.no_olm",
|
||||||
|
reason: WITHHELD_MESSAGES["m.no_olm"],
|
||||||
|
deviceInfo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// notify blocked devices that they're blocked
|
// notify blocked devices that they're blocked
|
||||||
await self._notifyBlockedDevices(session, blockedMap);
|
await self._notifyBlockedDevices(session, blockedMap);
|
||||||
}
|
}
|
||||||
@@ -346,10 +360,14 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
|
|||||||
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
||||||
* map from userid to list of devices
|
* map from userid to list of devices
|
||||||
*
|
*
|
||||||
|
* @param {array<object>} errorDevices
|
||||||
|
* array that will be populated with the devices that can't get an
|
||||||
|
* olm session for
|
||||||
|
*
|
||||||
* @return {array<object<userid, deviceInfo>>}
|
* @return {array<object<userid, deviceInfo>>}
|
||||||
*/
|
*/
|
||||||
MegolmEncryption.prototype._splitUserDeviceMap = function(
|
MegolmEncryption.prototype._splitUserDeviceMap = function(
|
||||||
session, chainIndex, devicemap, devicesByUser,
|
session, chainIndex, devicemap, devicesByUser, errorDevices,
|
||||||
) {
|
) {
|
||||||
const maxUsersPerRequest = 20;
|
const maxUsersPerRequest = 20;
|
||||||
|
|
||||||
@@ -381,6 +399,8 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
|
|||||||
// to claim a one-time-key for dead devices on every message.
|
// to claim a one-time-key for dead devices on every message.
|
||||||
session.markSharedWithDevice(userId, deviceId, chainIndex);
|
session.markSharedWithDevice(userId, deviceId, chainIndex);
|
||||||
|
|
||||||
|
errorDevices.push({userId, deviceInfo});
|
||||||
|
|
||||||
// ensureOlmSessionsForUsers has already done the logging,
|
// ensureOlmSessionsForUsers has already done the logging,
|
||||||
// so just skip it.
|
// so just skip it.
|
||||||
continue;
|
continue;
|
||||||
@@ -550,6 +570,10 @@ MegolmEncryption.prototype._sendBlockedNotificationsToDevices = async function(
|
|||||||
const message = Object.assign({}, payload);
|
const message = Object.assign({}, payload);
|
||||||
message.code = blockedInfo.code;
|
message.code = blockedInfo.code;
|
||||||
message.reason = blockedInfo.reason;
|
message.reason = blockedInfo.reason;
|
||||||
|
if (message.code === "m.no_olm") {
|
||||||
|
delete message.room_id;
|
||||||
|
delete message.session_id;
|
||||||
|
}
|
||||||
|
|
||||||
if (!contentMap[userId]) {
|
if (!contentMap[userId]) {
|
||||||
contentMap[userId] = {};
|
contentMap[userId] = {};
|
||||||
@@ -663,8 +687,14 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
|
|||||||
*
|
*
|
||||||
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
||||||
* map from userid to list of devices
|
* map from userid to list of devices
|
||||||
|
*
|
||||||
|
* @param {array<object>} errorDevices
|
||||||
|
* array that will be populated with the devices that we can't get an
|
||||||
|
* olm session for
|
||||||
*/
|
*/
|
||||||
MegolmEncryption.prototype._shareKeyWithDevices = async function(session, devicesByUser) {
|
MegolmEncryption.prototype._shareKeyWithDevices = async function(
|
||||||
|
session, devicesByUser, errorDevices,
|
||||||
|
) {
|
||||||
const key = this._olmDevice.getOutboundGroupSessionKey(session.sessionId);
|
const key = this._olmDevice.getOutboundGroupSessionKey(session.sessionId);
|
||||||
const payload = {
|
const payload = {
|
||||||
type: "m.room_key",
|
type: "m.room_key",
|
||||||
@@ -682,7 +712,7 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
|
|||||||
);
|
);
|
||||||
|
|
||||||
const userDeviceMaps = this._splitUserDeviceMap(
|
const userDeviceMaps = this._splitUserDeviceMap(
|
||||||
session, key.chain_index, devicemap, devicesByUser,
|
session, key.chain_index, devicemap, devicesByUser, errorDevices,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let i = 0; i < userDeviceMaps.length; i++) {
|
for (let i = 0; i < userDeviceMaps.length; i++) {
|
||||||
@@ -915,6 +945,11 @@ function MegolmDecryption(params) {
|
|||||||
}
|
}
|
||||||
utils.inherits(MegolmDecryption, base.DecryptionAlgorithm);
|
utils.inherits(MegolmDecryption, base.DecryptionAlgorithm);
|
||||||
|
|
||||||
|
const PROBLEM_DESCRIPTIONS = {
|
||||||
|
no_olm: "The sender was unable to establish a secure channel.",
|
||||||
|
unknown: "The secure channel with the sender was corrupted.",
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*
|
*
|
||||||
@@ -981,6 +1016,28 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
|||||||
// event is still in the pending list; if not, a retry will have been
|
// event is still in the pending list; if not, a retry will have been
|
||||||
// scheduled, so we needn't send out the request here.)
|
// scheduled, so we needn't send out the request here.)
|
||||||
this._requestKeysForEvent(event);
|
this._requestKeysForEvent(event);
|
||||||
|
|
||||||
|
// See if there was a problem with the olm session at the time the
|
||||||
|
// event was sent. Use a fuzz factor of 2 minutes.
|
||||||
|
const problem = await this._olmDevice.sessionMayHaveProblems(
|
||||||
|
content.sender_key, event.getTs() - 120000,
|
||||||
|
);
|
||||||
|
if (problem) {
|
||||||
|
let problemDescription = PROBLEM_DESCRIPTIONS[problem.type]
|
||||||
|
|| PROBLEM_DESCRIPTIONS.unknown;
|
||||||
|
if (problem.fixed) {
|
||||||
|
problemDescription +=
|
||||||
|
" Trying to create a new secure channel and re-requesting the keys.";
|
||||||
|
}
|
||||||
|
throw new base.DecryptionError(
|
||||||
|
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
||||||
|
problemDescription,
|
||||||
|
{
|
||||||
|
session: content.sender_key + '|' + content.session_id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
throw new base.DecryptionError(
|
throw new base.DecryptionError(
|
||||||
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
||||||
"The sender's device has not sent us the keys for this message.",
|
"The sender's device has not sent us the keys for this message.",
|
||||||
@@ -1036,11 +1093,16 @@ MegolmDecryption.prototype._requestKeysForEvent = function(event) {
|
|||||||
*/
|
*/
|
||||||
MegolmDecryption.prototype._addEventToPendingList = function(event) {
|
MegolmDecryption.prototype._addEventToPendingList = function(event) {
|
||||||
const content = event.getWireContent();
|
const content = event.getWireContent();
|
||||||
const k = content.sender_key + "|" + content.session_id;
|
const senderKey = content.sender_key;
|
||||||
if (!this._pendingEvents[k]) {
|
const sessionId = content.session_id;
|
||||||
this._pendingEvents[k] = new Set();
|
if (!this._pendingEvents[senderKey]) {
|
||||||
|
this._pendingEvents[senderKey] = new Map();
|
||||||
}
|
}
|
||||||
this._pendingEvents[k].add(event);
|
const senderPendingEvents = this._pendingEvents[senderKey];
|
||||||
|
if (!senderPendingEvents.has(sessionId)) {
|
||||||
|
senderPendingEvents.set(sessionId, new Set());
|
||||||
|
}
|
||||||
|
senderPendingEvents.get(sessionId).add(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1052,14 +1114,20 @@ MegolmDecryption.prototype._addEventToPendingList = function(event) {
|
|||||||
*/
|
*/
|
||||||
MegolmDecryption.prototype._removeEventFromPendingList = function(event) {
|
MegolmDecryption.prototype._removeEventFromPendingList = function(event) {
|
||||||
const content = event.getWireContent();
|
const content = event.getWireContent();
|
||||||
const k = content.sender_key + "|" + content.session_id;
|
const senderKey = content.sender_key;
|
||||||
if (!this._pendingEvents[k]) {
|
const sessionId = content.session_id;
|
||||||
|
const senderPendingEvents = this._pendingEvents[senderKey];
|
||||||
|
const pendingEvents = senderPendingEvents && senderPendingEvents.get(sessionId);
|
||||||
|
if (!pendingEvents) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._pendingEvents[k].delete(event);
|
pendingEvents.delete(event);
|
||||||
if (this._pendingEvents[k].size === 0) {
|
if (pendingEvents.size === 0) {
|
||||||
delete this._pendingEvents[k];
|
senderPendingEvents.delete(senderKey);
|
||||||
|
}
|
||||||
|
if (senderPendingEvents.size === 0) {
|
||||||
|
delete this._pendingEvents[senderKey];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1170,11 +1238,69 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
|||||||
*/
|
*/
|
||||||
MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
|
MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
|
||||||
const content = event.getContent();
|
const content = event.getContent();
|
||||||
|
const senderKey = content.sender_key;
|
||||||
|
|
||||||
|
if (content.code === "m.no_olm") {
|
||||||
|
const sender = event.getSender();
|
||||||
|
// if the sender says that they haven't been able to establish an olm
|
||||||
|
// session, let's proactively establish one
|
||||||
|
|
||||||
|
// Note: after we record that the olm session has had a problem, we
|
||||||
|
// trigger retrying decryption for all the messages from the sender's
|
||||||
|
// key, so that we can update the error message to indicate the olm
|
||||||
|
// session problem.
|
||||||
|
|
||||||
|
if (await this._olmDevice.getSessionIdForDevice(senderKey)) {
|
||||||
|
// a session has already been established, so we don't need to
|
||||||
|
// create a new one.
|
||||||
|
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
|
||||||
|
this.retryDecryptionFromSender(senderKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const device = this._crypto._deviceList.getDeviceByIdentityKey(
|
||||||
|
content.algorithm, senderKey,
|
||||||
|
);
|
||||||
|
if (!device) {
|
||||||
|
logger.info(
|
||||||
|
"Couldn't find device for identity key " + senderKey +
|
||||||
|
": not establishing session",
|
||||||
|
);
|
||||||
|
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", false);
|
||||||
|
this.retryDecryptionFromSender(senderKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await olmlib.ensureOlmSessionsForDevices(
|
||||||
|
this._olmDevice, this._baseApis, {[sender]: [device]}, false,
|
||||||
|
);
|
||||||
|
const encryptedContent = {
|
||||||
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
|
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||||
|
ciphertext: {},
|
||||||
|
};
|
||||||
|
await olmlib.encryptMessageForDevice(
|
||||||
|
encryptedContent.ciphertext,
|
||||||
|
this._userId,
|
||||||
|
this._deviceId,
|
||||||
|
this._olmDevice,
|
||||||
|
sender,
|
||||||
|
device,
|
||||||
|
{type: "m.dummy"},
|
||||||
|
);
|
||||||
|
|
||||||
|
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
|
||||||
|
this.retryDecryptionFromSender(senderKey);
|
||||||
|
|
||||||
|
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||||
|
[sender]: {
|
||||||
|
[device.deviceId]: encryptedContent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
await this._olmDevice.addInboundGroupSessionWithheld(
|
await this._olmDevice.addInboundGroupSessionWithheld(
|
||||||
content.room_id, content.sender_key, content.session_id, content.code,
|
content.room_id, senderKey, content.session_id, content.code,
|
||||||
content.reason,
|
content.reason,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1320,13 +1446,20 @@ MegolmDecryption.prototype.importRoomKey = function(session) {
|
|||||||
* @return {Boolean} whether all messages were successfully decrypted
|
* @return {Boolean} whether all messages were successfully decrypted
|
||||||
*/
|
*/
|
||||||
MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionId) {
|
MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionId) {
|
||||||
const k = senderKey + "|" + sessionId;
|
const senderPendingEvents = this._pendingEvents[senderKey];
|
||||||
const pending = this._pendingEvents[k];
|
if (!senderPendingEvents) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pending = senderPendingEvents.get(sessionId);
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this._pendingEvents[k];
|
pending.delete(sessionId);
|
||||||
|
if (pending.size === 0) {
|
||||||
|
this._pendingEvents[senderKey];
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all([...pending].map(async (ev) => {
|
await Promise.all([...pending].map(async (ev) => {
|
||||||
try {
|
try {
|
||||||
@@ -1336,7 +1469,32 @@ MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionI
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return !this._pendingEvents[k];
|
// ev.attemptDecryption will re-add to this._pendingEvents if an event
|
||||||
|
// couldn't be decrypted
|
||||||
|
return !((this._pendingEvents[senderKey] || {})[sessionId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
MegolmDecryption.prototype.retryDecryptionFromSender = async function(senderKey) {
|
||||||
|
const senderPendingEvents = this._pendingEvents[senderKey];
|
||||||
|
logger.warn(senderPendingEvents);
|
||||||
|
if (!senderPendingEvents) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this._pendingEvents[senderKey];
|
||||||
|
|
||||||
|
await Promise.all([...senderPendingEvents].map(async ([_sessionId, pending]) => {
|
||||||
|
await Promise.all([...pending].map(async (ev) => {
|
||||||
|
try {
|
||||||
|
logger.warn(ev.getId());
|
||||||
|
await ev.attemptDecryption(this._crypto);
|
||||||
|
} catch (e) {
|
||||||
|
// don't die if something goes wrong
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
return !this._pendingEvents[senderKey];
|
||||||
};
|
};
|
||||||
|
|
||||||
base.registerAlgorithm(
|
base.registerAlgorithm(
|
||||||
|
|||||||
@@ -2502,16 +2502,31 @@ Crypto.prototype._onRoomKeyEvent = function(event) {
|
|||||||
Crypto.prototype._onRoomKeyWithheldEvent = function(event) {
|
Crypto.prototype._onRoomKeyWithheldEvent = function(event) {
|
||||||
const content = event.getContent();
|
const content = event.getContent();
|
||||||
|
|
||||||
if (!content.room_id || !content.session_id || !content.algorithm
|
if ((content.code !== "m.no_olm" && (!content.room_id || !content.session_id))
|
||||||
|| !content.sender_key) {
|
|| !content.algorithm || !content.sender_key) {
|
||||||
logger.error("key withheld event is missing fields");
|
logger.error("key withheld event is missing fields");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Got room key withheld event from ${event.getSender()} (${content.sender_key}) `
|
||||||
|
+ `for ${content.algorithm}/${content.room_id}/${content.session_id} `
|
||||||
|
+ `with reason ${content.code} (${content.reason})`,
|
||||||
|
);
|
||||||
|
|
||||||
const alg = this._getRoomDecryptor(content.room_id, content.algorithm);
|
const alg = this._getRoomDecryptor(content.room_id, content.algorithm);
|
||||||
if (alg.onRoomKeyWithheldEvent) {
|
if (alg.onRoomKeyWithheldEvent) {
|
||||||
alg.onRoomKeyWithheldEvent(event);
|
alg.onRoomKeyWithheldEvent(event);
|
||||||
}
|
}
|
||||||
|
if (!content.room_id) {
|
||||||
|
// retry decryption for all events sent by the sender_key. This will
|
||||||
|
// update the events to show a message indicating that the olm session was
|
||||||
|
// wedged.
|
||||||
|
const roomDecryptors = this._getRoomDecryptors(content.algorithm);
|
||||||
|
for (const decryptor of roomDecryptors) {
|
||||||
|
decryptor.retryDecryptionFromSender(content.sender_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2627,6 +2642,16 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
|
|||||||
const algorithm = content.algorithm;
|
const algorithm = content.algorithm;
|
||||||
const deviceKey = content.sender_key;
|
const deviceKey = content.sender_key;
|
||||||
|
|
||||||
|
// retry decryption for all events sent by the sender_key. This will
|
||||||
|
// update the events to show a message indicating that the olm session was
|
||||||
|
// wedged.
|
||||||
|
const retryDecryption = () => {
|
||||||
|
const roomDecryptors = this._getRoomDecryptors(olmlib.MEGOLM_ALGORITHM);
|
||||||
|
for (const decryptor of roomDecryptors) {
|
||||||
|
decryptor.retryDecryptionFromSender(deviceKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (sender === undefined || deviceKey === undefined || deviceKey === undefined) {
|
if (sender === undefined || deviceKey === undefined || deviceKey === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2640,6 +2665,8 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
|
|||||||
"New session already forced with device " + sender + ":" + deviceKey +
|
"New session already forced with device " + sender + ":" + deviceKey +
|
||||||
" at " + lastNewSessionForced + ": not forcing another",
|
" at " + lastNewSessionForced + ": not forcing another",
|
||||||
);
|
);
|
||||||
|
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true);
|
||||||
|
retryDecryption();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2653,6 +2680,8 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
|
|||||||
"Couldn't find device for identity key " + deviceKey +
|
"Couldn't find device for identity key " + deviceKey +
|
||||||
": not re-establishing session",
|
": not re-establishing session",
|
||||||
);
|
);
|
||||||
|
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", false);
|
||||||
|
retryDecryption();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const devicesByUser = {};
|
const devicesByUser = {};
|
||||||
@@ -2684,6 +2713,9 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
|
|||||||
{type: "m.dummy"},
|
{type: "m.dummy"},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true);
|
||||||
|
retryDecryption();
|
||||||
|
|
||||||
await this._baseApis.sendToDevice("m.room.encrypted", {
|
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||||
[sender]: {
|
[sender]: {
|
||||||
[device.deviceId]: encryptedContent,
|
[device.deviceId]: encryptedContent,
|
||||||
@@ -2965,6 +2997,24 @@ Crypto.prototype._getRoomDecryptor = function(roomId, algorithm) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the room decryptors for a given encryption algorithm.
|
||||||
|
*
|
||||||
|
* @param {string} algorithm The encryption algorithm
|
||||||
|
*
|
||||||
|
* @return {array} An array of room decryptors
|
||||||
|
*/
|
||||||
|
Crypto.prototype._getRoomDecryptors = function(algorithm) {
|
||||||
|
const decryptors = [];
|
||||||
|
for (const d of Object.values(this._roomDecryptors)) {
|
||||||
|
if (algorithm in d) {
|
||||||
|
decryptors.push(d[algorithm]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decryptors;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sign the given object with our ed25519 key
|
* sign the given object with our ed25519 key
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ limitations under the License.
|
|||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
|
|
||||||
export const VERSION = 8;
|
export const VERSION = 9;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of a CryptoStore which is backed by an existing
|
* Implementation of a CryptoStore which is backed by an existing
|
||||||
@@ -426,6 +426,74 @@ export class Backend {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||||
|
const txn = this._db.transaction("session_problems", "readwrite");
|
||||||
|
const objectStore = txn.objectStore("session_problems");
|
||||||
|
objectStore.put({
|
||||||
|
deviceKey,
|
||||||
|
type,
|
||||||
|
fixed,
|
||||||
|
time: Date.now(),
|
||||||
|
});
|
||||||
|
return promiseifyTxn(txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||||
|
let result;
|
||||||
|
const txn = this._db.transaction("session_problems", "readwrite");
|
||||||
|
const objectStore = txn.objectStore("session_problems");
|
||||||
|
const index = objectStore.index("deviceKey");
|
||||||
|
const req = index.getAll(deviceKey);
|
||||||
|
req.onsuccess = (event) => {
|
||||||
|
const problems = req.result;
|
||||||
|
if (!problems.length) {
|
||||||
|
result = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
problems.sort((a, b) => {
|
||||||
|
return a.time - b.time;
|
||||||
|
});
|
||||||
|
const lastProblem = problems[problems.length - 1];
|
||||||
|
for (const problem of problems) {
|
||||||
|
if (problem.time > timestamp) {
|
||||||
|
result = Object.assign({}, problem, {fixed: lastProblem.fixed});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastProblem.fixed) {
|
||||||
|
result = null;
|
||||||
|
} else {
|
||||||
|
result = lastProblem;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await promiseifyTxn(txn);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: we should probably prune this when devices get deleted
|
||||||
|
async filterOutNotifiedErrorDevices(devices) {
|
||||||
|
const txn = this._db.transaction("notified_error_devices", "readwrite");
|
||||||
|
const objectStore = txn.objectStore("notified_error_devices");
|
||||||
|
|
||||||
|
const ret = [];
|
||||||
|
|
||||||
|
await Promise.all(devices.map((device) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const {userId, deviceInfo} = device;
|
||||||
|
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
|
||||||
|
getReq.onsuccess = function() {
|
||||||
|
if (!getReq.result) {
|
||||||
|
objectStore.put({userId, deviceId: deviceInfo.deviceId});
|
||||||
|
ret.push(device);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// Inbound group sessions
|
// Inbound group sessions
|
||||||
|
|
||||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||||
@@ -699,6 +767,16 @@ export function upgradeDatabase(db, oldVersion) {
|
|||||||
keyPath: ["senderCurve25519Key", "sessionId"],
|
keyPath: ["senderCurve25519Key", "sessionId"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 9) {
|
||||||
|
const problemsStore = db.createObjectStore("session_problems", {
|
||||||
|
keyPath: ["deviceKey", "time"],
|
||||||
|
});
|
||||||
|
problemsStore.createIndex("deviceKey", "deviceKey");
|
||||||
|
|
||||||
|
db.createObjectStore("notified_error_devices", {
|
||||||
|
keyPath: ["userId", "deviceId"],
|
||||||
|
});
|
||||||
|
}
|
||||||
// Expand as needed.
|
// Expand as needed.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,6 +409,24 @@ export default class IndexedDBCryptoStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||||
|
return this._backendPromise.then(async (backend) => {
|
||||||
|
await backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||||
|
return this._backendPromise.then(async (backend) => {
|
||||||
|
return await backend.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filterOutNotifiedErrorDevices(devices) {
|
||||||
|
return this._backendPromise.then(async (backend) => {
|
||||||
|
return await backend.filterOutNotifiedErrorDevices(devices);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Inbound group sessions
|
// Inbound group sessions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import MemoryCryptoStore from './memory-crypto-store';
|
|||||||
const E2E_PREFIX = "crypto.";
|
const E2E_PREFIX = "crypto.";
|
||||||
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
|
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
|
||||||
const KEY_CROSS_SIGNING_KEYS = E2E_PREFIX + "cross_signing_keys";
|
const KEY_CROSS_SIGNING_KEYS = E2E_PREFIX + "cross_signing_keys";
|
||||||
|
const KEY_NOTIFIED_ERROR_DEVICES = E2E_PREFIX + "notified_error_devices";
|
||||||
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
|
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
|
||||||
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
|
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
|
||||||
const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.withheld/";
|
const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.withheld/";
|
||||||
@@ -41,6 +42,10 @@ function keyEndToEndSessions(deviceKey) {
|
|||||||
return E2E_PREFIX + "sessions/" + deviceKey;
|
return E2E_PREFIX + "sessions/" + deviceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keyEndToEndSessionProblems(deviceKey) {
|
||||||
|
return E2E_PREFIX + "session.problems/" + deviceKey;
|
||||||
|
}
|
||||||
|
|
||||||
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
||||||
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
|
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
|
||||||
}
|
}
|
||||||
@@ -128,6 +133,58 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||||
|
const key = keyEndToEndSessionProblems(deviceKey);
|
||||||
|
const problems = getJsonItem(this.store, key) || [];
|
||||||
|
problems.push({type, fixed, time: Date.now()});
|
||||||
|
problems.sort((a, b) => {
|
||||||
|
return a.time - b.time;
|
||||||
|
});
|
||||||
|
setJsonItem(this.store, key, problems);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||||
|
const key = keyEndToEndSessionProblems(deviceKey);
|
||||||
|
const problems = getJsonItem(this.store, key) || [];
|
||||||
|
if (!problems.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const lastProblem = problems[problems.length - 1];
|
||||||
|
for (const problem of problems) {
|
||||||
|
if (problem.time > timestamp) {
|
||||||
|
return Object.assign({}, problem, {fixed: lastProblem.fixed});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastProblem.fixed) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return lastProblem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async filterOutNotifiedErrorDevices(devices) {
|
||||||
|
const notifiedErrorDevices =
|
||||||
|
getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {};
|
||||||
|
const ret = [];
|
||||||
|
|
||||||
|
for (const device of devices) {
|
||||||
|
const {userId, deviceInfo} = device;
|
||||||
|
if (userId in notifiedErrorDevices) {
|
||||||
|
if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) {
|
||||||
|
ret.push(device);
|
||||||
|
notifiedErrorDevices[userId][deviceInfo.deviceId] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret.push(device);
|
||||||
|
notifiedErrorDevices[userId] = {[deviceInfo.deviceId]: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES, notifiedErrorDevices);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// Inbound Group Sessions
|
// Inbound Group Sessions
|
||||||
|
|
||||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ export default class MemoryCryptoStore {
|
|||||||
|
|
||||||
// Map of {devicekey -> {sessionId -> session pickle}}
|
// Map of {devicekey -> {sessionId -> session pickle}}
|
||||||
this._sessions = {};
|
this._sessions = {};
|
||||||
|
// Map of {devicekey -> array of problems}
|
||||||
|
this._sessionProblems = {};
|
||||||
|
// Map of {userId -> deviceId -> true}
|
||||||
|
this._notifiedErrorDevices = {};
|
||||||
// Map of {senderCurve25519Key+'/'+sessionId -> session data object}
|
// Map of {senderCurve25519Key+'/'+sessionId -> session data object}
|
||||||
this._inboundGroupSessions = {};
|
this._inboundGroupSessions = {};
|
||||||
this._inboundGroupSessionsWithheld = {};
|
this._inboundGroupSessionsWithheld = {};
|
||||||
@@ -275,6 +279,53 @@ export default class MemoryCryptoStore {
|
|||||||
deviceSessions[sessionId] = sessionInfo;
|
deviceSessions[sessionId] = sessionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
|
||||||
|
const problems = this._sessionProblems[deviceKey]
|
||||||
|
= this._sessionProblems[deviceKey] || [];
|
||||||
|
problems.push({type, fixed, time: Date.now()});
|
||||||
|
problems.sort((a, b) => {
|
||||||
|
return a.time - b.time;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEndToEndSessionProblem(deviceKey, timestamp) {
|
||||||
|
const problems = this._sessionProblems[deviceKey] || [];
|
||||||
|
if (!problems.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const lastProblem = problems[problems.length - 1];
|
||||||
|
for (const problem of problems) {
|
||||||
|
if (problem.time > timestamp) {
|
||||||
|
return Object.assign({}, problem, {fixed: lastProblem.fixed});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastProblem.fixed) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return lastProblem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async filterOutNotifiedErrorDevices(devices) {
|
||||||
|
const notifiedErrorDevices = this._notifiedErrorDevices;
|
||||||
|
const ret = [];
|
||||||
|
|
||||||
|
for (const device of devices) {
|
||||||
|
const {userId, deviceInfo} = device;
|
||||||
|
if (userId in notifiedErrorDevices) {
|
||||||
|
if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) {
|
||||||
|
ret.push(device);
|
||||||
|
notifiedErrorDevices[userId][deviceInfo.deviceId] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret.push(device);
|
||||||
|
notifiedErrorDevices[userId] = {[deviceInfo.deviceId]: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// Inbound Group Sessions
|
// Inbound Group Sessions
|
||||||
|
|
||||||
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
|
||||||
|
|||||||
Reference in New Issue
Block a user