Compare commits

..

2 Commits

4 changed files with 147 additions and 14 deletions

View File

@@ -1,11 +1,12 @@
import { Call } from "@/app/components/Call/Call";
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
import { createContext, useEffect, useRef, useState } from "react";
import { createContext, useRef, useState } from "react";
import nacl from 'tweetnacl';
import { useSender } from "../ProtocolProvider/useSender";
import { PacketSignal, SignalType } from "../ProtocolProvider/protocol/packets/packet.signal";
import { PacketSignalPeer, SignalType } from "../ProtocolProvider/protocol/packets/packet.signal.peer";
import { usePacket } from "../ProtocolProvider/usePacket";
import { usePublicKey } from "../AccountProvider/usePublicKey";
import { PacketWebRTC, WebRTCSignalType } from "../ProtocolProvider/protocol/packets/packet.webrtc";
export interface CallContextValue {
@@ -64,15 +65,40 @@ export function CallProvider(props : CallProviderProps) {
const send = useSender();
const publicKey = usePublicKey();
const peerConnectionRef = useRef<RTCPeerConnection | null>(null);
const roomIdRef = useRef<string>("");
const roleRef = useRef<CallRole | null>(null);
const [sharedSecret, setSharedSecret] = useState<string>("");
useEffect(() => {
console.info("TRACE -> ", sharedSecret)
}, [sharedSecret]);
usePacket(27, async (packet: PacketWebRTC) => {
if(!activeCall || callState != CallState.WEB_RTC_EXCHANGE){
/**
* Нет активного звонка или мы не на стадии обмена WebRTC сигналами, игнорируем
*/
return;
}
const signalType = packet.getSignalType();
if(signalType == WebRTCSignalType.ANSWER){
/**
* Другая сторона (сервер SFU) отправил нам SDP ответ на наш оффер
*/
const sdp = JSON.parse(packet.getSdpOrCandidate());
await peerConnectionRef.current?.setRemoteDescription(new RTCSessionDescription(sdp));
info("Received WebRTC answer and set remote description");
return;
}
if(signalType == WebRTCSignalType.ICE_CANDIDATE){
/**
* Другая сторона отправила нам ICE кандидата для установления WebRTC соединения
*/
const candidate = JSON.parse(packet.getSdpOrCandidate());
await peerConnectionRef.current?.addIceCandidate(new RTCIceCandidate(candidate));
info("Received WebRTC ICE candidate and added to peer connection");
return;
}
}, [activeCall, sessionKeys, callState]);
usePacket(26, (packet: PacketSignal) => {
usePacket(26, async (packet: PacketSignalPeer) => {
const signalType = packet.getSignalType();
if(activeCall){
/**
@@ -123,13 +149,19 @@ export function CallProvider(props : CallProviderProps) {
/**
* Нам нужно отправить свой публичный ключ другой стороне, чтобы она тоже могла создать общую секретную сессию
*/
const signalPacket = new PacketSignal();
const signalPacket = new PacketSignalPeer();
signalPacket.setSrc(publicKey);
signalPacket.setDst(packet.getSrc());
signalPacket.setSignalType(SignalType.KEY_EXCHANGE);
signalPacket.setSharedPublic(Buffer.from(sessionKeys.publicKey).toString('hex'));
send(signalPacket);
setCallState(CallState.WEB_RTC_EXCHANGE);
/**
* Создаем комнату на сервере SFU, комнату создает звонящий
*/
let webRtcSignal = new PacketSignalPeer();
webRtcSignal.setSignalType(SignalType.CREATE_ROOM);
send(webRtcSignal);
}
if(signalType == SignalType.KEY_EXCHANGE && roleRef.current == CallRole.CALLEE){
console.info("EXCHANGE SIGNAL RECEIVED, CALLEE ROLE");
@@ -152,6 +184,32 @@ export function CallProvider(props : CallProviderProps) {
setSharedSecret(Buffer.from(computedSharedSecret).toString('hex'));
setCallState(CallState.WEB_RTC_EXCHANGE);
}
if(signalType == SignalType.CREATE_ROOM) {
/**
* Создана комната для обмена WebRTC потоками
*/
roomIdRef.current = packet.getRoomId();
info("WebRTC room created with id: " + packet.getRoomId());
/**
* Нужно отправить свой SDP оффер другой стороне, чтобы установить WebRTC соединение
*/
peerConnectionRef.current = new RTCPeerConnection({
//Experemental
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
});
/**
* Отправляем свой оффер другой стороне
*/
let offer = await peerConnectionRef.current.createOffer();
peerConnectionRef.current.setLocalDescription(offer);
let offerSignal = new PacketWebRTC();
offerSignal.setSignalType(WebRTCSignalType.OFFER);
offerSignal.setSdpOrCandidate(JSON.stringify(offer));
send(offerSignal);
return;
}
}, [activeCall, sessionKeys]);
const generateSessionKeys = () => {
@@ -165,7 +223,7 @@ export function CallProvider(props : CallProviderProps) {
setActiveCall(dialog);
setCallState(CallState.CONNECTING);
setShowCallView(true);
const signalPacket = new PacketSignal();
const signalPacket = new PacketSignalPeer();
signalPacket.setSrc(publicKey);
signalPacket.setDst(dialog);
signalPacket.setSignalType(SignalType.CALL);
@@ -174,12 +232,13 @@ export function CallProvider(props : CallProviderProps) {
}
const close = () => {
const packetSignal = new PacketSignal();
const packetSignal = new PacketSignalPeer();
packetSignal.setSrc(publicKey);
packetSignal.setDst(activeCall);
packetSignal.setSignalType(SignalType.END_CALL);
send(packetSignal);
peerConnectionRef.current = null;
roomIdRef.current = "";
setActiveCall("");
setCallState(CallState.ENDED);
setShowCallView(false);
@@ -199,7 +258,7 @@ export function CallProvider(props : CallProviderProps) {
* Звонок принят, генерируем ключи для сессии и отправляем их другой стороне для установления защищенного канала связи
*/
const keys = generateSessionKeys();
const signalPacket = new PacketSignal();
const signalPacket = new PacketSignalPeer();
signalPacket.setSrc(publicKey);
signalPacket.setDst(activeCall);
signalPacket.setSignalType(SignalType.KEY_EXCHANGE);

View File

@@ -5,13 +5,14 @@ export enum SignalType {
CALL = 0,
KEY_EXCHANGE = 1,
ACTIVE_CALL = 2,
END_CALL = 3
END_CALL = 3,
CREATE_ROOM = 4
}
/**
* Пакет сигналинга, для сигналов WebRTC используется отдельный пакет 27 PacketWebRTCExchange
*/
export class PacketSignal extends Packet {
export class PacketSignalPeer extends Packet {
private src: string = "";
/**
@@ -25,6 +26,13 @@ export class PacketSignal extends Packet {
private signalType: SignalType = SignalType.CALL;
/**
* Используется если SignalType == CREATE_ROOM,
* для идентификации комнаты на SFU сервере, в которой будет происходить обмен сигналами
* WebRTC для установления P2P соединения между участниками звонка
*/
private roomId: string = "";
public getPacketId(): number {
return 26;
@@ -37,6 +45,9 @@ export class PacketSignal extends Packet {
if(this.signalType == SignalType.KEY_EXCHANGE){
this.sharedPublic = stream.readString();
}
if(this.signalType == SignalType.CREATE_ROOM){
this.roomId = stream.readString();
}
}
public _send(): Promise<Stream> | Stream {
@@ -48,6 +59,9 @@ export class PacketSignal extends Packet {
if(this.signalType == SignalType.KEY_EXCHANGE){
stream.writeString(this.sharedPublic);
}
if(this.signalType == SignalType.CREATE_ROOM){
stream.writeString(this.roomId);
}
return stream;
}
@@ -83,4 +97,12 @@ export class PacketSignal extends Packet {
this.src = src;
}
public getRoomId(): string {
return this.roomId;
}
public setRoomId(roomId: string) {
this.roomId = roomId;
}
}

View File

@@ -0,0 +1,52 @@
import Packet from "../packet";
import Stream from "../stream";
export enum WebRTCSignalType {
OFFER = 0,
ANSWER = 1,
ICE_CANDIDATE = 2
}
/**
* Пакет для обмена сигналами WebRTC, такими как оффер, ответ и ICE кандидаты.
* Используется на стадии WEB_RTC_EXCHANGE в сигналинге звонков.
*/
export class PacketWebRTC extends Packet {
private signalType: WebRTCSignalType = WebRTCSignalType.OFFER;
private sdpOrCandidate: string = "";
public getPacketId(): number {
return 27;
}
public _receive(stream: Stream): void {
this.signalType = stream.readInt8();
this.sdpOrCandidate = stream.readString();
}
public _send(): Promise<Stream> | Stream {
let stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeInt8(this.signalType);
stream.writeString(this.sdpOrCandidate);
return stream;
}
public setSignalType(type: WebRTCSignalType) {
this.signalType = type;
}
public getSignalType(): WebRTCSignalType {
return this.signalType;
}
public setSdpOrCandidate(data: string) {
this.sdpOrCandidate = data;
}
public getSdpOrCandidate(): string {
return this.sdpOrCandidate;
}
}

View File

@@ -25,7 +25,7 @@ import { PacketDeviceNew } from "./packets/packet.device.new";
import { PacketDeviceList } from "./packets/packet.device.list";
import { PacketDeviceResolve } from "./packets/packet.device.resolve";
import { PacketSync } from "./packets/packet.sync";
import { PacketSignal } from "./packets/packet.signal";
import { PacketSignalPeer } from "./packets/packet.signal.peer";
export default class Protocol extends EventEmitter {
private serverAddress: string;
@@ -126,7 +126,7 @@ export default class Protocol extends EventEmitter {
this._supportedPackets.set(0x17, new PacketDeviceList());
this._supportedPackets.set(0x18, new PacketDeviceResolve());
this._supportedPackets.set(25, new PacketSync());
this._supportedPackets.set(26, new PacketSignal());
this._supportedPackets.set(26, new PacketSignalPeer());
}
private _findWaiters(packetId: number): ((packet: Packet) => void)[] {