Merge pull request #2075 from thecodingmachine/codeAPI
Refactoring Error Screen
This commit is contained in:
commit
8e1a462667
@ -2,6 +2,7 @@
|
|||||||
import type { Game } from "../Phaser/Game/Game";
|
import type { Game } from "../Phaser/Game/Game";
|
||||||
import { chatVisibilityStore } from "../Stores/ChatStore";
|
import { chatVisibilityStore } from "../Stores/ChatStore";
|
||||||
import { errorStore } from "../Stores/ErrorStore";
|
import { errorStore } from "../Stores/ErrorStore";
|
||||||
|
import { errorScreenStore } from "../Stores/ErrorScreenStore";
|
||||||
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
||||||
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
||||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||||
@ -13,11 +14,16 @@
|
|||||||
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||||
|
import ErrorScreen from "./UI/ErrorScreen.svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $errorStore.length > 0}
|
{#if $errorScreenStore !== undefined}
|
||||||
|
<div>
|
||||||
|
<ErrorScreen />
|
||||||
|
</div>
|
||||||
|
{:else if $errorStore.length > 0}
|
||||||
<div>
|
<div>
|
||||||
<ErrorDialog />
|
<ErrorDialog />
|
||||||
</div>
|
</div>
|
||||||
|
144
front/src/Components/UI/ErrorScreen.svelte
Normal file
144
front/src/Components/UI/ErrorScreen.svelte
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
|
||||||
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
import { onDestroy } from "svelte";
|
||||||
|
|
||||||
|
import logoImg from "../images/logo-min-white.png";
|
||||||
|
let logo = gameManager?.currentStartedRoom?.loginSceneLogo ?? logoImg;
|
||||||
|
import reload from "../images/reload.png";
|
||||||
|
let errorScreen = get(errorScreenStore);
|
||||||
|
|
||||||
|
function click() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
let details = errorScreen.details;
|
||||||
|
let timeVar = errorScreen.timeToRetry ?? 0;
|
||||||
|
|
||||||
|
if (errorScreen.type === "retry") {
|
||||||
|
let interval = setInterval(() => {
|
||||||
|
if (timeVar <= 1000) click();
|
||||||
|
timeVar -= 1000;
|
||||||
|
}, 1000);
|
||||||
|
onDestroy(() => clearInterval(interval));
|
||||||
|
}
|
||||||
|
|
||||||
|
$: detailsStylized = (details ?? "").replace("{time}", `${timeVar / 1000}`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="errorScreen" transition:fly={{ y: -200, duration: 500 }}>
|
||||||
|
<div style="width: 90%;">
|
||||||
|
<img src={logo} alt="WorkAdventure" class="logo" />
|
||||||
|
<div><img src={$errorScreenStore.image} alt="" class="icon" /></div>
|
||||||
|
{#if $errorScreenStore.type !== "retry"}<h2>{$errorScreenStore.title}</h2>{/if}
|
||||||
|
<p>{$errorScreenStore.subtitle}</p>
|
||||||
|
{#if $errorScreenStore.type !== "retry"}<p class="code">Code : {$errorScreenStore.code}</p>{/if}
|
||||||
|
<p class="details">
|
||||||
|
{detailsStylized}{#if $errorScreenStore.type === "retry"}<div class="loading" />{/if}
|
||||||
|
</p>
|
||||||
|
{#if $errorScreenStore.type === "retry" && $errorScreenStore.canRetryManual}
|
||||||
|
<button type="button" class="nes-btn is-primary button" on:click={click}>
|
||||||
|
<img src={reload} alt="" class="reload" />
|
||||||
|
{$errorScreenStore.buttonTitle}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
main.errorScreen {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
min-width: 300px;
|
||||||
|
z-index: 700;
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: 20px 0;
|
||||||
|
.logo {
|
||||||
|
width: 50%;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
max-height: 25vh;
|
||||||
|
max-width: 50vw;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
height: 125px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
max-height: 25vh;
|
||||||
|
max-width: 50vw;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
p.code {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.6;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
p.details {
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 80%;
|
||||||
|
margin: 0 auto 35px auto;
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 20px;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.loading:after {
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: bottom;
|
||||||
|
-webkit-animation: ellipsis steps(4, end) 900ms infinite;
|
||||||
|
animation: ellipsis steps(4, end) 900ms infinite;
|
||||||
|
content: "\2026";
|
||||||
|
width: 0;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
font-size: 16px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: -19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ellipsis {
|
||||||
|
to {
|
||||||
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes ellipsis {
|
||||||
|
to {
|
||||||
|
width: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
font-size: 14px;
|
||||||
|
.reload {
|
||||||
|
margin-top: -4px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
BIN
front/src/Components/images/logo-min-white.png
Normal file
BIN
front/src/Components/images/logo-min-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
BIN
front/src/Components/images/reload.png
Normal file
BIN
front/src/Components/images/reload.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
@ -25,6 +25,7 @@ import {
|
|||||||
TokenExpiredMessage,
|
TokenExpiredMessage,
|
||||||
WorldConnexionMessage,
|
WorldConnexionMessage,
|
||||||
ErrorMessage as ErrorMessageTsProto,
|
ErrorMessage as ErrorMessageTsProto,
|
||||||
|
ErrorScreenMessage as ErrorScreenMessageTsProto,
|
||||||
UserMovedMessage as UserMovedMessageTsProto,
|
UserMovedMessage as UserMovedMessageTsProto,
|
||||||
GroupUpdateMessage as GroupUpdateMessageTsProto,
|
GroupUpdateMessage as GroupUpdateMessageTsProto,
|
||||||
GroupDeleteMessage as GroupDeleteMessageTsProto,
|
GroupDeleteMessage as GroupDeleteMessageTsProto,
|
||||||
@ -47,6 +48,7 @@ import { Subject } from "rxjs";
|
|||||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||||
import { gameManager } from "../Phaser/Game/GameManager";
|
import { gameManager } from "../Phaser/Game/GameManager";
|
||||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
|
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
|
||||||
|
import { errorScreenStore } from "../Stores/ErrorScreenStore";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -62,6 +64,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
private readonly _errorMessageStream = new Subject<ErrorMessageTsProto>();
|
private readonly _errorMessageStream = new Subject<ErrorMessageTsProto>();
|
||||||
public readonly errorMessageStream = this._errorMessageStream.asObservable();
|
public readonly errorMessageStream = this._errorMessageStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _errorScreenMessageStream = new Subject<ErrorScreenMessageTsProto>();
|
||||||
|
public readonly errorScreenMessageStream = this._errorScreenMessageStream.asObservable();
|
||||||
|
|
||||||
private readonly _roomJoinedMessageStream = new Subject<{
|
private readonly _roomJoinedMessageStream = new Subject<{
|
||||||
connection: RoomConnection;
|
connection: RoomConnection;
|
||||||
room: RoomJoinedMessageInterface;
|
room: RoomJoinedMessageInterface;
|
||||||
@ -477,6 +482,13 @@ export class RoomConnection implements RoomConnection {
|
|||||||
console.error("An error occurred server side: " + message.errorMessage.message);
|
console.error("An error occurred server side: " + message.errorMessage.message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "errorScreenMessage": {
|
||||||
|
this._errorScreenMessageStream.next(message.errorScreenMessage);
|
||||||
|
if (message.errorScreenMessage.code !== "retry") this.closed = true;
|
||||||
|
console.error("An error occurred server side: " + message.errorScreenMessage.code);
|
||||||
|
errorScreenStore.setError(message.errorScreenMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
// Security check: if we forget a "case", the line below will catch the error at compile-time.
|
// Security check: if we forget a "case", the line below will catch the error at compile-time.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { gameManager } from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import { Scene } from "phaser";
|
import { Scene } from "phaser";
|
||||||
import { ErrorScene } from "../Reconnecting/ErrorScene";
|
import { ErrorScene } from "../Reconnecting/ErrorScene";
|
||||||
import { WAError } from "../Reconnecting/WAError";
|
|
||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import { localeDetector } from "../../i18n/locales";
|
import { localeDetector } from "../../i18n/locales";
|
||||||
|
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
|
||||||
|
import { isErrorApiData } from "../../Messages/JsonMessages/ErrorApiData";
|
||||||
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
|
|
||||||
export const EntrySceneName = "EntryScene";
|
export const EntrySceneName = "EntryScene";
|
||||||
|
|
||||||
@ -47,27 +47,13 @@ export class EntryScene extends Scene {
|
|||||||
this.scene.start(nextSceneName);
|
this.scene.start(nextSceneName);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const $LL = get(LL);
|
const errorType = isErrorApiData.safeParse(err?.response?.data);
|
||||||
if (err.response && err.response.status == 404) {
|
if (errorType.success) {
|
||||||
ErrorScene.showError(
|
if (errorType.data.type === "unauthorized") {
|
||||||
new WAError(
|
void connectionManager.logout();
|
||||||
$LL.error.accessLink.title(),
|
} else if (errorType.data.type === "redirect") {
|
||||||
$LL.error.accessLink.subTitle(),
|
window.location.assign(errorType.data.urlToRedirect);
|
||||||
$LL.error.accessLink.details()
|
} else errorScreenStore.setError(err?.response?.data);
|
||||||
),
|
|
||||||
this.scene
|
|
||||||
);
|
|
||||||
} else if (err.response && err.response.status == 403) {
|
|
||||||
ErrorScene.showError(
|
|
||||||
new WAError(
|
|
||||||
$LL.error.connectionRejected.title(),
|
|
||||||
$LL.error.connectionRejected.subTitle({
|
|
||||||
error: err.response.data ? ". \n\r \n\r" + `${err.response.data}` : "",
|
|
||||||
}),
|
|
||||||
$LL.error.connectionRejected.details()
|
|
||||||
),
|
|
||||||
this.scene
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
ErrorScene.showError(err, this.scene);
|
ErrorScene.showError(err, this.scene);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import Image = Phaser.GameObjects.Image;
|
|||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import Text = Phaser.GameObjects.Text;
|
import Text = Phaser.GameObjects.Text;
|
||||||
import ScenePlugin = Phaser.Scenes.ScenePlugin;
|
import ScenePlugin = Phaser.Scenes.ScenePlugin;
|
||||||
import { WAError } from "./WAError";
|
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
|
|
||||||
export const ErrorSceneName = "ErrorScene";
|
export const ErrorSceneName = "ErrorScene";
|
||||||
@ -88,12 +87,6 @@ export class ErrorScene extends Phaser.Scene {
|
|||||||
title: "An error occurred",
|
title: "An error occurred",
|
||||||
subTitle: error,
|
subTitle: error,
|
||||||
});
|
});
|
||||||
} else if (error instanceof WAError) {
|
|
||||||
scene.start(ErrorSceneName, {
|
|
||||||
title: error.title,
|
|
||||||
subTitle: error.subTitle,
|
|
||||||
message: error.details,
|
|
||||||
});
|
|
||||||
} else if (Axios.isAxiosError(error) && error.response) {
|
} else if (Axios.isAxiosError(error) && error.response) {
|
||||||
// Axios HTTP error
|
// Axios HTTP error
|
||||||
// client received an error response (5xx, 4xx)
|
// client received an error response (5xx, 4xx)
|
||||||
|
@ -1,26 +1,65 @@
|
|||||||
export class WAError extends Error {
|
export class WAError extends Error {
|
||||||
|
private _type: string;
|
||||||
|
private _code: string;
|
||||||
private _title: string;
|
private _title: string;
|
||||||
private _subTitle: string;
|
private _subtitle: string;
|
||||||
private _details: string;
|
private _details: string;
|
||||||
|
private _timeToRetry: number;
|
||||||
|
private _canRetryManual: boolean;
|
||||||
|
private _urlToRedirect: string;
|
||||||
|
private _buttonTitle: string;
|
||||||
|
|
||||||
constructor(title: string, subTitle: string, details: string) {
|
constructor(
|
||||||
super(title + " - " + subTitle + " - " + details);
|
type: string,
|
||||||
|
code: string,
|
||||||
|
title: string,
|
||||||
|
subtitle: string,
|
||||||
|
details: string,
|
||||||
|
timeToRetry: number,
|
||||||
|
canRetryManual: boolean,
|
||||||
|
urlToRedirect: string,
|
||||||
|
buttonTitle: string
|
||||||
|
) {
|
||||||
|
super(title + " - " + subtitle + " - " + details);
|
||||||
|
|
||||||
|
this._type = type;
|
||||||
|
this._code = code;
|
||||||
this._title = title;
|
this._title = title;
|
||||||
this._subTitle = subTitle;
|
this._subtitle = subtitle;
|
||||||
this._details = details;
|
this._details = details;
|
||||||
|
this._timeToRetry = timeToRetry;
|
||||||
|
this._canRetryManual = canRetryManual;
|
||||||
|
this._urlToRedirect = urlToRedirect;
|
||||||
|
this._buttonTitle = buttonTitle;
|
||||||
// Set the prototype explicitly.
|
// Set the prototype explicitly.
|
||||||
Object.setPrototypeOf(this, WAError.prototype);
|
Object.setPrototypeOf(this, WAError.prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get type(): string {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
get code(): string {
|
||||||
|
return this._code;
|
||||||
|
}
|
||||||
get title(): string {
|
get title(): string {
|
||||||
return this._title;
|
return this._title;
|
||||||
}
|
}
|
||||||
|
get subtitle(): string {
|
||||||
get subTitle(): string {
|
return this._subtitle;
|
||||||
return this._subTitle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get details(): string {
|
get details(): string {
|
||||||
return this._details;
|
return this._details;
|
||||||
}
|
}
|
||||||
|
get timeToRetry(): number {
|
||||||
|
return this._timeToRetry;
|
||||||
|
}
|
||||||
|
get buttonTitle(): string {
|
||||||
|
return this._buttonTitle;
|
||||||
|
}
|
||||||
|
get urlToRedirect(): string {
|
||||||
|
return this._urlToRedirect;
|
||||||
|
}
|
||||||
|
get canRetryManual(): boolean {
|
||||||
|
return this._canRetryManual;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
18
front/src/Stores/ErrorScreenStore.ts
Normal file
18
front/src/Stores/ErrorScreenStore.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
import { ErrorScreenMessage } from "../Messages/ts-proto-generated/protos/messages";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains one error of type WAError to be displayed.
|
||||||
|
*/
|
||||||
|
function createErrorScreenStore() {
|
||||||
|
const { subscribe, set } = writable<ErrorScreenMessage>(undefined);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
setError: (e: ErrorScreenMessage): void => {
|
||||||
|
set(e);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const errorScreenStore = createErrorScreenStore();
|
46
messages/JsonMessages/ErrorApiData.ts
Normal file
46
messages/JsonMessages/ErrorApiData.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING! The original file is in /messages/JsonMessages.
|
||||||
|
* All other files are automatically copied from this file on container startup / build
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const isErrorApiErrorData = z.object({
|
||||||
|
// @ts-ignore
|
||||||
|
type: z.literal("error"),
|
||||||
|
code: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
subtitle: z.string(),
|
||||||
|
details: z.string(),
|
||||||
|
image: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isErrorApiRetryData = z.object({
|
||||||
|
type: z.literal("retry"),
|
||||||
|
code: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
subtitle: z.string(),
|
||||||
|
details: z.string(),
|
||||||
|
image: z.string(),
|
||||||
|
buttonTitle: z.optional(z.nullable(z.string())),
|
||||||
|
timeToRetry: z.number(),
|
||||||
|
canRetryManual: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isErrorApiRedirectData = z.object({
|
||||||
|
type: z.literal("redirect"),
|
||||||
|
urlToRedirect: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isErrorApiUnauthorizedData = z.object({
|
||||||
|
type: z.literal("unauthorized"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isErrorApiData = z.discriminatedUnion("type", [
|
||||||
|
isErrorApiErrorData,
|
||||||
|
isErrorApiRetryData,
|
||||||
|
isErrorApiRedirectData,
|
||||||
|
isErrorApiUnauthorizedData,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type ErrorApiData = z.infer<typeof isErrorApiData>;
|
@ -217,10 +217,29 @@ message UserLeftMessage {
|
|||||||
int32 userId = 1;
|
int32 userId = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ErrorMessage is only used to console.error the message in the front
|
||||||
|
*/
|
||||||
message ErrorMessage {
|
message ErrorMessage {
|
||||||
string message = 1;
|
string message = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ErrorScreenMessage is used to show the ErrorScreen in the front
|
||||||
|
*/
|
||||||
|
message ErrorScreenMessage {
|
||||||
|
string type = 1;
|
||||||
|
google.protobuf.StringValue code = 2;
|
||||||
|
google.protobuf.StringValue title = 3;
|
||||||
|
google.protobuf.StringValue subtitle = 4;
|
||||||
|
google.protobuf.StringValue details = 5;
|
||||||
|
google.protobuf.Int32Value timeToRetry = 6;
|
||||||
|
google.protobuf.BoolValue canRetryManual = 7;
|
||||||
|
google.protobuf.StringValue urlToRedirect = 8;
|
||||||
|
google.protobuf.StringValue buttonTitle = 9;
|
||||||
|
google.protobuf.StringValue image = 10;
|
||||||
|
}
|
||||||
|
|
||||||
message ItemStateMessage {
|
message ItemStateMessage {
|
||||||
int32 itemId = 1;
|
int32 itemId = 1;
|
||||||
string stateJson = 2;
|
string stateJson = 2;
|
||||||
@ -332,6 +351,7 @@ message ServerToClientMessage {
|
|||||||
FollowAbortMessage followAbortMessage = 23;
|
FollowAbortMessage followAbortMessage = 23;
|
||||||
InvalidTextureMessage invalidTextureMessage = 24;
|
InvalidTextureMessage invalidTextureMessage = 24;
|
||||||
GroupUsersUpdateMessage groupUsersUpdateMessage = 25;
|
GroupUsersUpdateMessage groupUsersUpdateMessage = 25;
|
||||||
|
ErrorScreenMessage errorScreenMessage = 26;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@anatine/zod-openapi": "^1.3.0",
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.21.2",
|
||||||
"circular-json": "^0.5.9",
|
"circular-json": "^0.5.9",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
@ -48,6 +49,7 @@
|
|||||||
"hyper-express": "^5.8.1",
|
"hyper-express": "^5.8.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
|
"openapi3-ts": "^2.0.2",
|
||||||
"openid-client": "^4.7.4",
|
"openid-client": "^4.7.4",
|
||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"qs": "^6.10.3",
|
"qs": "^6.10.3",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { BaseHttpController } from "./BaseHttpController";
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { openIDClient } from "../Services/OpenIDClient";
|
import { openIDClient } from "../Services/OpenIDClient";
|
||||||
@ -172,7 +172,8 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
authTokenData.identifier,
|
authTokenData.identifier,
|
||||||
playUri as string,
|
playUri as string,
|
||||||
IPAddress,
|
IPAddress,
|
||||||
[]
|
[],
|
||||||
|
req.header("accept-language")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (authTokenData.accessToken == undefined) {
|
if (authTokenData.accessToken == undefined) {
|
||||||
@ -224,7 +225,13 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
|
|
||||||
//Get user data from Admin Back Office
|
//Get user data from Admin Back Office
|
||||||
//This is very important to create User Local in LocalStorage in WorkAdventure
|
//This is very important to create User Local in LocalStorage in WorkAdventure
|
||||||
const data = await adminService.fetchMemberDataByUuid(email, playUri as string, IPAddress, []);
|
const data = await adminService.fetchMemberDataByUuid(
|
||||||
|
email,
|
||||||
|
playUri as string,
|
||||||
|
IPAddress,
|
||||||
|
[],
|
||||||
|
req.header("accept-language")
|
||||||
|
);
|
||||||
|
|
||||||
return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale });
|
return res.json({ ...data, authToken, username: userInfo?.username, locale: userInfo?.locale });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -327,13 +334,18 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof organizationMemberToken != "string") throw new Error("No organization token");
|
if (typeof organizationMemberToken != "string") throw new Error("No organization token");
|
||||||
const data = await adminApi.fetchMemberDataByToken(organizationMemberToken, playUri);
|
const data = await adminService.fetchMemberDataByToken(
|
||||||
|
organizationMemberToken,
|
||||||
|
playUri,
|
||||||
|
req.header("accept-language")
|
||||||
|
);
|
||||||
const userUuid = data.userUuid;
|
const userUuid = data.userUuid;
|
||||||
const email = data.email;
|
const email = data.email;
|
||||||
const roomUrl = data.roomUrl;
|
const roomUrl = data.roomUrl;
|
||||||
const mapUrlStart = data.mapUrlStart;
|
const mapUrlStart = data.mapUrlStart;
|
||||||
|
|
||||||
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
|
const authToken = jwtTokenManager.createAuthToken(email || userUuid);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
authToken,
|
authToken,
|
||||||
userUuid,
|
userUuid,
|
||||||
@ -419,7 +431,7 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
|
|
||||||
//get login profile
|
//get login profile
|
||||||
res.status(302);
|
res.status(302);
|
||||||
res.setHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
res.setHeader("Location", adminService.getProfileUrl(authTokenData.accessToken));
|
||||||
res.send("");
|
res.send("");
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -485,7 +497,8 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
* @param email
|
* @param email
|
||||||
* @param playUri
|
* @param playUri
|
||||||
* @param IPAddress
|
* @param IPAddress
|
||||||
* @return FetchMemberDataByUuidResponse|object
|
* @return
|
||||||
|
|object
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private async getUserByUserIdentifier(
|
private async getUserByUserIdentifier(
|
||||||
@ -503,7 +516,7 @@ export class AuthenticateController extends BaseHttpController {
|
|||||||
userRoomToken: undefined,
|
userRoomToken: undefined,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress, []);
|
data = await adminService.fetchMemberDataByUuid(email, playUri, IPAddress, []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("openIDCallback => fetchMemberDataByUuid", err);
|
console.error("openIDCallback => fetchMemberDataByUuid", err);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Server } from "hyper-express";
|
import { Server } from "hyper-express";
|
||||||
import Response from "hyper-express/types/components/http/Response";
|
import Response from "hyper-express/types/components/http/Response";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||||
|
|
||||||
export class BaseHttpController {
|
export class BaseHttpController {
|
||||||
constructor(protected app: Server) {
|
constructor(protected app: Server) {
|
||||||
@ -31,12 +32,15 @@ export class BaseHttpController {
|
|||||||
|
|
||||||
if (axios.isAxiosError(e) && e.response) {
|
if (axios.isAxiosError(e) && e.response) {
|
||||||
res.status(e.response.status);
|
res.status(e.response.status);
|
||||||
res.send(
|
const errorType = isErrorApiData.safeParse(e?.response?.data);
|
||||||
"An error occurred: " +
|
if (!errorType.success) {
|
||||||
e.response.status +
|
res.send(
|
||||||
" " +
|
"An error occurred: " +
|
||||||
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
|
e.response.status +
|
||||||
);
|
" " +
|
||||||
|
(e.response.data && e.response.data.message ? e.response.data.message : e.response.statusText)
|
||||||
|
);
|
||||||
|
} else res.json(errorType.data);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
res.status(500);
|
res.status(500);
|
||||||
|
@ -25,7 +25,7 @@ import {
|
|||||||
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
|
import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenManager";
|
||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import { socketManager } from "../Services/SocketManager";
|
import { socketManager } from "../Services/SocketManager";
|
||||||
import { emitInBatch } from "../Services/IoSocketHelpers";
|
import { emitInBatch } from "../Services/IoSocketHelpers";
|
||||||
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||||
@ -39,6 +39,8 @@ import { localWokaService } from "../Services/LocalWokaService";
|
|||||||
import { WebSocket } from "uWebSockets.js";
|
import { WebSocket } from "uWebSockets.js";
|
||||||
import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
import { WokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { adminService } from "../Services/AdminService";
|
||||||
|
import { ErrorApiData, isErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The object passed between the "open" and the "upgrade" methods when opening a websocket
|
* The object passed between the "open" and the "upgrade" methods when opening a websocket
|
||||||
@ -66,13 +68,21 @@ interface UpgradeData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpgradeFailedData {
|
interface UpgradeFailedInvalidData {
|
||||||
rejected: true;
|
rejected: true;
|
||||||
reason: "tokenInvalid" | "textureInvalid" | null;
|
reason: "tokenInvalid" | "textureInvalid" | null;
|
||||||
message: string;
|
message: string;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UpgradeFailedErrorData {
|
||||||
|
rejected: true;
|
||||||
|
reason: "error";
|
||||||
|
error: ErrorApiData;
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpgradeFailedData = UpgradeFailedErrorData | UpgradeFailedInvalidData;
|
||||||
|
|
||||||
export class IoSocketController {
|
export class IoSocketController {
|
||||||
private nextUserId: number = 1;
|
private nextUserId: number = 1;
|
||||||
|
|
||||||
@ -234,6 +244,7 @@ export class IoSocketController {
|
|||||||
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
const websocketProtocol = req.getHeader("sec-websocket-protocol");
|
||||||
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
const websocketExtensions = req.getHeader("sec-websocket-extensions");
|
||||||
const IPAddress = req.getHeader("x-forwarded-for");
|
const IPAddress = req.getHeader("x-forwarded-for");
|
||||||
|
const locale = req.getHeader("accept-language");
|
||||||
|
|
||||||
const roomId = query.roomId;
|
const roomId = query.roomId;
|
||||||
try {
|
try {
|
||||||
@ -302,41 +313,46 @@ export class IoSocketController {
|
|||||||
if (ADMIN_API_URL) {
|
if (ADMIN_API_URL) {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
userData = await adminApi.fetchMemberDataByUuid(
|
userData = await adminService.fetchMemberDataByUuid(
|
||||||
userIdentifier,
|
userIdentifier,
|
||||||
roomId,
|
roomId,
|
||||||
IPAddress,
|
IPAddress,
|
||||||
characterLayers
|
characterLayers,
|
||||||
|
locale
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (Axios.isAxiosError(err)) {
|
if (Axios.isAxiosError(err)) {
|
||||||
if (err?.response?.status == 404) {
|
const errorType = isErrorApiData.safeParse(err?.response?.data);
|
||||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
if (errorType.success) {
|
||||||
|
|
||||||
console.warn(
|
|
||||||
'Cannot find user with email "' +
|
|
||||||
(userIdentifier || "anonymous") +
|
|
||||||
'". Performing an anonymous login instead.'
|
|
||||||
);
|
|
||||||
} else if (err?.response?.status == 403) {
|
|
||||||
// If we get an HTTP 403, the world is full. We need to broadcast a special error to the client.
|
|
||||||
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
|
|
||||||
return res.upgrade(
|
return res.upgrade(
|
||||||
{
|
{
|
||||||
rejected: true,
|
rejected: true,
|
||||||
message: err?.response?.data.message,
|
reason: "error",
|
||||||
status: err?.response?.status,
|
status: err?.response?.status,
|
||||||
roomId,
|
error: errorType.data,
|
||||||
},
|
} as UpgradeFailedData,
|
||||||
|
websocketKey,
|
||||||
|
websocketProtocol,
|
||||||
|
websocketExtensions,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return res.upgrade(
|
||||||
|
{
|
||||||
|
rejected: true,
|
||||||
|
reason: null,
|
||||||
|
status: 500,
|
||||||
|
message: err?.response?.data,
|
||||||
|
roomId: roomId,
|
||||||
|
} as UpgradeFailedData,
|
||||||
websocketKey,
|
websocketKey,
|
||||||
websocketProtocol,
|
websocketProtocol,
|
||||||
websocketExtensions,
|
websocketExtensions,
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
memberMessages = userData.messages;
|
memberMessages = userData.messages;
|
||||||
memberTags = userData.tags;
|
memberTags = userData.tags;
|
||||||
@ -480,8 +496,8 @@ export class IoSocketController {
|
|||||||
socketManager.emitTokenExpiredMessage(ws);
|
socketManager.emitTokenExpiredMessage(ws);
|
||||||
} else if (ws.reason === "textureInvalid") {
|
} else if (ws.reason === "textureInvalid") {
|
||||||
socketManager.emitInvalidTextureMessage(ws);
|
socketManager.emitInvalidTextureMessage(ws);
|
||||||
} else if (ws.message === "World is full") {
|
} else if (ws.reason === "error") {
|
||||||
socketManager.emitWorldFullMessage(ws);
|
socketManager.emitErrorScreenMessage(ws, ws.error);
|
||||||
} else {
|
} else {
|
||||||
socketManager.emitConnexionErrorMessage(ws, ws.message);
|
socketManager.emitConnexionErrorMessage(ws, ws.message);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { adminApi } from "../Services/AdminApi";
|
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||||
import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
|
||||||
import { isMapDetailsData, MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
|
||||||
import { InvalidTokenError } from "./InvalidTokenError";
|
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { BaseHttpController } from "./BaseHttpController";
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
|
import { adminService } from "../Services/AdminService";
|
||||||
|
|
||||||
export class MapController extends BaseHttpController {
|
export class MapController extends BaseHttpController {
|
||||||
// Returns a map mapping map name to file name of the map
|
// Returns a map mapping map name to file name of the map
|
||||||
@ -107,66 +104,17 @@ export class MapController extends BaseHttpController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no admin URL is set, let's react on '/_/[instance]/[map url]' URLs
|
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
const roomUrl = new URL(query.playUri);
|
|
||||||
|
|
||||||
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
|
|
||||||
if (!match) {
|
|
||||||
res.status(404);
|
|
||||||
res.json({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapUrl = roomUrl.protocol + "//" + match[1];
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
mapUrl,
|
|
||||||
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
|
|
||||||
roomSlug: null, // Deprecated
|
|
||||||
group: null,
|
|
||||||
tags: [],
|
|
||||||
contactPage: null,
|
|
||||||
authenticationMandatory: DISABLE_ANONYMOUS,
|
|
||||||
} as MapDetailsData);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
let userId: string | undefined = undefined;
|
|
||||||
if (query.authToken != undefined) {
|
|
||||||
let authTokenData: AuthTokenData;
|
|
||||||
try {
|
|
||||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string);
|
|
||||||
userId = authTokenData.identifier;
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
// Decode token, in this case we don't need to create new token.
|
|
||||||
authTokenData = jwtTokenManager.verifyJWTToken(query.authToken as string, true);
|
|
||||||
userId = authTokenData.identifier;
|
|
||||||
console.info("JWT expire, but decoded", userId);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof InvalidTokenError) {
|
|
||||||
// The token was not good, redirect user on login page
|
|
||||||
res.status(401);
|
|
||||||
res.send("Token decrypted error");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.castErrorToResponse(e, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mapDetails = isMapDetailsData.parse(
|
const mapDetails = isMapDetailsData.parse(
|
||||||
await adminApi.fetchMapDetails(query.playUri as string, userId)
|
await adminService.fetchMapDetails(
|
||||||
|
query.playUri as string,
|
||||||
|
query.authToken as string,
|
||||||
|
req.header("accept-language")
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (DISABLE_ANONYMOUS) {
|
if (DISABLE_ANONYMOUS) mapDetails.authenticationMandatory = true;
|
||||||
mapDetails.authenticationMandatory = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(mapDetails);
|
res.json(mapDetails);
|
||||||
return;
|
return;
|
||||||
|
@ -7,6 +7,8 @@ import { z } from "zod";
|
|||||||
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
||||||
import qs from "qs";
|
import qs from "qs";
|
||||||
import { AdminInterface } from "./AdminInterface";
|
import { AdminInterface } from "./AdminInterface";
|
||||||
|
import { AuthTokenData, jwtTokenManager } from "./JWTTokenManager";
|
||||||
|
import { InvalidTokenError } from "../Controller/InvalidTokenError";
|
||||||
|
|
||||||
export interface AdminBannedData {
|
export interface AdminBannedData {
|
||||||
is_banned: boolean;
|
is_banned: boolean;
|
||||||
@ -27,14 +29,37 @@ export const isFetchMemberDataByUuidResponse = z.object({
|
|||||||
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
|
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
|
||||||
|
|
||||||
class AdminApi implements AdminInterface {
|
class AdminApi implements AdminInterface {
|
||||||
/**
|
async fetchMapDetails(
|
||||||
* @var playUri: is url of the room
|
playUri: string,
|
||||||
* @var userId: can to be undefined or email or uuid
|
authToken?: string,
|
||||||
* @return MapDetailsData|RoomRedirect
|
locale?: string
|
||||||
*/
|
): Promise<MapDetailsData | RoomRedirect> {
|
||||||
async fetchMapDetails(playUri: string, userId?: string): Promise<MapDetailsData | RoomRedirect> {
|
let userId: string | undefined = undefined;
|
||||||
if (!ADMIN_API_URL) {
|
if (authToken != undefined) {
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
let authTokenData: AuthTokenData;
|
||||||
|
try {
|
||||||
|
authTokenData = jwtTokenManager.verifyJWTToken(authToken);
|
||||||
|
userId = authTokenData.identifier;
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
// Decode token, in this case we don't need to create new token.
|
||||||
|
authTokenData = jwtTokenManager.verifyJWTToken(authToken, true);
|
||||||
|
userId = authTokenData.identifier;
|
||||||
|
console.info("JWT expire, but decoded", userId);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof InvalidTokenError) {
|
||||||
|
throw new Error("Token decrypted error");
|
||||||
|
// The token was not good, redirect user on login page
|
||||||
|
//res.status(401);
|
||||||
|
//res.send("Token decrypted error");
|
||||||
|
//return;
|
||||||
|
} else {
|
||||||
|
throw new Error("Error on decryption of token :" + e);
|
||||||
|
//this.castErrorToResponse(e, res);
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const params: { playUri: string; userId?: string } = {
|
const params: { playUri: string; userId?: string } = {
|
||||||
@ -43,7 +68,7 @@ class AdminApi implements AdminInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
|
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,11 +94,9 @@ class AdminApi implements AdminInterface {
|
|||||||
userIdentifier: string,
|
userIdentifier: string,
|
||||||
playUri: string,
|
playUri: string,
|
||||||
ipAddress: string,
|
ipAddress: string,
|
||||||
characterLayers: string[]
|
characterLayers: string[],
|
||||||
|
locale?: string
|
||||||
): Promise<FetchMemberDataByUuidResponse> {
|
): Promise<FetchMemberDataByUuidResponse> {
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
|
||||||
}
|
|
||||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
|
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
|
||||||
params: {
|
params: {
|
||||||
userIdentifier,
|
userIdentifier,
|
||||||
@ -81,7 +104,7 @@ class AdminApi implements AdminInterface {
|
|||||||
ipAddress,
|
ipAddress,
|
||||||
characterLayers,
|
characterLayers,
|
||||||
},
|
},
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
paramsSerializer: (p) => {
|
paramsSerializer: (p) => {
|
||||||
return qs.stringify(p, { arrayFormat: "brackets" });
|
return qs.stringify(p, { arrayFormat: "brackets" });
|
||||||
},
|
},
|
||||||
@ -100,14 +123,15 @@ class AdminApi implements AdminInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchMemberDataByToken(organizationMemberToken: string, playUri: string | null): Promise<AdminApiData> {
|
async fetchMemberDataByToken(
|
||||||
if (!ADMIN_API_URL) {
|
organizationMemberToken: string,
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
playUri: string | null,
|
||||||
}
|
locale?: string
|
||||||
|
): Promise<AdminApiData> {
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||||
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
||||||
params: { playUri },
|
params: { playUri },
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const adminApiData = isAdminApiData.safeParse(res.data);
|
const adminApiData = isAdminApiData.safeParse(res.data);
|
||||||
@ -125,11 +149,9 @@ class AdminApi implements AdminInterface {
|
|||||||
reportedUserUuid: string,
|
reportedUserUuid: string,
|
||||||
reportedUserComment: string,
|
reportedUserComment: string,
|
||||||
reporterUserUuid: string,
|
reporterUserUuid: string,
|
||||||
reportWorldSlug: string
|
reportWorldSlug: string,
|
||||||
|
locale?: string
|
||||||
) {
|
) {
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
|
||||||
}
|
|
||||||
return Axios.post(
|
return Axios.post(
|
||||||
`${ADMIN_API_URL}/api/report`,
|
`${ADMIN_API_URL}/api/report`,
|
||||||
{
|
{
|
||||||
@ -139,15 +161,17 @@ class AdminApi implements AdminInterface {
|
|||||||
reportWorldSlug,
|
reportWorldSlug,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string): Promise<AdminBannedData> {
|
async verifyBanUser(
|
||||||
if (!ADMIN_API_URL) {
|
userUuid: string,
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
ipAddress: string,
|
||||||
}
|
roomUrl: string,
|
||||||
|
locale?: string
|
||||||
|
): Promise<AdminBannedData> {
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||||
return Axios.get(
|
return Axios.get(
|
||||||
ADMIN_API_URL +
|
ADMIN_API_URL +
|
||||||
@ -158,28 +182,20 @@ class AdminApi implements AdminInterface {
|
|||||||
encodeURIComponent(userUuid) +
|
encodeURIComponent(userUuid) +
|
||||||
"&roomUrl=" +
|
"&roomUrl=" +
|
||||||
encodeURIComponent(roomUrl),
|
encodeURIComponent(roomUrl),
|
||||||
{ headers: { Authorization: `${ADMIN_API_TOKEN}` } }
|
{ headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" } }
|
||||||
).then((data) => {
|
).then((data) => {
|
||||||
return data.data;
|
return data.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUrlRoomsFromSameWorld(roomUrl: string): Promise<string[]> {
|
async getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]> {
|
||||||
if (!ADMIN_API_URL) {
|
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}` },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
}).then((data) => {
|
}).then((data) => {
|
||||||
return data.data;
|
return data.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param accessToken
|
|
||||||
*/
|
|
||||||
getProfileUrl(accessToken: string): string {
|
getProfileUrl(accessToken: string): string {
|
||||||
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
||||||
throw new Error("No admin backoffice set!");
|
throw new Error("No admin backoffice set!");
|
||||||
@ -187,7 +203,7 @@ class AdminApi implements AdminInterface {
|
|||||||
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async logoutOauth(token: string) {
|
async logoutOauth(token: string): Promise<void> {
|
||||||
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,82 @@
|
|||||||
import { FetchMemberDataByUuidResponse } from "./AdminApi";
|
import { AdminBannedData, FetchMemberDataByUuidResponse } from "./AdminApi";
|
||||||
|
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
|
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||||
|
import { AdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||||
|
|
||||||
export interface AdminInterface {
|
export interface AdminInterface {
|
||||||
|
/**
|
||||||
|
* @var playUri: is url of the room
|
||||||
|
* @var userIdentifier: can to be undefined or email or uuid
|
||||||
|
* @var ipAddress
|
||||||
|
* @var characterLayers
|
||||||
|
* @return MapDetailsData|RoomRedirect
|
||||||
|
*/
|
||||||
fetchMemberDataByUuid(
|
fetchMemberDataByUuid(
|
||||||
userIdentifier: string,
|
userIdentifier: string,
|
||||||
playUri: string,
|
playUri: string,
|
||||||
ipAddress: string,
|
ipAddress: string,
|
||||||
characterLayers: string[]
|
characterLayers: string[],
|
||||||
|
locale?: string
|
||||||
): Promise<FetchMemberDataByUuidResponse>;
|
): Promise<FetchMemberDataByUuidResponse>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var playUri: is url of the room
|
||||||
|
* @var userId: can to be undefined or email or uuid
|
||||||
|
* @return MapDetailsData|RoomRedirect
|
||||||
|
*/
|
||||||
|
fetchMapDetails(playUri: string, authToken?: string, locale?: string): Promise<MapDetailsData | RoomRedirect>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale
|
||||||
|
* @param organizationMemberToken
|
||||||
|
* @param playUri
|
||||||
|
* @return AdminApiData
|
||||||
|
*/
|
||||||
|
fetchMemberDataByToken(
|
||||||
|
organizationMemberToken: string,
|
||||||
|
playUri: string | null,
|
||||||
|
locale?: string
|
||||||
|
): Promise<AdminApiData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale
|
||||||
|
* @param reportedUserUuid
|
||||||
|
* @param reportedUserComment
|
||||||
|
* @param reporterUserUuid
|
||||||
|
* @param reportWorldSlug
|
||||||
|
*/
|
||||||
|
reportPlayer(
|
||||||
|
reportedUserUuid: string,
|
||||||
|
reportedUserComment: string,
|
||||||
|
reporterUserUuid: string,
|
||||||
|
reportWorldSlug: string,
|
||||||
|
locale?: string
|
||||||
|
): Promise<unknown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale
|
||||||
|
* @param userUuid
|
||||||
|
* @param ipAddress
|
||||||
|
* @param roomUrl
|
||||||
|
* @return AdminBannedData
|
||||||
|
*/
|
||||||
|
verifyBanUser(userUuid: string, ipAddress: string, roomUrl: string, locale?: string): Promise<AdminBannedData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale
|
||||||
|
* @param roomUrl
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param accessToken
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
getProfileUrl(accessToken: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param token
|
||||||
|
*/
|
||||||
|
logoutOauth(token: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { FetchMemberDataByUuidResponse } from "./AdminApi";
|
import { AdminBannedData, FetchMemberDataByUuidResponse } from "./AdminApi";
|
||||||
import { AdminInterface } from "./AdminInterface";
|
import { AdminInterface } from "./AdminInterface";
|
||||||
|
import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
|
import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||||
|
import { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||||
|
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||||
|
import { AdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A local class mocking a real admin if no admin is configured.
|
* A local class mocking a real admin if no admin is configured.
|
||||||
@ -12,7 +17,9 @@ class LocalAdmin implements AdminInterface {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
ipAddress: string,
|
ipAddress: string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
characterLayers: string[]
|
characterLayers: string[],
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
locale?: string
|
||||||
): Promise<FetchMemberDataByUuidResponse> {
|
): Promise<FetchMemberDataByUuidResponse> {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
email: userIdentifier,
|
email: userIdentifier,
|
||||||
@ -24,6 +31,99 @@ class LocalAdmin implements AdminInterface {
|
|||||||
userRoomToken: undefined,
|
userRoomToken: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchMapDetails(
|
||||||
|
playUri: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
authToken?: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
locale?: string
|
||||||
|
): Promise<MapDetailsData | RoomRedirect> {
|
||||||
|
const roomUrl = new URL(playUri);
|
||||||
|
|
||||||
|
const match = /\/_\/[^/]+\/(.+)/.exec(roomUrl.pathname);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error("URL format is not good");
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapUrl = roomUrl.protocol + "//" + match[1];
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
mapUrl,
|
||||||
|
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
|
||||||
|
tags: [],
|
||||||
|
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||||
|
roomSlug: null,
|
||||||
|
contactPage: null,
|
||||||
|
group: null,
|
||||||
|
iframeAuthentication: null,
|
||||||
|
loadingLogo: null,
|
||||||
|
loginSceneLogo: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchMemberDataByToken(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
organizationMemberToken: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
playUri: string | null,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
locale?: string
|
||||||
|
): Promise<AdminApiData> {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
reportPlayer(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
reportedUserUuid: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
reportedUserComment: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
reporterUserUuid: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
reportWorldSlug: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
locale?: string
|
||||||
|
) {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyBanUser(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
userUuid: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
ipAddress: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
roomUrl: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
locale?: string
|
||||||
|
): Promise<AdminBannedData> {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUrlRoomsFromSameWorld(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
roomUrl: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
locale?: string
|
||||||
|
): Promise<string[]> {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
getProfileUrl(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
accessToken: string
|
||||||
|
): string {
|
||||||
|
new Error("No admin backoffice set!");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async logoutOauth(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
token: string
|
||||||
|
): Promise<void> {
|
||||||
|
return Promise.reject(new Error("No admin backoffice set!"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const localAdmin = new LocalAdmin();
|
export const localAdmin = new LocalAdmin();
|
||||||
|
@ -39,10 +39,10 @@ import {
|
|||||||
PlayerDetailsUpdatedMessage,
|
PlayerDetailsUpdatedMessage,
|
||||||
LockGroupPromptMessage,
|
LockGroupPromptMessage,
|
||||||
InvalidTextureMessage,
|
InvalidTextureMessage,
|
||||||
|
ErrorScreenMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
|
||||||
import { adminApi } from "./AdminApi";
|
|
||||||
import { emitInBatch } from "./IoSocketHelpers";
|
import { emitInBatch } from "./IoSocketHelpers";
|
||||||
import Jwt from "jsonwebtoken";
|
import Jwt from "jsonwebtoken";
|
||||||
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
import { clientEventsEmitter } from "./ClientEventsEmitter";
|
||||||
@ -53,6 +53,9 @@ import Debug from "debug";
|
|||||||
import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface";
|
import { ExAdminSocketInterface } from "../Model/Websocket/ExAdminSocketInterface";
|
||||||
import { compressors } from "hyper-express";
|
import { compressors } from "hyper-express";
|
||||||
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
|
import { adminService } from "./AdminService";
|
||||||
|
import { ErrorApiData } from "../Messages/JsonMessages/ErrorApiData";
|
||||||
|
import { BoolValue, Int32Value, StringValue } from "google-protobuf/google/protobuf/wrappers_pb";
|
||||||
|
|
||||||
const debug = Debug("socket");
|
const debug = Debug("socket");
|
||||||
|
|
||||||
@ -375,7 +378,8 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
||||||
try {
|
try {
|
||||||
await adminApi.reportPlayer(
|
await adminService.reportPlayer(
|
||||||
|
"en",
|
||||||
reportPlayerMessage.getReporteduseruuid(),
|
reportPlayerMessage.getReporteduseruuid(),
|
||||||
reportPlayerMessage.getReportcomment(),
|
reportPlayerMessage.getReportcomment(),
|
||||||
client.userUuid,
|
client.userUuid,
|
||||||
@ -461,7 +465,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateRoomWithAdminData(room: PusherRoom): Promise<void> {
|
public async updateRoomWithAdminData(room: PusherRoom): Promise<void> {
|
||||||
const data = await adminApi.fetchMapDetails(room.roomUrl);
|
const data = await adminService.fetchMapDetails(room.roomUrl);
|
||||||
const mapDetailsData = isMapDetailsData.safeParse(data);
|
const mapDetailsData = isMapDetailsData.safeParse(data);
|
||||||
|
|
||||||
if (mapDetailsData.success) {
|
if (mapDetailsData.success) {
|
||||||
@ -661,6 +665,34 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitErrorScreenMessage(client: compressors.WebSocket, errorApi: ErrorApiData) {
|
||||||
|
const errorMessage = new ErrorScreenMessage();
|
||||||
|
errorMessage.setType(errorApi.type);
|
||||||
|
if (errorApi.type == "retry" || errorApi.type == "error") {
|
||||||
|
errorMessage.setCode(new StringValue().setValue(errorApi.code));
|
||||||
|
errorMessage.setTitle(new StringValue().setValue(errorApi.title));
|
||||||
|
errorMessage.setSubtitle(new StringValue().setValue(errorApi.subtitle));
|
||||||
|
errorMessage.setDetails(new StringValue().setValue(errorApi.details));
|
||||||
|
errorMessage.setImage(new StringValue().setValue(errorApi.image));
|
||||||
|
}
|
||||||
|
if (errorApi.type == "retry") {
|
||||||
|
if (errorApi.buttonTitle) errorMessage.setButtontitle(new StringValue().setValue(errorApi.buttonTitle));
|
||||||
|
if (errorApi.canRetryManual !== undefined)
|
||||||
|
errorMessage.setCanretrymanual(new BoolValue().setValue(errorApi.canRetryManual));
|
||||||
|
if (errorApi.timeToRetry)
|
||||||
|
errorMessage.setTimetoretry(new Int32Value().setValue(Number(errorApi.timeToRetry)));
|
||||||
|
}
|
||||||
|
if (errorApi.type == "redirect" && errorApi.urlToRedirect)
|
||||||
|
errorMessage.setUrltoredirect(new StringValue().setValue(errorApi.urlToRedirect));
|
||||||
|
|
||||||
|
const serverToClientMessage = new ServerToClientMessage();
|
||||||
|
serverToClientMessage.setErrorscreenmessage(errorMessage);
|
||||||
|
|
||||||
|
//if (!client.disconnecting) {
|
||||||
|
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
private refreshRoomData(roomId: string, versionNumber: number): void {
|
private refreshRoomData(roomId: string, versionNumber: number): void {
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
|
//this function is run for every users connected to the room, so we need to make sure the room wasn't already refreshed.
|
||||||
@ -688,7 +720,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
let tabUrlRooms: string[];
|
let tabUrlRooms: string[];
|
||||||
|
|
||||||
if (playGlobalMessageEvent.getBroadcasttoworld()) {
|
if (playGlobalMessageEvent.getBroadcasttoworld()) {
|
||||||
tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl);
|
tabUrlRooms = await adminService.getUrlRoomsFromSameWorld("en", clientRoomUrl);
|
||||||
} else {
|
} else {
|
||||||
tabUrlRooms = [clientRoomUrl];
|
tabUrlRooms = [clientRoomUrl];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user