diff --git a/src/matrix/error.js b/src/matrix/error.js index a4979952..f8a0c57c 100644 --- a/src/matrix/error.js +++ b/src/matrix/error.js @@ -6,14 +6,20 @@ export class HomeServerError extends Error { this.statusCode = status; } - get isFatal() { - switch (this.errcode) { - - } + get name() { + return "HomeServerError"; } } export {AbortError} from "../utils/error.js"; -export class ConnectionError extends Error { +export class ConnectionError extends Error { + constructor(message, isTimeout) { + super(message || "ConnectionError"); + this.isTimeout = isTimeout; + } + + get name() { + return "ConnectionError"; + } } diff --git a/src/matrix/net/HomeServerApi.js b/src/matrix/net/HomeServerApi.js index 008ee8a9..20a1dd92 100644 --- a/src/matrix/net/HomeServerApi.js +++ b/src/matrix/net/HomeServerApi.js @@ -1,12 +1,13 @@ import { HomeServerError, ConnectionError, + AbortError } from "../error.js"; class RequestWrapper { - constructor(method, url, requestResult) { + constructor(method, url, requestResult, responsePromise) { this._requestResult = requestResult; - this._promise = this._requestResult.response().then(response => { + this._promise = responsePromise.then(response => { // ok? if (response.status >= 200 && response.status < 300) { return response.body; @@ -43,6 +44,35 @@ export class HomeServerApi { return `${this._homeserver}/_matrix/client/r0${csPath}`; } + _abortOnTimeout(timeoutAmount, requestResult, responsePromise) { + const timeout = this._createTimeout(timeoutAmount); + // abort request if timeout finishes first + let timedOut = false; + timeout.elapsed().then( + () => { + timedOut = true; + requestResult.abort(); + }, + () => {} // ignore AbortError + ); + // abort timeout if request finishes first + return responsePromise.then( + response => { + timeout.abort(); + return response; + }, + err => { + timeout.abort(); + // map error to TimeoutError + if (err instanceof AbortError && timedOut) { + throw new ConnectionError(`Request timed out after ${timeoutAmount}ms`, true); + } else { + throw err; + } + } + ); + } + _request(method, url, queryParams, body, options) { const queryString = Object.entries(queryParams || {}) .filter(([, value]) => value !== undefined) @@ -70,23 +100,21 @@ export class HomeServerApi { body: bodyString, }); + let responsePromise = requestResult.response(); + if (options && options.timeout) { - const timeout = this._createTimeout(options.timeout); - // abort request if timeout finishes first - timeout.elapsed().then( - () => requestResult.abort(), - () => {} // ignore AbortError + responsePromise = this._abortOnTimeout( + options.timeout, + requestResult, + responsePromise ); - // abort timeout if request finishes first - const abort = () => timeout.abort(); - requestResult.response().then(abort, abort); } - const wrapper = new RequestWrapper(method, url, requestResult); + const wrapper = new RequestWrapper(method, url, requestResult, responsePromise); if (this._reconnector) { wrapper.response().catch(err => { - if (err instanceof ConnectionError) { + if (err.name === "ConnectionError") { this._reconnector.onRequestFailed(this); } }); diff --git a/src/matrix/net/Reconnector.js b/src/matrix/net/Reconnector.js index 78e5e66f..6855d3e5 100644 --- a/src/matrix/net/Reconnector.js +++ b/src/matrix/net/Reconnector.js @@ -1,6 +1,4 @@ import {createEnum} from "../../utils/enum.js"; -import {AbortError} from "../../utils/error.js"; -import {ConnectionError} from "../error.js" import {ObservableValue} from "../../observable/ObservableValue.js"; export const ConnectionStatus = createEnum( @@ -84,13 +82,14 @@ export class Reconnector { while (!this._versionsResponse) { try { this._setState(ConnectionStatus.Reconnecting); - // use 10s timeout, because we don't want to be waiting for + // use 30s timeout, as a tradeoff between not giving up + // too quickly on a slow server, and not waiting for // a stale connection when we just came online again - const versionsRequest = hsApi.versions({timeout: 10000}); + const versionsRequest = hsApi.versions({timeout: 30000}); this._versionsResponse = await versionsRequest.response(); this._setState(ConnectionStatus.Online); } catch (err) { - if (err instanceof ConnectionError) { + if (err.name === "ConnectionError") { this._setState(ConnectionStatus.Waiting); await this._retryDelay.waitForRetry(); } else { @@ -104,6 +103,7 @@ export class Reconnector { import {Clock as MockClock} from "../../mocks/Clock.js"; import {ExponentialRetryDelay} from "./ExponentialRetryDelay.js"; +import {ConnectionError} from "../error.js" export function tests() { function createHsApiMock(remainingFailures) { diff --git a/src/utils/error.js b/src/utils/error.js index f10f7590..98bea1ce 100644 --- a/src/utils/error.js +++ b/src/utils/error.js @@ -1,2 +1,5 @@ export class AbortError extends Error { -} \ No newline at end of file + get name() { + return "AbortError"; + } +}