FEATURE: implemented a client side blacklist

This commit is contained in:
kharhamel 2021-02-02 18:19:51 +01:00
parent b92b7304b0
commit 0c892e0243
16 changed files with 338 additions and 186 deletions

View file

@ -464,7 +464,8 @@ export class RoomConnection implements RoomConnection {
});
}
public getUserId(): number|null {
public getUserId(): number {
if (this.userId === null) throw 'UserId cannot be null!'
return this.userId;
}

View file

@ -1,7 +1,6 @@
import {GameScene} from "../Game/GameScene";
import {PointInterface} from "../../Connexion/ConnexionModels";
import {Character} from "../Entity/Character";
import {Sprite} from "./Sprite";
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
@ -23,6 +22,11 @@ export class RemotePlayer extends Character {
//set data
this.userId = userId;
//todo: implement on click action
/*this.playerName.setInteractive();
this.playerName.on('pointerup', () => {
});*/
}
updatePosition(position: PointInterface): void {

View file

@ -2,17 +2,16 @@ import {LoginScene, LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {gameManager} from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {mediaManager, ReportCallback, ShowReportCallBack} from "../../WebRtc/MediaManager";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {GameConnexionTypes} from "../../Url/UrlManager";
import {mediaManager} from "../../WebRtc/MediaManager";
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../../Url/UrlManager";
export const MenuSceneName = 'MenuScene';
const gameMenuKey = 'gameMenu';
const gameMenuIconKey = 'gameMenuIcon';
const gameSettingsMenuKey = 'gameSettingsMenu';
const gameShare = 'gameShare';
const gameReport = 'gameReport';
const closedSideMenuX = -200;
const openedSideMenuX = 0;
@ -24,11 +23,10 @@ export class MenuScene extends Phaser.Scene {
private menuElement!: Phaser.GameObjects.DOMElement;
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
private gameShareElement!: Phaser.GameObjects.DOMElement;
private gameReportElement!: Phaser.GameObjects.DOMElement;
private gameReportElement!: ReportMenu;
private sideMenuOpened = false;
private settingsMenuOpened = false;
private gameShareOpened = false;
private gameReportOpened = false;
private gameQualityValue: number;
private videoQualityValue: number;
private menuButton!: Phaser.GameObjects.DOMElement;
@ -45,21 +43,21 @@ export class MenuScene extends Phaser.Scene {
this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html');
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
this.load.html(gameShare, 'resources/html/gameShare.html');
this.load.html(gameReport, 'resources/html/gameReport.html');
this.load.html(gameReportKey, gameReportRessource);
}
create() {
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
this.menuElement.setOrigin(0);
this.revealMenusAfterInit(this.menuElement, 'gameMenu');
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
const middleX = (window.innerWidth / 3) - 298;
this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey);
this.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare);
this.revealMenusAfterInit(this.gameShareElement, gameShare);
MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare);
this.gameShareElement.addListener('click');
this.gameShareElement.on('click', (event:MouseEvent) => {
event.preventDefault();
@ -70,18 +68,11 @@ export class MenuScene extends Phaser.Scene {
}
});
this.gameReportElement = this.add.dom(middleX, -400).createFromCache(gameReport);
this.revealMenusAfterInit(this.gameReportElement, gameReport);
this.gameReportElement.addListener('click');
this.gameReportElement.on('click', (event:MouseEvent) => {
event.preventDefault();
if((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') {
this.submitReport();
}else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') {
this.closeGameReport();
}
this.gameReportElement = new ReportMenu(this, connectionManager.getConnexionType === GameConnexionTypes.anonymous);
mediaManager.setShowReportModalCallBacks((userId, userName) => {
this.closeAll();
this.gameReportElement.open(parseInt(userId), userName);
});
mediaManager.setShowReportModalCallBacks(this.openGameReport.bind(this));
this.input.keyboard.on('keyup-TAB', () => {
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
@ -96,7 +87,8 @@ export class MenuScene extends Phaser.Scene {
this.menuElement.on('click', this.onMenuClick.bind(this));
}
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//todo put this method in a parent menuElement class
static revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
setTimeout(() => {
@ -245,71 +237,6 @@ export class MenuScene extends Phaser.Scene {
});
}
private openGameReport(userId: string, userName: string|undefined){
if (this.gameReportOpened) {
this.closeGameReport();
return;
}
//close all
this.closeAll();
const gameTitleReport = this.gameReportElement.getChildByID('nameReported') as HTMLElement;
gameTitleReport.innerText = userName ? `Report user: ${userName}` : 'Report user';
const gameIdUserReported = this.gameReportElement.getChildByID('idUserReported') as HTMLInputElement;
gameIdUserReported.value = userId;
this.gameReportOpened = true;
let middleY = (window.innerHeight / 3) - (257);
if(middleY < 0){
middleY = 0;
}
let middleX = (window.innerWidth / 3) - 298;
if(middleX < 0){
middleX = 0;
}
gameManager.getCurrentGameScene(this).userInputManager.clearAllKeys();
this.tweens.add({
targets: this.gameReportElement,
y: middleY,
x: middleX,
duration: 1000,
ease: 'Power3'
});
return;
}
private closeGameReport(): void{
this.gameReportOpened = false;
gameManager.getCurrentGameScene(this).userInputManager.initKeyBoardEvent();
this.tweens.add({
targets: this.gameReportElement,
y: -400,
duration: 1000,
ease: 'Power3'
});
}
private submitReport(): void{
const gamePError = this.gameReportElement.getChildByID('gameReportErr') as HTMLParagraphElement;
gamePError.innerText = '';
gamePError.style.display = 'none';
const gameTextArea = this.gameReportElement.getChildByID('gameReportInput') as HTMLInputElement;
const gameIdUserReported = this.gameReportElement.getChildByID('idUserReported') as HTMLInputElement;
if(!gameTextArea || !gameTextArea.value || !gameIdUserReported || !gameIdUserReported.value){
gamePError.innerText = 'Report message cannot to be empty.';
gamePError.style.display = 'block';
return;
}
gameManager.getCurrentGameScene(this).connection.emitReportPlayerMessage(
parseInt(gameIdUserReported.value),
gameTextArea.value
);
this.closeGameReport();
}
private onMenuClick(event:MouseEvent) {
if((event?.target as HTMLInputElement).classList.contains('not-button')){
return;
@ -372,6 +299,6 @@ export class MenuScene extends Phaser.Scene {
private closeAll(){
this.closeGameQualityMenu();
this.closeGameShare();
this.closeGameReport();
this.gameReportElement.close();
}
}

View file

@ -0,0 +1,119 @@
import {MenuScene} from "./MenuScene";
import {gameManager} from "../Game/GameManager";
import {blackListManager} from "../../WebRtc/BlackListManager";
export const gameReportKey = 'gameReport';
export const gameReportRessource = 'resources/html/gameReport.html';
export class ReportMenu extends Phaser.GameObjects.DOMElement {
private opened: boolean = false;
private userId!: number;
private userName!: string|undefined;
private anonymous: boolean;
constructor(scene: Phaser.Scene, anonymous: boolean) {
super(scene, -2000, -2000);
this.anonymous = anonymous;
this.createFromCache(gameReportKey);
if (this.anonymous) {
const divToHide = this.getChildByID('reportSection') as HTMLElement;
divToHide.hidden = true;
const textToHide = this.getChildByID('askActionP') as HTMLElement;
textToHide.hidden = true;
}
scene.add.existing(this);
MenuScene.revealMenusAfterInit(this, gameReportKey);
this.addListener('click');
this.on('click', (event:MouseEvent) => {
event.preventDefault();
if ((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') {
this.submitReport();
} else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') {
this.close();
} else if((event?.target as HTMLInputElement).id === 'toggleBlockButton') {
this.toggleBlock();
}
});
}
public open(userId: number, userName: string|undefined): void {
if (this.opened) {
this.close();
return;
}
this.userId = userId;
this.userName = userName;
const mainEl = this.getChildByID('gameReport') as HTMLElement;
this.x = this.getCenteredX(mainEl);
this.y = this.getHiddenY(mainEl);
const gameTitleReport = this.getChildByID('nameReported') as HTMLElement;
gameTitleReport.innerText = userName || '';
const blockButton = this.getChildByID('toggleBlockButton') as HTMLElement;
blockButton.innerText = blackListManager.isBlackListed(this.userId) ? 'Unblock this user' : 'Block this user';
this.opened = true;
gameManager.getCurrentGameScene(this.scene).userInputManager.clearAllKeys();
this.scene.tweens.add({
targets: this,
y: this.getCenteredY(mainEl),
duration: 1000,
ease: 'Power3'
});
}
public close(): void {
this.opened = false;
gameManager.getCurrentGameScene(this.scene).userInputManager.initKeyBoardEvent();
const mainEl = this.getChildByID('gameReport') as HTMLElement;
this.scene.tweens.add({
targets: this,
y: this.getHiddenY(mainEl),
duration: 1000,
ease: 'Power3'
});
}
//todo: into a parent class?
private getCenteredX(mainEl: HTMLElement): number {
return window.innerWidth / 4 - mainEl.clientWidth / 2;
}
private getHiddenY(mainEl: HTMLElement): number {
return - mainEl.clientHeight - 50;
}
private getCenteredY(mainEl: HTMLElement): number {
return window.innerHeight / 4 - mainEl.clientHeight / 2;
}
private toggleBlock(): void {
!blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId);
this.close();
}
private submitReport(): void{
const gamePError = this.getChildByID('gameReportErr') as HTMLParagraphElement;
gamePError.innerText = '';
gamePError.style.display = 'none';
const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement;
const gameIdUserReported = this.getChildByID('idUserReported') as HTMLInputElement;
if(!gameTextArea || !gameTextArea.value || !gameIdUserReported || !gameIdUserReported.value){
gamePError.innerText = 'Report message cannot to be empty.';
gamePError.style.display = 'block';
return;
}
gameManager.getCurrentGameScene(this.scene).connection.emitReportPlayerMessage(
parseInt(gameIdUserReported.value),
gameTextArea.value
);
this.close();
}
}

View file

@ -0,0 +1,24 @@
import {Subject} from 'rxjs';
class BlackListManager {
private list: number[] = [];
public onBlockStream: Subject<number> = new Subject();
public onUnBlockStream: Subject<number> = new Subject();
isBlackListed(userId: number): boolean {
return this.list.find((data) => data === userId) !== undefined;
}
blackList(userId: number): void {
if (this.isBlackListed(userId)) return;
this.list.push(userId);
this.onBlockStream.next(userId);
}
cancelBlackList(userId: number): void {
this.list.splice(this.list.findIndex(data => data === userId), 1);
this.onUnBlockStream.next(userId);
}
}
export const blackListManager = new BlackListManager();

View file

@ -3,8 +3,7 @@ import {HtmlUtils} from "./HtmlUtils";
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
import {UserSimplePeerInterface} from "./SimplePeer";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT);
@ -28,7 +27,6 @@ export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
// TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!)
export class MediaManager {
localStream: MediaStream|null = null;
localScreenCapture: MediaStream|null = null;
@ -473,8 +471,9 @@ export class MediaManager {
return this.getCamera();
}
addActiveVideo(userId: string, userName: string = "", anonymous: boolean = true){
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
this.webrtcInAudio.play();
const userId = ''+user.userId
userName = userName.toUpperCase();
const color = this.getColorByString(userName);
@ -484,22 +483,18 @@ export class MediaManager {
<div class="connecting-spinner"></div>
<div class="rtc-error" style="display: none"></div>
<i id="name-${userId}" style="background-color: ${color};">${userName}</i>
<img id="microphone-${userId}" src="resources/logos/microphone-close.svg">
` +
((anonymous === false)?`
<button id="report-${userId}" class="report">
<img src="resources/logos/report.svg">
<span>Report</span>
</button>
`:''
)
+
`<video id="${userId}" autoplay></video>
<img id="microphone-${userId}" title="mute" src="resources/logos/microphone-close.svg">
<button id="report-${userId}" class="report">
<img title="report this user" src="resources/logos/report.svg">
<span>Report/Block</span>
</button>
<video id="${userId}" autoplay></video>
<img src="resources/logos/blockSign.svg" id="blocking-${userId}" class="block-logo">
</div>
`;
layoutManager.add(DivImportance.Normal, userId, html);
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
//permit to create participant in discussion part
@ -510,18 +505,17 @@ export class MediaManager {
};
this.addNewParticipant(userId, userName, undefined, showReportUser);
if(!anonymous){
const reportBanUserAction: HTMLImageElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>(`report-${userId}`);
reportBanUserAction.addEventListener('click', (e) => {
e.preventDefault();
showReportUser();
});
}
const reportBanUserActionEl: HTMLImageElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>(`report-${userId}`);
reportBanUserActionEl.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
showReportUser();
});
}
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
userId = `screen-sharing-${userId}`;
userId = this.getScreenSharingId(userId);
const html = `
<div id="div-${userId}" class="video-container">
<video id="${userId}" autoplay></video>
@ -532,7 +526,11 @@ export class MediaManager {
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
}
private getScreenSharingId(userId: string): string {
return `screen-sharing-${userId}`;
}
disabledMicrophoneByUserId(userId: number){
const element = document.getElementById(`microphone-${userId}`);
if(!element){
@ -571,6 +569,10 @@ export class MediaManager {
}
}
toggleBlockLogo(userId: number, show: boolean): void {
const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-'+userId);
show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active');
}
addStreamRemoteVideo(userId: string, stream : MediaStream): void {
const remoteVideo = this.remoteVideo.get(userId);
if (remoteVideo === undefined) {
@ -580,12 +582,12 @@ export class MediaManager {
}
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
const remoteVideo = this.remoteVideo.get(`screen-sharing-${userId}`);
const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId));
if (remoteVideo === undefined) {
this.addScreenSharingActiveVideo(userId);
}
this.addStreamRemoteVideo(`screen-sharing-${userId}`, stream);
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
}
removeActiveVideo(userId: string){
@ -596,7 +598,7 @@ export class MediaManager {
this.removeParticipant(userId);
}
removeActiveScreenSharingVideo(userId: string) {
this.removeActiveVideo(`screen-sharing-${userId}`)
this.removeActiveVideo(this.getScreenSharingId(userId))
}
playWebrtcOutSound(): void {
@ -632,7 +634,7 @@ export class MediaManager {
errorDiv.style.display = 'block';
}
isErrorScreenSharing(userId: string): void {
this.isError(`screen-sharing-${userId}`);
this.isError(this.getScreenSharingId(userId));
}

View file

@ -9,10 +9,11 @@ import {
UpdatedLocalStreamCallback
} from "./MediaManager";
import {ScreenSharingPeer} from "./ScreenSharingPeer";
import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
import {MESSAGE_TYPE_BLOCKED, MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
import {RoomConnection} from "../Connexion/RoomConnection";
import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
import {blackListManager} from "./BlackListManager";
export interface UserSimplePeerInterface{
userId: number;
@ -38,6 +39,7 @@ export class SimplePeer {
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
private readonly userId: number;
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
@ -48,6 +50,7 @@ export class SimplePeer {
mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback);
mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback);
mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback);
this.userId = Connection.getUserId();
this.initialise();
}
@ -91,8 +94,7 @@ export class SimplePeer {
});
}
private receiveWebrtcStart(user: UserSimplePeerInterface) {
//this.WebRtcRoomId = data.roomId;
private receiveWebrtcStart(user: UserSimplePeerInterface): void {
this.Users.push(user);
// Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group)
// So we can receive a request we already had before. (which will abort at the first line of createPeerConnection)
@ -136,13 +138,13 @@ export class SimplePeer {
mediaManager.removeActiveVideo("" + user.userId);
mediaManager.addActiveVideo("" + user.userId, name, connectionManager.getConnexionType === GameConnexionTypes.anonymous);
mediaManager.addActiveVideo(user, name);
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection);
//permit to send message
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message})));
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), userId: this.userId, message: message})));
});
peer.toClose = false;
@ -298,6 +300,7 @@ export class SimplePeer {
}
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
if (blackListManager.isBlackListed(data.userId)) return;
console.log("receiveWebrtcScreenSharingSignal", data);
try {
//if offer type, create peer connection
@ -390,6 +393,7 @@ export class SimplePeer {
}
private sendLocalScreenSharingStreamToUser(userId: number): void {
if (blackListManager.isBlackListed(userId)) return;
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
if (this.PeerScreenSharingConnectionArray.has(userId)) {
this.pushScreenSharingToRemoteUser(userId);

View file

@ -2,19 +2,30 @@ import * as SimplePeerNamespace from "simple-peer";
import {mediaManager} from "./MediaManager";
import {STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "../Connexion/RoomConnection";
import {blackListManager} from "./BlackListManager";
import {Subscription} from "rxjs";
import {UserSimplePeerInterface} from "./SimplePeer";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
export const MESSAGE_TYPE_MESSAGE = 'message';
export const MESSAGE_TYPE_BLOCKED = 'blocked';
export const MESSAGE_TYPE_UNBLOCKED = 'unblocked';
/**
* A peer connection used to transmit video / audio signals between 2 peers.
*/
export class VideoPeer extends Peer {
public toClose: boolean = false;
public _connected: boolean = false;
private remoteStream!: MediaStream;
private blocked: boolean = false;
private userId: number;
private userName: string;
private onBlockSubscribe: Subscription;
private onUnBlockSubscribe: Subscription;
constructor(public userId: number, initiator: boolean, private connection: RoomConnection) {
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
super({
initiator: initiator ? initiator : false,
reconnectTimer: 10000,
@ -31,35 +42,15 @@ export class VideoPeer extends Peer {
]
}
});
console.log('PEER SETUP ', {
initiator: initiator ? initiator : false,
reconnectTimer: 10000,
config: {
iceServers: [
{
urls: STUN_SERVER.split(',')
},
{
urls: TURN_SERVER.split(','),
username: TURN_USER,
credential: TURN_PASSWORD
},
]
}
});
this.userId = user.userId;
this.userName = user.name || '';
//start listen signal for the peer connection
this.on('signal', (data: unknown) => {
this.sendWebrtcSignal(data);
});
this.on('stream', (stream: MediaStream) => {
this.stream(stream);
});
/*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => {
});*/
this.on('stream', (stream: MediaStream) => this.stream(stream));
this.on('close', () => {
this._connected = false;
@ -70,7 +61,7 @@ export class VideoPeer extends Peer {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.on('error', (err: any) => {
console.error(`error => ${this.userId} => ${err.code}`, err);
mediaManager.isError("" + userId);
mediaManager.isError("" + this.userId);
});
this.on('connect', () => {
@ -81,8 +72,6 @@ export class VideoPeer extends Peer {
this.on('data', (chunk: Buffer) => {
const message = JSON.parse(chunk.toString('utf8'));
console.log("data", message);
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
if (message.audio) {
mediaManager.enabledMicrophoneByUserId(this.userId);
@ -95,8 +84,19 @@ export class VideoPeer extends Peer {
} else {
mediaManager.disabledVideoByUserId(this.userId);
}
} else if(message.type === 'message') {
mediaManager.addNewMessage(message.name, message.message);
} else if(message.type === MESSAGE_TYPE_MESSAGE) {
if (!blackListManager.isBlackListed(message.userId)) {
mediaManager.addNewMessage(message.name, message.message);
}
} 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.
// Find a way to block A's output stream in A's js client
//However, the output stream stream B is correctly blocked in A client
this.blocked = true;
this.toggleRemoteStream(false);
} else if(message.type === MESSAGE_TYPE_UNBLOCKED) {
this.blocked = false;
this.toggleRemoteStream(true);
}
});
@ -105,6 +105,31 @@ export class VideoPeer extends Peer {
});
this.pushVideoToRemoteUser();
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => {
if (userId === this.userId) {
this.toggleRemoteStream(false);
this.sendBlockMessage(true);
}
});
this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userId) => {
if (userId === this.userId) {
this.toggleRemoteStream(true);
this.sendBlockMessage(false);
}
});
if (blackListManager.isBlackListed(this.userId)) {
this.sendBlockMessage(true)
}
}
private sendBlockMessage(blocking: boolean) {
this.write(new Buffer(JSON.stringify({type: blocking ? MESSAGE_TYPE_BLOCKED : MESSAGE_TYPE_UNBLOCKED, name: this.userName.toUpperCase(), userId: this.userId, message: ''})));
}
private toggleRemoteStream(enable: boolean) {
this.remoteStream.getTracks().forEach(track => track.enabled = enable);
mediaManager.toggleBlockLogo(this.userId, !enable);
}
private sendWebrtcSignal(data: unknown) {
@ -120,13 +145,13 @@ export class VideoPeer extends Peer {
*/
private stream(stream: MediaStream) {
try {
this.remoteStream = stream;
if (blackListManager.isBlackListed(this.userId) || this.blocked) {
this.toggleRemoteStream(false);
}
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
}catch (err){
console.error(err);
//Force add streem video
/*setTimeout(() => {
this.stream(stream);
}, 500);*/ //todo: find a way to prevent infinite regression.
}
}
@ -139,6 +164,8 @@ export class VideoPeer extends Peer {
if(!this.toClose){
return;
}
this.onBlockSubscribe.unsubscribe();
this.onUnBlockSubscribe.unsubscribe();
mediaManager.removeActiveVideo("" + 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.