Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 127 additions & 48 deletions Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
// Lucid.Rtc high-level API
private RtcConnection? _rtcConnection;
private readonly ConcurrentDictionary<string, Peer> _peers = new();
private bool _nativeClientAvailable;

// 接続状態
private int _activeConnections;
Expand Down Expand Up @@ -59,8 +60,16 @@

try
{
// Initialize Lucid.Rtc connection
InitializeRtcConnection();
// Initialize Lucid.Rtc connection (if native client is enabled)
if (_settings.TryUseNativeClient)
{
InitializeRtcConnection();
}
else
{
_logger?.LogInformation("Native WebRTC client disabled, using simulation mode");
_nativeClientAvailable = false;
}

// SignalRハブ接続を構築
_hubConnection = new HubConnectionBuilder()
Expand Down Expand Up @@ -105,8 +114,9 @@
// 既存のピアを取得して接続開始
await DiscoverAndConnectPeersAsync();

_logger?.LogInformation("WebRTC interface started. E2EE: {E2ee}, Backend: Lucid.Rtc",
_settings.EnableE2ee ? "Enabled" : "Disabled");
_logger?.LogInformation("WebRTC interface started. E2EE: {E2ee}, Mode: {Mode}",
_settings.EnableE2ee ? "Enabled" : "Disabled",
_nativeClientAvailable ? "Native (Lucid.Rtc)" : "SignalR (Fallback)");
}
catch (Exception ex)
{
Expand All @@ -117,43 +127,59 @@

private void InitializeRtcConnection()
{
var builder = new RtcConnectionBuilder();

// STUN servers
foreach (var stunServer in _settings.StunServers)
try
{
builder.WithStunServer(stunServer);
}
var builder = new RtcConnectionBuilder();

// TURN server (optional)
if (!string.IsNullOrEmpty(_settings.TurnServerUrl))
{
builder.WithTurnServer(
_settings.TurnServerUrl,
_settings.TurnUsername ?? "",
_settings.TurnPassword ?? "");
}
// STUN servers
foreach (var stunServer in _settings.StunServers)
{
builder.WithStunServer(stunServer);
}

// Other settings
builder
.WithIceConnectionTimeout(_settings.IceConnectionTimeoutMs)
.WithDataChannelReliable(_settings.DataChannelReliable);
// TURN server (optional)
if (!string.IsNullOrEmpty(_settings.TurnServerUrl))
{
builder.WithTurnServer(
_settings.TurnServerUrl,
_settings.TurnUsername ?? "",
_settings.TurnPassword ?? "");
}

// Other settings
builder
.WithIceConnectionTimeout(_settings.IceConnectionTimeoutMs)
.WithDataChannelReliable(_settings.DataChannelReliable);

_rtcConnection = builder.Build();
_rtcConnection = builder.Build();

// Register event handlers with method chaining
_rtcConnection
.On<Lucid.Rtc.PeerConnectedEvent>(e => OnPeerConnected(e.PeerId, e.Peer))
.On<Lucid.Rtc.PeerDisconnectedEvent>(e => OnPeerDisconnected(e.PeerId))
.On<Lucid.Rtc.MessageReceivedEvent>(e => HandleMessage(e.PeerId, e.Data))
.On<Lucid.Rtc.IceCandidateEvent>(e => SendIceCandidate(e.PeerId, e.Candidate))
.On<Lucid.Rtc.OfferReadyEvent>(e => SendOffer(e.PeerId, e.Sdp))
.On<Lucid.Rtc.AnswerReadyEvent>(e => SendAnswer(e.PeerId, e.Sdp))
.On<Lucid.Rtc.DataChannelOpenEvent>(e => OnDataChannelOpen(e.PeerId, e.Peer))
.On<Lucid.Rtc.DataChannelClosedEvent>(e => OnDataChannelClosed(e.PeerId, e.Peer))
.On<Lucid.Rtc.ErrorEvent>(e => _logger?.LogError("Lucid.Rtc error: {Message}", e.Message));
// Register event handlers with method chaining
_rtcConnection
.On<Lucid.Rtc.PeerConnectedEvent>(e => OnPeerConnected(e.PeerId, e.Peer))

Check warning on line 158 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peer' in 'void WebRtcChatInterface.OnPeerConnected(string peerId, Peer peer)'.

Check warning on line 158 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.OnPeerConnected(string peerId, Peer peer)'.

Check warning on line 158 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peer' in 'void WebRtcChatInterface.OnPeerConnected(string peerId, Peer peer)'.

Check warning on line 158 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.OnPeerConnected(string peerId, Peer peer)'.
.On<Lucid.Rtc.PeerDisconnectedEvent>(e => OnPeerDisconnected(e.PeerId))

Check warning on line 159 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.OnPeerDisconnected(string peerId)'.

Check warning on line 159 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.OnPeerDisconnected(string peerId)'.
.On<Lucid.Rtc.MessageReceivedEvent>(e => HandleMessage(e.PeerId, e.Data))

Check warning on line 160 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.HandleMessage(string peerId, byte[] data)'.

Check warning on line 160 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.HandleMessage(string peerId, byte[] data)'.
.On<Lucid.Rtc.IceCandidateEvent>(e => SendIceCandidate(e.PeerId, e.Candidate))

Check warning on line 161 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.SendIceCandidate(string peerId, IceCandidate candidate)'.

Check warning on line 161 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.SendIceCandidate(string peerId, IceCandidate candidate)'.
.On<Lucid.Rtc.OfferReadyEvent>(e => SendOffer(e.PeerId, e.Sdp))

Check warning on line 162 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.SendOffer(string peerId, string sdp)'.

Check warning on line 162 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.SendOffer(string peerId, string sdp)'.
.On<Lucid.Rtc.AnswerReadyEvent>(e => SendAnswer(e.PeerId, e.Sdp))

Check warning on line 163 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.SendAnswer(string peerId, string sdp)'.

Check warning on line 163 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.SendAnswer(string peerId, string sdp)'.
.On<Lucid.Rtc.DataChannelOpenEvent>(e => OnDataChannelOpen(e.PeerId, e.Peer))

Check warning on line 164 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peer' in 'void WebRtcChatInterface.OnDataChannelOpen(string peerId, Peer peer)'.

Check warning on line 164 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.OnDataChannelOpen(string peerId, Peer peer)'.

Check warning on line 164 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peer' in 'void WebRtcChatInterface.OnDataChannelOpen(string peerId, Peer peer)'.

Check warning on line 164 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.OnDataChannelOpen(string peerId, Peer peer)'.
.On<Lucid.Rtc.DataChannelClosedEvent>(e => OnDataChannelClosed(e.PeerId, e.Peer))

Check warning on line 165 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.OnDataChannelClosed(string peerId, Peer peer)'.

Check warning on line 165 in Clawleash.Interfaces.WebRTC/WebRtcChatInterface.cs

View workflow job for this annotation

GitHub Actions / pack-interfaces (Clawleash.Interfaces.WebRTC)

Possible null reference argument for parameter 'peerId' in 'void WebRtcChatInterface.OnDataChannelClosed(string peerId, Peer peer)'.
.On<Lucid.Rtc.ErrorEvent>(e => _logger?.LogError("Lucid.Rtc error: {Message}", e.Message));

_logger?.LogInformation("Lucid.Rtc connection initialized");
_nativeClientAvailable = true;
_logger?.LogInformation("Lucid.Rtc connection initialized");
}
catch (DllNotFoundException ex)
{
_logger?.LogWarning(ex, "Native WebRTC library not found, falling back to SignalR mode");
_nativeClientAvailable = false;
_rtcConnection = null;
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to initialize native WebRTC, falling back to SignalR mode");
_nativeClientAvailable = false;
_rtcConnection = null;
}
}

