Обмен SDP, создание комнаты, улучшенная организация кода

This commit is contained in:
RoyceDa
2026-03-14 15:28:01 +02:00
parent e79282755b
commit ca36a8d818
2 changed files with 177 additions and 10 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

@@ -0,0 +1,108 @@
import Packet from "../packet";
import Stream from "../stream";
export enum SignalType {
CALL = 0,
KEY_EXCHANGE = 1,
ACTIVE_CALL = 2,
END_CALL = 3,
CREATE_ROOM = 4
}
/**
* Пакет сигналинга, для сигналов WebRTC используется отдельный пакет 27 PacketWebRTCExchange
*/
export class PacketSignalPeer extends Packet {
private src: string = "";
/**
* Назначение
*/
private dst: string = "";
/**
* Используется если SignalType == KEY_EXCHANGE, для идентификации сессии обмена ключами
*/
private sharedPublic: string = "";
private signalType: SignalType = SignalType.CALL;
/**
* Используется если SignalType == CREATE_ROOM,
* для идентификации комнаты на SFU сервере, в которой будет происходить обмен сигналами
* WebRTC для установления P2P соединения между участниками звонка
*/
private roomId: string = "";
public getPacketId(): number {
return 26;
}
public _receive(stream: Stream): void {
this.signalType = stream.readInt8();
this.src = stream.readString();
this.dst = stream.readString();
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 {
const stream = new Stream();
stream.writeInt16(this.getPacketId());
stream.writeInt8(this.signalType);
stream.writeString(this.src);
stream.writeString(this.dst);
if(this.signalType == SignalType.KEY_EXCHANGE){
stream.writeString(this.sharedPublic);
}
if(this.signalType == SignalType.CREATE_ROOM){
stream.writeString(this.roomId);
}
return stream;
}
public setDst(dst: string) {
this.dst = dst;
}
public setSharedPublic(sharedPublic: string) {
this.sharedPublic = sharedPublic;
}
public setSignalType(signalType: SignalType) {
this.signalType = signalType;
}
public getDst(): string {
return this.dst;
}
public getSharedPublic(): string {
return this.sharedPublic;
}
public getSignalType(): SignalType {
return this.signalType;
}
public getSrc(): string {
return this.src;
}
public setSrc(src: string) {
this.src = src;
}
public getRoomId(): string {
return this.roomId;
}
public setRoomId(roomId: string) {
this.roomId = roomId;
}
}