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
15
.github/workflows/end_to_end_tests.yml
vendored
15
.github/workflows/end_to_end_tests.yml
vendored
@ -20,6 +20,15 @@ jobs:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2.0.0"
|
||||
|
||||
- name: "Setup NodeJS"
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: "Install dependencies"
|
||||
run: npm install
|
||||
working-directory: "tests"
|
||||
|
||||
- name: "Setup .env file"
|
||||
run: cp .env.template .env
|
||||
|
||||
@ -27,10 +36,10 @@ jobs:
|
||||
run: sudo chown 1000:1000 -R .
|
||||
|
||||
- name: "Start environment"
|
||||
run: docker-compose up -d
|
||||
run: LIVE_RELOAD=0 docker-compose up -d
|
||||
|
||||
- name: "Wait for environment to build (and downloading testcafe image)"
|
||||
run: (docker-compose -f docker-compose.testcafe.yml pull &) && docker-compose logs -f --tail=0 front | grep -q "Compiled successfully"
|
||||
run: (docker-compose -f docker-compose.testcafe.yml build &) && docker-compose logs -f --tail=0 front | grep -q "Compiled successfully"
|
||||
|
||||
# - name: "temp debug: display logs"
|
||||
# run: docker-compose logs
|
||||
@ -42,7 +51,7 @@ jobs:
|
||||
# run: docker-compose logs -f pusher | grep -q "WorkAdventure starting on port"
|
||||
|
||||
- name: "Run tests"
|
||||
run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
||||
run: PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
||||
|
||||
- name: Upload failed tests
|
||||
if: ${{ failure() }}
|
||||
|
@ -59,9 +59,43 @@ $ docker-compose exec back yarn run pretty
|
||||
|
||||
WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test.
|
||||
|
||||
Nevertheless, if your code can be unit tested, please provide a unit test (we use Jasmine).
|
||||
Nevertheless, if your code can be unit tested, please provide a unit test (we use Jasmine), or an end-to-end test (we use Testcafe).
|
||||
|
||||
If you are providing a new feature, you should setup a test map in the `maps/tests` directory. The test map should contain
|
||||
some description text describing how to test the feature. Finally, you should modify the `maps/tests/index.html` file
|
||||
to add a reference to your newly created test map.
|
||||
some description text describing how to test the feature.
|
||||
|
||||
* if the features is meant to be manually tested, you should modify the `maps/tests/index.html` file to add a reference
|
||||
to your newly created test map
|
||||
* if the features can be automatically tested, please provide a testcafe test
|
||||
|
||||
#### Running testcafe tests
|
||||
|
||||
End-to-end tests are available in the "/tests" directory.
|
||||
|
||||
To run these tests locally:
|
||||
|
||||
```console
|
||||
$ LIVE_RELOAD=0 docker-compose up -d
|
||||
$ cd tests
|
||||
$ npm install
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
Note: If your tests fail on a Javascript error in "sockjs", this is due to the
|
||||
Webpack live reload. The Webpack live reload feature is conflicting with testcafe. This is why we recommend starting
|
||||
WorkAdventure with the `LIVE_RELOAD=0` environment variable.
|
||||
|
||||
End-to-end tests can take a while to run. To run only one test, use:
|
||||
|
||||
```console
|
||||
$ npm run test -- tests/[name of the test file].ts
|
||||
```
|
||||
|
||||
You can also run the tests inside a container (but you will not have visual feedbacks on your test, so we recommend using
|
||||
the local tests).
|
||||
|
||||
```console
|
||||
$ LIVE_RELOAD=0 docker-compose up -d
|
||||
# Wait 2-3 minutes for the environment to start, then:
|
||||
$ PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up
|
||||
```
|
||||
|
@ -12,6 +12,7 @@ export class DebugController {
|
||||
|
||||
getDump() {
|
||||
this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => {
|
||||
(async () => {
|
||||
const query = parse(req.getQuery());
|
||||
|
||||
if (query.token !== ADMIN_API_TOKEN) {
|
||||
@ -22,7 +23,9 @@ export class DebugController {
|
||||
.writeStatus("200 OK")
|
||||
.writeHeader("Content-Type", "application/json")
|
||||
.end(
|
||||
stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => {
|
||||
stringify(
|
||||
await Promise.all(socketManager.getWorlds().values()),
|
||||
(key: unknown, value: unknown) => {
|
||||
if (key === "listeners") {
|
||||
return "Listeners";
|
||||
}
|
||||
@ -47,8 +50,14 @@ export class DebugController {
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
);
|
||||
})().catch((e) => {
|
||||
console.error(e);
|
||||
res.writeStatus("500");
|
||||
res.end("An error occurred");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,19 @@
|
||||
version: "3"
|
||||
version: "3.5"
|
||||
services:
|
||||
testcafe:
|
||||
image: testcafe/testcafe:1.17.1
|
||||
working_dir: /tests
|
||||
build: tests/
|
||||
working_dir: /project/tests
|
||||
command:
|
||||
- --dev
|
||||
# Run as root to have the right to access /var/run/docker.sock
|
||||
user: root
|
||||
environment:
|
||||
BROWSER: "chromium --use-fake-device-for-media-stream"
|
||||
PROJECT_DIR: ${PROJECT_DIR}
|
||||
ADMIN_API_TOKEN: ${ADMIN_API_TOKEN}
|
||||
volumes:
|
||||
- ./tests:/tests
|
||||
- ./:/project
|
||||
- ./maps:/maps
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# security_opt:
|
||||
# - seccomp:unconfined
|
||||
|
@ -1,20 +1,21 @@
|
||||
version: "3"
|
||||
version: "3.5"
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik:v2.0
|
||||
image: traefik:v2.5
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --providers.docker
|
||||
- --entryPoints.web.address=:80
|
||||
- --entryPoints.websecure.address=:443
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
# The Web UI (enabled by --api.insecure=true)
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- back
|
||||
- front
|
||||
#depends_on:
|
||||
# - back
|
||||
# - front
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
@ -51,10 +52,12 @@ services:
|
||||
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
||||
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||
OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
|
||||
LIVE_RELOAD: "$LIVE_RELOAD:-true"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
- ./front:/usr/src/app
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)"
|
||||
- "traefik.http.routers.front.entryPoints=web"
|
||||
- "traefik.http.services.front.loadbalancer.server.port=8080"
|
||||
@ -87,6 +90,7 @@ services:
|
||||
volumes:
|
||||
- ./pusher:/usr/src/app
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.pusher.rule=Host(`pusher.workadventure.localhost`)"
|
||||
- "traefik.http.routers.pusher.entryPoints=web"
|
||||
- "traefik.http.services.pusher.loadbalancer.server.port=8080"
|
||||
@ -111,6 +115,7 @@ services:
|
||||
volumes:
|
||||
- ./maps:/var/www/html
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.maps.rule=Host(`maps.workadventure.localhost`)"
|
||||
- "traefik.http.routers.maps.entryPoints=web,traefik"
|
||||
- "traefik.http.services.maps.loadbalancer.server.port=80"
|
||||
@ -142,6 +147,7 @@ services:
|
||||
volumes:
|
||||
- ./back:/usr/src/app
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.back.rule=Host(`api.workadventure.localhost`)"
|
||||
- "traefik.http.routers.back.entryPoints=web"
|
||||
- "traefik.http.services.back.loadbalancer.server.port=8080"
|
||||
@ -160,6 +166,7 @@ services:
|
||||
volumes:
|
||||
- ./uploader:/usr/src/app
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.uploader.rule=Host(`uploader.workadventure.localhost`)"
|
||||
- "traefik.http.routers.uploader.entryPoints=web"
|
||||
- "traefik.http.services.uploader.loadbalancer.server.port=8080"
|
||||
@ -187,6 +194,7 @@ services:
|
||||
redisinsight:
|
||||
image: redislabs/redisinsight:latest
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.redisinsight.rule=Host(`redis.workadventure.localhost`)"
|
||||
- "traefik.http.routers.redisinsight.entryPoints=web"
|
||||
- "traefik.http.services.redisinsight.loadbalancer.server.port=8001"
|
||||
@ -198,6 +206,7 @@ services:
|
||||
icon:
|
||||
image: matthiasluedtke/iconserver:v3.13.0
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.icon.rule=Host(`icon.workadventure.localhost`)"
|
||||
- "traefik.http.routers.icon.entryPoints=web"
|
||||
- "traefik.http.services.icon.loadbalancer.server.port=8080"
|
||||
|
@ -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>
|
||||
{#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
|
||||
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"
|
||||
|
33
maps/tests/Variables/E2ETests/script.js
Normal file
33
maps/tests/Variables/E2ETests/script.js
Normal file
@ -0,0 +1,33 @@
|
||||
WA.onInit().then(() => {
|
||||
console.log('Trying to read variable "doorOpened" whose default property is true. This should display "true".');
|
||||
console.log('doorOpened', WA.state.loadVariable('doorOpened'));
|
||||
|
||||
console.log('Trying to set variable "not_exists". This should display an error in the console, followed by a log saying the error was caught.')
|
||||
WA.state.saveVariable('not_exists', 'foo').catch((e) => {
|
||||
console.log('Successfully caught error: ', e);
|
||||
});
|
||||
|
||||
console.log('Trying to set variable "myvar". This should work.');
|
||||
WA.state.saveVariable('myvar', {'foo': 'bar'});
|
||||
|
||||
console.log('Trying to read variable "myvar". This should display a {"foo": "bar"} object.');
|
||||
console.log(WA.state.loadVariable('myvar'));
|
||||
|
||||
console.log('Trying to set variable "myvar" using proxy. This should work.');
|
||||
WA.state.myvar = {'baz': 42};
|
||||
|
||||
console.log('Trying to read variable "myvar" using proxy. This should display a {"baz": 42} object.');
|
||||
console.log(WA.state.myvar);
|
||||
|
||||
console.log('Trying to set variable "config". This should not work because we are not logged as admin.');
|
||||
WA.state.saveVariable('config', {'foo': 'bar'}).catch(e => {
|
||||
console.log('Successfully caught error because variable "config" is not writable: ', e);
|
||||
});
|
||||
|
||||
console.log('Trying to read variable "readableByAdmin" that can only be read by "admin". We are not admin so we should not get the default value.');
|
||||
if (WA.state.readableByAdmin === true) {
|
||||
console.error('Failed test: readableByAdmin can be read.');
|
||||
} else {
|
||||
console.log('Success test: readableByAdmin was not read.');
|
||||
}
|
||||
});
|
47
maps/tests/Variables/E2ETests/shared_variables.html
Normal file
47
maps/tests/Variables/E2ETests/shared_variables.html
Normal file
@ -0,0 +1,47 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script>
|
||||
var script = document.createElement('script');
|
||||
// Don't do this at home kids! The "document.referrer" part is actually inserting a XSS security.
|
||||
// We are OK in this precise case because the HTML page is hosted on the "maps" domain that contains only static files.
|
||||
document.head.appendChild(script);
|
||||
window.addEventListener('load', () => {
|
||||
console.log('On load');
|
||||
WA.onInit().then(() => {
|
||||
console.log('After WA init');
|
||||
const textField = document.getElementById('textField');
|
||||
textField.value = WA.state.textField;
|
||||
|
||||
textField.addEventListener('change', function (evt) {
|
||||
console.log('saving variable')
|
||||
WA.state.textField = this.value;
|
||||
});
|
||||
|
||||
WA.state.onVariableChange('textField').subscribe((value) => {
|
||||
console.log('variable changed received')
|
||||
textField.value = value;
|
||||
});
|
||||
|
||||
document.getElementById('btn').addEventListener('click', () => {
|
||||
console.log(WA.state.loadVariable('textField'));
|
||||
document.getElementById('placeholder').innerText = WA.state.loadVariable('textField');
|
||||
});
|
||||
|
||||
document.getElementById('setUndefined').addEventListener('click', () => {
|
||||
WA.state.textField = undefined;
|
||||
document.getElementById('textField').value = '';
|
||||
});
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="text" id="textField" />
|
||||
|
||||
<button id="setUndefined">Delete variable</button>
|
||||
|
||||
<button id="btn">Display textField variable value</button>
|
||||
<div id="placeholder"></div>
|
||||
</body>
|
||||
</html>
|
131
maps/tests/Variables/E2ETests/shared_variables.json
Normal file
131
maps/tests/Variables/E2ETests/shared_variables.json
Normal file
@ -0,0 +1,131 @@
|
||||
{ "compressionlevel":-1,
|
||||
"height":10,
|
||||
"infinite":false,
|
||||
"layers":[
|
||||
{
|
||||
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
"height":10,
|
||||
"id":1,
|
||||
"name":"floor",
|
||||
"opacity":1,
|
||||
"properties":[
|
||||
{
|
||||
"name":"openWebsite",
|
||||
"type":"string",
|
||||
"value":"shared_variables.html"
|
||||
},
|
||||
{
|
||||
"name":"openWebsiteAllowApi",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
}],
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"height":10,
|
||||
"id":2,
|
||||
"name":"start",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":3,
|
||||
"name":"floorLayer",
|
||||
"objects":[
|
||||
{
|
||||
"height":67,
|
||||
"id":3,
|
||||
"name":"",
|
||||
"rotation":0,
|
||||
"text":
|
||||
{
|
||||
"fontfamily":"Sans Serif",
|
||||
"pixelsize":11,
|
||||
"text":"Test:\nChange the form\nConnect with another user\n\nResult:\nThe form should open in the same state for the other user\nAlso, a change on one user is directly propagated to the other user",
|
||||
"wrap":true
|
||||
},
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":252.4375,
|
||||
"x":2.78125,
|
||||
"y":2.5
|
||||
},
|
||||
{
|
||||
"height":0,
|
||||
"id":5,
|
||||
"name":"textField",
|
||||
"point":true,
|
||||
"properties":[
|
||||
{
|
||||
"name":"default",
|
||||
"type":"string",
|
||||
"value":"default value"
|
||||
},
|
||||
{
|
||||
"name":"jsonSchema",
|
||||
"type":"string",
|
||||
"value":"{}"
|
||||
},
|
||||
{
|
||||
"name":"persist",
|
||||
"type":"bool",
|
||||
"value":true
|
||||
},
|
||||
{
|
||||
"name":"readableBy",
|
||||
"type":"string",
|
||||
"value":""
|
||||
},
|
||||
{
|
||||
"name":"writableBy",
|
||||
"type":"string",
|
||||
"value":""
|
||||
}],
|
||||
"rotation":0,
|
||||
"type":"variable",
|
||||
"visible":true,
|
||||
"width":0,
|
||||
"x":57.5,
|
||||
"y":111
|
||||
}],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":8,
|
||||
"nextobjectid":10,
|
||||
"orientation":"orthogonal",
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"2021.03.23",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
"columns":11,
|
||||
"firstgid":1,
|
||||
"image":"..\/tileset1.png",
|
||||
"imageheight":352,
|
||||
"imagewidth":352,
|
||||
"margin":0,
|
||||
"name":"tileset1",
|
||||
"spacing":0,
|
||||
"tilecount":121,
|
||||
"tileheight":32,
|
||||
"tilewidth":32
|
||||
}],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":1.5,
|
||||
"width":10
|
||||
}
|
@ -274,12 +274,12 @@ message ServerToClientMessage {
|
||||
SendJitsiJwtMessage sendJitsiJwtMessage = 11;
|
||||
SendUserMessage sendUserMessage = 12;
|
||||
BanUserMessage banUserMessage = 13;
|
||||
AdminRoomMessage adminRoomMessage = 14;
|
||||
//AdminRoomMessage adminRoomMessage = 14;
|
||||
WorldFullWarningMessage worldFullWarningMessage = 15;
|
||||
WorldFullMessage worldFullMessage = 16;
|
||||
RefreshRoomMessage refreshRoomMessage = 17;
|
||||
WorldConnexionMessage worldConnexionMessage = 18;
|
||||
EmoteEventMessage emoteEventMessage = 19;
|
||||
//EmoteEventMessage emoteEventMessage = 19;
|
||||
TokenExpiredMessage tokenExpiredMessage = 20;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana
|
||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||
import { SocketManager, socketManager } from "../Services/SocketManager";
|
||||
import { emitInBatch } from "../Services/IoSocketHelpers";
|
||||
import { ADMIN_SOCKETS_TOKEN, ADMIN_API_URL, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_API_URL, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable";
|
||||
import { Zone } from "_Model/Zone";
|
||||
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
||||
import { CharacterTexture } from "../Services/AdminApi/CharacterTexture";
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, ALLOW_ARTILLERY, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
||||
import { uuid } from "uuidv4";
|
||||
import Jwt, { verify } from "jsonwebtoken";
|
||||
import { TokenInterface } from "../Controller/AuthenticateController";
|
||||
import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||
import { ADMIN_SOCKETS_TOKEN, SECRET_KEY } from "../Enum/EnvironmentVariable";
|
||||
import Jwt from "jsonwebtoken";
|
||||
|
||||
export interface AuthTokenData {
|
||||
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||
|
@ -3,11 +3,28 @@ const BROWSER = process.env.BROWSER || "chrome --use-fake-ui-for-media-stream --
|
||||
module.exports = {
|
||||
"browsers": BROWSER,
|
||||
"hostname": "localhost",
|
||||
//"skipJsErrors": true,
|
||||
"src": "tests/",
|
||||
"screenshots": {
|
||||
"path": "screenshots/",
|
||||
"takeOnFails": true,
|
||||
"thumbnails": false,
|
||||
},
|
||||
"assertionTimeout": 20000,
|
||||
"selectorTimeout": 60000,
|
||||
|
||||
|
||||
"videoPath": "screenshots/videos",
|
||||
"videoOptions": {
|
||||
"failedOnly": true,
|
||||
}
|
||||
/*"skipJsErrors": true,
|
||||
"clientScripts": [ { "content": `
|
||||
window.addEventListener('error', function (e) {
|
||||
if (e instanceof Error && e.message.includes('_jp')) {
|
||||
console.log('Ignoring sockjs related error');
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
` } ]*/
|
||||
}
|
||||
|
5
tests/Dockerfile
Normal file
5
tests/Dockerfile
Normal file
@ -0,0 +1,5 @@
|
||||
FROM testcafe/testcafe:1.17.1
|
||||
|
||||
USER root
|
||||
RUN apk add docker-compose
|
||||
USER user
|
734
tests/package-lock.json
generated
734
tests/package-lock.json
generated
@ -4,7 +4,13 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@types/dockerode": "^3.3.0",
|
||||
"axios": "^0.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dockerode": "^3.3.1",
|
||||
"testcafe": "^1.17.1"
|
||||
}
|
||||
},
|
||||
@ -1804,6 +1810,123 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/darwin-arm64": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz",
|
||||
"integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/darwin-x64": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz",
|
||||
"integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/ffmpeg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz",
|
||||
"integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==",
|
||||
"optionalDependencies": {
|
||||
"@ffmpeg-installer/darwin-arm64": "4.1.5",
|
||||
"@ffmpeg-installer/darwin-x64": "4.1.0",
|
||||
"@ffmpeg-installer/linux-arm": "4.1.3",
|
||||
"@ffmpeg-installer/linux-arm64": "4.1.4",
|
||||
"@ffmpeg-installer/linux-ia32": "4.1.0",
|
||||
"@ffmpeg-installer/linux-x64": "4.1.0",
|
||||
"@ffmpeg-installer/win32-ia32": "4.1.0",
|
||||
"@ffmpeg-installer/win32-x64": "4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/linux-arm": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz",
|
||||
"integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/linux-arm64": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz",
|
||||
"integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/linux-ia32": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz",
|
||||
"integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/linux-x64": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz",
|
||||
"integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/win32-ia32": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz",
|
||||
"integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@ffmpeg-installer/win32-x64": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz",
|
||||
"integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@miherlosev/esm": {
|
||||
"version": "3.2.26",
|
||||
"resolved": "https://registry.npmjs.org/@miherlosev/esm/-/esm-3.2.26.tgz",
|
||||
@ -1848,6 +1971,24 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/docker-modem": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.2.tgz",
|
||||
"integrity": "sha512-qC7prjoEYR2QEe6SmCVfB1x3rfcQtUr1n4x89+3e0wSTMQ/KYCyf+/RAA9n2tllkkNc6//JMUZePdFRiGIWfaQ==",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/ssh2": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/dockerode": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.0.tgz",
|
||||
"integrity": "sha512-3Mc0b2gnypJB8Gwmr+8UVPkwjpf4kg1gVxw8lAI4Y/EzpK50LixU1wBSPN9D+xqiw2Ubb02JO8oM0xpwzvi2mg==",
|
||||
"dependencies": {
|
||||
"@types/docker-modem": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/error-stack-parser": {
|
||||
"version": "1.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/error-stack-parser/-/error-stack-parser-1.3.18.tgz",
|
||||
@ -1885,8 +2026,24 @@
|
||||
"node_modules/@types/node": {
|
||||
"version": "12.20.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz",
|
||||
"integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA=="
|
||||
},
|
||||
"node_modules/@types/ssh2": {
|
||||
"version": "0.5.49",
|
||||
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.49.tgz",
|
||||
"integrity": "sha512-ffxhQhJqgTzrw8NxHTgkaDtAmAj2qxCyoves7ztpRgqvzbHcZTpTcm+ATWuuCbPQzxnnF4F3SGGTLGEWTZpwqA==",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/ssh2-streams": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ssh2-streams": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.9.tgz",
|
||||
"integrity": "sha512-I2J9jKqfmvXLR5GomDiCoHrEJ58hAOmFrekfFqmCFd+A6gaEStvWnPykoWUwld1PNg4G5ag1LwdA+Lz1doRJqg==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-hammerhead": {
|
||||
"version": "0.5.0",
|
||||
@ -2012,6 +2169,15 @@
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||
@ -2048,6 +2214,14 @@
|
||||
"node": ">= 4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-dynamic-import-node": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
|
||||
@ -2153,12 +2327,46 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bin-v8-flags-filter": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bin-v8-flags-filter/-/bin-v8-flags-filter-1.2.0.tgz",
|
||||
"integrity": "sha512-g8aeYkY7GhyyKRvQMBsJQZjhm2iCX3dKYvfrMpwVR8IxmUGrkpCBFoKbB9Rh0o3sTLCjU/1tFpZ4C7j3f+D+3g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl/node_modules/readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
@ -2225,6 +2433,30 @@
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@ -2319,6 +2551,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/chrome-remote-interface": {
|
||||
"version": "0.30.1",
|
||||
"resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.30.1.tgz",
|
||||
@ -2449,6 +2687,20 @@
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cpu-features": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz",
|
||||
"integrity": "sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"nan": "^2.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -2649,6 +2901,60 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/docker-modem": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.3.tgz",
|
||||
"integrity": "sha512-Tgkn2a+yiNP9FoZgMa/D9Wk+D2Db///0KOyKSYZRJa8w4+DzKyzQMkczKSdR/adQ0x46BOpeNkoyEOKjPhCzjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"readable-stream": "^3.5.0",
|
||||
"split-ca": "^1.0.1",
|
||||
"ssh2": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/docker-modem/node_modules/readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/dockerode": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.1.tgz",
|
||||
"integrity": "sha512-AS2mr8Lp122aa5n6d99HkuTNdRV1wkkhHwBdcnY6V0+28D3DSYwhxAk85/mM9XwD3RMliTxyr63iuvn5ZblFYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"docker-modem": "^3.0.0",
|
||||
"tar-fs": "~2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dockerode/node_modules/tar-fs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
|
||||
"integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.3.906",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.906.tgz",
|
||||
@ -2852,6 +3158,31 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz",
|
||||
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@ -3100,6 +3431,26 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz",
|
||||
@ -3549,6 +3900,12 @@
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
@ -3582,6 +3939,13 @@
|
||||
"npm": ">=1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
|
||||
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.3.4.tgz",
|
||||
@ -4281,6 +4645,30 @@
|
||||
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/split-ca": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
|
||||
"integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ssh2": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.5.0.tgz",
|
||||
"integrity": "sha512-iUmRkhH9KGeszQwDW7YyyqjsMTf4z+0o48Cp4xOwlY5LjtbIAvyd3fwnsoUZW/hXmTCRA3yt7S/Jb9uVjErVlA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"asn1": "^0.2.4",
|
||||
"bcrypt-pbkdf": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"cpu-features": "0.0.2",
|
||||
"nan": "^2.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackframe": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-0.3.1.tgz",
|
||||
@ -4355,6 +4743,36 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/testcafe": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/testcafe/-/testcafe-1.17.1.tgz",
|
||||
@ -4889,6 +5307,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
@ -6330,6 +6754,69 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@ffmpeg-installer/darwin-arm64": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz",
|
||||
"integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==",
|
||||
"optional": true
|
||||
},
|
||||
"@ffmpeg-installer/darwin-x64": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz",
|
||||
"integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==",
|
||||
"optional": true
|
||||
},
|
||||
"@ffmpeg-installer/ffmpeg": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz",
|
||||
"integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==",
|
||||
"requires": {
|
||||
"@ffmpeg-installer/darwin-arm64": "4.1.5",
|
||||
"@ffmpeg-installer/darwin-x64": "4.1.0",
|
||||
"@ffmpeg-installer/linux-arm": "4.1.3",
|
||||
"@ffmpeg-installer/linux-arm64": "4.1.4",
|
||||
"@ffmpeg-installer/linux-ia32": "4.1.0",
|
||||
"@ffmpeg-installer/linux-x64": "4.1.0",
|
||||
"@ffmpeg-installer/win32-ia32": "4.1.0",
|
||||
"@ffmpeg-installer/win32-x64": "4.1.0"
|
||||
}
|
||||
},
|
||||
"@ffmpeg-installer/linux-arm": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz",
|
||||
"integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==",
|
||||
"optional": true
|
||||
},
|
||||
"@ffmpeg-installer/linux-arm64": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz",
|
||||
"integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==",
|
||||
"optional": true
|
||||
},
|
||||
"@ffmpeg-installer/linux-ia32": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz",
|
||||
"integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@ffmpeg-installer/linux-x64": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz",
|
||||
"integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==",
|
||||
"optional": true
|
||||
},
|
||||
"@ffmpeg-installer/win32-ia32": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz",
|
||||
"integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==",
|
||||
"optional": true
|
||||
},
|
||||
"@ffmpeg-installer/win32-x64": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz",
|
||||
"integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==",
|
||||
"optional": true
|
||||
},
|
||||
"@miherlosev/esm": {
|
||||
"version": "3.2.26",
|
||||
"resolved": "https://registry.npmjs.org/@miherlosev/esm/-/esm-3.2.26.tgz",
|
||||
@ -6362,6 +6849,24 @@
|
||||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@types/docker-modem": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.2.tgz",
|
||||
"integrity": "sha512-qC7prjoEYR2QEe6SmCVfB1x3rfcQtUr1n4x89+3e0wSTMQ/KYCyf+/RAA9n2tllkkNc6//JMUZePdFRiGIWfaQ==",
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/ssh2": "*"
|
||||
}
|
||||
},
|
||||
"@types/dockerode": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.0.tgz",
|
||||
"integrity": "sha512-3Mc0b2gnypJB8Gwmr+8UVPkwjpf4kg1gVxw8lAI4Y/EzpK50LixU1wBSPN9D+xqiw2Ubb02JO8oM0xpwzvi2mg==",
|
||||
"requires": {
|
||||
"@types/docker-modem": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/error-stack-parser": {
|
||||
"version": "1.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/error-stack-parser/-/error-stack-parser-1.3.18.tgz",
|
||||
@ -6399,8 +6904,24 @@
|
||||
"@types/node": {
|
||||
"version": "12.20.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz",
|
||||
"integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA=="
|
||||
},
|
||||
"@types/ssh2": {
|
||||
"version": "0.5.49",
|
||||
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.49.tgz",
|
||||
"integrity": "sha512-ffxhQhJqgTzrw8NxHTgkaDtAmAj2qxCyoves7ztpRgqvzbHcZTpTcm+ATWuuCbPQzxnnF4F3SGGTLGEWTZpwqA==",
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/ssh2-streams": "*"
|
||||
}
|
||||
},
|
||||
"@types/ssh2-streams": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.9.tgz",
|
||||
"integrity": "sha512-I2J9jKqfmvXLR5GomDiCoHrEJ58hAOmFrekfFqmCFd+A6gaEStvWnPykoWUwld1PNg4G5ag1LwdA+Lz1doRJqg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"acorn-hammerhead": {
|
||||
"version": "0.5.0",
|
||||
@ -6498,6 +7019,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"assertion-error": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||
@ -6522,6 +7052,14 @@
|
||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"babel-plugin-dynamic-import-node": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
|
||||
@ -6600,12 +7138,45 @@
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"bin-v8-flags-filter": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bin-v8-flags-filter/-/bin-v8-flags-filter-1.2.0.tgz",
|
||||
"integrity": "sha512-g8aeYkY7GhyyKRvQMBsJQZjhm2iCX3dKYvfrMpwVR8IxmUGrkpCBFoKbB9Rh0o3sTLCjU/1tFpZ4C7j3f+D+3g==",
|
||||
"dev": true
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
@ -6659,6 +7230,16 @@
|
||||
"picocolors": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@ -6734,6 +7315,12 @@
|
||||
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
|
||||
"dev": true
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
},
|
||||
"chrome-remote-interface": {
|
||||
"version": "0.30.1",
|
||||
"resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.30.1.tgz",
|
||||
@ -6842,6 +7429,16 @@
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"dev": true
|
||||
},
|
||||
"cpu-features": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz",
|
||||
"integrity": "sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "^2.14.1"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -7000,6 +7597,55 @@
|
||||
"path-type": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"docker-modem": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.3.tgz",
|
||||
"integrity": "sha512-Tgkn2a+yiNP9FoZgMa/D9Wk+D2Db///0KOyKSYZRJa8w4+DzKyzQMkczKSdR/adQ0x46BOpeNkoyEOKjPhCzjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"readable-stream": "^3.5.0",
|
||||
"split-ca": "^1.0.1",
|
||||
"ssh2": "^1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dockerode": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.1.tgz",
|
||||
"integrity": "sha512-AS2mr8Lp122aa5n6d99HkuTNdRV1wkkhHwBdcnY6V0+28D3DSYwhxAk85/mM9XwD3RMliTxyr63iuvn5ZblFYQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"docker-modem": "^3.0.0",
|
||||
"tar-fs": "~2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tar-fs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
|
||||
"integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.906",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.906.tgz",
|
||||
@ -7165,6 +7811,17 @@
|
||||
"locate-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz",
|
||||
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA=="
|
||||
},
|
||||
"fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@ -7357,6 +8014,12 @@
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz",
|
||||
@ -7699,6 +8362,12 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
@ -7723,6 +8392,13 @@
|
||||
"integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
|
||||
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.3.4.tgz",
|
||||
@ -8257,6 +8933,24 @@
|
||||
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
|
||||
"dev": true
|
||||
},
|
||||
"split-ca": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
|
||||
"integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=",
|
||||
"dev": true
|
||||
},
|
||||
"ssh2": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.5.0.tgz",
|
||||
"integrity": "sha512-iUmRkhH9KGeszQwDW7YyyqjsMTf4z+0o48Cp4xOwlY5LjtbIAvyd3fwnsoUZW/hXmTCRA3yt7S/Jb9uVjErVlA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asn1": "^0.2.4",
|
||||
"bcrypt-pbkdf": "^1.0.2",
|
||||
"cpu-features": "0.0.2",
|
||||
"nan": "^2.15.0"
|
||||
}
|
||||
},
|
||||
"stackframe": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-0.3.1.tgz",
|
||||
@ -8316,6 +9010,32 @@
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"testcafe": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/testcafe/-/testcafe-1.17.1.tgz",
|
||||
@ -8772,6 +9492,12 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
|
||||
"dev": true
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
|
@ -1,8 +1,14 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"dockerode": "^3.3.1",
|
||||
"testcafe": "^1.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "testcafe"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@types/dockerode": "^3.3.0",
|
||||
"axios": "^0.24.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,38 @@
|
||||
import {assertLogMessage} from "./utils/log";
|
||||
|
||||
const fs = require('fs')
|
||||
const fs = require('fs');
|
||||
const Docker = require('dockerode');
|
||||
import { Selector } from 'testcafe';
|
||||
import {userAlice} from "./utils/roles";
|
||||
import {login} from "./utils/roles";
|
||||
import {findContainer, rebootBack, rebootPusher, resetRedis, startContainer, stopContainer} from "./utils/containers";
|
||||
|
||||
// Note: we are also testing that we can connect if the URL contains a random query string
|
||||
fixture `Variables`
|
||||
.page `http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json?somerandomparam=1`;
|
||||
fixture `Reconnection`
|
||||
.page `http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/mousewheel.json`;
|
||||
|
||||
test("Test that connection can succeed even if WorkAdventure starts while pusher is down", async (t: TestController) => {
|
||||
// Let's stop the pusher
|
||||
const container = await findContainer('pusher');
|
||||
await stopContainer(container);
|
||||
|
||||
const errorMessage = Selector('.error-div');
|
||||
|
||||
await t
|
||||
.navigateTo('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/mousewheel.json')
|
||||
.expect(errorMessage.innerText).contains('Unable to connect to WorkAdventure')
|
||||
|
||||
await startContainer(container);
|
||||
|
||||
await login(t, 'http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/mousewheel.json');
|
||||
|
||||
t.ctx.passed = true;
|
||||
}).after(async t => {
|
||||
if (!t.ctx.passed) {
|
||||
console.log("Test failed. Browser logs:")
|
||||
console.log(await t.getBrowserConsoleMessages());
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
test("Test that variables cache in the back don't prevent setting a variable in case the map changes", async (t: TestController) => {
|
||||
// Let's start by visiting a map that DOES not have the variable.
|
||||
fs.copyFileSync('../maps/tests/Variables/Cache/variables_cache_1.json', '../maps/tests/Variables/Cache/variables_tmp.json');
|
||||
@ -18,11 +43,9 @@ test("Test that variables cache in the back don't prevent setting a variable in
|
||||
// Let's REPLACE the map by a map that has a new variable
|
||||
// At this point, the back server contains a cache of the old map (with no variables)
|
||||
fs.copyFileSync('../maps/tests/Variables/Cache/variables_cache_2.json', '../maps/tests/Variables/Cache/variables_tmp.json');
|
||||
await t.openWindow('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json');
|
||||
|
||||
await t.resizeWindow(960, 800);
|
||||
|
||||
await t.useRole(userAlice);
|
||||
await t.openWindow('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json')
|
||||
.resizeWindow(960, 800)
|
||||
.useRole(userAlice);
|
||||
//.takeScreenshot('after_switch.png');
|
||||
|
||||
// Let's check we successfully manage to save the variable value.
|
||||
@ -35,3 +58,4 @@ test("Test that variables cache in the back don't prevent setting a variable in
|
||||
console.log(await t.getBrowserConsoleMessages());
|
||||
}
|
||||
});
|
||||
*/
|
85
tests/tests/utils/containers.ts
Normal file
85
tests/tests/utils/containers.ts
Normal file
@ -0,0 +1,85 @@
|
||||
//import Docker from "dockerode";
|
||||
//import * as Dockerode from "dockerode";
|
||||
import Dockerode = require( 'dockerode')
|
||||
const util = require('util');
|
||||
const exec = util.promisify(require('child_process').exec);
|
||||
const { execSync } = require('child_process');
|
||||
const path = require("path");
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* Execute Docker compose, passing the correct host directory (in case this is run from the TestCafe container)
|
||||
*/
|
||||
export function dockerCompose(command: string): void {
|
||||
let param = '';
|
||||
const projectDir = process.env.PROJECT_DIR;
|
||||
|
||||
if (!projectDir && fs.existsSync('/project')) {
|
||||
// We are probably in the docker-image AND we did not pass PROJECT_DIR env variable
|
||||
throw new Error('Incorrect docker-compose command used to fire testcafe tests. You need to add a PROJECT_DIR environment variable. Please refer to the CONTRIBUTING.md guide');
|
||||
}
|
||||
|
||||
if (projectDir) {
|
||||
const dirName = path.basename(projectDir);
|
||||
param = '--project-name '+dirName+' --project-directory '+projectDir;
|
||||
}
|
||||
|
||||
let stdout = execSync('docker-compose '+param+' '+command, {
|
||||
cwd: __dirname + '/../../../'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a container ID based on the container name.
|
||||
*/
|
||||
export async function findContainer(name: string): Promise<Dockerode.ContainerInfo> {
|
||||
const docker = new Dockerode();
|
||||
|
||||
const containers = await docker.listContainers();
|
||||
|
||||
const foundContainer = containers.find((container) => container.State === 'running' && container.Names.find((containerName) => containerName.includes(name)));
|
||||
|
||||
if (foundContainer === undefined) {
|
||||
throw new Error('Could not find a running container whose name contains "'+name+'"');
|
||||
}
|
||||
|
||||
return foundContainer;
|
||||
}
|
||||
|
||||
export async function stopContainer(container: Dockerode.ContainerInfo): Promise<void> {
|
||||
const docker = new Dockerode();
|
||||
|
||||
await docker.getContainer(container.Id).stop();
|
||||
}
|
||||
|
||||
export async function startContainer(container: Dockerode.ContainerInfo): Promise<void> {
|
||||
const docker = new Dockerode();
|
||||
|
||||
await docker.getContainer(container.Id).start();
|
||||
}
|
||||
|
||||
export async function rebootBack(): Promise<void> {
|
||||
dockerCompose('up --force-recreate -d back');
|
||||
}
|
||||
|
||||
export function rebootTraefik(): void {
|
||||
dockerCompose('up --force-recreate -d reverse-proxy');
|
||||
}
|
||||
|
||||
export async function rebootPusher(): Promise<void> {
|
||||
dockerCompose('up --force-recreate -d pusher');
|
||||
}
|
||||
|
||||
export async function resetRedis(): Promise<void> {
|
||||
dockerCompose('stop redis');
|
||||
dockerCompose('rm -f redis');
|
||||
dockerCompose('up --force-recreate -d redis');
|
||||
}
|
||||
|
||||
export function stopRedis(): void {
|
||||
dockerCompose('stop redis');
|
||||
}
|
||||
|
||||
export function startRedis(): void {
|
||||
dockerCompose('start redis');
|
||||
}
|
31
tests/tests/utils/debug.ts
Normal file
31
tests/tests/utils/debug.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import axios from "axios";
|
||||
const fs = require('fs');
|
||||
|
||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN;
|
||||
|
||||
export async function getPusherDump(): Promise<any> {
|
||||
let url = 'http://pusher.workadventure.localhost/dump?token='+ADMIN_API_TOKEN;
|
||||
if (fs.existsSync('/project')) {
|
||||
// We are inside a container. Let's use a direct route
|
||||
url = 'http://pusher:8080/dump?token='+ADMIN_API_TOKEN;
|
||||
}
|
||||
|
||||
return (await axios({
|
||||
url,
|
||||
method: 'get',
|
||||
})).data;
|
||||
}
|
||||
|
||||
export async function getBackDump(): Promise<any> {
|
||||
let url = 'http://api.workadventure.localhost/dump?token='+ADMIN_API_TOKEN;
|
||||
if (fs.existsSync('/project')) {
|
||||
// We are inside a container. Let's use a direct route
|
||||
url = 'http://back:8080/dump?token='+ADMIN_API_TOKEN;
|
||||
}
|
||||
|
||||
return (await axios({
|
||||
url,
|
||||
method: 'get',
|
||||
})).data;
|
||||
}
|
||||
|
@ -1,20 +1,15 @@
|
||||
import { Role } from 'testcafe';
|
||||
|
||||
export const userAlice = Role('http://play.workadventure.localhost/', async t => {
|
||||
await t
|
||||
.typeText('input[name="loginSceneName"]', 'Alice')
|
||||
.click('button.loginSceneFormSubmit')
|
||||
.click('button.selectCharacterButtonRight')
|
||||
.click('button.selectCharacterButtonRight')
|
||||
.click('button.selectCharacterSceneFormSubmit')
|
||||
.click('button.letsgo');
|
||||
});
|
||||
export function login(t: TestController, url: string, userName: string = "Alice", characterNumber: number = 2) {
|
||||
t = t
|
||||
.navigateTo(url)
|
||||
.typeText('input[name="loginSceneName"]', userName)
|
||||
.click('button.loginSceneFormSubmit');
|
||||
|
||||
export const userBob = Role('http://play.workadventure.localhost/', async t => {
|
||||
await t
|
||||
.typeText('input[name="loginSceneName"]', 'Bob')
|
||||
.click('button.loginSceneFormSubmit')
|
||||
.click('button.selectCharacterButtonRight')
|
||||
.click('button.selectCharacterSceneFormSubmit')
|
||||
for (let i = 0; i < characterNumber; i++) {
|
||||
t = t.click('button.selectCharacterButtonRight');
|
||||
}
|
||||
|
||||
return t.click('button.selectCharacterSceneFormSubmit')
|
||||
.click('button.letsgo');
|
||||
});
|
||||
}
|
||||
|
202
tests/tests/variables.ts
Normal file
202
tests/tests/variables.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import {assertLogMessage} from "./utils/log";
|
||||
|
||||
const fs = require('fs');
|
||||
const Docker = require('dockerode');
|
||||
import { Selector } from 'testcafe';
|
||||
import {login} from "./utils/roles";
|
||||
import {
|
||||
findContainer,
|
||||
rebootBack,
|
||||
rebootPusher,
|
||||
resetRedis,
|
||||
rebootTraefik,
|
||||
startContainer,
|
||||
stopContainer, stopRedis, startRedis
|
||||
} from "./utils/containers";
|
||||
import {getBackDump, getPusherDump} from "./utils/debug";
|
||||
|
||||
// Note: we are also testing that passing a random query parameter does not cause any issue.
|
||||
fixture `Variables`
|
||||
.page `http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json?somerandomparam=1`;
|
||||
|
||||
test("Test that variables storage works", async (t: TestController) => {
|
||||
|
||||
const variableInput = Selector('#textField');
|
||||
|
||||
await resetRedis();
|
||||
|
||||
await Promise.all([
|
||||
rebootBack(),
|
||||
rebootPusher(),
|
||||
]);
|
||||
|
||||
//const mainWindow = await t.getCurrentWindow();
|
||||
|
||||
await login(t, 'http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json?somerandomparam=1');
|
||||
|
||||
await t //.useRole(userAliceOnPage('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json?somerandomparam=1'))
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
.expect(variableInput.value).eql('default value')
|
||||
.selectText(variableInput)
|
||||
.pressKey('delete')
|
||||
.typeText(variableInput, 'new value')
|
||||
.pressKey('tab')
|
||||
.switchToMainWindow()
|
||||
//.switchToWindow(mainWindow)
|
||||
.wait(500)
|
||||
// reload
|
||||
.navigateTo('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json')
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
.expect(variableInput.value).eql('new value')
|
||||
//.debug()
|
||||
.switchToMainWindow()
|
||||
//.wait(5000)
|
||||
//.switchToWindow(mainWindow)
|
||||
/*
|
||||
.wait(5000)
|
||||
//.debug()
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
.expect(variableInput.value).eql('new value')
|
||||
.switchToMainWindow();*/
|
||||
|
||||
// Now, let's kill the reverse proxy to cut the connexion
|
||||
//console.log('Rebooting traefik');
|
||||
rebootTraefik();
|
||||
//console.log('Rebooting done');
|
||||
|
||||
// Maybe we should:
|
||||
// 1: stop Traefik
|
||||
// 2: detect reconnecting screen
|
||||
// 3: start Traefik again
|
||||
|
||||
|
||||
await t
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
.expect(variableInput.value).eql('new value')
|
||||
|
||||
stopRedis();
|
||||
|
||||
// Redis is stopped, let's try to modify a variable.
|
||||
await t.selectText(variableInput)
|
||||
.pressKey('delete')
|
||||
.typeText(variableInput, 'value set while Redis stopped')
|
||||
.pressKey('tab')
|
||||
.switchToMainWindow()
|
||||
|
||||
startRedis();
|
||||
|
||||
// Navigate to some other map so that the pusher connection is freed.
|
||||
await t.navigateTo('http://maps.workadventure.localhost/tests/')
|
||||
.wait(3000);
|
||||
|
||||
|
||||
const backDump = await getBackDump();
|
||||
//console.log('backDump', backDump);
|
||||
for (const room of backDump) {
|
||||
if (room.roomUrl === 'http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json') {
|
||||
throw new Error('Room still found in back');
|
||||
}
|
||||
}
|
||||
|
||||
const pusherDump = await getPusherDump();
|
||||
//console.log('pusherDump', pusherDump);
|
||||
await t.expect(pusherDump['http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json']).eql(undefined);
|
||||
|
||||
await t.navigateTo('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json')
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
// Redis will reconnect automatically and will store the variable on reconnect!
|
||||
// So we should see the new value.
|
||||
.expect(variableInput.value).eql('value set while Redis stopped')
|
||||
.switchToMainWindow()
|
||||
|
||||
|
||||
// Now, let's try to kill / reboot the back
|
||||
await rebootBack();
|
||||
|
||||
await t.navigateTo('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json')
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
.expect(variableInput.value).eql('value set while Redis stopped')
|
||||
.selectText(variableInput)
|
||||
.pressKey('delete')
|
||||
.typeText(variableInput, 'value set after back restart')
|
||||
.pressKey('tab')
|
||||
.switchToMainWindow()
|
||||
|
||||
.navigateTo('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json')
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
// Redis will reconnect automatically and will store the variable on reconnect!
|
||||
// So we should see the new value.
|
||||
.expect(variableInput.value).eql('value set after back restart')
|
||||
.switchToMainWindow()
|
||||
|
||||
// Now, let's try to kill / reboot the back
|
||||
await rebootPusher();
|
||||
|
||||
await t.navigateTo('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json')
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
.expect(variableInput.value).eql('value set after back restart')
|
||||
.selectText(variableInput)
|
||||
.pressKey('delete')
|
||||
.typeText(variableInput, 'value set after pusher restart')
|
||||
.pressKey('tab')
|
||||
.switchToMainWindow()
|
||||
|
||||
.navigateTo('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/shared_variables.json')
|
||||
.switchToIframe("#cowebsite-buffer iframe")
|
||||
// Redis will reconnect automatically and will store the variable on reconnect!
|
||||
// So we should see the new value.
|
||||
.expect(variableInput.value).eql('value set after pusher restart')
|
||||
.switchToMainWindow()
|
||||
|
||||
|
||||
t.ctx.passed = true;
|
||||
}).after(async t => {
|
||||
if (!t.ctx.passed) {
|
||||
console.log("Test 'Test that variables storage works' failed. Browser logs:")
|
||||
try {
|
||||
console.log(await t.getBrowserConsoleMessages());
|
||||
} catch (e) {
|
||||
console.error('Error while fetching browser logs (maybe linked to a closed iframe?)', e);
|
||||
try {
|
||||
console.log('Logs from main window:');
|
||||
console.log(await t.switchToMainWindow().getBrowserConsoleMessages());
|
||||
} catch (e) {
|
||||
console.error('Unable to retrieve logs', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test("Test that variables cache in the back don't prevent setting a variable in case the map changes", async (t: TestController) => {
|
||||
// Let's start by visiting a map that DOES not have the variable.
|
||||
fs.copyFileSync('../maps/tests/Variables/Cache/variables_cache_1.json', '../maps/tests/Variables/Cache/variables_tmp.json');
|
||||
|
||||
//const aliceOnPageTmp = userAliceOnPage('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json');
|
||||
|
||||
await login(t, 'http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json', 'Alice', 2);
|
||||
|
||||
//.takeScreenshot('before_switch.png');
|
||||
|
||||
// Let's REPLACE the map by a map that has a new variable
|
||||
// At this point, the back server contains a cache of the old map (with no variables)
|
||||
fs.copyFileSync('../maps/tests/Variables/Cache/variables_cache_2.json', '../maps/tests/Variables/Cache/variables_tmp.json');
|
||||
await t.openWindow('http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json')
|
||||
.resizeWindow(960, 800)
|
||||
|
||||
await login(t, 'http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Variables/Cache/variables_tmp.json', 'Bob', 3);
|
||||
|
||||
//.takeScreenshot('after_switch.png');
|
||||
|
||||
// Let's check we successfully manage to save the variable value.
|
||||
await assertLogMessage(t, 'SUCCESS!');
|
||||
|
||||
t.ctx.passed = true;
|
||||
}).after(async t => {
|
||||
if (!t.ctx.passed) {
|
||||
console.log("Test 'Test that variables cache in the back don't prevent setting a variable in case the map changes' failed. Browser logs:")
|
||||
console.log(await t.getBrowserConsoleMessages());
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user