refactor: remove all API cache reads/writes; live-only refresh via browser
This commit is contained in:
+23
-114
@@ -79,12 +79,8 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Check whether we have any evidence of a prior successful sign-in
|
// CookieStore being non-empty is our signal that the user has signed in.
|
||||||
bool hasCachedAuth = !string.IsNullOrEmpty(AppSettings.Default.Email) ||
|
if (string.IsNullOrEmpty(AppSettings.Default.CookieStore))
|
||||||
!string.IsNullOrEmpty(AppSettings.Default.OrgId) ||
|
|
||||||
!string.IsNullOrEmpty(AppSettings.Default.CookieStore);
|
|
||||||
|
|
||||||
if (!hasCachedAuth)
|
|
||||||
{
|
{
|
||||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
@@ -102,9 +98,9 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
string? email = null, orgId = null, planLabel = null;
|
string? email = null, orgId = null, planLabel = null;
|
||||||
string? refreshError = null;
|
string? refreshError = null;
|
||||||
|
|
||||||
// Primary path: persistent background WebView2 (always-live cookies, same as macOS).
|
// Primary: persistent background WebView2 — always-live cookies, same as macOS.
|
||||||
// Falls back to HttpClient with saved cookies only if the browser isn't ready yet
|
// Fallback: HttpClient with saved cookies, only if the browser isn't ready yet
|
||||||
// (i.e., the very first refresh that races with InitAsync completing).
|
// (the brief window while InitAsync is still navigating at startup).
|
||||||
if (App.BackgroundBrowser.IsReady)
|
if (App.BackgroundBrowser.IsReady)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -134,20 +130,6 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in blanks from cache
|
|
||||||
if (string.IsNullOrEmpty(email)) email = AppSettings.Default.Email;
|
|
||||||
if (string.IsNullOrEmpty(orgId)) orgId = AppSettings.Default.OrgId;
|
|
||||||
if (limits.Count == 0 && !string.IsNullOrEmpty(AppSettings.Default.UsageJson))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var cached = JsonSerializer.Deserialize<UsageResponse>(
|
|
||||||
AppSettings.Default.UsageJson, JsonOpts);
|
|
||||||
limits = BuildLimits(cached);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var limit in limits)
|
foreach (var limit in limits)
|
||||||
{
|
{
|
||||||
var key = limit.Window.ToString();
|
var key = limit.Window.ToString();
|
||||||
@@ -166,7 +148,6 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
Limits = limits;
|
Limits = limits;
|
||||||
Overage = overage;
|
Overage = overage;
|
||||||
Prepaid = prepaid;
|
Prepaid = prepaid;
|
||||||
// Only replace ExtraUsage when the response explicitly includes it
|
|
||||||
if (extraUsage != null) ExtraUsage = extraUsage;
|
if (extraUsage != null) ExtraUsage = extraUsage;
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(planLabel)) PlanLabel = planLabel;
|
if (!string.IsNullOrEmpty(planLabel)) PlanLabel = planLabel;
|
||||||
@@ -187,9 +168,8 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Browser-based refresh (primary path) ─────────────────────────────────
|
// ── Browser-based refresh (primary) ───────────────────────────────────────
|
||||||
// Runs JS on the persistent claude.ai page — same live cookies as macOS WKWebView.
|
// Runs JS on the persistent claude.ai page. Same live cookies as macOS WKWebView.
|
||||||
// Fetches bootstrap (orgId/email/planLabel) + usage + overage + prepaid in one shot.
|
|
||||||
|
|
||||||
private async Task<(List<AgentLimit>, OverageSpendLimit?, PrepaidCredits?, ExtraUsage?, string?, string?, string?)>
|
private async Task<(List<AgentLimit>, OverageSpendLimit?, PrepaidCredits?, ExtraUsage?, string?, string?, string?)>
|
||||||
TryBrowserRefreshAsync()
|
TryBrowserRefreshAsync()
|
||||||
@@ -237,42 +217,21 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
string? orgId = root.TryGetProperty("orgId", out var oi) && oi.ValueKind == JsonValueKind.String ? oi.GetString() : null;
|
string? orgId = root.TryGetProperty("orgId", out var oi) && oi.ValueKind == JsonValueKind.String ? oi.GetString() : null;
|
||||||
string? planLabel = root.TryGetProperty("planLabel", out var pl) && pl.ValueKind == JsonValueKind.String ? pl.GetString() : null;
|
string? planLabel = root.TryGetProperty("planLabel", out var pl) && pl.ValueKind == JsonValueKind.String ? pl.GetString() : null;
|
||||||
|
|
||||||
string? usageJson = null;
|
|
||||||
UsageResponse? usage = null;
|
UsageResponse? usage = null;
|
||||||
string? overageJson = null;
|
|
||||||
OverageSpendLimit? overage = null;
|
OverageSpendLimit? overage = null;
|
||||||
string? prepaidJson = null;
|
|
||||||
PrepaidCredits? prepaid = null;
|
PrepaidCredits? prepaid = null;
|
||||||
|
|
||||||
if (root.TryGetProperty("usage", out var us) && us.ValueKind == JsonValueKind.Object)
|
if (root.TryGetProperty("usage", out var us) && us.ValueKind == JsonValueKind.Object)
|
||||||
{
|
usage = JsonSerializer.Deserialize<UsageResponse>(us.GetRawText(), JsonOpts);
|
||||||
usageJson = us.GetRawText();
|
|
||||||
usage = JsonSerializer.Deserialize<UsageResponse>(usageJson, JsonOpts);
|
|
||||||
}
|
|
||||||
if (root.TryGetProperty("overage", out var ov) && ov.ValueKind == JsonValueKind.Object)
|
if (root.TryGetProperty("overage", out var ov) && ov.ValueKind == JsonValueKind.Object)
|
||||||
{
|
overage = JsonSerializer.Deserialize<OverageSpendLimit>(ov.GetRawText(), JsonOpts);
|
||||||
overageJson = ov.GetRawText();
|
|
||||||
overage = JsonSerializer.Deserialize<OverageSpendLimit>(overageJson, JsonOpts);
|
|
||||||
}
|
|
||||||
if (root.TryGetProperty("prepaid", out var pp) && pp.ValueKind == JsonValueKind.Object)
|
if (root.TryGetProperty("prepaid", out var pp) && pp.ValueKind == JsonValueKind.Object)
|
||||||
{
|
prepaid = JsonSerializer.Deserialize<PrepaidCredits>(pp.GetRawText(), JsonOpts);
|
||||||
prepaidJson = pp.GetRawText();
|
|
||||||
prepaid = JsonSerializer.Deserialize<PrepaidCredits>(prepaidJson, JsonOpts);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Persist fresh data to cache
|
|
||||||
if (usageJson != null) AppSettings.Default.UsageJson = usageJson;
|
|
||||||
if (!string.IsNullOrEmpty(email)) AppSettings.Default.Email = email!;
|
|
||||||
if (!string.IsNullOrEmpty(orgId)) AppSettings.Default.OrgId = orgId!;
|
|
||||||
if (!string.IsNullOrEmpty(planLabel)) AppSettings.Default.PlanLabel = planLabel!;
|
|
||||||
if (overageJson != null) AppSettings.Default.OverageJson = overageJson;
|
|
||||||
if (prepaidJson != null) AppSettings.Default.PrepaidJson = prepaidJson;
|
|
||||||
AppSettings.Default.Save();
|
|
||||||
|
|
||||||
return (BuildLimits(usage), overage, prepaid, usage?.ExtraUsage, email, orgId, planLabel);
|
return (BuildLimits(usage), overage, prepaid, usage?.ExtraUsage, email, orgId, planLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── HttpClient refresh (fallback — used only before browser is ready) ────
|
// ── HttpClient refresh (fallback — before browser is ready) ──────────────
|
||||||
|
|
||||||
private async Task<(List<AgentLimit>, OverageSpendLimit?, PrepaidCredits?, ExtraUsage?, string?, string?, string?, bool)>
|
private async Task<(List<AgentLimit>, OverageSpendLimit?, PrepaidCredits?, ExtraUsage?, string?, string?, string?, bool)>
|
||||||
TryHttpRefreshAsync(List<(string Name, string Value, string Domain, string Path)> cookies)
|
TryHttpRefreshAsync(List<(string Name, string Value, string Domain, string Path)> cookies)
|
||||||
@@ -289,15 +248,10 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(orgId))
|
if (string.IsNullOrEmpty(orgId))
|
||||||
orgId = await FetchOrgIdFromListAsync(http);
|
orgId = await FetchOrgIdFromListAsync(http);
|
||||||
if (string.IsNullOrEmpty(orgId) && !string.IsNullOrEmpty(AppSettings.Default.OrgId))
|
|
||||||
orgId = AppSettings.Default.OrgId;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(orgId))
|
if (string.IsNullOrEmpty(orgId))
|
||||||
return ([], null, null, null, email, orgId, planLabel, true);
|
return ([], null, null, null, email, orgId, planLabel, true);
|
||||||
|
|
||||||
AppSettings.Default.OrgId = orgId;
|
|
||||||
AppSettings.Default.Save();
|
|
||||||
|
|
||||||
var ut = http.GetAsync($"https://claude.ai/api/organizations/{orgId}/usage");
|
var ut = http.GetAsync($"https://claude.ai/api/organizations/{orgId}/usage");
|
||||||
var ot = FetchAsync<OverageSpendLimit>(http, $"https://claude.ai/api/organizations/{orgId}/overage_spend_limit");
|
var ot = FetchAsync<OverageSpendLimit>(http, $"https://claude.ai/api/organizations/{orgId}/overage_spend_limit");
|
||||||
var pt = FetchAsync<PrepaidCredits>(http, $"https://claude.ai/api/organizations/{orgId}/prepaid/credits");
|
var pt = FetchAsync<PrepaidCredits>(http, $"https://claude.ai/api/organizations/{orgId}/prepaid/credits");
|
||||||
@@ -309,56 +263,20 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
|
|
||||||
var usageJson = await usageResp.Content.ReadAsStringAsync();
|
var usageJson = await usageResp.Content.ReadAsStringAsync();
|
||||||
var usage = JsonSerializer.Deserialize<UsageResponse>(usageJson, JsonOpts);
|
var usage = JsonSerializer.Deserialize<UsageResponse>(usageJson, JsonOpts);
|
||||||
var overage = ot.Result;
|
|
||||||
var prepaid = pt.Result;
|
|
||||||
|
|
||||||
AppSettings.Default.UsageJson = usageJson;
|
return (BuildLimits(usage), ot.Result, pt.Result, usage?.ExtraUsage, email, orgId, planLabel, true);
|
||||||
if (!string.IsNullOrEmpty(planLabel)) AppSettings.Default.PlanLabel = planLabel;
|
|
||||||
if (overage != null) AppSettings.Default.OverageJson = JsonSerializer.Serialize(overage);
|
|
||||||
if (prepaid != null) AppSettings.Default.PrepaidJson = JsonSerializer.Serialize(prepaid);
|
|
||||||
AppSettings.Default.Save();
|
|
||||||
|
|
||||||
return (BuildLimits(usage), overage, prepaid, usage?.ExtraUsage, email, orgId, planLabel, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Startup cache ─────────────────────────────────────────────────────────
|
||||||
|
// Called before the browser is ready. Sets sign-in state and shows placeholders
|
||||||
|
// with any previously accumulated burn history so the popup isn't blank.
|
||||||
|
|
||||||
public async Task LoadFromCacheAsync()
|
public async Task LoadFromCacheAsync()
|
||||||
{
|
{
|
||||||
var email = AppSettings.Default.Email;
|
if (string.IsNullOrEmpty(AppSettings.Default.CookieStore)) return;
|
||||||
var orgId = AppSettings.Default.OrgId;
|
|
||||||
var cookie = AppSettings.Default.CookieStore;
|
|
||||||
|
|
||||||
bool isAuth = !string.IsNullOrEmpty(email) || !string.IsNullOrEmpty(orgId)
|
var placeholders = LoadPlaceholderLimits();
|
||||||
|| !string.IsNullOrEmpty(cookie);
|
foreach (var limit in placeholders)
|
||||||
if (!isAuth) return;
|
|
||||||
|
|
||||||
List<AgentLimit> limits = [];
|
|
||||||
ExtraUsage? extraUsage = null;
|
|
||||||
OverageSpendLimit? overage = null;
|
|
||||||
PrepaidCredits? prepaid = null;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(AppSettings.Default.UsageJson))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var cached = JsonSerializer.Deserialize<UsageResponse>(
|
|
||||||
AppSettings.Default.UsageJson, JsonOpts);
|
|
||||||
limits = BuildLimits(cached);
|
|
||||||
extraUsage = cached?.ExtraUsage;
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrEmpty(AppSettings.Default.OverageJson))
|
|
||||||
{
|
|
||||||
try { overage = JsonSerializer.Deserialize<OverageSpendLimit>(AppSettings.Default.OverageJson, JsonOpts); }
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrEmpty(AppSettings.Default.PrepaidJson))
|
|
||||||
{
|
|
||||||
try { prepaid = JsonSerializer.Deserialize<PrepaidCredits>(AppSettings.Default.PrepaidJson, JsonOpts); }
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var limit in limits)
|
|
||||||
{
|
{
|
||||||
var key = limit.Window.ToString();
|
var key = limit.Window.ToString();
|
||||||
if (_burnHistory.TryGetValue(key, out var hist) && hist.Count > 0)
|
if (_burnHistory.TryGetValue(key, out var hist) && hist.Count > 0)
|
||||||
@@ -367,30 +285,21 @@ public class UsageViewModel : INotifyPropertyChanged
|
|||||||
|
|
||||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(email)) UserEmail = email;
|
Limits = placeholders;
|
||||||
if (!string.IsNullOrEmpty(AppSettings.Default.PlanLabel)) PlanLabel = AppSettings.Default.PlanLabel;
|
IsSignedIn = true;
|
||||||
Limits = limits.Count > 0 ? limits : LoadPlaceholderLimits();
|
|
||||||
Overage = overage;
|
|
||||||
Prepaid = prepaid;
|
|
||||||
ExtraUsage = extraUsage;
|
|
||||||
IsSignedIn = true;
|
|
||||||
ErrorMessage = null;
|
|
||||||
LastUpdated = limits.Count > 0 ? DateTime.Now : LastUpdated;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SignOutAsync()
|
public async Task SignOutAsync()
|
||||||
{
|
{
|
||||||
AppSettings.Default.CookieStore = "";
|
AppSettings.Default.CookieStore = "";
|
||||||
AppSettings.Default.OrgId = "";
|
|
||||||
AppSettings.Default.Email = "";
|
|
||||||
AppSettings.Default.UsageJson = "";
|
|
||||||
AppSettings.Default.Save();
|
AppSettings.Default.Save();
|
||||||
|
|
||||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
IsSignedIn = false;
|
IsSignedIn = false;
|
||||||
UserEmail = "";
|
UserEmail = "";
|
||||||
|
PlanLabel = "Claude";
|
||||||
ErrorMessage = "Signed out.";
|
ErrorMessage = "Signed out.";
|
||||||
Limits = [];
|
Limits = [];
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user