Merge pull request #1602 from thecodingmachine/e2e_reconnect_tests
Adding a reconnect feature in case first Pusher request fails
This commit is contained in:
commit
6a8717c22f
33 changed files with 1580 additions and 113 deletions
|
@ -56,6 +56,7 @@
|
|||
"queue-typescript": "^1.0.1",
|
||||
"quill": "1.3.6",
|
||||
"quill-delta-to-html": "^0.12.0",
|
||||
"retry-axios": "^2.6.0",
|
||||
"rxjs": "^6.6.3",
|
||||
"simple-peer": "^9.11.0",
|
||||
"socket.io-client": "^2.3.0",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { errorStore } from "../../Stores/ErrorStore";
|
||||
import { errorStore, hasClosableMessagesInErrorStore } from "../../Stores/ErrorStore";
|
||||
|
||||
function close(): boolean {
|
||||
errorStore.clearMessages();
|
||||
errorStore.clearClosableMessages();
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
@ -11,12 +11,14 @@
|
|||
<p class="nes-text is-error title">Error</p>
|
||||
<div class="body">
|
||||
{#each $errorStore as error}
|
||||
<p>{error}</p>
|
||||
<p>{error.message}</p>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button class="nes-btn is-error" on:click={close}>Close</button>
|
||||
</div>
|
||||
{#if $hasClosableMessagesInErrorStore}
|
||||
<div class="button-bar">
|
||||
<button class="nes-btn is-error" on:click={close}>Close</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
37
front/src/Connexion/AxiosUtils.ts
Normal file
37
front/src/Connexion/AxiosUtils.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import axios from "axios";
|
||||
import * as rax from "retry-axios";
|
||||
import { errorStore } from "../Stores/ErrorStore";
|
||||
|
||||
/**
|
||||
* This instance of Axios will retry in case of an issue and display an error message as a HTML overlay.
|
||||
*/
|
||||
export const axiosWithRetry = axios.create();
|
||||
axiosWithRetry.defaults.raxConfig = {
|
||||
instance: axiosWithRetry,
|
||||
retry: Infinity,
|
||||
noResponseRetries: Infinity,
|
||||
|
||||
maxRetryAfter: 60_000,
|
||||
|
||||
// You can detect when a retry is happening, and figure out how many
|
||||
// retry attempts have been made
|
||||
onRetryAttempt: (err) => {
|
||||
const cfg = rax.getConfig(err);
|
||||
console.log(err);
|
||||
console.log(cfg);
|
||||
console.log(`Retry attempt #${cfg?.currentRetryAttempt} on URL '${err.config.url}'`);
|
||||
errorStore.addErrorMessage("Unable to connect to WorkAdventure. Are you connected to internet?", {
|
||||
closable: false,
|
||||
id: "axios_retry",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
axiosWithRetry.interceptors.response.use((res) => {
|
||||
if (res.status < 400) {
|
||||
errorStore.clearMessageById("axios_retry");
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
const interceptorId = rax.attach(axiosWithRetry);
|
|
@ -10,6 +10,7 @@ import { _ServiceWorker } from "../Network/ServiceWorker";
|
|||
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
|
||||
import { userIsConnected } from "../Stores/MenuStore";
|
||||
import { analyticsClient } from "../Administration/AnalyticsClient";
|
||||
import { axiosWithRetry } from "./AxiosUtils";
|
||||
|
||||
class ConnectionManager {
|
||||
private localUser!: LocalUser;
|
||||
|
@ -232,7 +233,7 @@ class ConnectionManager {
|
|||
}
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
|
||||
const data = await axiosWithRetry.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
|
||||
this.localUser = new LocalUser(data.userUuid, [], data.email);
|
||||
this.authToken = data.authToken;
|
||||
if (!isBenchmark) {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import * as rax from "retry-axios";
|
||||
import Axios from "axios";
|
||||
import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS, OPID_LOGIN_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
||||
import type { CharacterTexture } from "./LocalUser";
|
||||
import { localUserStore } from "./LocalUserStore";
|
||||
import axios from "axios";
|
||||
import { axiosWithRetry } from "./AxiosUtils";
|
||||
|
||||
export class MapDetail {
|
||||
constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {}
|
||||
|
@ -90,7 +93,7 @@ export class Room {
|
|||
|
||||
private async getMapDetail(): Promise<MapDetail | RoomRedirect> {
|
||||
try {
|
||||
const result = await Axios.get(`${PUSHER_URL}/map`, {
|
||||
const result = await axiosWithRetry.get(`${PUSHER_URL}/map`, {
|
||||
params: {
|
||||
playUri: this.roomUrl.toString(),
|
||||
authToken: localUserStore.getAuthToken(),
|
||||
|
|
|
@ -255,6 +255,9 @@ export class RoomConnection implements RoomConnection {
|
|||
warningContainerStore.activateWarningContainer();
|
||||
} else if (message.hasRefreshroommessage()) {
|
||||
//todo: implement a way to notify the user the room was refreshed.
|
||||
} else if (message.hasErrormessage()) {
|
||||
const errorMessage = message.getErrormessage() as ErrorMessage;
|
||||
console.error("An error occurred server side: " + errorMessage.getMessage());
|
||||
} else {
|
||||
throw new Error("Unknown message received");
|
||||
}
|
||||
|
|
|
@ -269,7 +269,9 @@ export class GameScene extends DirtyScene {
|
|||
// 127.0.0.1, localhost and *.localhost are considered secure, even on HTTP.
|
||||
// So if we are in https, we can still try to load a HTTP local resource (can be useful for testing purposes)
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure
|
||||
const url = new URL(file.src);
|
||||
const base = new URL(window.location.href);
|
||||
base.pathname = "";
|
||||
const url = new URL(file.src, base.toString());
|
||||
const host = url.host.split(":")[0];
|
||||
if (
|
||||
window.location.protocol === "https:" &&
|
||||
|
@ -322,7 +324,6 @@ export class GameScene extends DirtyScene {
|
|||
this.onMapLoad(data);
|
||||
}
|
||||
|
||||
this.load.bitmapFont("main_font", "resources/fonts/arcade.png", "resources/fonts/arcade.xml");
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(this.load as any).rexWebFont({
|
||||
custom: {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Scene } from "phaser";
|
|||
import { ErrorScene, ErrorSceneName } from "../Reconnecting/ErrorScene";
|
||||
import { WAError } from "../Reconnecting/WAError";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
||||
|
||||
export const EntrySceneName = "EntryScene";
|
||||
|
||||
|
@ -17,6 +18,14 @@ export class EntryScene extends Scene {
|
|||
});
|
||||
}
|
||||
|
||||
// From the very start, let's preload images used in the ReconnectingScene.
|
||||
preload() {
|
||||
this.load.image(ReconnectingTextures.icon, "static/images/favicons/favicon-32x32.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
this.load.bitmapFont(ReconnectingTextures.mainFont, "resources/fonts/arcade.png", "resources/fonts/arcade.xml");
|
||||
this.load.spritesheet("cat", "resources/characters/pipoya/Cat 01-1.png", { frameWidth: 32, frameHeight: 32 });
|
||||
}
|
||||
|
||||
create() {
|
||||
gameManager
|
||||
.init(this.scene)
|
||||
|
|
|
@ -4,6 +4,7 @@ import Sprite = Phaser.GameObjects.Sprite;
|
|||
import Text = Phaser.GameObjects.Text;
|
||||
import ScenePlugin = Phaser.Scenes.ScenePlugin;
|
||||
import { WAError } from "./WAError";
|
||||
import Axios from "axios";
|
||||
|
||||
export const ErrorSceneName = "ErrorScene";
|
||||
enum Textures {
|
||||
|
@ -36,7 +37,11 @@ export class ErrorScene extends Phaser.Scene {
|
|||
preload() {
|
||||
this.load.image(Textures.icon, "static/images/favicons/favicon-32x32.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
this.load.bitmapFont(Textures.mainFont, "resources/fonts/arcade.png", "resources/fonts/arcade.xml");
|
||||
if (!this.cache.bitmapFont.has("main_font")) {
|
||||
// We put this inside a "if" because despite the cache, Phaser will make a query to the XML file. And if there is no connection (which
|
||||
// is not unlikely given the fact we are in an error scene), this will cause an error.
|
||||
this.load.bitmapFont(Textures.mainFont, "resources/fonts/arcade.png", "resources/fonts/arcade.xml");
|
||||
}
|
||||
this.load.spritesheet("cat", "resources/characters/pipoya/Cat 01-1.png", { frameWidth: 32, frameHeight: 32 });
|
||||
}
|
||||
|
||||
|
@ -71,9 +76,9 @@ export class ErrorScene extends Phaser.Scene {
|
|||
/**
|
||||
* Displays the error page, with an error message matching the "error" parameters passed in.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public static showError(error: any, scene: ScenePlugin): void {
|
||||
public static showError(error: unknown, scene: ScenePlugin): void {
|
||||
console.error(error);
|
||||
console.trace();
|
||||
|
||||
if (typeof error === "string" || error instanceof String) {
|
||||
scene.start(ErrorSceneName, {
|
||||
|
@ -86,9 +91,10 @@ export class ErrorScene extends Phaser.Scene {
|
|||
subTitle: error.subTitle,
|
||||
message: error.details,
|
||||
});
|
||||
} else if (error.response) {
|
||||
} else if (Axios.isAxiosError(error) && error.response) {
|
||||
// Axios HTTP error
|
||||
// client received an error response (5xx, 4xx)
|
||||
console.error("Axios error. Request:", error.request, " - Response: ", error.response);
|
||||
scene.start(ErrorSceneName, {
|
||||
title:
|
||||
"HTTP " +
|
||||
|
@ -98,9 +104,10 @@ export class ErrorScene extends Phaser.Scene {
|
|||
subTitle: "An error occurred while accessing URL:",
|
||||
message: error.response.config.url,
|
||||
});
|
||||
} else if (error.request) {
|
||||
} else if (Axios.isAxiosError(error)) {
|
||||
// Axios HTTP error
|
||||
// client never received a response, or request never left
|
||||
console.error("Axios error. No full HTTP response received. Request to URL:", error.config.url);
|
||||
scene.start(ErrorSceneName, {
|
||||
title: "Network error",
|
||||
subTitle: error.message,
|
||||
|
|
|
@ -3,7 +3,7 @@ import Image = Phaser.GameObjects.Image;
|
|||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
|
||||
export const ReconnectingSceneName = "ReconnectingScene";
|
||||
enum ReconnectingTextures {
|
||||
export enum ReconnectingTextures {
|
||||
icon = "icon",
|
||||
mainFont = "main_font",
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ export class ReconnectingScene extends Phaser.Scene {
|
|||
"Connection lost. Reconnecting..."
|
||||
);
|
||||
|
||||
const cat = this.physics.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, "cat");
|
||||
const cat = this.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, "cat");
|
||||
this.anims.create({
|
||||
key: "right",
|
||||
frames: this.anims.generateFrameNumbers("cat", { start: 6, end: 8 }),
|
||||
|
|
|
@ -1,15 +1,27 @@
|
|||
import { writable } from "svelte/store";
|
||||
import { derived, writable } from "svelte/store";
|
||||
|
||||
interface ErrorMessage {
|
||||
id: string | undefined;
|
||||
closable: boolean; // Whether it can be closed by a user action or not
|
||||
message: string | number | boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A store that contains a list of error messages to be displayed.
|
||||
*/
|
||||
function createErrorStore() {
|
||||
const { subscribe, set, update } = writable<string[]>([]);
|
||||
const { subscribe, set, update } = writable<ErrorMessage[]>([]);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
addErrorMessage: (e: string | Error): void => {
|
||||
update((messages: string[]) => {
|
||||
addErrorMessage: (
|
||||
e: string | Error,
|
||||
options?: {
|
||||
closable?: boolean;
|
||||
id?: string;
|
||||
}
|
||||
): void => {
|
||||
update((messages: ErrorMessage[]) => {
|
||||
let message: string;
|
||||
if (e instanceof Error) {
|
||||
message = e.message;
|
||||
|
@ -17,17 +29,35 @@ function createErrorStore() {
|
|||
message = e;
|
||||
}
|
||||
|
||||
if (!messages.includes(message)) {
|
||||
messages.push(message);
|
||||
if (!messages.find((errorMessage) => errorMessage.message === message)) {
|
||||
messages.push({
|
||||
message,
|
||||
closable: options?.closable ?? true,
|
||||
id: options?.id,
|
||||
});
|
||||
}
|
||||
|
||||
return messages;
|
||||
});
|
||||
},
|
||||
clearMessages: (): void => {
|
||||
set([]);
|
||||
clearMessageById: (id: string): void => {
|
||||
update((messages: ErrorMessage[]) => {
|
||||
messages = messages.filter((message) => message.id !== id);
|
||||
return messages;
|
||||
});
|
||||
},
|
||||
clearClosableMessages: (): void => {
|
||||
update((messages: ErrorMessage[]) => {
|
||||
messages = messages.filter((message) => message.closable);
|
||||
return messages;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const errorStore = createErrorStore();
|
||||
|
||||
export const hasClosableMessagesInErrorStore = derived(errorStore, ($errorStore) => {
|
||||
const closableMessage = $errorStore.find((errorMessage) => errorMessage.closable);
|
||||
return !!closableMessage;
|
||||
});
|
||||
|
|
|
@ -32,6 +32,7 @@ module.exports = {
|
|||
rewrites: [{ from: /^_\/.*$/, to: "/index.html" }],
|
||||
disableDotRule: true,
|
||||
},
|
||||
liveReload: process.env.LIVE_RELOAD != "0" && process.env.LIVE_RELOAD != "false",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
|
|
@ -4872,6 +4872,11 @@ ret@~0.1.10:
|
|||
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
||||
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
|
||||
|
||||
retry-axios@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-2.6.0.tgz#d4dc5c8a8e73982e26a705e46a33df99a28723e0"
|
||||
integrity sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==
|
||||
|
||||
retry@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue