ec78c0cdf5
Introduce Luna.Identity project with user identity service, pairing code generation, and channel linking. Channels (Telegram, CLI) now verify link status before routing messages and prompt users to pair via the web interface. WebChannel handles /link <channel-type> <code> commands to complete the pairing flow. Fix inverted expiration check in IsPairingCodeExpired that caused codes to be treated as expired immediately after creation.
91 lines
3.1 KiB
C#
91 lines
3.1 KiB
C#
using Luna.Channels.Abstractions;
|
|
using Luna.Core.Abstractions;
|
|
using Luna.Identity.Abstractions;
|
|
using Luna.Shared;
|
|
|
|
namespace Luna.Channels.Web;
|
|
|
|
public class WebChannel(string channelId, IChatHubContext hubContext, IUserIdentityService identityService) : IChannel
|
|
{
|
|
public string ChannelId { get; } = channelId;
|
|
public string ChannelType => Abstractions.ChannelType.Web;
|
|
public string DisplayName { get; } = "Web Interface";
|
|
public bool IsConnected { get; }
|
|
|
|
public event ChannelMessageReceivedEventHandler? MessageReceived;
|
|
public event ChannelConnectionChangedEventHandler? ConnectionStateChanged;
|
|
|
|
public Task<bool> VerifyChannelLinkAsync()
|
|
=> Task.FromResult(true);
|
|
|
|
public async Task SendStreamingMessageAsync(IAsyncEnumerable<ChatStreamUpdate> messageStream, CancellationToken cancellationToken = default)
|
|
{
|
|
await hubContext.SendStreamingResponseAsync(ChannelId, messageStream, cancellationToken);
|
|
}
|
|
|
|
public async Task SendMessageAsync(ChannelMessage message, CancellationToken cancellationToken = default)
|
|
{
|
|
await hubContext.SendResponseAsync(ChannelId, message, cancellationToken);
|
|
}
|
|
|
|
public async Task RaiseMessageReceivedAsync(object? sender, ChannelMessage message)
|
|
{
|
|
if (message.Content.StartsWith("/"))
|
|
{
|
|
await HandleCommandAsync(message.Content);
|
|
return;
|
|
}
|
|
|
|
MessageReceived?.Invoke(sender, new ChannelMessageReceivedEventArgs
|
|
{
|
|
Channel = this,
|
|
Message = message,
|
|
IsHandled = false
|
|
});
|
|
}
|
|
|
|
private async Task HandleCommandAsync(string command)
|
|
{
|
|
var parts = command.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
var commandName = parts[0].ToLowerInvariant();
|
|
|
|
var responseText = commandName switch
|
|
{
|
|
"/link" => HandleLinkCommand(parts),
|
|
_ => $"Unknown command: {commandName}"
|
|
};
|
|
|
|
await SendMessageAsync(new ChannelMessage(
|
|
MessageId: Guid.NewGuid().ToString(),
|
|
ConversationId: Guid.NewGuid().ToString(),
|
|
SenderId: "luna",
|
|
SenderName: "Luna",
|
|
Content: responseText,
|
|
Timestamp: DateTimeOffset.UtcNow));
|
|
}
|
|
|
|
private string HandleLinkCommand(string[] parts)
|
|
{
|
|
if (parts.Length < 3)
|
|
return "Usage: /link <channel-type> <pairing-code>";
|
|
|
|
var channelType = parts[1].ToLowerInvariant();
|
|
var code = parts[2].ToUpperInvariant();
|
|
var currentUser = identityService.GetCurrentUser();
|
|
|
|
if (identityService.IsChannelLinked(currentUser.UserId, channelType))
|
|
return $"Channel {channelType} is already linked to your account.";
|
|
|
|
try
|
|
{
|
|
identityService.CompleteChannelLink(currentUser.UserId, channelType, code);
|
|
return $"Successfully linked {channelType} channel to your account.";
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return $"Failed to link channel: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
public void Dispose() { }
|
|
} |