1
0
mirror of https://github.com/pavlobu/deskreen.git synced 2025-05-17 07:50:17 -07:00
Pavlo Buidenkov b925803d9f better client UI code
huge work done on sharing desktop session
2020-11-22 17:07:01 +02:00

436 lines
13 KiB
TypeScript

/* eslint-disable promise/catch-or-return */
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/lines-between-class-members */
import { remote, ipcRenderer } from 'electron';
import uuid from 'uuid';
import SimplePeer from 'simple-peer';
import {
prepare as prepareMessage,
process as processMessage,
} from '../../utils/message';
import DeskreenCrypto from '../../utils/crypto';
import ConnectedDevicesService from '../ConnectedDevicesService';
import SharingSessionStatusEnum from '../SharingSessionsService/SharingSessionStatusEnum';
import RoomIDService from '../../server/RoomIDService';
import SharingSessionsService from '../SharingSessionsService';
import connectSocket from '../../server/connectSocket';
import Logger from '../../utils/logger';
import DesktopCapturerSources from '../DesktopCapturerSourcesService';
import setSdpMediaBitrate from './setSdpMediaBitrate';
import getDesktopSourceStreamBySourceID from './getDesktopSourceStreamBySourceID';
const log = new Logger(__filename);
interface PartnerPeerUser {
username: string;
publicKey: string;
}
interface ReceiveEncryptedMessagePayload {
payload: string;
signature: string;
iv: string;
keys: { sessionKey: string; signingKey: string }[];
}
interface SendEncryptedMessagePayload {
type: string;
payload: Record<string, unknown>;
}
type DisplaySize = { width: number; height: number };
const desktopCapturerSourcesService = remote.getGlobal(
'desktopCapturerSourcesService'
) as DesktopCapturerSources;
const nullUser = { username: '', publicKey: '', privateKey: '' };
const nullSimplePeer = new SimplePeer();
export default class PeerConnection {
sharingSessionID: string;
roomID: string;
socket: SocketIOClient.Socket;
crypto: DeskreenCrypto;
user: LocalPeerUser;
partner: PartnerPeerUser;
peer = nullSimplePeer;
desktopCapturerSourceID: string;
localStream: MediaStream | null;
isSocketRoomLocked: boolean;
partnerDeviceDetails = {} as Device;
signalsDataToCallUser: string[];
isCallStarted: boolean;
roomIDService: RoomIDService;
connectedDevicesService: ConnectedDevicesService;
sharingSessionsService: SharingSessionsService;
onDeviceConnectedCallback: (device: Device) => void;
prevStreamWidth: number;
prevStreamHeight: number;
displayID: string;
sourceDisplaySize: DisplaySize | undefined;
constructor(
roomID: string,
sharingSessionID: string,
user: LocalPeerUser,
roomIDService: RoomIDService,
connectedDevicesService: ConnectedDevicesService,
sharingSessionsService: SharingSessionsService
) {
this.roomIDService = roomIDService;
this.connectedDevicesService = connectedDevicesService;
this.sharingSessionsService = sharingSessionsService;
this.sharingSessionID = sharingSessionID;
this.isSocketRoomLocked = false;
this.roomID = encodeURI(roomID);
this.crypto = new DeskreenCrypto();
this.socket = connectSocket(this.roomID);
this.user = user;
this.partner = nullUser;
this.desktopCapturerSourceID = '';
this.signalsDataToCallUser = [];
this.isCallStarted = false;
this.localStream = null;
this.prevStreamWidth = -1;
this.prevStreamHeight = -1;
this.displayID = '';
this.sourceDisplaySize = undefined;
this.onDeviceConnectedCallback = () => {};
this.initSocketWhenUserCreatedCallback();
}
setDesktopCapturerSourceID(id: string) {
this.desktopCapturerSourceID = id;
if (process.env.RUN_MODE === 'test') return;
if (id.includes('screen')) {
this.displayID = desktopCapturerSourcesService.getSourceDisplayIDBySourceID(
id
);
if (this.displayID !== '') {
ipcRenderer
.invoke('get-display-size-by-display-id', this.displayID)
.then((size: DisplaySize | 'undefined') => {
if (size !== 'undefined') {
this.sourceDisplaySize = size;
}
return size;
})
.then(() => {
this.createPeer();
return undefined;
});
}
} else {
this.createPeer();
}
}
setOnDeviceConnectedCallback(callback: (device: Device) => void) {
this.onDeviceConnectedCallback = callback;
}
denyConnectionForPartner() {
this.sendEncryptedMessage({
type: 'DENY_TO_CONNECT',
payload: {},
});
this.disconnectPartner();
}
sendUserAllowedToConnect() {
this.sendEncryptedMessage({
type: 'ALLOWED_TO_CONNECT',
payload: {},
});
}
disconnectByHostMachineUser() {
this.sendEncryptedMessage({
type: 'DISCONNECT_BY_HOST_MACHINE_USER',
payload: {},
});
this.disconnectPartner();
this.selfDestrory();
}
disconnectPartner() {
this.socket.emit('DISCONNECT_SOCKET_BY_DEVICE_IP', {
ip: this.partnerDeviceDetails.deviceIP,
});
this.partnerDeviceDetails = {} as Device;
}
private initSocketWhenUserCreatedCallback() {
this.socket.removeAllListeners();
this.socket.on('disconnect', () => {
this.selfDestrory();
});
this.socket.on('connect', () => {
// this.emitUserEnter();
});
this.socket.on('USER_ENTER', (payload: { users: PartnerPeerUser[] }) => {
const filteredPartner = payload.users.filter((user: PartnerPeerUser) => {
return this.user.publicKey !== user.publicKey;
});
if (filteredPartner[0] === undefined) return;
[this.partner] = filteredPartner;
this.sendEncryptedMessage({
type: 'ADD_USER',
payload: {
username: this.user.username,
publicKey: this.user.publicKey,
isOwner: true,
id: this.user.username,
},
});
if (this.partner.publicKey !== '') {
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {
this.isSocketRoomLocked = true;
this.emitUserEnter();
});
}
});
this.socket.on('USER_EXIT', () => {
if (this.isSocketRoomLocked) {
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {});
this.isSocketRoomLocked = false;
if (this.isCallStarted) {
// TODO: display toast device is gone ....
this.selfDestrory();
}
}
});
this.socket.on(
'ENCRYPTED_MESSAGE',
(payload: ReceiveEncryptedMessagePayload) => {
this.receiveEncryptedMessage(payload);
}
);
this.socket.on('USER_DISCONNECT', () => {
this.socket.emit('TOGGLE_LOCK_ROOM', null, () => {});
});
// socketConnection.on('TOGGLE_LOCK_ROOM', payload => {
// this.props.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload);
// });
// socketConnection.on('ROOM_LOCKED', payload => {
// this.props.openModal('Room Locked');
// });
window.addEventListener('beforeunload', () => {
this.socket.emit('USER_DISCONNECT');
});
}
private selfDestrory() {
this.partner = nullUser;
this.connectedDevicesService.removeDeviceByID(this.partnerDeviceDetails.id);
if (this.peer !== nullSimplePeer) {
this.peer.destroy();
}
if (this.localStream) {
this.localStream.getTracks().forEach((track) => {
track.stop();
});
this.localStream = null;
}
const sharingSession = this.sharingSessionsService.sharingSessions.get(
this.sharingSessionID
);
sharingSession?.setStatus(SharingSessionStatusEnum.DESTROYED);
sharingSession?.destory();
this.sharingSessionsService.sharingSessions.delete(this.sharingSessionID);
this.onDeviceConnectedCallback = () => {};
this.isCallStarted = false;
this.socket.disconnect();
this.roomIDService.unmarkRoomIDAsTaken(this.roomID);
}
private emitUserEnter() {
if (!this.socket) return;
this.socket.emit('USER_ENTER', {
username: this.user.username,
publicKey: this.user.publicKey,
});
}
async sendEncryptedMessage(payload: SendEncryptedMessagePayload) {
if (!this.socket) return;
if (!this.user) return;
if (!this.partner) return;
const msg = await prepareMessage(payload, this.user, this.partner);
this.socket.emit('ENCRYPTED_MESSAGE', msg.toSend);
}
async receiveEncryptedMessage(payload: ReceiveEncryptedMessagePayload) {
if (!this.user) return;
const message = await processMessage(payload, this.user.privateKey);
log.info(message);
if (message.type === 'CALL_ACCEPTED') {
this.peer.signal(message.payload.signalData);
}
if (message.type === 'DEVICE_DETAILS') {
log.info(message);
log.info(message.payload.browser);
this.socket.emit(
'GET_IP_BY_SOCKET_ID',
message.payload.socketID,
(deviceIP: string) => {
const device = {
id: uuid.v4(),
deviceIP,
deviceType: message.payload.deviceType,
deviceOS: message.payload.os,
deviceBrowser: message.payload.browser,
deviceScreenWidth: message.payload.deviceScreenWidth,
deviceScreenHeight: message.payload.deviceScreenHeight,
sharingSessionID: this.sharingSessionID,
};
this.partnerDeviceDetails = device;
this.onDeviceConnectedCallback(device);
}
);
}
}
callPeer() {
if (process.env.RUN_MODE === 'test') return;
if (this.isCallStarted) return;
this.isCallStarted = true;
this.signalsDataToCallUser.forEach((data: string) => {
this.sendEncryptedMessage({
type: 'CALL_USER',
payload: {
signalData: data,
},
});
});
}
createPeer() {
this.createDesktopCapturerStream(this.desktopCapturerSourceID).then(() => {
const peer = new SimplePeer({
initiator: true,
// trickle: true,
// stream: this.localStream,
// allowHalfTrickle: false,
config: { iceServers: [] },
sdpTransform: (sdp) => {
let newSDP = sdp;
newSDP = setSdpMediaBitrate(
newSDP as string,
'video',
500000
) as typeof sdp;
return newSDP;
},
});
if (this.localStream !== null) {
peer.addStream(this.localStream);
}
peer.on('signal', (data: string) => {
// fired when simple peer and webrtc done preparation to start call on this machine
this.signalsDataToCallUser.push(data);
});
this.peer = peer;
this.peer.on('data', async (data) => {
if (`${data}` === 'set half quality') {
// TODO: later on change to more sophisticated quality change for app window
if (!this.desktopCapturerSourceID.includes('screen')) return;
const newStream = await getDesktopSourceStreamBySourceID(
this.desktopCapturerSourceID,
this.sourceDisplaySize?.width,
this.sourceDisplaySize?.height,
2,
2,
15,
30
);
const newVideoTrack = newStream.getVideoTracks()[0];
const oldTrack = this.localStream?.getVideoTracks()[0];
if (oldTrack && this.localStream) {
peer.replaceTrack(oldTrack, newVideoTrack, this.localStream);
oldTrack.stop();
}
} else if (`${data}` === 'set good quality') {
// TODO: later on change to more sophisticated quality change for app window
if (!this.desktopCapturerSourceID.includes('screen')) return;
const newStream = await getDesktopSourceStreamBySourceID(
this.desktopCapturerSourceID,
this.sourceDisplaySize?.width,
this.sourceDisplaySize?.height,
2,
1
);
const newVideoTrack = newStream.getVideoTracks()[0];
const oldTrack = this.localStream?.getVideoTracks()[0];
if (oldTrack && this.localStream) {
peer.replaceTrack(oldTrack, newVideoTrack, this.localStream);
oldTrack.stop();
}
}
});
return peer;
});
}
// TODO: move outside this file
createDesktopCapturerStream(sourceID: string) {
return new Promise((resolve) => {
try {
if (process.env.RUN_MODE === 'test') resolve();
if (!sourceID.includes('screen')) {
getDesktopSourceStreamBySourceID(sourceID).then((stream) => {
this.localStream = stream;
resolve();
return stream;
});
} else {
// when screen source id
getDesktopSourceStreamBySourceID(
sourceID,
this.sourceDisplaySize?.width,
this.sourceDisplaySize?.height,
2,
1
).then((stream) => {
this.localStream = stream;
resolve();
return stream;
});
}
} catch (e) {
log.error(e);
}
});
}
}