private void OnPeerConnected(string peerId, Peer peer)
Expand Down Expand Up @@ -381,6 +407,16 @@
{
await HandleE2eeKeyExchangeAsync(data.FromPeerId, data.SessionId, data.PublicKey);
});

// DataChannelメッセージ受信(SignalRフォールバック用)
_hubConnection!.On<DataChannelMessageEvent>("datachannel-message", async data =>
{
// ネイティブクライアントが利用できない場合のみ処理
if (!_nativeClientAvailable)
{
await HandleDataChannelMessageAsync(data.FromPeerId, Convert.FromBase64String(data.Payload));
}
});
}

private async Task RegisterAsync()
Expand Down Expand Up @@ -690,7 +726,7 @@
public async Task SendMessageAsync(string message, string? replyToMessageId = null,
CancellationToken cancellationToken = default)
{
if (_rtcConnection == null || string.IsNullOrEmpty(_localPeerId))
if (_hubConnection == null || string.IsNullOrEmpty(_localPeerId))
{
_logger?.LogWarning("WebRTC not connected");
return;
Expand All @@ -707,33 +743,70 @@
payload = Encoding.UTF8.GetBytes(message);
}

// 特定のピアに送信(返信の場合)
if (!string.IsNullOrEmpty(replyToMessageId) && _channelTracking.TryGetValue(replyToMessageId, out var targetPeerId))
// ネイティブクライアントが利用可能な場合はLucid.Rtcを使用
if (_nativeClientAvailable && _rtcConnection != null)
{
if (_peers.TryGetValue(targetPeerId, out var peer))
// 特定のピアに送信(返信の場合)
if (!string.IsNullOrEmpty(replyToMessageId) && _channelTracking.TryGetValue(replyToMessageId, out var targetPeerId))
{
if (_peers.TryGetValue(targetPeerId, out var peer))
{
try
{
peer.Send(payload);
_logger?.LogDebug("Sent message to peer {PeerId} via native", targetPeerId);
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to send message to peer {PeerId}", targetPeerId);
}
}
}
else
{
// 全ピアにブロードキャスト
try
{
peer.Send(payload);
_logger?.LogDebug("Sent message to peer {PeerId}", targetPeerId);
_rtcConnection.Broadcast(payload);
_logger?.LogDebug("Broadcast message to all peers via native");
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to send message to peer {PeerId}", targetPeerId);
_logger?.LogWarning(ex, "Failed to broadcast message");
}
}
}
else
{
// 全ピアにブロードキャスト
try
// SignalRフォールバックモード
var payloadBase64 = Convert.ToBase64String(payload);

if (!string.IsNullOrEmpty(replyToMessageId) && _channelTracking.TryGetValue(replyToMessageId, out var targetPeerId))
{
_rtcConnection.Broadcast(payload);
_logger?.LogDebug("Broadcast message to all peers");
try
{
await _hubConnection.InvokeAsync("SendDataChannelMessage", targetPeerId, payloadBase64, cancellationToken);
_logger?.LogDebug("Sent message to peer {PeerId} via SignalR", targetPeerId);
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to send message to peer {PeerId} via SignalR", targetPeerId);
}
}
catch (Exception ex)
else
{
_logger?.LogWarning(ex, "Failed to broadcast message");
// 全ピアに送信
foreach (var peerId in _peers.Keys)
{
try
{
await _hubConnection.InvokeAsync("SendDataChannelMessage", peerId, payloadBase64, cancellationToken);
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to send message to peer {PeerId} via SignalR", peerId);
}
}
}
}
}
Expand Down Expand Up @@ -875,3 +948,9 @@
public string SessionId { get; set; } = string.Empty;
public string PublicKey { get; set; } = string.Empty;
}

internal class DataChannelMessageEvent
{
public string FromPeerId { get; set; } = string.Empty;
public string Payload { get; set; } = string.Empty;
}
Loading