SignalR chat connection OK
This commit is contained in:
parent
bb773ba29a
commit
b22a26f61e
43 changed files with 430 additions and 252 deletions
|
|
@ -14,7 +14,7 @@ reinstall: copy-binaries
|
||||||
@echo "💯 backend started"
|
@echo "💯 backend started"
|
||||||
|
|
||||||
reinstall-prod:
|
reinstall-prod:
|
||||||
make reinstall APP_INSTALL_ENV= APP_INSTALL_PREFIX= APP_INSTALL_ENV=production
|
make reinstall APP_INSTALL_PREFIX= APP_INSTALL_ENV=production
|
||||||
|
|
||||||
generated/$(APP_INSTALL_PREFIX)yavscOrg.service:
|
generated/$(APP_INSTALL_PREFIX)yavscOrg.service:
|
||||||
generated/$(APP_INSTALL_PREFIX)yavscBlogs.service:
|
generated/$(APP_INSTALL_PREFIX)yavscBlogs.service:
|
||||||
|
|
|
||||||
|
|
@ -57,5 +57,25 @@ server {
|
||||||
proxy_read_timeout 100s;
|
proxy_read_timeout 100s;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# SignalR (ChatHub) : WebSocket exige Upgrade/Connection et un timeout
|
||||||
|
# long, sinon la connexion tombe au premier silence ou l'upgrade échoue.
|
||||||
|
# Connection est fixé à "upgrade" (pas de map $connection_upgrade, qui
|
||||||
|
# exigerait une directive map {} globale hors de ce vhost) : SignalR
|
||||||
|
# négocie toujours un upgrade sur ce chemin, et la négociation initiale
|
||||||
|
# (POST /negotiate, sans en-tête Upgrade) n'est pas affectée par cet
|
||||||
|
# en-tête côté Kestrel.
|
||||||
|
location /chathub {
|
||||||
|
proxy_pass $BACKEND_SCHEME://$HTTP_HOST:$BACKEND_PORT;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_buffering off;
|
||||||
|
}
|
||||||
include server-status.conf;
|
include server-status.conf;
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
<application
|
<application
|
||||||
android:label="PostIt"
|
android:label="PostIt"
|
||||||
android:icon="@drawable/Icon"
|
android:icon="@drawable/Icon"
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:hardwareAccelerated="true">
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using PostIt.Models;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using Avalonia.Controls;
|
||||||
using Avalonia.Headless.XUnit;
|
using Avalonia.Headless.XUnit;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
using PostIt.Controls;
|
using PostIt.Controls;
|
||||||
|
using PostIt.Models;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
|
|
||||||
namespace PostIt.Tests;
|
namespace PostIt.Tests;
|
||||||
|
|
@ -21,7 +22,9 @@ public class StatusBarTests
|
||||||
[AvaloniaFact]
|
[AvaloniaFact]
|
||||||
public void Status_message_is_rendered_by_a_SelectableTextBlock()
|
public void Status_message_is_rendered_by_a_SelectableTextBlock()
|
||||||
{
|
{
|
||||||
var bar = new StatusBar { DataContext = StatusNotice.Error("Échec de la suppression : boom") };
|
var bar = new StatusBar {
|
||||||
|
DataContext =
|
||||||
|
StatusNotice.Error("Échec de la suppression : boom") };
|
||||||
var window = new Window { Content = bar };
|
var window = new Window { Content = bar };
|
||||||
window.Show();
|
window.Show();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,15 @@
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
Foreground="{Binding Foreground}"
|
Foreground="{Binding Foreground}"
|
||||||
VerticalAlignment="Top"/>
|
VerticalAlignment="Top"/>
|
||||||
<!-- SelectableTextBlock so the user can select + copy the
|
|
||||||
status message (TextBlock is not selectable in
|
|
||||||
Avalonia). Keeps Text/TextWrapping/Foreground. -->
|
|
||||||
<SelectableTextBlock Text="{Binding Message}"
|
<SelectableTextBlock Text="{Binding Message}"
|
||||||
Foreground="{Binding Foreground}"
|
Foreground="{Binding Foreground}"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
|
<Button Content="{Binding CheckCommandLabel}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding HasCheckCommand}"
|
||||||
|
Command="{Binding CheckCommand}"
|
||||||
|
/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||
14
src/PostIt/PostIt/Models/ChatMessageDto.cs
Normal file
14
src/PostIt/PostIt/Models/ChatMessageDto.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace PostIt.ViewModels.Chat
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// DTO minimal calqué sur l'objet anonyme émis par le hub
|
||||||
|
/// (<c>{ Name, Room, Message }</c>) via l'événement
|
||||||
|
/// <c>ReceiveMessage</c>.
|
||||||
|
/// </summary>
|
||||||
|
public class ChatMessageDto
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Room { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/PostIt/PostIt/Models/ChatRoomInfo.cs
Normal file
11
src/PostIt/PostIt/Models/ChatRoomInfo.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace PostIt.ViewModels.Chat
|
||||||
|
{
|
||||||
|
// DTO minimal calqué sur votre logique serveur ChatRoomInfo
|
||||||
|
public class ChatRoomInfo
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public List<string> Users { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/PostIt/PostIt/Models/IWithLabel.cs
Normal file
6
src/PostIt/PostIt/Models/IWithLabel.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
internal interface IWithLabel
|
||||||
|
{
|
||||||
|
string Label { get; }
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
namespace PostIt.ViewModels;
|
|
||||||
|
|
||||||
public enum StatusSeverity
|
|
||||||
{
|
|
||||||
Info,
|
|
||||||
Warning,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class StatusNotice
|
|
||||||
{
|
|
||||||
public string Message { get; }
|
|
||||||
public StatusSeverity Severity { get; }
|
|
||||||
public string Glyph { get; }
|
|
||||||
public string Background { get; }
|
|
||||||
public string BorderBrush { get; }
|
|
||||||
public string Foreground { get; }
|
|
||||||
|
|
||||||
public StatusNotice(string message, StatusSeverity severity)
|
|
||||||
{
|
|
||||||
Message = string.IsNullOrWhiteSpace(message) ? "Pret." : message;
|
|
||||||
Severity = severity;
|
|
||||||
|
|
||||||
(Glyph, Background, BorderBrush, Foreground) = severity switch
|
|
||||||
{
|
|
||||||
StatusSeverity.Error => ("!", "#7F1D1D", "#C62828", "#e1f0f6"),
|
|
||||||
StatusSeverity.Warning => ("~", "#7C4A03", "#E6A700", "#eaeaea"),
|
|
||||||
_ => ("i", "#E8F0FE", "#5B8DEF", "#1E3A8A"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StatusNotice Info(string message) => new(message, StatusSeverity.Info);
|
|
||||||
public static StatusNotice Warning(string message) => new(message, StatusSeverity.Warning);
|
|
||||||
public static StatusNotice Error(string message) => new(message, StatusSeverity.Error);
|
|
||||||
}
|
|
||||||
8
src/PostIt/PostIt/Models/StatusSeverity.cs
Normal file
8
src/PostIt/PostIt/Models/StatusSeverity.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace PostIt.Models;
|
||||||
|
|
||||||
|
public enum StatusSeverity
|
||||||
|
{
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ using Avalonia;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using PostIt.Helpers;
|
using PostIt.Models;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
using Yavsc.Api.Client.Dtos;
|
using Yavsc.Api.Client.Dtos;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using Yavsc.Api.Client.Dtos;
|
||||||
using Yavsc.Abstract.BlogSpot;
|
using Yavsc.Abstract.BlogSpot;
|
||||||
using Yavsc.Abstract.Identity.Security;
|
using Yavsc.Abstract.Identity.Security;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using PostIt.Models;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using Avalonia;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using PostIt.Helpers;
|
using PostIt.Helpers;
|
||||||
|
using PostIt.Models;
|
||||||
using Yavsc.Abstract.Workflow;
|
using Yavsc.Abstract.Workflow;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using PostIt.Helpers;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
using Yavsc.Abstract.Workflow;
|
using Yavsc.Abstract.Workflow;
|
||||||
|
using PostIt.Models;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Avalonia;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using PostIt.Helpers;
|
using PostIt.Helpers;
|
||||||
|
using PostIt.Models;
|
||||||
using Yavsc.Abstract.Workflow;
|
using Yavsc.Abstract.Workflow;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using Avalonia;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using PostIt.Helpers;
|
using PostIt.Helpers;
|
||||||
|
using PostIt.Models;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using Avalonia;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using PostIt.Helpers;
|
using PostIt.Helpers;
|
||||||
|
using PostIt.Models;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using PostIt.Helpers;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Abstract.Workflow;
|
using Yavsc.Abstract.Workflow;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
using PostIt.Models;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ using Yavsc.Api.Client;
|
||||||
using Yavsc.Abstract.Files;
|
using Yavsc.Abstract.Files;
|
||||||
using PostIt.Helpers;
|
using PostIt.Helpers;
|
||||||
using AvaloniaEdit.Document;
|
using AvaloniaEdit.Document;
|
||||||
|
using PostIt.Models;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,37 @@ using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using PostIt.Models;
|
||||||
|
using PostIt.Services;
|
||||||
|
using Yavsc;
|
||||||
|
|
||||||
namespace PostIt.ViewModels.Chat
|
namespace PostIt.ViewModels.Chat
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel pour la gestion du chat,
|
||||||
|
/// incluant la connexion au Hub SignalR,
|
||||||
|
/// la gestion des salons et des utilisateurs connectés.
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
public partial class ChatViewModel : ViewModelBase
|
public partial class ChatViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly Settings _settings;
|
||||||
|
private readonly YavscApiClient? _api;
|
||||||
|
|
||||||
|
// Le paramètre optionnel permet l'instanciation design-time
|
||||||
|
// (Design.DataContext dans ChatPage.axaml) sans conteneur DI.
|
||||||
|
public ChatViewModel(YavscApiClient? api = null)
|
||||||
|
{
|
||||||
|
var app = App.Current as App;
|
||||||
|
_settings = app!.ServiceProvider!.GetRequiredService<Settings>(); // Assurez-vous que _settings est initialisé avant utilisation.
|
||||||
|
_api = api;
|
||||||
|
Status = new StatusNotice("Déconnecté", StatusSeverity.Info,
|
||||||
|
new RelayCheckCommand("Connecter", async () => await ConnectAsync()));
|
||||||
|
}
|
||||||
|
|
||||||
private HubConnection? _connection;
|
private HubConnection? _connection;
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string ServerUrl { get; protected set; } = "https://yavsc.pschneider.fr/chathub";
|
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string CurrentRoom { get; set; } = "General";
|
public partial string CurrentRoom { get; set; } = "General";
|
||||||
|
|
||||||
|
|
@ -24,7 +43,6 @@ namespace PostIt.ViewModels.Chat
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial StatusNotice Status { get; set; }
|
public partial StatusNotice Status { get; set; }
|
||||||
= new StatusNotice("Déconnecté", StatusSeverity.Info);
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial bool IsConnected { get; set; } = false;
|
public partial bool IsConnected { get; set; } = false;
|
||||||
|
|
@ -40,20 +58,61 @@ namespace PostIt.ViewModels.Chat
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task JoinRoomCommand() => await JoinRoomAsync();
|
public async Task JoinRoomCommand() => await JoinRoomAsync();
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
public async Task ChangeNickCommand() => await ChangeNickAsync();
|
|
||||||
|
|
||||||
public override bool CanNavigateNext { get; protected set; } = false;
|
public override bool CanNavigateNext { get; protected set; } = false;
|
||||||
public override bool CanNavigatePrevious { get; protected set; } = true;
|
public override bool CanNavigatePrevious { get; protected set; } = true;
|
||||||
|
|
||||||
|
private async Task DisconnectAsync()
|
||||||
|
{
|
||||||
|
if (_connection != null)
|
||||||
|
{
|
||||||
|
await _connection.StopAsync();
|
||||||
|
await _connection.DisposeAsync();
|
||||||
|
_connection = null;
|
||||||
|
}
|
||||||
|
IsConnected = false;
|
||||||
|
Status = new StatusNotice("Déconnecté", StatusSeverity.Info,
|
||||||
|
new RelayCheckCommand("Connecter", async () => await ConnectAsync()));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ConnectAsync()
|
private async Task ConnectAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Status = new StatusNotice("Connexion en cours...", StatusSeverity.Info);
|
Status = new StatusNotice("Connexion en cours...", StatusSeverity.Info,
|
||||||
|
new RelayCheckCommand("Vérifier la connexion", async () => await CheckConnectionAsync()));
|
||||||
|
|
||||||
|
if (_api == null || !_api.HasValidSession)
|
||||||
|
{
|
||||||
|
Status = new StatusNotice("Session expirée : reconnectez-vous avant d'ouvrir le chat.", StatusSeverity.Error,
|
||||||
|
new RelayCheckCommand("Reconnecter", async () => await ConnectAsync()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Le hub est mappé à la racine du service ("/chathub"),
|
||||||
|
// pas sous le préfixe versionné "/api/v1" : on le retire.
|
||||||
|
var hubBase = _settings.ApiUrl.TrimEnd('/');
|
||||||
|
if (hubBase.EndsWith("/api/v1", StringComparison.OrdinalIgnoreCase))
|
||||||
|
hubBase = hubBase[..^"/api/v1".Length];
|
||||||
|
var hubUrl = hubBase + "/" + Constants.ChatHubPath;
|
||||||
|
|
||||||
_connection = new HubConnectionBuilder()
|
_connection = new HubConnectionBuilder()
|
||||||
.WithUrl(ServerUrl)
|
.WithUrl(hubUrl,
|
||||||
|
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
// Le hub est [Authorize] : on attache le Bearer OIDC.
|
||||||
|
// AccessTokenProvider est rappelé à chaque reconnexion,
|
||||||
|
// donc on tente un refresh silencieux à chaque appel.
|
||||||
|
// Un refresh mort (invalid_grant) ne doit pas faire
|
||||||
|
// échouer StartAsync avec une exception interne : on
|
||||||
|
// renvoie le token courant (éventuellement expiré) et
|
||||||
|
// le serveur répondra un 401 propre, surfacé à l'UI.
|
||||||
|
options.AccessTokenProvider = async () =>
|
||||||
|
{
|
||||||
|
await _api.TrySilentLoginAsync();
|
||||||
|
return _api.CurrentAccessToken;
|
||||||
|
};
|
||||||
|
})
|
||||||
.WithAutomaticReconnect()
|
.WithAutomaticReconnect()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|
@ -88,15 +147,20 @@ namespace PostIt.ViewModels.Chat
|
||||||
|
|
||||||
await _connection.StartAsync();
|
await _connection.StartAsync();
|
||||||
IsConnected = true;
|
IsConnected = true;
|
||||||
Status = new StatusNotice("Connecté au Hub SignalR", StatusSeverity.Info);
|
Status = new StatusNotice("Connecté au Hub SignalR", StatusSeverity.Info, new RelayCheckCommand("Déconnecter", async () => await DisconnectAsync()));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Status = new StatusNotice($"Erreur : {ex.Message}", StatusSeverity.Error);
|
Status = new StatusNotice($"Erreur : {ex.Message}", StatusSeverity.Error, new RelayCheckCommand("Reconnecter", async () => await ConnectAsync()));
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CheckConnectionAsync()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task JoinRoomAsync()
|
private async Task JoinRoomAsync()
|
||||||
{
|
{
|
||||||
if (_connection == null || string.IsNullOrWhiteSpace(CurrentRoom)) return;
|
if (_connection == null || string.IsNullOrWhiteSpace(CurrentRoom)) return;
|
||||||
|
|
@ -115,21 +179,5 @@ namespace PostIt.ViewModels.Chat
|
||||||
Messages.Add($"Erreur Join: {ex.Message}");
|
Messages.Add($"Erreur Join: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ChangeNickAsync()
|
|
||||||
{
|
|
||||||
if (_connection == null || string.IsNullOrWhiteSpace(NickName)) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Appel de la méthode publique 'Nick' du Hub (void)
|
|
||||||
await _connection.InvokeAsync("Nick", NickName);
|
|
||||||
Messages.Add($"Demande de changement de pseudo vers '{NickName}' envoyée.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Messages.Add($"Erreur Nick: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
33
src/PostIt/PostIt/ViewModels/Chat/RelayCheckCommand.cs
Normal file
33
src/PostIt/PostIt/ViewModels/Chat/RelayCheckCommand.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace PostIt.ViewModels.Chat
|
||||||
|
{
|
||||||
|
internal class RelayCheckCommand : ICommand, IWithLabel
|
||||||
|
{
|
||||||
|
public string Label { get; }
|
||||||
|
private Func<Task> value;
|
||||||
|
|
||||||
|
public RelayCheckCommand(string v, Func<Task> value)
|
||||||
|
{
|
||||||
|
this.Label = v;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler? CanExecuteChanged;
|
||||||
|
|
||||||
|
public bool CanExecute(object? parameter)
|
||||||
|
{
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(object? parameter)
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
_ = value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using PostIt.Models;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Abstract.Workflow;
|
using Yavsc.Abstract.Workflow;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using PostIt.Models;
|
||||||
using Yavsc.Abstract.Workflow;
|
using Yavsc.Abstract.Workflow;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
using Yavsc.Models.Billing;
|
using Yavsc.Models.Billing;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using PostIt.Models;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
public interface IActionStatusViewModel
|
public interface IActionStatusViewModel
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Avalonia;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using PostIt.Helpers;
|
using PostIt.Helpers;
|
||||||
|
using PostIt.Models;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Net.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using PostIt.Models;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("PostIt.Tests")]
|
[assembly: InternalsVisibleTo("PostIt.Tests")]
|
||||||
|
|
||||||
|
|
|
||||||
43
src/PostIt/PostIt/ViewModels/StatusNotice.cs
Normal file
43
src/PostIt/PostIt/ViewModels/StatusNotice.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System.Windows.Input;
|
||||||
|
using PostIt.Models;
|
||||||
|
|
||||||
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
public sealed class StatusNotice
|
||||||
|
{
|
||||||
|
public string Message { get; }
|
||||||
|
public StatusSeverity Severity { get; }
|
||||||
|
public string Glyph { get; }
|
||||||
|
public string Background { get; }
|
||||||
|
public string BorderBrush { get; }
|
||||||
|
public string Foreground { get; }
|
||||||
|
|
||||||
|
public bool HasCheckCommand { get; }
|
||||||
|
|
||||||
|
public ICommand? CheckCommand { get; }
|
||||||
|
|
||||||
|
public string CheckCommandLabel { get; }
|
||||||
|
|
||||||
|
public StatusNotice(string message, StatusSeverity severity, ICommand? checkCommand)
|
||||||
|
{
|
||||||
|
Message = string.IsNullOrWhiteSpace(message) ? "Pret." : message;
|
||||||
|
Severity = severity;
|
||||||
|
|
||||||
|
(Glyph, Background, BorderBrush, Foreground) = severity switch
|
||||||
|
{
|
||||||
|
StatusSeverity.Error => ("!", "#7F1D1D", "#C62828", "#e1f0f6"),
|
||||||
|
StatusSeverity.Warning => ("~", "#7C4A03", "#E6A700", "#eaeaea"),
|
||||||
|
_ => ("i", "#E8F0FE", "#5B8DEF", "#1E3A8A"),
|
||||||
|
};
|
||||||
|
HasCheckCommand = checkCommand != null;
|
||||||
|
CheckCommand = checkCommand;
|
||||||
|
CheckCommandLabel = HasCheckCommand ? "Check" :
|
||||||
|
checkCommand == null ?
|
||||||
|
string.Empty :
|
||||||
|
checkCommand is IWithLabel labeled ? labeled.Label : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StatusNotice Info(string message, ICommand? checkCommand = null) => new(message, StatusSeverity.Info, checkCommand);
|
||||||
|
public static StatusNotice Warning(string message, ICommand? checkCommand = null) => new(message, StatusSeverity.Warning, checkCommand);
|
||||||
|
public static StatusNotice Error(string message, ICommand? checkCommand = null) => new(message, StatusSeverity.Error, checkCommand);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<ContentPage
|
<ContentPage
|
||||||
|
x:Class="PostIt.Views.Chat.ChatPage"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
||||||
|
|
@ -9,38 +10,71 @@
|
||||||
xmlns:postitControls="using:PostIt.Controls"
|
xmlns:postitControls="using:PostIt.Controls"
|
||||||
xmlns:views="using:PostIt.Views.Chat"
|
xmlns:views="using:PostIt.Views.Chat"
|
||||||
xmlns:vm="using:PostIt.ViewModels.Chat"
|
xmlns:vm="using:PostIt.ViewModels.Chat"
|
||||||
x:Class="PostIt.Views.Chat.ChatPage"
|
x:DataType="vm:ChatViewModel"
|
||||||
x:DataType="vm:ChatViewModel" >
|
>
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:ChatViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto, *, Auto" Margin="10">
|
<Grid Margin="10" RowDefinitions="Auto, *, Auto">
|
||||||
|
|
||||||
<Grid Grid.Row="1" ColumnDefinitions="*, 200" Margin="0,0,0,10">
|
<Grid
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
ColumnDefinitions="*, 200">
|
||||||
|
|
||||||
<!-- Journal des flux de messages & notifications -->
|
<!-- Journal des flux de messages & notifications -->
|
||||||
<Grid Grid.Column="0" RowDefinitions="Auto, *">
|
<Grid Grid.Column="0" RowDefinitions="Auto, *">
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="10" Margin="0,0,0,5">
|
<StackPanel
|
||||||
<TextBox Text="{Binding CurrentRoom}" Watermark="Nom du salon" Width="200" VerticalAlignment="Center"/>
|
Grid.Row="0"
|
||||||
<Button Content="Rejoindre Salon (Join)" Command="{Binding JoinRoomCommand}"/>
|
Margin="0,0,0,5"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10">
|
||||||
|
<TextBox
|
||||||
|
Width="200"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding CurrentRoom}"
|
||||||
|
PlaceholderText="Nom du salon" />
|
||||||
|
<Button Command="{Binding JoinRoomCommand}" Content="Rejoindre Salon (Join)" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ListBox Grid.Row="1" ItemsSource="{Binding Messages}" BorderBrush="#E0E0E0" BorderThickness="1">
|
<ListBox
|
||||||
|
Grid.Row="1"
|
||||||
|
BorderBrush="#E0E0E0"
|
||||||
|
BorderThickness="1"
|
||||||
|
ItemsSource="{Binding Messages}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding}" TextWrapping="Wrap"/>
|
<TextBlock Text="{Binding}" TextWrapping="Wrap" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Liste des utilisateurs présents (MàJ via l'événement 'joint') -->
|
<!-- Liste des utilisateurs présents (MàJ via l'événement 'joint') -->
|
||||||
<Border Grid.Column="1" Margin="10,0,0,0" BorderBrush="#E0E0E0" BorderThickness="1" >
|
<Border
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
BorderBrush="#E0E0E0"
|
||||||
|
BorderThickness="1">
|
||||||
<Grid RowDefinitions="Auto, *">
|
<Grid RowDefinitions="Auto, *">
|
||||||
<TextBlock Grid.Row="0" Text="Membres Présents" FontWeight="Bold" Margin="5" HorizontalAlignment="Center"/>
|
<TextBlock
|
||||||
<ListBox Grid.Row="1" ItemsSource="{Binding ConnectedUsers}" BorderThickness="0"/>
|
Grid.Row="0"
|
||||||
|
Margin="5"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Text="Membres Présents" />
|
||||||
|
<ListBox
|
||||||
|
Grid.Row="1"
|
||||||
|
BorderThickness="0"
|
||||||
|
ItemsSource="{Binding ConnectedUsers}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
<postitControls:StatusBar Grid.Row="4" DataContext="{Binding Status}" x:Name="StatusBar" />
|
<postitControls:StatusBar
|
||||||
|
x:Name="StatusBar"
|
||||||
|
Grid.Row="4"
|
||||||
|
DataContext="{Binding Status}" />
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ public partial class ChatPage : ContentPage
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<views:SessionStatusBanner
|
<views:SessionStatusBanner
|
||||||
x:Name="SessionBanner"
|
x:Name="SessionBanner"
|
||||||
|
DataContext="{Binding SessionStatus}"
|
||||||
DockPanel.Dock="Bottom"/>
|
DockPanel.Dock="Bottom"/>
|
||||||
<NavigationPage x:Name="NavRoot"/>
|
<NavigationPage x:Name="NavRoot"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using PostIt.Models;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,8 @@ namespace Yavsc.Abstract.Chat
|
||||||
{
|
{
|
||||||
public static class ChatHubConstants
|
public static class ChatHubConstants
|
||||||
{
|
{
|
||||||
public const string HubGroupAuthenticated = "authenticated";
|
|
||||||
public const string HubGroupAnonymous = "anonymous";
|
|
||||||
public const string HubGroupCops= "cops";
|
public const string HubGroupCops= "cops";
|
||||||
public const string HubGroupRomsPrefix = "room_";
|
public const string HubGroupRoomsPrefix = "room_";
|
||||||
public const int MaxChanelName = 255;
|
public const int MaxChanelName = 255;
|
||||||
|
|
||||||
public const string HubGroupFollowingPrefix = "fol ";
|
public const string HubGroupFollowingPrefix = "fol ";
|
||||||
|
|
@ -21,6 +19,6 @@ namespace Yavsc.Abstract.Chat
|
||||||
public const string HopWontKickOp = "Half operator cannot kick any operator";
|
public const string HopWontKickOp = "Half operator cannot kick any operator";
|
||||||
public const string LabAuthChatUser = "Authenticated chat user";
|
public const string LabAuthChatUser = "Authenticated chat user";
|
||||||
public const string NoKickOnCop = "No, you won´t, you´ĺl never do kick a cop, it is the bad.";
|
public const string NoKickOnCop = "No, you won´t, you´ĺl never do kick a cop, it is the bad.";
|
||||||
public const string LabNoJoinNoSend = "LabnoJoinNoSend";
|
public const string LabNoJoinNoSend = "LabNoJoinNoSend";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ namespace Yavsc
|
||||||
public const string BlogTagPath = "blogtag";
|
public const string BlogTagPath = "blogtag";
|
||||||
public const string CirclePath = "circle";
|
public const string CirclePath = "circle";
|
||||||
public const string CommentsPath = "blogcomments";
|
public const string CommentsPath = "blogcomments";
|
||||||
|
public const string ChatHubPath = "chathub";
|
||||||
|
|
||||||
public static readonly Scope[] SiteScopes = {
|
public static readonly Scope[] SiteScopes = {
|
||||||
new Scope { Id = "profile", Description = "Your profile informations" },
|
new Scope { Id = "profile", Description = "Your profile informations" },
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Yavsc.Models;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Yavsc.Services;
|
using Yavsc.Services;
|
||||||
|
using Yavsc.Server.Hubs;
|
||||||
|
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
|
|
@ -114,13 +115,11 @@ internal class Program
|
||||||
.UseRouting()
|
.UseRouting()
|
||||||
.UseAuthentication()
|
.UseAuthentication()
|
||||||
.UseAuthorization()
|
.UseAuthorization()
|
||||||
.UseCors("default")
|
.UseCors("default");
|
||||||
;
|
|
||||||
app.MapIdentityApi<ApplicationUser>().RequireAuthorization("ApiScope");
|
app.MapIdentityApi<ApplicationUser>().RequireAuthorization("ApiScope");
|
||||||
app.MapDefaultControllerRoute();
|
app.MapDefaultControllerRoute();
|
||||||
|
|
||||||
|
|
||||||
app.UseSession();
|
app.UseSession();
|
||||||
|
app.MapHub<ChatHub>("/" + Constants.ChatHubPath);
|
||||||
await app.RunAsync();
|
await app.RunAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@ namespace Yavsc.Controllers
|
||||||
|
|
||||||
public IActionResult SetUserName()
|
public IActionResult SetUserName()
|
||||||
{
|
{
|
||||||
return View(new SetUserNameViewModel() { UserName = User.Identity.Name });
|
return View(new SetUserNameViewModel() { UserName = User.GetUserName() });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|
|
||||||
|
|
@ -1053,8 +1053,6 @@ public static class HostingExtensions
|
||||||
app.UseCors("default");
|
app.UseCors("default");
|
||||||
app.MapDefaultControllerRoute();
|
app.MapDefaultControllerRoute();
|
||||||
app.MapStaticAssets(staticAssetsManifestPath);
|
app.MapStaticAssets(staticAssetsManifestPath);
|
||||||
//app.MapRazorPages();
|
|
||||||
app.MapHub<ChatHub>("/chathub");
|
|
||||||
|
|
||||||
WorkflowHelpers.ConfigureBillingService();
|
WorkflowHelpers.ConfigureBillingService();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,29 @@ public static class ServiceExtensions
|
||||||
options.ClaimsIssuer = authority;
|
options.ClaimsIssuer = authority;
|
||||||
options.Audience = audiences[0];
|
options.Audience = audiences[0];
|
||||||
|
|
||||||
|
// Sending the access token in the query string is required when using WebSockets or ServerSentEvents
|
||||||
|
// due to a limitation in Browser APIs. We restrict it to only calls to the
|
||||||
|
// SignalR hub in this code.
|
||||||
|
// See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
|
||||||
|
// for more information about security considerations when using
|
||||||
|
// the query string to transmit the access token.
|
||||||
|
options.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnMessageReceived = context =>
|
||||||
|
{
|
||||||
|
var accessToken = context.Request.Query["access_token"];
|
||||||
|
|
||||||
|
// If the request is for our hub...
|
||||||
|
var path = context.HttpContext.Request.Path;
|
||||||
|
if (!string.IsNullOrEmpty(accessToken) &&
|
||||||
|
(path.StartsWithSegments("/hubs/chat")))
|
||||||
|
{
|
||||||
|
// Read the token out of the query string
|
||||||
|
context.Token = accessToken;
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
};
|
||||||
// Dev: every Yavsc resource service (Yavsc.Api, Yavsc.Blogs,
|
// Dev: every Yavsc resource service (Yavsc.Api, Yavsc.Blogs,
|
||||||
// Yavsc.Org itself) validates JWTs against the OP that runs
|
// Yavsc.Org itself) validates JWTs against the OP that runs
|
||||||
// on https://localhost:5001 with a self-signed dev cert.
|
// on https://localhost:5001 with a self-signed dev cert.
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
#pragma warning disable CS4014 // Dans la mesure où cet appel n'est pas attendu, l'exécution de la méthode actuelle continue avant la fin de l'appel
|
#pragma warning disable CS4014 // Dans la mesure où cet appel n'est pas attendu, l'exécution de la méthode actuelle continue avant la fin de l'appel
|
||||||
|
|
||||||
namespace Yavsc.Server.Hubs
|
namespace Yavsc.Server.Hubs
|
||||||
|
|
@ -36,6 +37,8 @@ namespace Yavsc.Server.Hubs
|
||||||
using Yavsc.Models.Chat;
|
using Yavsc.Models.Chat;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Yavsc.Services;
|
using Yavsc.Services;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
public partial class ChatHub : Hub, IDisposable
|
public partial class ChatHub : Hub, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContext _dbContext;
|
private readonly ApplicationDbContext _dbContext;
|
||||||
|
|
@ -54,39 +57,30 @@ namespace Yavsc.Server.Hubs
|
||||||
_localizer = stringLocalizerFactory.Create(typeof(ChatHub));
|
_localizer = stringLocalizerFactory.Create(typeof(ChatHub));
|
||||||
|
|
||||||
_cxManager = connexionManager;
|
_cxManager = connexionManager;
|
||||||
_cxManager.SetErrorHandler ((context, error) =>
|
_cxManager.SetErrorHandler((context, error) =>
|
||||||
{
|
{
|
||||||
NotifyUser(NotificationTypes.Error, context, error);
|
NotifyUser(NotificationTypes.Error, context, error);
|
||||||
});
|
});
|
||||||
_logger = loggerFactory.CreateLogger<ChatHub>();
|
_logger = loggerFactory.CreateLogger<ChatHub>();
|
||||||
InputValidator = new HubInputValidator(_localizer) {
|
InputValidator = new HubInputValidator(_localizer)
|
||||||
|
|
||||||
NotifyUser = async (type, target, msg) => await this.NotifyUser(type, target, msg) };
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetUserName(string cxId, string userName)
|
|
||||||
{
|
{
|
||||||
_cxManager.SetUserName(cxId, userName);
|
|
||||||
|
NotifyUser = async (type, target, msg) => await this.NotifyUser(type, target, msg)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnConnectedAsync()
|
public override async Task OnConnectedAsync()
|
||||||
{
|
{
|
||||||
bool isAuth = Context.User?.Identity?.IsAuthenticated ?? false;
|
var user = Context.User;
|
||||||
bool isCop = false;
|
bool isCop = false;
|
||||||
string userName = setUserName();
|
string userName = user.GetUserName();
|
||||||
if (isAuth)
|
|
||||||
{
|
|
||||||
|
|
||||||
var group = isAuth ?
|
|
||||||
ChatHubConstants.HubGroupAuthenticated : ChatHubConstants.HubGroupAnonymous;
|
|
||||||
// Log ("Cx: " + group);
|
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, group);
|
|
||||||
_logger.LogInformation(_localizer.GetString(ChatHubConstants.LabAuthChatUser));
|
_logger.LogInformation(_localizer.GetString(ChatHubConstants.LabAuthChatUser));
|
||||||
|
|
||||||
var userId = _dbContext.Users.First(u => u.UserName == Context.User.Identity.Name).Id;
|
var userId = _dbContext.Users.First(u => u.UserName == userName).Id;
|
||||||
|
|
||||||
await Clients.Group(ChatHubConstants.HubGroupFollowingPrefix + userId).SendAsync("notifyUser", NotificationTypes.Connected, userName, null);
|
await Clients.Group(ChatHubConstants.HubGroupFollowingPrefix + userId).SendAsync("notifyUser", NotificationTypes.Connected, userName, null);
|
||||||
isCop = Context.User.IsInMsRole(Constants.AdminGroupName) ;
|
isCop = Context.User.IsInMsRole(Constants.AdminGroupName);
|
||||||
if (isCop)
|
if (isCop)
|
||||||
{
|
{
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, ChatHubConstants.HubGroupCops);
|
await Groups.AddToGroupAsync(Context.ConnectionId, ChatHubConstants.HubGroupCops);
|
||||||
|
|
@ -96,57 +90,36 @@ namespace Yavsc.Server.Hubs
|
||||||
{
|
{
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, ChatHubConstants.HubGroupFollowingPrefix + uid);
|
await Groups.AddToGroupAsync(Context.ConnectionId, ChatHubConstants.HubGroupFollowingPrefix + uid);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, ChatHubConstants.HubGroupAnonymous);
|
|
||||||
}
|
|
||||||
_cxManager.OnConnected(Context.ConnectionId, isCop);
|
_cxManager.OnConnected(Context.ConnectionId, isCop);
|
||||||
await base.OnConnectedAsync();
|
await base.OnConnectedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
string setUserName(string queryUname = "anon")
|
|
||||||
{
|
|
||||||
if (Context.User != null)
|
|
||||||
if (Context.User.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
SetUserName(Context.ConnectionId, Context.User.Identity.Name);
|
|
||||||
return Context.User.Identity.Name;
|
|
||||||
}
|
|
||||||
anonymousSequence++;
|
|
||||||
var anonymousName = $"{ChatHubConstants.AnonymousUserNamePrefix}{queryUname}{anonymousSequence}";
|
|
||||||
SetUserName(Context.ConnectionId, anonymousName);
|
|
||||||
return anonymousName;
|
|
||||||
}
|
|
||||||
|
|
||||||
static long anonymousSequence = 0;
|
|
||||||
|
|
||||||
public override async Task OnDisconnectedAsync(Exception ex)
|
public override async Task OnDisconnectedAsync(Exception ex)
|
||||||
{
|
{
|
||||||
string userName = Context.User?.Identity.Name;
|
string userName = Context.User?.GetUserName();
|
||||||
if (userName != null)
|
if (userName != null)
|
||||||
{
|
{
|
||||||
var user = _dbContext.Users.FirstOrDefault(u => u.UserName == userName);
|
var user = _dbContext.Users.FirstOrDefault(u => u.UserName == userName);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
var userId = user.Id;
|
var userId = user.Id;
|
||||||
await Clients.Group(ChatHubConstants.HubGroupFollowingPrefix + userId).SendAsync("notifyUser", NotificationTypes.DisConnected, userName, null);
|
await Clients.Group(ChatHubConstants.HubGroupFollowingPrefix + userId).SendAsync("notifyUser", NotificationTypes.DisConnected, userName, null);
|
||||||
|
}
|
||||||
|
|
||||||
_cxManager.OnDisconnected(Context.ConnectionId);
|
_cxManager.OnDisconnected(Context.ConnectionId);
|
||||||
}
|
}
|
||||||
await base.OnDisconnectedAsync(ex);
|
await base.OnDisconnectedAsync(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
public async Task Nick(string nickName)
|
/// Renvoie le login résolu côté serveur (claim <c>name</c> de
|
||||||
|
/// l'organisation Yavsc.Org, via <see cref="UserHelpers.GetUserName"/>).
|
||||||
|
/// Permet au client d'afficher l'identité réellement authentifiée,
|
||||||
|
/// sans jamais faire confiance à un pseudo fourni par le client.
|
||||||
|
/// </summary>
|
||||||
|
public string WhoAmI()
|
||||||
{
|
{
|
||||||
if (!InputValidator.ValidateUserName(nickName)) return;
|
return Context.User?.GetUserName() ?? string.Empty;
|
||||||
|
|
||||||
var candidate = "?" + nickName;
|
|
||||||
if (_cxManager.IsConnected(candidate))
|
|
||||||
{
|
|
||||||
await NotifyUser(NotificationTypes.ExistingUserName, nickName, "aborting");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_cxManager.SetUserName( Context.ConnectionId, candidate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsPresent(string roomName, string userName)
|
bool IsPresent(string roomName, string userName)
|
||||||
|
|
@ -163,7 +136,7 @@ namespace Yavsc.Server.Hubs
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomGroupName = ChatHubConstants.HubGroupRomsPrefix + roomName;
|
var roomGroupName = ChatHubConstants.HubGroupRoomsPrefix + roomName;
|
||||||
var user = _cxManager.GetUserName(Context.ConnectionId);
|
var user = _cxManager.GetUserName(Context.ConnectionId);
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, roomGroupName);
|
await Groups.AddToGroupAsync(Context.ConnectionId, roomGroupName);
|
||||||
ChatRoomInfo channelInfo;
|
ChatRoomInfo channelInfo;
|
||||||
|
|
@ -172,7 +145,9 @@ namespace Yavsc.Server.Hubs
|
||||||
_logger.LogInformation($"Joining");
|
_logger.LogInformation($"Joining");
|
||||||
channelInfo = _cxManager.Join(roomName, Context.ConnectionId);
|
channelInfo = _cxManager.Join(roomName, Context.ConnectionId);
|
||||||
await Clients.Group(roomGroupName).SendAsync("notifyRoom", NotificationTypes.UserJoin, roomName, user);
|
await Clients.Group(roomGroupName).SendAsync("notifyRoom", NotificationTypes.UserJoin, roomName, user);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
_logger.LogInformation($"already present");
|
_logger.LogInformation($"already present");
|
||||||
// in case in an additional connection,
|
// in case in an additional connection,
|
||||||
// one only send info on room without
|
// one only send info on room without
|
||||||
|
|
@ -185,10 +160,9 @@ namespace Yavsc.Server.Hubs
|
||||||
return channelInfo;
|
return channelInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
public void Register(string room)
|
public void Register(string room)
|
||||||
{
|
{
|
||||||
if (!InputValidator.ValidateRoomName(room)) return ;
|
if (!InputValidator.ValidateRoomName(room)) return;
|
||||||
var existent = _dbContext.ChatRoom.Any(r => r.Name == room);
|
var existent = _dbContext.ChatRoom.Any(r => r.Name == room);
|
||||||
if (existent)
|
if (existent)
|
||||||
{
|
{
|
||||||
|
|
@ -212,27 +186,25 @@ namespace Yavsc.Server.Hubs
|
||||||
_dbContext.SaveChanges(user.Id);
|
_dbContext.SaveChanges(user.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
public void KickBan(string roomName, string userName, string reason)
|
public void KickBan(string roomName, string userName, string reason)
|
||||||
{
|
{
|
||||||
if (!InputValidator.ValidateRoomName(roomName)) return ;
|
if (!InputValidator.ValidateRoomName(roomName)) return;
|
||||||
if (!InputValidator.ValidateUserName(userName)) return ;
|
if (!InputValidator.ValidateUserName(userName)) return;
|
||||||
if (!InputValidator.ValidateReason(reason)) return;
|
if (!InputValidator.ValidateReason(reason)) return;
|
||||||
Kick(roomName, userName, reason);
|
Kick(roomName, userName, reason);
|
||||||
Ban(roomName, userName, reason);
|
Ban(roomName, userName, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
public async Task Kick(string roomName, string userName, string reason)
|
public async Task Kick(string roomName, string userName, string reason)
|
||||||
{
|
{
|
||||||
if (!InputValidator.ValidateRoomName(roomName)) return ;
|
if (!InputValidator.ValidateRoomName(roomName)) return;
|
||||||
if (!InputValidator.ValidateUserName(userName)) return ;
|
if (!InputValidator.ValidateUserName(userName)) return;
|
||||||
if (!InputValidator.ValidateReason(reason)) return;
|
if (!InputValidator.ValidateReason(reason)) return;
|
||||||
ChatRoomInfo channelInfo;
|
ChatRoomInfo channelInfo;
|
||||||
var roomGroupName = ChatHubConstants.HubGroupRomsPrefix + roomName;
|
var roomGroupName = ChatHubConstants.HubGroupRoomsPrefix + roomName;
|
||||||
if (_cxManager.TryGetChanInfo(roomName, out channelInfo))
|
if (_cxManager.TryGetChanInfo(roomName, out channelInfo))
|
||||||
{
|
{
|
||||||
if (!_cxManager.IsPresent(roomName,userName))
|
if (!_cxManager.IsPresent(roomName, userName))
|
||||||
{
|
{
|
||||||
NotifyErrorToCallerInRoom(roomName, $"{userName} was not found in {roomName}.");
|
NotifyErrorToCallerInRoom(roomName, $"{userName} was not found in {roomName}.");
|
||||||
return;
|
return;
|
||||||
|
|
@ -242,42 +214,42 @@ namespace Yavsc.Server.Hubs
|
||||||
if (!_cxManager.Kick(Context.ConnectionId, userName, roomName, reason)) return;
|
if (!_cxManager.Kick(Context.ConnectionId, userName, roomName, reason)) return;
|
||||||
}
|
}
|
||||||
var cxIds = _cxManager.GetConnexionIds(userName);
|
var cxIds = _cxManager.GetConnexionIds(userName);
|
||||||
if (cxIds!=null) foreach(var cx in cxIds)
|
if (cxIds != null) foreach (var cx in cxIds)
|
||||||
await Groups.RemoveFromGroupAsync(cx, roomGroupName);
|
await Groups.RemoveFromGroupAsync(cx, roomGroupName);
|
||||||
await Clients.Group(roomGroupName).SendAsync("notifyRoom", NotificationTypes.Kick, roomName, $"{userName}: {reason}");
|
await Clients.Group(roomGroupName).SendAsync("notifyRoom", NotificationTypes.Kick, roomName, $"{userName}: {reason}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
public void Ban(string roomName, string userName, string reason)
|
public void Ban(string roomName, string userName, string reason)
|
||||||
{
|
{
|
||||||
if (!InputValidator.ValidateRoomName(roomName)) return ;
|
if (!InputValidator.ValidateRoomName(roomName)) return;
|
||||||
if (!InputValidator.ValidateUserName(userName)) return ;
|
if (!InputValidator.ValidateUserName(userName)) return;
|
||||||
if (!InputValidator.ValidateReason(reason)) return;
|
if (!InputValidator.ValidateReason(reason)) return;
|
||||||
var cxIds = _cxManager.GetConnexionIds(userName);
|
var cxIds = _cxManager.GetConnexionIds(userName);
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize(Constants.AdminGroupName)]
|
||||||
public void GLine(string userName, string reason)
|
public void GLine(string userName, string reason)
|
||||||
{
|
{
|
||||||
if (!InputValidator.ValidateUserName(userName)) return ;
|
if (!InputValidator.ValidateUserName(userName)) return;
|
||||||
if (!InputValidator.ValidateReason(reason)) return;
|
if (!InputValidator.ValidateReason(reason)) return;
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Part(string roomName, string reason)
|
public void Part(string roomName, string reason)
|
||||||
{
|
{
|
||||||
if (!InputValidator.ValidateRoomName(roomName)) return ;
|
if (!InputValidator.ValidateRoomName(roomName)) return;
|
||||||
if (!InputValidator.ValidateReason(reason)) return;
|
if (!InputValidator.ValidateReason(reason)) return;
|
||||||
if (_cxManager.Part(Context.ConnectionId, roomName, reason))
|
if (_cxManager.Part(Context.ConnectionId, roomName, reason))
|
||||||
{
|
{
|
||||||
var roomGroupName = ChatHubConstants.HubGroupRomsPrefix + roomName;
|
var roomGroupName = ChatHubConstants.HubGroupRoomsPrefix + roomName;
|
||||||
var group = Clients.Group(roomGroupName);
|
var group = Clients.Group(roomGroupName);
|
||||||
var userName = _cxManager.GetUserName(Context.ConnectionId);
|
var userName = _cxManager.GetUserName(Context.ConnectionId);
|
||||||
group.SendAsync("notifyRoom", NotificationTypes.UserPart, roomName, $"{userName}: {reason}");
|
group.SendAsync("notifyRoom", NotificationTypes.UserPart, roomName, $"{userName}: {reason}");
|
||||||
Groups.RemoveFromGroupAsync(Context.ConnectionId, roomGroupName);
|
Groups.RemoveFromGroupAsync(Context.ConnectionId, roomGroupName);
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
_logger.LogError("Could not part");
|
_logger.LogError("Could not part");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -291,16 +263,18 @@ namespace Yavsc.Server.Hubs
|
||||||
public async Task Send(string roomName, string message)
|
public async Task Send(string roomName, string message)
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Send {roomName} {message}");
|
_logger.LogInformation($"Send {roomName} {message}");
|
||||||
if (!InputValidator.ValidateRoomName(roomName)) {
|
if (!InputValidator.ValidateRoomName(roomName))
|
||||||
|
{
|
||||||
_logger.LogError($"Invalid roomName : {roomName}");
|
_logger.LogError($"Invalid roomName : {roomName}");
|
||||||
return ;
|
return;
|
||||||
}
|
}
|
||||||
if (!InputValidator.ValidateMessage(message)) {
|
if (!InputValidator.ValidateMessage(message))
|
||||||
|
{
|
||||||
_logger.LogError($"Invalid message : {message}");
|
_logger.LogError($"Invalid message : {message}");
|
||||||
return ;
|
return;
|
||||||
}
|
}
|
||||||
var groupName = ChatHubConstants.HubGroupRomsPrefix + roomName;
|
var groupName = ChatHubConstants.HubGroupRoomsPrefix + roomName;
|
||||||
ChatRoomInfo channelInfo ;
|
ChatRoomInfo channelInfo;
|
||||||
if (!_cxManager.TryGetChanInfo(roomName, out channelInfo))
|
if (!_cxManager.TryGetChanInfo(roomName, out channelInfo))
|
||||||
{
|
{
|
||||||
_logger.LogError($"No such room : {roomName}");
|
_logger.LogError($"No such room : {roomName}");
|
||||||
|
|
@ -317,7 +291,7 @@ namespace Yavsc.Server.Hubs
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var group = Clients.Group(groupName);
|
var group = Clients.Group(groupName);
|
||||||
var msg = new { Name = userName, Room = roomName, Message = message};
|
var msg = new { Name = userName, Room = roomName, Message = message };
|
||||||
await group.SendAsync("ReceiveMessage", msg);
|
await group.SendAsync("ReceiveMessage", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,7 +306,6 @@ namespace Yavsc.Server.Hubs
|
||||||
await Clients.Caller.SendAsync("notifyUserInRoom", type, room, message);
|
await Clients.Caller.SendAsync("notifyUserInRoom", type, room, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
public async Task SendPV(string userName, string message)
|
public async Task SendPV(string userName, string message)
|
||||||
{
|
{
|
||||||
// Authorized code
|
// Authorized code
|
||||||
|
|
@ -342,17 +315,17 @@ namespace Yavsc.Server.Hubs
|
||||||
if (!InputValidator.ValidateUserName(userName))
|
if (!InputValidator.ValidateUserName(userName))
|
||||||
{
|
{
|
||||||
_logger.LogError($"Invalid username : {userName}");
|
_logger.LogError($"Invalid username : {userName}");
|
||||||
return ;
|
return;
|
||||||
}
|
}
|
||||||
if (!InputValidator.ValidateMessage(message))
|
if (!InputValidator.ValidateMessage(message))
|
||||||
{
|
{
|
||||||
_logger.LogError($"Invalid message : {message}");
|
_logger.LogError($"Invalid message : {message}");
|
||||||
return ;
|
return;
|
||||||
}
|
}
|
||||||
_logger.LogInformation($"Message form is validated.");
|
_logger.LogInformation($"Message form is validated.");
|
||||||
var identityUserName = Context.User.GetUserName();
|
var identityUserName = Context.User.GetUserName();
|
||||||
|
|
||||||
if (userName[0] != '?' && Context.User!=null)
|
if (userName[0] != '?' && Context.User != null)
|
||||||
if (!Context.User.IsInMsRole(Constants.AdminGroupName))
|
if (!Context.User.IsInMsRole(Constants.AdminGroupName))
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -373,7 +346,7 @@ namespace Yavsc.Server.Hubs
|
||||||
|
|
||||||
_logger.LogInformation("getting cx id´s");
|
_logger.LogInformation("getting cx id´s");
|
||||||
var cxIds = _cxManager.GetConnexionIds(userName);
|
var cxIds = _cxManager.GetConnexionIds(userName);
|
||||||
if (cxIds==null || cxIds.Count()==0)
|
if (cxIds == null || cxIds.Count() == 0)
|
||||||
_logger.LogError($"No such connected user : {userName}");
|
_logger.LogError($"No such connected user : {userName}");
|
||||||
else foreach (var connectionId in cxIds)
|
else foreach (var connectionId in cxIds)
|
||||||
{
|
{
|
||||||
|
|
@ -385,18 +358,18 @@ namespace Yavsc.Server.Hubs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
public async Task SendStream(string connectionId, long streamId, string message)
|
public async Task SendStream(string connectionId, long streamId, string message)
|
||||||
{
|
{
|
||||||
// Authorized code
|
// Authorized code
|
||||||
Debug.Assert(Context.User != null);
|
Debug.Assert(Context.User != null);
|
||||||
Debug.Assert(Context.User.Identity != null);
|
Debug.Assert(Context.User.Identity != null);
|
||||||
if (!InputValidator.ValidateMessage(message)) return;
|
if (!InputValidator.ValidateMessage(message)) return;
|
||||||
var sender = Context.User.Identity.Name;
|
var sender = Context.User.GetUserName();
|
||||||
var cli = Clients.Client(connectionId);
|
var cli = Clients.Client(connectionId);
|
||||||
await cli.SendAsync("addStreamInfo", sender, streamId, message);
|
await cli.SendAsync("addStreamInfo", sender, streamId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore CS4014 // Dans la mesure où cet appel n'est pas attendu, l'exécution de la méthode actuelle continue avant la fin de l'appel
|
|
||||||
|
#pragma warning restore CS4014
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ using Yavsc.ViewModels.Chat;
|
||||||
namespace Yavsc.Services
|
namespace Yavsc.Services
|
||||||
{
|
{
|
||||||
public interface IConnexionManager {
|
public interface IConnexionManager {
|
||||||
void SetUserName(string cxId, string userName);
|
|
||||||
|
|
||||||
string GetUserName (string cxId);
|
string GetUserName (string cxId);
|
||||||
void OnConnected(string cxId, bool isCop);
|
void OnConnected(string cxId, bool isCop);
|
||||||
bool IsConnected(string candidate);
|
bool IsConnected(string candidate);
|
||||||
|
|
|
||||||
|
|
@ -53,23 +53,6 @@ namespace Yavsc.Services
|
||||||
_localizer = stringLocFactory.Create(typeof(HubConnectionManager));
|
_localizer = stringLocFactory.Create(typeof(HubConnectionManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetUserName(string cxId, string userName)
|
|
||||||
{
|
|
||||||
string oldUname;
|
|
||||||
if (ChatUserNames.TryGetValue(cxId, out oldUname))
|
|
||||||
{
|
|
||||||
// this is a rename
|
|
||||||
if (oldUname == userName) return;
|
|
||||||
ChatCxIds[userName] = ChatCxIds[oldUname];
|
|
||||||
ChatCxIds[oldUname] = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// this is a connexion
|
|
||||||
ChatCxIds[userName] = new List<string>() { cxId };
|
|
||||||
}
|
|
||||||
ChatUserNames[cxId] = userName;
|
|
||||||
}
|
|
||||||
// Username must have been set before calling this method.
|
// Username must have been set before calling this method.
|
||||||
public void OnConnected(string cxId, bool isCop)
|
public void OnConnected(string cxId, bool isCop)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue