From 5202037eeb3c9f73ab0b8aa65ee3945254cda20a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:16:29 +0100 Subject: [PATCH 1/4] Retry joinRoom up to 5 times in the case of a 504 GATEWAY TIMEOUT --- src/stores/RoomViewStore.tsx | 25 +++++++++++++++++++------ src/utils/promise.ts | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index a0f0fb8f68..be1141fa1e 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import {Store} from 'flux/utils'; +import {MatrixError} from "matrix-js-sdk/src/http-api"; import dis from '../dispatcher/dispatcher'; import {MatrixClientPeg} from '../MatrixClientPeg'; @@ -26,6 +27,9 @@ import Modal from '../Modal'; import { _t } from '../languageHandler'; import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache'; import {ActionPayload} from "../dispatcher/payloads"; +import {retry} from "../utils/promise"; + +const NUM_JOIN_RETRY = 5; const INITIAL_STATE = { // Whether we're joining the currently viewed room (see isJoining()) @@ -259,24 +263,32 @@ class RoomViewStore extends Store { }); } - private joinRoom(payload: ActionPayload) { + private async joinRoom(payload: ActionPayload) { this.setState({ joining: true, }); - MatrixClientPeg.get().joinRoom( - this.state.roomAlias || this.state.roomId, payload.opts, - ).then(() => { + + const cli = MatrixClientPeg.get(); + const address = this.state.roomAlias || this.state.roomId; + try { + await retry(() => cli.joinRoom(address, payload.opts), NUM_JOIN_RETRY, (err) => { + // if we received a Gateway timeout then retry + return err.httpStatus === 504; + }); + // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. dis.dispatch({ action: 'join_room_ready' }); - }, (err) => { + } catch (err) { dis.dispatch({ action: 'join_room_error', err: err, }); + let msg = err.message ? err.message : JSON.stringify(err); console.log("Failed to join room:", msg); + if (err.name === "ConnectionError") { msg = _t("There was an error joining the room"); } else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') { @@ -296,12 +308,13 @@ class RoomViewStore extends Store { } } } + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, { title: _t("Failed to join room"), description: msg, }); - }); + } } private getInvitingUserId(roomId: string): string { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index d3ae2c3d1b..f828ddfdaf 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -68,3 +68,21 @@ export function allSettled(promises: Promise[]): Promise(fn: () => Promise, num: number, predicate?: (e: E) => boolean) { + let lastErr: E; + for (let i = 0; i < num; i++) { + try { + const v = await fn(); + // If `await fn()` throws then we won't reach here + return v; + } catch (err) { + if (predicate && !predicate(err)) { + throw err; + } + lastErr = err; + } + } + throw lastErr; +} From 608249745ae3215c51acb6f28a1af61640f0f9e7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 13:19:47 +0100 Subject: [PATCH 2/4] Attempt to fix tests some more --- src/languageHandler.tsx | 15 ++++++++++++--- test/i18n-test/languageHandler-test.js | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index d9feec95b1..e699f8e301 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -27,6 +27,7 @@ import PlatformPeg from "./PlatformPeg"; // @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config import webpackLangJsonUrl from "$webapp/i18n/languages.json"; import { SettingLevel } from "./settings/SettingLevel"; +import {retry} from "./utils/promise"; const i18nFolder = 'i18n/'; @@ -327,7 +328,7 @@ export function setLanguage(preferredLangs: string | string[]) { console.error("Unable to find an appropriate language"); } - return getLanguage(i18nFolder + availLangs[langToUse].fileName); + return getLanguageRetry(i18nFolder + availLangs[langToUse].fileName); }).then((langData) => { counterpart.registerTranslations(langToUse, langData); counterpart.setLocale(langToUse); @@ -336,7 +337,7 @@ export function setLanguage(preferredLangs: string | string[]) { // Set 'en' as fallback language: if (langToUse !== "en") { - return getLanguage(i18nFolder + availLangs['en'].fileName); + return getLanguageRetry(i18nFolder + availLangs['en'].fileName); } }).then((langData) => { if (langData) counterpart.registerTranslations('en', langData); @@ -482,7 +483,15 @@ function weblateToCounterpart(inTrs: object): object { return outTrs; } -function getLanguage(langPath: string): object { +async function getLanguageRetry(langPath: string, num = 3): Promise { + return retry(() => getLanguage(langPath), num, e => { + console.log("Failed to load i18n", langPath); + console.error(e); + return true; // always retry + }); +} + +function getLanguage(langPath: string): Promise { return new Promise((resolve, reject) => { request( { method: "GET", url: langPath }, diff --git a/test/i18n-test/languageHandler-test.js b/test/i18n-test/languageHandler-test.js index 7968186e9e..b9bc955269 100644 --- a/test/i18n-test/languageHandler-test.js +++ b/test/i18n-test/languageHandler-test.js @@ -12,11 +12,11 @@ describe('languageHandler', function() { languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]); }); - it('translates a string to german', function() { + it('translates a string to german', function(done) { languageHandler.setLanguage('de').then(function() { const translated = languageHandler._t('Rooms'); expect(translated).toBe('Räume'); - }); + }).then(done); }); it('handles plurals', function() { From 8e871c455ce457596bda265a3e6414dd3edd79b0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 13:24:20 +0100 Subject: [PATCH 3/4] Fix german i18n test which was previously broken due to async --- __mocks__/browser-request.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/__mocks__/browser-request.js b/__mocks__/browser-request.js index 7d231fb9db..391be7c54f 100644 --- a/__mocks__/browser-request.js +++ b/__mocks__/browser-request.js @@ -1,4 +1,5 @@ const en = require("../src/i18n/strings/en_EN"); +const de = require("../src/i18n/strings/de_DE"); module.exports = jest.fn((opts, cb) => { const url = opts.url || opts.uri; @@ -8,9 +9,15 @@ module.exports = jest.fn((opts, cb) => { "fileName": "en_EN.json", "label": "English", }, + "de": { + "fileName": "de_DE.json", + "label": "German", + }, })); } else if (url && url.endsWith("en_EN.json")) { cb(undefined, {status: 200}, JSON.stringify(en)); + } else if (url && url.endsWith("de_DE.json")) { + cb(undefined, {status: 200}, JSON.stringify(de)); } else { cb(true, {status: 404}, ""); } From 5f6dec7d189e93bdc7ed15d0ff9895b89e34cea3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 24 Sep 2020 10:09:34 +0100 Subject: [PATCH 4/4] add comments Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- __mocks__/browser-request.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/__mocks__/browser-request.js b/__mocks__/browser-request.js index 391be7c54f..4c59e8a43a 100644 --- a/__mocks__/browser-request.js +++ b/__mocks__/browser-request.js @@ -1,6 +1,10 @@ const en = require("../src/i18n/strings/en_EN"); const de = require("../src/i18n/strings/de_DE"); +// Mock the browser-request for the languageHandler tests to return +// Fake languages.json containing references to en_EN and de_DE +// en_EN.json +// de_DE.json module.exports = jest.fn((opts, cb) => { const url = opts.url || opts.uri; if (url && url.endsWith("languages.json")) {