feat(entitlement): pivot to paid app ownership

Check Steam ownership of the paid app with BIsSubscribedApp; keep Debug fake ownership out of Release. Ref: https://partner.steamgames.com/doc/api/ISteamApps#BIsSubscribedApp
This commit is contained in:
2026-11-02 09:18:03 -08:00
parent 54902cb838
commit ff5e8d94ae
17 changed files with 229 additions and 217 deletions
+27 -26
View File
@@ -4,44 +4,45 @@ using ytLive.Services.Entitlement;
namespace ytLive.ViewModels;
/// <summary>
/// The entitlement surface: whether this session may emit output above the free budget.
/// The app-wide Steam ownership flag and its output-profile consumer.
///
/// <para>Deliberately a separate partial from <c>License.cs</c>: that file owns *how* a
/// licence is validated (Polar today), this one owns *what the answer means* for output.
/// Steam's DLC check and Polar's lease both collapse into <see cref="IsPremium"/>; nothing
/// downstream of <see cref="HasProOutput"/> cares which one proved it.</para>
/// <para>Only the entitlement service decides whether the current Steam account owns the paid app.
/// Development builds use a fake owned result; Release builds use Steam.</para>
/// </summary>
public partial class MainViewModel
{
/// <summary>
/// The single input to <see cref="Models.OutputProfile.Resolve"/>.
///
/// <para><b>Fail closed:</b> an unresolved or unreadable licence is simply false, so a
/// build with no entitlement emits the free profile. There is no "unknown" branch to get
/// wrong.</para>
///
/// <para><b>Developer mode never ships:</b> <see cref="DeveloperMode"/> is only ever
/// settable inside a development build (see <see cref="DevBuild"/>), so this expression
/// reduces to <c>IsPremium</c> in a Release binary.</para>
/// </summary>
public bool HasProOutput => IsPremium || DeveloperMode || _entitlement.IsPro;
private IEntitlement _entitlement = CreateEntitlement();
private bool _isAppOwned;
/// <summary>
/// The entitlement rail for this session. Lifetime = Steam DLC; until the DLC app id exists it
/// is <see cref="SteamEntitlement.NotConfigured"/>, which reads false (fail closed). Tests
/// inject a fake through <see cref="EntitlementOverride"/>.
/// </summary>
private IEntitlement _entitlement = SteamEntitlement.NotConfigured();
public bool IsAppOwned
{
get => _isAppOwned;
private set
{
if (!SetProperty(ref _isAppOwned, value)) return;
OnPropertyChanged(nameof(HasProOutput));
}
}
/// <summary>Test seam: swap the rail without a Steam client. Internal — the test assembly is
/// the only caller, and a public setter here would be a licence bypass surface.</summary>
public bool HasProOutput => DeveloperMode || IsAppOwned;
private static IEntitlement CreateEntitlement()
{
#if DEBUG
return new FakeEntitlement(true);
#else
return SteamEntitlement.NotConfigured();
#endif
}
/// <summary>Test seam: swap the ownership source without a Steam client.</summary>
internal IEntitlement EntitlementOverride
{
get => _entitlement;
set
{
_entitlement = value;
OnPropertyChanged(nameof(HasProOutput));
IsAppOwned = value.IsAppOwned;
}
}
+1
View File
@@ -192,6 +192,7 @@ public partial class MainViewModel : ViewModelBase
public MainViewModel()
{
AppLog.Write("MainViewModel ctor begin");
IsAppOwned = _entitlement.IsAppOwned;
_youtubeAuth = new YouTubeAuthService(OAuthCredentials.ClientId, OAuthCredentials.ClientSecret,
sessionChanged: ch => TokenStore.Save(ch));
_youtubeStream = new YouTubeStreamService(_youtubeAuth);