OAuth session persists via DPAPI; sign-in lives in the Start Stream dialog; add xUnit test project (6 passing)
This commit is contained in:
+42
-11
@@ -126,18 +126,14 @@ public class MainViewModel : ViewModelBase
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isConnected, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(IsNotConnected));
|
||||
OnPropertyChanged(nameof(ShowStartStream));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOffline => StreamStatus == StreamStatus.Offline;
|
||||
public bool IsLive => StreamStatus == StreamStatus.Streaming;
|
||||
public bool LiveIndicatorVisible => IsLive;
|
||||
public bool IsNotConnected => !IsConnected;
|
||||
public bool ShowStartStream => IsConnected && IsOffline;
|
||||
public bool ShowStartStream => IsOffline;
|
||||
public bool ShowChatInactiveMessage => !IsLive;
|
||||
public bool ShowEmptySceneHint => ActiveScene != null && ActiveScene.Sources.Count == 0;
|
||||
public bool ShowPreviewPlaceholder => !ShowEmptySceneHint && ActiveBackgroundImage == null;
|
||||
@@ -406,7 +402,6 @@ public class MainViewModel : ViewModelBase
|
||||
public ICommand AddSourceCommand { get; }
|
||||
public ICommand AddImageCommand { get; }
|
||||
public ICommand RemoveSourceCommand { get; }
|
||||
public ICommand ConnectCommand { get; }
|
||||
public ICommand StartStreamCommand { get; }
|
||||
public ICommand EndStreamCommand { get; }
|
||||
public ICommand OpenSettingsCommand { get; }
|
||||
@@ -424,7 +419,8 @@ public class MainViewModel : ViewModelBase
|
||||
public MainViewModel()
|
||||
{
|
||||
AppLog.Write("MainViewModel ctor begin");
|
||||
_youtubeAuth = new YouTubeAuthService(OAuthCredentials.ClientId, OAuthCredentials.ClientSecret);
|
||||
_youtubeAuth = new YouTubeAuthService(OAuthCredentials.ClientId, OAuthCredentials.ClientSecret,
|
||||
sessionChanged: ch => TokenStore.Save(ch));
|
||||
_youtubeStream = new YouTubeStreamService(_youtubeAuth);
|
||||
_youtubeChat = new YouTubeChatService(_youtubeAuth);
|
||||
AppLog.Write("MainViewModel ctor: services created");
|
||||
@@ -462,7 +458,6 @@ public class MainViewModel : ViewModelBase
|
||||
SubmitBugCommand = new RelayCommand(_ => SubmitBug());
|
||||
SubmitFeatureCommand = new RelayCommand(_ => SubmitFeature());
|
||||
OpenChannelCommand = new RelayCommand(_ => OpenUrl(ChannelUrl));
|
||||
ConnectCommand = new RelayCommand(_ => _ = ConnectAsync());
|
||||
StartStreamCommand = new RelayCommand(_ => BeginGoLive());
|
||||
EndStreamCommand = new RelayCommand(_ => StopStream(), _ => IsLive);
|
||||
SaveLayoutCommand = new RelayCommand(_ => SaveLayoutNow());
|
||||
@@ -472,9 +467,40 @@ public class MainViewModel : ViewModelBase
|
||||
_layoutStore = new LayoutStore(DefaultLayoutPath);
|
||||
_activeLayoutPath = _layoutStore.ActivePath;
|
||||
LoadLayout();
|
||||
_ = LoadSavedSessionAsync();
|
||||
AppLog.Write("MainViewModel ctor end");
|
||||
}
|
||||
|
||||
// Restores the DPAPI-saved OAuth session so sign-in survives restarts.
|
||||
// Best-effort: refresh a near-expiry access token; a session that can no
|
||||
// longer refresh is discarded and the user signs in again.
|
||||
private async Task LoadSavedSessionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var saved = TokenStore.Load();
|
||||
if (saved == null) return;
|
||||
|
||||
_youtubeAuth.SetSession(saved);
|
||||
if (saved.TokenExpiry <= DateTime.UtcNow.AddMinutes(5))
|
||||
{
|
||||
var refreshed = await Task.Run(() => _youtubeAuth.RefreshToken());
|
||||
if (!refreshed)
|
||||
{
|
||||
TokenStore.Clear();
|
||||
AppLog.Write("Saved session could not be refreshed; signed out");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
IsConnected = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLog.Write($"LoadSavedSessionAsync failed: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string DefaultLayoutPath => Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"ytLlive",
|
||||
@@ -870,7 +896,10 @@ public class MainViewModel : ViewModelBase
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private async Task ConnectAsync()
|
||||
// Runs the OAuth flow (browser + loopback callback) on a background thread.
|
||||
// The resulting channel is returned to the caller and persisted via the
|
||||
// auth service's sessionChanged hook.
|
||||
private async Task<YouTubeChannel?> SignInAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -879,15 +908,17 @@ public class MainViewModel : ViewModelBase
|
||||
{
|
||||
MessageBox.Show("Sign-in was unsuccessful. Please try again.", "ytLlive",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
IsConnected = true;
|
||||
return channel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Sign-in failed: {ex.Message}", "ytLlive",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,7 +934,7 @@ public class MainViewModel : ViewModelBase
|
||||
|
||||
private void BeginGoLive()
|
||||
{
|
||||
var dialog = new GoLiveViewModel
|
||||
var dialog = new GoLiveViewModel(() => SignInAsync(), _youtubeAuth.CurrentChannel)
|
||||
{
|
||||
StreamTitle = DefaultStreamTitle,
|
||||
StreamDescription = DefaultStreamDescription,
|
||||
|
||||
Reference in New Issue
Block a user