diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7496ba..29cc0cb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,17 @@ - Use `WA.room.hideLayer(): void` to hide a layer - Use `WA.room.setProperty() : void` to add, delete or change existing property of a layer - Use `WA.player.onPlayerMove(): void` to track the movement of the current player - - Use `WA.room.getCurrentUser(): Promise` to get the ID, name and tags of the current player + - Use `WA.player.getCurrentUser(): Promise` to get the ID, name and tags of the current player - Use `WA.room.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started - Use `WA.ui.registerMenuCommand(): void` to add a custom menu - Use `WA.room.setTiles(): void` to add, delete or change an array of tiles - Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked. +- The text chat was redesigned to be prettier and to use more features : + - The chat is now persistent bewteen discussions and always accesible + - The chat now tracks incoming and outcoming users in your conversation + - The chat allows your to see the visit card of users + - You can close the chat window with the escape key +- Added a 'Enable notifications' button in the menu. ## Version 1.4.3 - 1.4.4 - 1.4.5 diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 020f4c29..71d2124e 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -15,12 +15,6 @@ import { Admin } from "../Model/Admin"; export type ConnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void; -export enum GameRoomPolicyTypes { - ANONYMOUS_POLICY = 1, - MEMBERS_ONLY_POLICY, - USE_TAGS_POLICY, -} - export class GameRoom { private readonly minDistance: number; private readonly groupRadius: number; diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 8d04e713..8d1659df 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -436,10 +436,7 @@ export class SocketManager { const serverToClientMessage1 = new ServerToClientMessage(); serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1); - //if (!user.socket.disconnecting) { user.socket.write(serverToClientMessage1); - //console.log('Sending webrtcstart initiator to '+user.socket.userId) - //} const webrtcStartMessage2 = new WebRtcStartMessage(); webrtcStartMessage2.setUserid(user.id); @@ -453,10 +450,7 @@ export class SocketManager { const serverToClientMessage2 = new ServerToClientMessage(); serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2); - //if (!otherUser.socket.disconnecting) { otherUser.socket.write(serverToClientMessage2); - //console.log('Sending webrtcstart to '+otherUser.socket.userId) - //} } } diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index f483731e..0efe2941 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -1,6 +1,7 @@ {.section-title.accent.text-primary} # API Player functions Reference + ### Listen to player movement ``` WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void; diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 456fca71..8bc2b3d2 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -79,44 +79,6 @@ Example : WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); ``` -### Getting information on the current room -``` -WA.room.getCurrentRoom(): Promise -``` -Return a promise that resolves to a `Room` object with the following attributes : -* **id (string) :** ID of the current room -* **map (ITiledMap) :** contains the JSON map file with the properties that were set by the script if `setProperty` was called. -* **mapUrl (string) :** Url of the JSON map file -* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer - -Example : -```javascript -WA.room.getCurrentRoom((room) => { - if (room.id === '42') { - console.log(room.map); - window.open(room.mapUrl, '_blank'); - } -}) -``` - -### Getting information on the current user -``` -WA.player.getCurrentUser(): Promise -``` -Return a promise that resolves to a `User` object with the following attributes : -* **id (string) :** ID of the current user -* **nickName (string) :** name displayed above the current user -* **tags (string[]) :** list of all the tags of the current user - -Example : -```javascript -WA.room.getCurrentUser().then((user) => { - if (user.nickName === 'ABC') { - console.log(user.tags); - } -}) -``` - ### Changing tiles ``` WA.room.setTiles(tiles: TileDescriptor[]): void diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index aa63229f..30ea8353 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -37,8 +37,7 @@
-
-
+
diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html index 26be2a1c..bb0a6e1e 100644 --- a/front/dist/resources/html/gameMenu.html +++ b/front/dist/resources/html/gameMenu.html @@ -57,6 +57,9 @@
+
+ +
diff --git a/front/dist/static/images/send.png b/front/dist/static/images/send.png new file mode 100644 index 00000000..1f75634a Binary files /dev/null and b/front/dist/static/images/send.png differ diff --git a/front/package.json b/front/package.json index 9c592578..4e4d66c9 100644 --- a/front/package.json +++ b/front/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "@fontsource/press-start-2p": "^4.3.0", - "@types/simple-peer": "^9.6.0", + "@types/simple-peer": "^9.11.1", "@types/socket.io-client": "^1.4.32", "axios": "^0.21.1", "cross-env": "^7.0.3", @@ -51,7 +51,7 @@ "queue-typescript": "^1.0.1", "quill": "1.3.6", "rxjs": "^6.6.3", - "simple-peer": "^9.6.2", + "simple-peer": "^9.11.0", "socket.io-client": "^2.3.0", "standardized-audio-context": "^25.2.4" }, diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index e130d3f2..cad66a36 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -2,8 +2,15 @@ import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribut import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent"; import { Subject } from "rxjs"; import { apiCallback } from "./registeredCallbacks"; +import { getGameState } from "./room"; import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; +interface User { + id: string | undefined; + nickName: string | null; + tags: string[]; +} + const moveStream = new Subject(); export class WorkadventurePlayerCommands extends IframeApiContribution { @@ -24,6 +31,11 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { + return getGameState().then((gameState) => { + return { id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags }; + }); + } } export default new WorkadventurePlayerCommands(); diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index a9ee52ce..0256dfc8 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -14,7 +14,6 @@ import type { GameStateEvent } from "../Events/GameStateEvent"; const enterStreams: Map> = new Map>(); const leaveStreams: Map> = new Map>(); const dataLayerResolver = new Subject(); -const stateResolvers = new Subject(); let immutableDataPromise: Promise | undefined = undefined; @@ -25,12 +24,6 @@ interface Room { startLayer: string | null; } -interface User { - id: string | undefined; - nickName: string | null; - tags: string[]; -} - interface TileDescriptor { x: number; y: number; @@ -38,7 +31,7 @@ interface TileDescriptor { layer: string; } -function getGameState(): Promise { +export function getGameState(): Promise { if (immutableDataPromise === undefined) { immutableDataPromise = queryWorkadventure({ type: "getState", data: undefined }); } @@ -121,11 +114,6 @@ export class WorkadventureRoomCommands extends IframeApiContribution { - return getGameState().then((gameState) => { - return { id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags }; - }); - } setTiles(tiles: TileDescriptor[]) { sendToWorkadventure({ type: "setTiles", diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 8ade9398..0f808074 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -10,12 +10,14 @@ import {errorStore} from "../Stores/ErrorStore"; import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte"; import LoginScene from "./Login/LoginScene.svelte"; + import Chat from "./Chat/Chat.svelte"; import {loginSceneVisibleStore} from "../Stores/LoginSceneStore"; import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte"; import VisitCard from "./VisitCard/VisitCard.svelte"; import {requestVisitCardsStore} from "../Stores/GameStore"; import type {Game} from "../Phaser/Game/Game"; + import {chatVisibilityStore} from "../Stores/ChatStore"; import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore"; import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte"; import AudioPlaying from "./UI/AudioPlaying.svelte"; @@ -61,14 +63,6 @@
{/if} - - {#if $gameOverlayVisibilityStore}
@@ -94,4 +88,7 @@
{/if} + {#if $chatVisibilityStore} + + {/if}
diff --git a/front/src/Components/Chat/Chat.svelte b/front/src/Components/Chat/Chat.svelte new file mode 100644 index 00000000..0f302126 --- /dev/null +++ b/front/src/Components/Chat/Chat.svelte @@ -0,0 +1,104 @@ + + + + + + + + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatElement.svelte b/front/src/Components/Chat/ChatElement.svelte new file mode 100644 index 00000000..66ed724b --- /dev/null +++ b/front/src/Components/Chat/ChatElement.svelte @@ -0,0 +1,83 @@ + + +
+
+ {#if message.type === ChatMessageTypes.userIncoming} + >> {#each targets as target, index}{#if !isLastIteration(index)}, {/if}{/each} entered ({renderDate(message.date)}) + {:else if message.type === ChatMessageTypes.userOutcoming} + << {#each targets as target, index}{#if !isLastIteration(index)}, {/if}{/each} left ({renderDate(message.date)}) + {:else if message.type === ChatMessageTypes.me} +

Me: ({renderDate(message.date)})

+ {#each texts as text} +

{@html urlifyText(text)}

+ {/each} + {:else} +

: ({renderDate(message.date)})

+ {#each texts as text} +

{@html urlifyText(text)}

+ {/each} + {/if} +
+
+ + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatMessageForm.svelte b/front/src/Components/Chat/ChatMessageForm.svelte new file mode 100644 index 00000000..01ea6ee3 --- /dev/null +++ b/front/src/Components/Chat/ChatMessageForm.svelte @@ -0,0 +1,56 @@ + + +
+ + +
+ + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatPlayerName.svelte b/front/src/Components/Chat/ChatPlayerName.svelte new file mode 100644 index 00000000..9b0630c0 --- /dev/null +++ b/front/src/Components/Chat/ChatPlayerName.svelte @@ -0,0 +1,51 @@ + + + + + {player.name} + + {#if isSubMenuOpen} + + {/if} + + + + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatSubMenu.svelte b/front/src/Components/Chat/ChatSubMenu.svelte new file mode 100644 index 00000000..6690699e --- /dev/null +++ b/front/src/Components/Chat/ChatSubMenu.svelte @@ -0,0 +1,33 @@ + + +
    +
  • +
  • +
+ + + \ No newline at end of file diff --git a/front/src/Components/Video/VideoMediaBox.svelte b/front/src/Components/Video/VideoMediaBox.svelte index 1a581914..d46f3ca7 100644 --- a/front/src/Components/Video/VideoMediaBox.svelte +++ b/front/src/Components/Video/VideoMediaBox.svelte @@ -37,9 +37,7 @@ Report this user Report/Block - {#if $streamStore } - {/if} {#if $constraintStore && $constraintStore.audio !== false} diff --git a/front/src/Components/Video/utils.ts b/front/src/Components/Video/utils.ts index ca1f3b41..06bfcfa7 100644 --- a/front/src/Components/Video/utils.ts +++ b/front/src/Components/Video/utils.ts @@ -1,3 +1,6 @@ +import type { UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; +import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../../Enum/EnvironmentVariable"; + export function getColorByString(str: string): string | null { let hash = 0; if (str.length === 0) { @@ -15,7 +18,7 @@ export function getColorByString(str: string): string | null { return color; } -export function srcObject(node: HTMLVideoElement, stream: MediaStream) { +export function srcObject(node: HTMLVideoElement, stream: MediaStream | null) { node.srcObject = stream; return { update(newStream: MediaStream) { @@ -25,3 +28,19 @@ export function srcObject(node: HTMLVideoElement, stream: MediaStream) { }, }; } + +export function getIceServersConfig(user: UserSimplePeerInterface): RTCIceServer[] { + const config: RTCIceServer[] = [ + { + urls: STUN_SERVER.split(","), + }, + ]; + if (TURN_SERVER !== "") { + config.push({ + urls: TURN_SERVER.split(","), + username: user.webRtcUser || TURN_USER, + credential: user.webRtcPassword || TURN_PASSWORD, + }); + } + return config; +} diff --git a/front/src/Components/VisitCard/VisitCard.svelte b/front/src/Components/VisitCard/VisitCard.svelte index 78f10359..e9eca3b1 100644 --- a/front/src/Components/VisitCard/VisitCard.svelte +++ b/front/src/Components/VisitCard/VisitCard.svelte @@ -45,8 +45,9 @@ .visitCard { pointer-events: all; - margin-left: auto; - margin-right: auto; + position: absolute; + left: 50%; + transform: translate(-50%, 0); margin-top: 200px; max-width: 80vw; diff --git a/front/src/Phaser/Components/OpenChatIcon.ts b/front/src/Phaser/Components/OpenChatIcon.ts index ab07a80c..8c648bc1 100644 --- a/front/src/Phaser/Components/OpenChatIcon.ts +++ b/front/src/Phaser/Components/OpenChatIcon.ts @@ -1,7 +1,7 @@ -import {discussionManager} from "../../WebRtc/DiscussionManager"; -import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes"; +import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes"; +import { chatVisibilityStore } from "../../Stores/ChatStore"; -export const openChatIconName = 'openChatIcon'; +export const openChatIconName = "openChatIcon"; export class OpenChatIcon extends Phaser.GameObjects.Image { constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, openChatIconName, 3); @@ -9,9 +9,9 @@ export class OpenChatIcon extends Phaser.GameObjects.Image { this.setScrollFactor(0, 0); this.setOrigin(0, 1); this.setInteractive(); - this.setVisible(false); + //this.setVisible(false); this.setDepth(DEPTH_INGAME_TEXT_INDEX); - this.on("pointerup", () => discussionManager.showDiscussionPart()); + this.on("pointerup", () => chatVisibilityStore.set(true)); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index d2a659ec..3c47c9d9 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -101,7 +101,6 @@ export const createLoadingPromise = ( frameConfig: FrameConfig ) => { return new Promise((res, rej) => { - console.log("count", loadPlugin.listenerCount("loaderror")); if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { return res(playerResourceDescriptor); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 4c6598d1..3bf69846 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -92,6 +92,7 @@ import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore"; import { videoFocusStore } from "../../Stores/VideoFocusStore"; import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore"; import { playersStore } from "../../Stores/PlayersStore"; +import { chatVisibilityStore } from "../../Stores/ChatStore"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -169,6 +170,7 @@ export class GameScene extends DirtyScene { private createPromiseResolve!: (value?: void | PromiseLike) => void; private iframeSubscriptionList!: Array; private peerStoreUnsubscribe!: () => void; + private chatVisibilityUnsubscribe!: () => void; private biggestAvailableAreaStoreUnsubscribe!: () => void; MapUrlFile: string; RoomId: string; @@ -571,6 +573,10 @@ export class GameScene extends DirtyScene { } oldPeerNumber = newPeerNumber; }); + + this.chatVisibilityUnsubscribe = chatVisibilityStore.subscribe((v) => { + this.openChatIcon.setVisible(!v); + }); } /** @@ -692,12 +698,12 @@ export class GameScene extends DirtyScene { const self = this; this.simplePeer.registerPeerConnectionListener({ onConnect(peer) { - self.openChatIcon.setVisible(true); + //self.openChatIcon.setVisible(true); audioManager.decreaseVolume(); }, onDisconnect(userId: number) { if (self.simplePeer.getNbConnections() === 0) { - self.openChatIcon.setVisible(false); + //self.openChatIcon.setVisible(false); audioManager.restoreVolume(); } }, @@ -1173,6 +1179,7 @@ ${escapedMessage} this.pinchManager?.destroy(); this.emoteManager.destroy(); this.peerStoreUnsubscribe(); + this.chatVisibilityUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); iframeListener.unregisterAnswerer("getState"); diff --git a/front/src/Phaser/Game/PlayerInterface.ts b/front/src/Phaser/Game/PlayerInterface.ts index 5a81c89a..6ab439df 100644 --- a/front/src/Phaser/Game/PlayerInterface.ts +++ b/front/src/Phaser/Game/PlayerInterface.ts @@ -7,4 +7,5 @@ export interface PlayerInterface { visitCardUrl: string | null; companion: string | null; userUuid: string; + color?: string; } diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index da59cecb..4cf18cce 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -19,6 +19,7 @@ import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem"; import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore"; import { get } from "svelte/store"; import { playersStore } from "../../Stores/PlayersStore"; +import { mediaManager } from "../../WebRtc/MediaManager"; export const MenuSceneName = "MenuScene"; const gameMenuKey = "gameMenu"; @@ -98,6 +99,10 @@ export class MenuScene extends Phaser.Scene { this.menuElement.setOrigin(0); MenuScene.revealMenusAfterInit(this.menuElement, "gameMenu"); + if (mediaManager.hasNotification()) { + HtmlUtils.getElementByIdOrFail("enableNotification").hidden = true; + } + const middleX = window.innerWidth / 3 - 298; this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey); MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, "gameQuality"); @@ -357,6 +362,9 @@ export class MenuScene extends Phaser.Scene { case "toggleFullscreen": this.toggleFullscreen(); break; + case "enableNotification": + this.enableNotification(); + break; case "adminConsoleButton": if (get(consoleGlobalMessageManagerVisibleStore)) { consoleGlobalMessageManagerVisibleStore.set(false); @@ -419,4 +427,12 @@ export class MenuScene extends Phaser.Scene { public isDirty(): boolean { return false; } + + private enableNotification() { + mediaManager.requestNotification().then(() => { + if (mediaManager.hasNotification()) { + HtmlUtils.getElementByIdOrFail("enableNotification").hidden = true; + } + }); + } } diff --git a/front/src/Stores/ChatStore.ts b/front/src/Stores/ChatStore.ts new file mode 100644 index 00000000..df488115 --- /dev/null +++ b/front/src/Stores/ChatStore.ts @@ -0,0 +1,118 @@ +import { writable } from "svelte/store"; +import { playersStore } from "./PlayersStore"; +import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; + +export const chatVisibilityStore = writable(false); +export const chatInputFocusStore = writable(false); + +export const newChatMessageStore = writable(null); + +export enum ChatMessageTypes { + text = 1, + me, + userIncoming, + userOutcoming, +} + +export interface ChatMessage { + type: ChatMessageTypes; + date: Date; + author?: PlayerInterface; + targets?: PlayerInterface[]; + text?: string[]; +} + +function getAuthor(authorId: number): PlayerInterface { + const author = playersStore.getPlayerById(authorId); + if (!author) { + throw "Could not find data for author " + authorId; + } + return author; +} + +function createChatMessagesStore() { + const { subscribe, update } = writable([]); + + return { + subscribe, + addIncomingUser(authorId: number) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.userIncoming && lastMessage.targets) { + lastMessage.targets.push(getAuthor(authorId)); + } else { + list.push({ + type: ChatMessageTypes.userIncoming, + targets: [getAuthor(authorId)], + date: new Date(), + }); + } + return list; + }); + }, + addOutcomingUser(authorId: number) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.userOutcoming && lastMessage.targets) { + lastMessage.targets.push(getAuthor(authorId)); + } else { + list.push({ + type: ChatMessageTypes.userOutcoming, + targets: [getAuthor(authorId)], + date: new Date(), + }); + } + return list; + }); + }, + addPersonnalMessage(text: string) { + newChatMessageStore.set(text); + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.me && lastMessage.text) { + lastMessage.text.push(text); + } else { + list.push({ + type: ChatMessageTypes.me, + text: [text], + date: new Date(), + }); + } + return list; + }); + }, + addExternalMessage(authorId: number, text: string) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.text && lastMessage.text) { + lastMessage.text.push(text); + } else { + list.push({ + type: ChatMessageTypes.text, + text: [text], + author: getAuthor(authorId), + date: new Date(), + }); + } + return list; + }); + }, + }; +} +export const chatMessagesStore = createChatMessagesStore(); + +function createChatSubMenuVisibilityStore() { + const { subscribe, update } = writable(""); + + return { + subscribe, + openSubMenu(playerName: string, index: number) { + const id = playerName + index; + update((oldValue) => { + return oldValue === id ? "" : id; + }); + }, + }; +} + +export const chatSubMenuVisbilityStore = createChatSubMenuVisibilityStore(); diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts index 6c21de7a..2ea988bb 100644 --- a/front/src/Stores/PlayersStore.ts +++ b/front/src/Stores/PlayersStore.ts @@ -1,6 +1,7 @@ import { writable } from "svelte/store"; import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; import type { RoomConnection } from "../Connexion/RoomConnection"; +import { getRandomColor } from "../WebRtc/ColorGenerator"; /** * A store that contains the list of players currently known. @@ -24,6 +25,7 @@ function createPlayersStore() { visitCardUrl: message.visitCardUrl, companion: message.companion, userUuid: message.userUuid, + color: getRandomColor(), }); return users; }); diff --git a/front/src/Stores/UserInputStore.ts b/front/src/Stores/UserInputStore.ts index cbb7f0c3..993d8795 100644 --- a/front/src/Stores/UserInputStore.ts +++ b/front/src/Stores/UserInputStore.ts @@ -1,10 +1,11 @@ -import {derived} from "svelte/store"; -import {consoleGlobalMessageManagerFocusStore} from "./ConsoleGlobalMessageManagerStore"; +import { derived } from "svelte/store"; +import { consoleGlobalMessageManagerFocusStore } from "./ConsoleGlobalMessageManagerStore"; +import { chatInputFocusStore } from "./ChatStore"; //derived from the focus on Menu, ConsoleGlobal, Chat and ... export const enableUserInputsStore = derived( - consoleGlobalMessageManagerFocusStore, - ($consoleGlobalMessageManagerFocusStore) => { - return !$consoleGlobalMessageManagerFocusStore; + [consoleGlobalMessageManagerFocusStore, chatInputFocusStore], + ([$consoleGlobalMessageManagerFocusStore, $chatInputFocusStore]) => { + return !$consoleGlobalMessageManagerFocusStore && !$chatInputFocusStore; } -); \ No newline at end of file +); diff --git a/front/src/WebRtc/ColorGenerator.ts b/front/src/WebRtc/ColorGenerator.ts new file mode 100644 index 00000000..be192f9f --- /dev/null +++ b/front/src/WebRtc/ColorGenerator.ts @@ -0,0 +1,52 @@ +export function getRandomColor(): string { + const golden_ratio_conjugate = 0.618033988749895; + let hue = Math.random(); + hue += golden_ratio_conjugate; + hue %= 1; + return hsv_to_rgb(hue, 0.5, 0.95); +} + +//todo: test this. +function hsv_to_rgb(hue: number, saturation: number, brightness: number): string { + const h_i = Math.floor(hue * 6); + const f = hue * 6 - h_i; + const p = brightness * (1 - saturation); + const q = brightness * (1 - f * saturation); + const t = brightness * (1 - (1 - f) * saturation); + let r: number, g: number, b: number; + switch (h_i) { + case 0: + r = brightness; + g = t; + b = p; + break; + case 1: + r = q; + g = brightness; + b = p; + break; + case 2: + r = p; + g = brightness; + b = t; + break; + case 3: + r = p; + g = q; + b = brightness; + break; + case 4: + r = t; + g = p; + b = brightness; + break; + case 5: + r = brightness; + g = p; + b = q; + break; + default: + throw "h_i cannot be " + h_i; + } + return "#" + Math.floor(r * 256).toString(16) + Math.floor(g * 256).toString(16) + Math.floor(b * 256).toString(16); +} diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index ae351f76..a3c928f4 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -1,232 +1,12 @@ -import { HtmlUtils } from "./HtmlUtils"; -import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { connectionManager } from "../Connexion/ConnectionManager"; -import { GameConnexionTypes } from "../Url/UrlManager"; import { iframeListener } from "../Api/IframeListener"; -import { showReportScreenStore } from "../Stores/ShowReportScreenStore"; - -export type SendMessageCallback = (message: string) => void; +import { chatMessagesStore, chatVisibilityStore } from "../Stores/ChatStore"; export class DiscussionManager { - private mainContainer: HTMLDivElement; - - private divDiscuss?: HTMLDivElement; - private divParticipants?: HTMLDivElement; - private nbpParticipants?: HTMLParagraphElement; - private divMessages?: HTMLParagraphElement; - - private participants: Map = new Map(); - - private activeDiscussion: boolean = false; - - private sendMessageCallBack: Map = new Map< - number | string, - SendMessageCallback - >(); - - private userInputManager?: UserInputManager; - constructor() { - this.mainContainer = HtmlUtils.getElementByIdOrFail("main-container"); - this.createDiscussPart(""); //todo: why do we always use empty string? - iframeListener.chatStream.subscribe((chatEvent) => { - this.addMessage(chatEvent.author, chatEvent.message, false); - this.showDiscussion(); + chatMessagesStore.addExternalMessage(parseInt(chatEvent.author), chatEvent.message); + chatVisibilityStore.set(true); }); - this.onSendMessageCallback("iframe_listener", (message) => { - iframeListener.sendUserInputChat(message); - }); - } - - private createDiscussPart(name: string) { - this.divDiscuss = document.createElement("div"); - this.divDiscuss.classList.add("discussion"); - - const buttonCloseDiscussion: HTMLButtonElement = document.createElement("button"); - buttonCloseDiscussion.classList.add("close-btn"); - buttonCloseDiscussion.innerHTML = ``; - buttonCloseDiscussion.addEventListener("click", () => { - this.hideDiscussion(); - }); - this.divDiscuss.appendChild(buttonCloseDiscussion); - - const myName: HTMLParagraphElement = document.createElement("p"); - myName.innerText = name.toUpperCase(); - this.nbpParticipants = document.createElement("p"); - this.nbpParticipants.innerText = "PARTICIPANTS (1)"; - - this.divParticipants = document.createElement("div"); - this.divParticipants.classList.add("participants"); - - this.divMessages = document.createElement("div"); - this.divMessages.classList.add("messages"); - this.divMessages.innerHTML = "

Local messages

"; - - this.divDiscuss.appendChild(myName); - this.divDiscuss.appendChild(this.nbpParticipants); - this.divDiscuss.appendChild(this.divParticipants); - this.divDiscuss.appendChild(this.divMessages); - - const sendDivMessage: HTMLDivElement = document.createElement("div"); - sendDivMessage.classList.add("send-message"); - const inputMessage: HTMLInputElement = document.createElement("input"); - inputMessage.onfocus = () => { - if (this.userInputManager) { - this.userInputManager.disableControls(); - } - }; - inputMessage.onblur = () => { - if (this.userInputManager) { - this.userInputManager.restoreControls(); - } - }; - inputMessage.type = "text"; - inputMessage.addEventListener("keyup", (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - if (inputMessage.value === null || inputMessage.value === "" || inputMessage.value === undefined) { - return; - } - this.addMessage(name, inputMessage.value, true); - for (const callback of this.sendMessageCallBack.values()) { - callback(inputMessage.value); - } - inputMessage.value = ""; - } - }); - sendDivMessage.appendChild(inputMessage); - this.divDiscuss.appendChild(sendDivMessage); - - //append in main container - this.mainContainer.appendChild(this.divDiscuss); - - this.addParticipant("me", "Moi", undefined, true); - } - - public addParticipant( - userId: number | "me", - name: string | undefined, - img?: string | undefined, - isMe: boolean = false - ) { - const divParticipant: HTMLDivElement = document.createElement("div"); - divParticipant.classList.add("participant"); - divParticipant.id = `participant-${userId}`; - - const divImgParticipant: HTMLImageElement = document.createElement("img"); - divImgParticipant.src = "resources/logos/boy.svg"; - if (img !== undefined) { - divImgParticipant.src = img; - } - const divPParticipant: HTMLParagraphElement = document.createElement("p"); - if (!name) { - name = "Anonymous"; - } - divPParticipant.innerText = name; - - divParticipant.appendChild(divImgParticipant); - divParticipant.appendChild(divPParticipant); - - if ( - !isMe && - connectionManager.getConnexionType && - connectionManager.getConnexionType !== GameConnexionTypes.anonymous && - userId !== "me" - ) { - const reportBanUserAction: HTMLButtonElement = document.createElement("button"); - reportBanUserAction.classList.add("report-btn"); - reportBanUserAction.innerText = "Report"; - reportBanUserAction.addEventListener("click", () => { - showReportScreenStore.set({ userId: userId, userName: name ? name : "" }); - }); - divParticipant.appendChild(reportBanUserAction); - } - - this.divParticipants?.appendChild(divParticipant); - - this.participants.set(userId, divParticipant); - - this.updateParticipant(this.participants.size); - } - - public updateParticipant(nb: number) { - if (!this.nbpParticipants) { - return; - } - this.nbpParticipants.innerText = `PARTICIPANTS (${nb})`; - } - - public addMessage(name: string, message: string, isMe: boolean = false) { - const divMessage: HTMLDivElement = document.createElement("div"); - divMessage.classList.add("message"); - if (isMe) { - divMessage.classList.add("me"); - } - - const pMessage: HTMLParagraphElement = document.createElement("p"); - const date = new Date(); - if (isMe) { - name = "Me"; - } else { - name = HtmlUtils.escapeHtml(name); - } - pMessage.innerHTML = `${name} - - ${date.getHours()}:${date.getMinutes()} - `; - divMessage.appendChild(pMessage); - - const userMessage: HTMLParagraphElement = document.createElement("p"); - userMessage.innerHTML = HtmlUtils.urlify(message); - userMessage.classList.add("body"); - divMessage.appendChild(userMessage); - this.divMessages?.appendChild(divMessage); - - //automatic scroll when there are new message - setTimeout(() => { - this.divMessages?.scroll({ - top: this.divMessages?.scrollTop + divMessage.getBoundingClientRect().y, - behavior: "smooth", - }); - }, 200); - } - - public removeParticipant(userId: number | string) { - const element = this.participants.get(userId); - if (element) { - element.remove(); - this.participants.delete(userId); - } - //if all participant leave, hide discussion button - - this.sendMessageCallBack.delete(userId); - } - - public onSendMessageCallback(userId: string | number, callback: SendMessageCallback): void { - this.sendMessageCallBack.set(userId, callback); - } - - get activatedDiscussion() { - return this.activeDiscussion; - } - - private showDiscussion() { - this.activeDiscussion = true; - this.divDiscuss?.classList.add("active"); - } - - private hideDiscussion() { - this.activeDiscussion = false; - this.divDiscuss?.classList.remove("active"); - } - - public setUserInputManager(userInputManager: UserInputManager) { - this.userInputManager = userInputManager; - } - - public showDiscussionPart() { - this.showDiscussion(); } } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index d9847f44..d7e9f514 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,16 +1,10 @@ -import { DivImportance, layoutManager } from "./LayoutManager"; +import { layoutManager } from "./LayoutManager"; import { HtmlUtils } from "./HtmlUtils"; -import { discussionManager, SendMessageCallback } from "./DiscussionManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { localUserStore } from "../Connexion/LocalUserStore"; -import type { UserSimplePeerInterface } from "./SimplePeer"; -import { SoundMeter } from "../Phaser/Components/SoundMeter"; -import { DISABLE_NOTIFICATIONS } from "../Enum/EnvironmentVariable"; import { localStreamStore } from "../Stores/MediaStore"; import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore"; -export type UpdatedLocalStreamCallback = (media: MediaStream | null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void; @@ -21,16 +15,11 @@ export class MediaManager { startScreenSharingCallBacks: Set = new Set(); stopScreenSharingCallBacks: Set = new Set(); - private focused: boolean = true; - private triggerCloseJistiFrame: Map = new Map(); private userInputManager?: UserInputManager; constructor() { - //Check of ask notification navigator permission - this.getNotification(); - localStreamStore.subscribe((result) => { if (result.type === "error") { console.error(result.error); @@ -182,67 +171,35 @@ export class MediaManager { } } - public addNewMessage(name: string, message: string, isMe: boolean = false) { - discussionManager.addMessage(name, message, isMe); - - //when there are new message, show discussion - if (!discussionManager.activatedDiscussion) { - discussionManager.showDiscussionPart(); - } - } - - public addSendMessageCallback(userId: string | number, callback: SendMessageCallback) { - discussionManager.onSendMessageCallback(userId, callback); - } - public setUserInputManager(userInputManager: UserInputManager) { this.userInputManager = userInputManager; - discussionManager.setUserInputManager(userInputManager); } - public getNotification() { - //Get notification - if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") { - if (this.checkNotificationPromise()) { - Notification.requestPermission().catch((err) => { - console.error(`Notification permission error`, err); - }); - } else { - Notification.requestPermission(); - } - } + public hasNotification(): boolean { + return Notification.permission === "granted"; } - /** - * Return true if the browser supports the modern version of the Notification API (which is Promise based) or false - * if we are on Safari... - * - * See https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API - */ - private checkNotificationPromise(): boolean { - try { - Notification.requestPermission().then(); - } catch (e) { - return false; + public requestNotification() { + if (window.Notification && Notification.permission !== "granted") { + return Notification.requestPermission(); + } else { + return Promise.reject(); } - - return true; } public createNotification(userName: string) { - if (this.focused) { + if (document.hasFocus()) { return; } - if (window.Notification && Notification.permission === "granted") { - const title = "WorkAdventure"; + + if (this.hasNotification()) { + const title = `${userName} wants to discuss with you`; const options = { - body: `Hi! ${userName} wants to discuss with you, don't be afraid!`, icon: "/resources/logos/logo-WA-min.png", image: "/resources/logos/logo-WA-min.png", badge: "/resources/logos/logo-WA-min.png", }; new Notification(title, options); - //new Notification(`Hi! ${userName} wants to discuss with you, don't be afraid!`); } } } diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 9beab732..18810182 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -1,11 +1,10 @@ import type * as SimplePeerNamespace from "simple-peer"; -import { mediaManager } from "./MediaManager"; -import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../Enum/EnvironmentVariable"; import type { RoomConnection } from "../Connexion/RoomConnection"; import { MESSAGE_TYPE_CONSTRAINT, PeerStatus } from "./VideoPeer"; import type { UserSimplePeerInterface } from "./SimplePeer"; -import { Readable, readable, writable, Writable } from "svelte/store"; +import { Readable, readable } from "svelte/store"; import { videoFocusStore } from "../Stores/VideoFocusStore"; +import { getIceServersConfig } from "../Components/Video/utils"; const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer"); @@ -32,21 +31,9 @@ export class ScreenSharingPeer extends Peer { stream: MediaStream | null ) { super({ - initiator: initiator ? initiator : false, - //reconnectTimer: 10000, + initiator, config: { - iceServers: [ - { - urls: STUN_SERVER.split(","), - }, - TURN_SERVER !== "" - ? { - urls: TURN_SERVER.split(","), - username: user.webRtcUser || TURN_USER, - credential: user.webRtcPassword || TURN_PASSWORD, - } - : undefined, - ].filter((value) => value !== undefined), + iceServers: getIceServersConfig(user), }, }); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 5045a5a3..e30f1b1f 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -12,6 +12,7 @@ import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { discussionManager } from "./DiscussionManager"; import { playersStore } from "../Stores/PlayersStore"; +import { newChatMessageStore } from "../Stores/ChatStore"; export interface UserSimplePeerInterface { userId: number; @@ -155,27 +156,11 @@ export class SimplePeer { const name = this.getName(user.userId); - discussionManager.removeParticipant(user.userId); - this.lastWebrtcUserName = user.webRtcUser; this.lastWebrtcPassword = user.webRtcPassword; const peer = new VideoPeer(user, user.initiator ? user.initiator : false, name, this.Connection, localStream); - //permit to send message - mediaManager.addSendMessageCallback(user.userId, (message: string) => { - peer.write( - new Buffer( - JSON.stringify({ - type: MESSAGE_TYPE_MESSAGE, - name: this.myName.toUpperCase(), - userId: this.userId, - message: message, - }) - ) - ); - }); - peer.toClose = false; // When a connection is established to a video stream, and if a screen sharing is taking place, // the user sharing screen should also initiate a connection to the remote user! diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index bde0bcde..9fadef8c 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -1,14 +1,14 @@ import type * as SimplePeerNamespace from "simple-peer"; import { mediaManager } from "./MediaManager"; -import { STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER } from "../Enum/EnvironmentVariable"; import type { RoomConnection } from "../Connexion/RoomConnection"; import { blackListManager } from "./BlackListManager"; import type { Subscription } from "rxjs"; import type { UserSimplePeerInterface } from "./SimplePeer"; -import { get, readable, Readable } from "svelte/store"; +import { get, readable, Readable, Unsubscriber } from "svelte/store"; import { obtainedMediaConstraintStore } from "../Stores/MediaStore"; -import { discussionManager } from "./DiscussionManager"; import { playersStore } from "../Stores/PlayersStore"; +import { chatMessagesStore, chatVisibilityStore, newChatMessageStore } from "../Stores/ChatStore"; +import { getIceServersConfig } from "../Components/Video/utils"; const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer"); @@ -34,6 +34,8 @@ export class VideoPeer extends Peer { public readonly streamStore: Readable; public readonly statusStore: Readable; public readonly constraintsStore: Readable; + private newMessageunsubscriber: Unsubscriber | null = null; + private closing: Boolean = false; //this is used to prevent destroy() from being called twice constructor( public user: UserSimplePeerInterface, @@ -43,21 +45,9 @@ export class VideoPeer extends Peer { localStream: MediaStream | null ) { super({ - initiator: initiator ? initiator : false, - //reconnectTimer: 10000, + initiator, config: { - iceServers: [ - { - urls: STUN_SERVER.split(","), - }, - TURN_SERVER !== "" - ? { - urls: TURN_SERVER.split(","), - username: user.webRtcUser || TURN_USER, - credential: user.webRtcPassword || TURN_PASSWORD, - } - : undefined, - ].filter((value) => value !== undefined), + iceServers: getIceServersConfig(user), }, }); @@ -147,6 +137,20 @@ export class VideoPeer extends Peer { this.on("connect", () => { this._connected = true; + chatMessagesStore.addIncomingUser(this.userId); + + this.newMessageunsubscriber = newChatMessageStore.subscribe((newMessage) => { + if (!newMessage) return; + this.write( + new Buffer( + JSON.stringify({ + type: MESSAGE_TYPE_MESSAGE, + message: newMessage, + }) + ) + ); //send more data + newChatMessageStore.set(null); //This is to prevent a newly created SimplePeer to send an old message a 2nd time. Is there a better way? + }); }); this.on("data", (chunk: Buffer) => { @@ -164,8 +168,9 @@ export class VideoPeer extends Peer { mediaManager.disabledVideoByUserId(this.userId); } } else if (message.type === MESSAGE_TYPE_MESSAGE) { - if (!blackListManager.isBlackListed(message.userId)) { - mediaManager.addNewMessage(message.name, message.message); + if (!blackListManager.isBlackListed(this.userUuid)) { + chatMessagesStore.addExternalMessage(this.userId, message.message); + chatVisibilityStore.set(true); } } else if (message.type === MESSAGE_TYPE_BLOCKED) { //FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream. @@ -245,18 +250,18 @@ export class VideoPeer extends Peer { /** * This is triggered twice. Once by the server, and once by a remote client disconnecting */ - public destroy(error?: Error): void { + public destroy(): void { try { this._connected = false; - if (!this.toClose) { + if (!this.toClose || this.closing) { return; } + this.closing = true; this.onBlockSubscribe.unsubscribe(); this.onUnBlockSubscribe.unsubscribe(); - discussionManager.removeParticipant(this.userId); - // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" - // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. - super.destroy(error); + if (this.newMessageunsubscriber) this.newMessageunsubscriber(); + chatMessagesStore.addOutcomingUser(this.userId); + super.destroy(); } catch (err) { console.error("VideoPeer::destroy", err); } diff --git a/front/style/fonts.scss b/front/style/fonts.scss index a49d3967..526f6615 100644 --- a/front/style/fonts.scss +++ b/front/style/fonts.scss @@ -1,9 +1,5 @@ @import "~@fontsource/press-start-2p/index.css"; -*{ - font-family: PixelFont-7,monospace; -} - .nes-btn { font-family: "Press Start 2P"; } diff --git a/front/style/style.scss b/front/style/style.scss index eb34287a..24da5a96 100644 --- a/front/style/style.scss +++ b/front/style/style.scss @@ -1,5 +1,5 @@ *{ - font-family: 'Open Sans', sans-serif; + font-family: Lato; cursor: url('./images/cursor_normal.png'), auto; } * a, button, select{ diff --git a/front/webpack.config.ts b/front/webpack.config.ts index b6efb389..37362baf 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -7,7 +7,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin"; import sveltePreprocess from "svelte-preprocess"; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import NodePolyfillPlugin from "node-polyfill-webpack-plugin"; -import { DISPLAY_TERMS_OF_USE } from "./src/Enum/EnvironmentVariable"; const mode = process.env.NODE_ENV ?? "development"; const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS; diff --git a/front/yarn.lock b/front/yarn.lock index fec87661..6ee607d3 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -262,10 +262,10 @@ "@types/mime" "^1" "@types/node" "*" -"@types/simple-peer@^9.6.0": - version "9.6.3" - resolved "https://registry.yarnpkg.com/@types/simple-peer/-/simple-peer-9.6.3.tgz#aa118a57e036f4ce2059a7e25367526a4764206d" - integrity sha512-zrXEBch9tF4NgkZDsGR3c1D0kq99M1bBCjzEyL0PVfEWzCIXrK64TuxRz3XKOx1B0KoEQ9kTs+AhMDuQaHy5RQ== +"@types/simple-peer@^9.11.1": + version "9.11.1" + resolved "https://registry.yarnpkg.com/@types/simple-peer/-/simple-peer-9.11.1.tgz#bef6ff1e75178d83438e33aa6a4df2fd98fded1d" + integrity sha512-Pzqbau/WlivSXdRC0He2Wz/ANj2wbi4gzJrtysZz93jvOyI2jo/ibMjUe6AvPllFl/UO6QXT/A0Rcp44bDQB5A== dependencies: "@types/node" "*" @@ -5008,7 +5008,7 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -simple-peer@^9.6.2: +simple-peer@^9.11.0: version "9.11.0" resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.11.0.tgz#e8d27609c7a610c3ddd75767da868e8daab67571" integrity sha512-qvdNu/dGMHBm2uQ7oLhQBMhYlrOZC1ywXNCH/i8I4etxR1vrjCnU6ZSQBptndB1gcakjo2+w4OHo7Sjza1SHxg== diff --git a/maps/tests/Metadata/getCurrentUser.html b/maps/tests/Metadata/getCurrentUser.html index 02be24f7..4122cc50 100644 --- a/maps/tests/Metadata/getCurrentUser.html +++ b/maps/tests/Metadata/getCurrentUser.html @@ -5,7 +5,7 @@