mirror of
https://github.com/pavlobu/deskreen.git
synced 2025-05-17 07:50:17 -07:00
436 lines
13 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
}
|
|
}
|