diff --git a/EbayListingTool/EbayListingTool.csproj b/EbayListingTool/EbayListingTool.csproj
index e7d79a6..4f84d80 100644
--- a/EbayListingTool/EbayListingTool.csproj
+++ b/EbayListingTool/EbayListingTool.csproj
@@ -24,6 +24,7 @@
+
diff --git a/EbayListingTool/Services/EbayAuthService.cs b/EbayListingTool/Services/EbayAuthService.cs
index 6552324..3afb69c 100644
--- a/EbayListingTool/Services/EbayAuthService.cs
+++ b/EbayListingTool/Services/EbayAuthService.cs
@@ -1,8 +1,8 @@
-using System.Diagnostics;
-using System.Net;
using System.Net.Http.Headers;
using System.Text;
+using System.Windows;
using EbayListingTool.Models;
+using EbayListingTool.Views;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -27,7 +27,6 @@ public class EbayAuthService
[
"https://api.ebay.com/oauth/api_scope",
"https://api.ebay.com/oauth/api_scope/sell.inventory",
- "https://api.ebay.com/oauth/api_scope/sell.listing",
"https://api.ebay.com/oauth/api_scope/sell.fulfillment",
"https://api.ebay.com/oauth/api_scope/sell.account"
];
@@ -69,7 +68,6 @@ public class EbayAuthService
public async Task LoginAsync()
{
- var redirectUri = $"http://localhost:{_settings.RedirectPort}/";
var scopeString = Uri.EscapeDataString(string.Join(" ", Scopes));
var authBase = _settings.Sandbox
? "https://auth.sandbox.ebay.com/oauth2/authorize"
@@ -79,39 +77,39 @@ public class EbayAuthService
$"&redirect_uri={Uri.EscapeDataString(_settings.RuName)}" +
$"&response_type=code&scope={scopeString}";
- // Start local listener before opening browser
- using var listener = new HttpListener();
- listener.Prefixes.Add(redirectUri);
- listener.Start();
+ Log($"LoginAsync start — RuName={_settings.RuName}, ClientId={_settings.ClientId}");
- // Open browser
- Process.Start(new ProcessStartInfo(authUrl) { UseShellExecute = true });
+ string? code = null;
- // Wait for redirect with code (60s timeout)
- var contextTask = listener.GetContextAsync();
- if (await Task.WhenAny(contextTask, Task.Delay(TimeSpan.FromSeconds(60))) != contextTask)
+ // Open embedded browser dialog on UI thread and block until it closes
+ await Application.Current.Dispatcher.InvokeAsync(() =>
{
- listener.Stop();
- throw new TimeoutException("eBay login timed out. Please try again.");
+ var win = new EbayLoginWindow(authUrl);
+ var result = win.ShowDialog();
+ if (result == true)
+ code = win.AuthCode;
+ });
+
+ if (string.IsNullOrEmpty(code))
+ {
+ Log("User cancelled login or no code returned");
+ throw new InvalidOperationException("eBay login was cancelled or did not return an authorisation code.");
}
- var context = await contextTask;
- var code = context.Request.QueryString["code"]
- ?? throw new InvalidOperationException("No authorisation code received from eBay.");
-
- // Send OK page to browser
- var responseHtml = "Connected! You can close this tab.
";
- var responseBytes = Encoding.UTF8.GetBytes(responseHtml);
- context.Response.ContentType = "text/html";
- context.Response.ContentLength64 = responseBytes.Length;
- await context.Response.OutputStream.WriteAsync(responseBytes);
- context.Response.Close();
- listener.Stop();
-
+ Log($"Auth code received (length={code.Length})");
await ExchangeCodeForTokenAsync(code);
return _token!.EbayUsername;
}
+ private static readonly string LogFile = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "EbayListingTool", "auth_log.txt");
+
+ private static void Log(string msg)
+ {
+ try { File.AppendAllText(LogFile, $"{DateTime.Now:HH:mm:ss} {msg}\n"); } catch { }
+ }
+
private async Task ExchangeCodeForTokenAsync(string code)
{
var tokenUrl = _settings.Sandbox
@@ -130,20 +128,33 @@ public class EbayAuthService
["redirect_uri"] = _settings.RuName
});
+ Log($"Token exchange → POST {tokenUrl}");
var response = await _http.SendAsync(codeRequest);
var json = await response.Content.ReadAsStringAsync();
+ Log($"Token exchange ← {(int)response.StatusCode}: {json}");
+ System.Diagnostics.Debug.WriteLine($"[eBay token exchange] {(int)response.StatusCode}: {json}");
if (!response.IsSuccessStatusCode)
- throw new HttpRequestException($"Token exchange failed: {json}");
+ throw new HttpRequestException($"Token exchange failed ({(int)response.StatusCode}): {json}");
var obj = JObject.Parse(json);
+
+ var accessToken = obj["access_token"]?.ToString()
+ ?? throw new InvalidOperationException($"No access_token in response: {json}");
+ var expiresIn = obj["expires_in"]?.Value() ?? 7200;
+ var refreshToken = obj["refresh_token"]?.ToString() ?? "";
+ var refreshExpiresIn = obj["refresh_token_expires_in"]?.Value() ?? 0;
+
_token = new EbayToken
{
- AccessToken = obj["access_token"]!.ToString(),
- RefreshToken = obj["refresh_token"]!.ToString(),
- AccessTokenExpiry = DateTime.UtcNow.AddSeconds(obj["expires_in"]!.Value()),
- RefreshTokenExpiry = DateTime.UtcNow.AddSeconds(obj["refresh_token_expires_in"]!.Value()),
+ AccessToken = accessToken,
+ RefreshToken = refreshToken,
+ AccessTokenExpiry = DateTime.UtcNow.AddSeconds(expiresIn),
+ RefreshTokenExpiry = refreshExpiresIn > 0
+ ? DateTime.UtcNow.AddSeconds(refreshExpiresIn)
+ : DateTime.UtcNow.AddDays(18 * 30), // eBay default: 18 months
};
+ Log($"Token set — AccessToken length={accessToken.Length}, Expiry={_token.AccessTokenExpiry:HH:mm:ss}, IsValid={_token.IsAccessTokenValid}");
// Fetch username
_token.EbayUsername = await FetchUsernameAsync(_token.AccessToken);
diff --git a/EbayListingTool/Views/EbayLoginWindow.xaml b/EbayListingTool/Views/EbayLoginWindow.xaml
new file mode 100644
index 0000000..bad421f
--- /dev/null
+++ b/EbayListingTool/Views/EbayLoginWindow.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EbayListingTool/Views/EbayLoginWindow.xaml.cs b/EbayListingTool/Views/EbayLoginWindow.xaml.cs
new file mode 100644
index 0000000..d82c14b
--- /dev/null
+++ b/EbayListingTool/Views/EbayLoginWindow.xaml.cs
@@ -0,0 +1,92 @@
+using System.IO;
+using System.Web;
+using System.Windows;
+using Microsoft.Web.WebView2.Core;
+
+namespace EbayListingTool.Views;
+
+public partial class EbayLoginWindow : Window
+{
+ public string? AuthCode { get; private set; }
+
+ private readonly string _authUrl;
+
+ private static readonly string LogFile = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "EbayListingTool", "auth_log.txt");
+
+ private static void Log(string msg)
+ {
+ try { File.AppendAllText(LogFile, $"{DateTime.Now:HH:mm:ss} [Browser] {msg}\n"); } catch { }
+ }
+
+ public EbayLoginWindow(string authUrl)
+ {
+ InitializeComponent();
+ _authUrl = authUrl;
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ Log($"WebView2 initialising...");
+ try
+ {
+ await Browser.EnsureCoreWebView2Async();
+ Browser.CoreWebView2.NavigationStarting += CoreWebView2_NavigationStarting;
+ Log($"Navigating to auth URL");
+ Browser.CoreWebView2.Navigate(_authUrl);
+ }
+ catch (Exception ex)
+ {
+ Log($"WebView2 init failed: {ex.Message}");
+ MessageBox.Show($"Browser could not initialise: {ex.Message}\n\nEnsure Microsoft Edge WebView2 Runtime is installed.",
+ "Browser Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogResult = false;
+ Close();
+ }
+ }
+
+ private void CoreWebView2_NavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs e)
+ {
+ var url = e.Uri ?? "";
+ Log($"NavigationStarting → {url}");
+
+ if (!url.Contains("ThirdPartyAuth", StringComparison.OrdinalIgnoreCase))
+ return;
+
+ e.Cancel = true;
+ Log($"ThirdPartyAuth intercepted: {url}");
+
+ try
+ {
+ var uri = new Uri(url);
+ var qs = HttpUtility.ParseQueryString(uri.Query);
+ var code = qs["code"];
+
+ Log($"Query params: {string.Join(", ", qs.AllKeys.Select(k => $"{k}={qs[k]?.Substring(0, Math.Min(qs[k]?.Length ?? 0, 30))}"))}");
+
+ if (!string.IsNullOrEmpty(code))
+ {
+ AuthCode = code;
+ DialogResult = true;
+ }
+ else
+ {
+ var error = qs["error"] ?? qs["error_id"] ?? "unknown";
+ var desc = qs["error_description"] ?? qs["error_message"] ?? "";
+ Log($"No code — error={error}, desc={desc}");
+ MessageBox.Show($"eBay login error: {error}\n{desc}", "Login Failed",
+ MessageBoxButton.OK, MessageBoxImage.Warning);
+ DialogResult = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log($"Exception parsing redirect: {ex.Message}");
+ DialogResult = false;
+ }
+
+ Dispatcher.Invoke(Close);
+ }
+}
diff --git a/EbayListingTool/Views/MainWindow.xaml.cs b/EbayListingTool/Views/MainWindow.xaml.cs
index 131bd7a..09965b5 100644
--- a/EbayListingTool/Views/MainWindow.xaml.cs
+++ b/EbayListingTool/Views/MainWindow.xaml.cs
@@ -63,7 +63,11 @@ public partial class MainWindow : MetroWindow
MessageBox.Show(ex.Message, "eBay Login Failed",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
- finally { ConnectBtn.IsEnabled = true; }
+ finally
+ {
+ ConnectBtn.IsEnabled = true;
+ UpdateConnectionState(); // always sync UI to actual auth state
+ }
}
private void DisconnectBtn_Click(object sender, RoutedEventArgs e)