1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-28 05:03:59 +03:00

Fix idempotency issue around token refresh (#4730)

* Fix idempotency issue around token refresh

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2025-02-27 18:37:47 +00:00
committed by GitHub
parent a3bbc49e02
commit 72b997d1f3
10 changed files with 145 additions and 17 deletions

View File

@@ -31,6 +31,7 @@ import {
} from "./interface.ts";
import { anySignal, parseErrorResponse, timeoutSignal } from "./utils.ts";
import { type QueryDict } from "../utils.ts";
import { singleAsyncExecution } from "../utils/decorators.ts";
interface TypedResponse<T> extends Response {
json(): Promise<T>;
@@ -106,6 +107,12 @@ export class FetchHttpApi<O extends IHttpOpts> {
return this.requestOtherUrl(method, fullUri, body, opts);
}
/**
* Promise used to block authenticated requests during a token refresh to avoid repeated expected errors.
* @private
*/
private tokenRefreshPromise?: Promise<unknown>;
/**
* Perform an authorised request to the homeserver.
* @param method - The HTTP method e.g. "GET".
@@ -162,13 +169,17 @@ export class FetchHttpApi<O extends IHttpOpts> {
}
try {
// Await any ongoing token refresh
await this.tokenRefreshPromise;
const response = await this.request<T>(method, path, queryParams, body, opts);
return response;
} catch (error) {
const err = error as MatrixError;
if (err.errcode === "M_UNKNOWN_TOKEN" && !opts.doNotAttemptTokenRefresh) {
const shouldRetry = await this.tryRefreshToken();
const tokenRefreshPromise = this.tryRefreshToken();
this.tokenRefreshPromise = Promise.allSettled([tokenRefreshPromise]);
const shouldRetry = await tokenRefreshPromise;
// if we got a new token retry the request
if (shouldRetry) {
return this.authedRequest(method, path, queryParams, body, {
@@ -177,6 +188,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
});
}
}
// otherwise continue with error handling
if (err.errcode == "M_UNKNOWN_TOKEN" && !opts?.inhibitLogoutEmit) {
this.eventEmitter.emit(HttpApiEvent.SessionLoggedOut, err);
@@ -193,6 +205,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
* On success, sets new access and refresh tokens in opts.
* @returns Promise that resolves to a boolean - true when token was refreshed successfully
*/
@singleAsyncExecution
private async tryRefreshToken(): Promise<boolean> {
if (!this.opts.refreshToken || !this.opts.tokenRefreshFunction) {
return false;