You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
Make sure that MegolmEncryption.setupPromise always resolves (#2960)
ensureOutboundSession uses and modifies the setupPromise of the
MegolmEncryption class. Some comments suggest that setupPromise will
always resolve, in other words it should never contain a promise that
will get rejected.
Other comments also seem to suggest that the return value of
ensureOutboundSession, a promise as well, may fail.
The critical error here is that the promise that gets set as
the next setupPromise, as well as the promise that ensureOutboundSession
returns, is the same promise.
It seems that the intention was for setupPromise to contain a promise
that will always resolve to either `null` or `OutboundSessionInfo`.
We can see that a couple of lines before we set setupPromise to its new
value we construct a promise that logs and discards errors using the
`Promise.catch()` method.
The `Promise.catch()` method does not mutate the promise, instead it
returns a new promise. The intention of the original author might have
been to set the next setupPromise to the promise which `Promise.catch()`
produces.
This patch modifies the updating of setupPromise in the
ensureOutboundSession so that setupPromise discards errors correctly.
Using `>>=` to represent the promise chaining operation, setupPromise is
now updated using the following logic:
setupPromise = previousSetupPromise >>= setup >>= discardErrors
This commit is contained in:
@@ -490,6 +490,26 @@ describe("MegolmDecryption", function () {
|
||||
|
||||
expect(mockBaseApis.queueToDevice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shouldn't wedge the setup promise if sharing a room key fails", async () => {
|
||||
// @ts-ignore - private field access
|
||||
const initialSetupPromise = await megolmEncryption.setupPromise;
|
||||
expect(initialSetupPromise).toBe(null);
|
||||
|
||||
// @ts-ignore - private field access
|
||||
megolmEncryption.prepareSession = () => {
|
||||
throw new Error("Can't prepare session");
|
||||
};
|
||||
|
||||
await expect(() =>
|
||||
// @ts-ignore - private field access
|
||||
megolmEncryption.ensureOutboundSession(mockRoom, {}, {}, true),
|
||||
).rejects.toThrow();
|
||||
|
||||
// @ts-ignore - private field access
|
||||
const finalSetupPromise = await megolmEncryption.setupPromise;
|
||||
expect(finalSetupPromise).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -248,6 +248,23 @@ export class MegolmEncryption extends EncryptionAlgorithm {
|
||||
* @param singleOlmCreationPhase - Only perform one round of olm
|
||||
* session creation
|
||||
*
|
||||
* This method updates the setupPromise field of the class by chaining a new
|
||||
* call on top of the existing promise, and then catching and discarding any
|
||||
* errors that might happen while setting up the outbound group session. This
|
||||
* is done to ensure that `setupPromise` always resolves to `null` or the
|
||||
* `OutboundSessionInfo`.
|
||||
*
|
||||
* Using `>>=` to represent the promise chaining operation, it does the
|
||||
* following:
|
||||
*
|
||||
* ```
|
||||
* setupPromise = previousSetupPromise >>= setup >>= discardErrors
|
||||
* ```
|
||||
*
|
||||
* The initial value for the `setupPromise` is a promise that resolves to
|
||||
* `null`. The forceDiscardSession() resets setupPromise to this initial
|
||||
* promise.
|
||||
*
|
||||
* @returns Promise which resolves to the
|
||||
* OutboundSessionInfo when setup is complete.
|
||||
*/
|
||||
@@ -278,18 +295,20 @@ export class MegolmEncryption extends EncryptionAlgorithm {
|
||||
};
|
||||
|
||||
// first wait for the previous share to complete
|
||||
const prom = this.setupPromise.then(setup);
|
||||
const fallible = this.setupPromise.then(setup);
|
||||
|
||||
// Ensure any failures are logged for debugging
|
||||
prom.catch((e) => {
|
||||
// Ensure any failures are logged for debugging and make sure that the
|
||||
// promise chain remains unbroken
|
||||
//
|
||||
// setupPromise resolves to `null` or the `OutboundSessionInfo` whether
|
||||
// or not the share succeeds
|
||||
this.setupPromise = fallible.catch((e) => {
|
||||
logger.error(`Failed to setup outbound session in ${this.roomId}`, e);
|
||||
return null;
|
||||
});
|
||||
|
||||
// setupPromise resolves to `session` whether or not the share succeeds
|
||||
this.setupPromise = prom;
|
||||
|
||||
// but we return a promise which only resolves if the share was successful.
|
||||
return prom;
|
||||
return fallible;
|
||||
}
|
||||
|
||||
private async prepareSession(
|
||||
|
||||
Reference in New Issue
Block a user