Skip to content

Commit

Permalink
Refine rewarded ad handling and session management
Browse files Browse the repository at this point in the history
Enhanced connection and ad extension logic to handle both acceptance and rejection of ads in AdTest.cs.
  • Loading branch information
trudyhood committed Nov 30, 2024
1 parent 35b34c2 commit d3fde9d
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 29 deletions.
27 changes: 20 additions & 7 deletions Tests/VpnHood.Test/Tests/AdTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using VpnHood.Common.Exceptions;
using VpnHood.Common.Messaging;
using VpnHood.Common.Tokens;
using VpnHood.Common.Utils;
using VpnHood.Test.Device;
using VpnHood.Test.Providers;

Expand Down Expand Up @@ -122,13 +123,17 @@ public async Task RewardedAd_expiration_must_be_increased_by_plan_id(bool accept

// create access token
var token = accessManager.CreateToken();

// connect
var clientProfile = app.ClientProfileService.ImportAccessKey(token.ToAccessKey());
await app.Connect(clientProfile.ClientProfileId, ConnectPlanId.PremiumByRewardedAd);

// assert
Assert.AreEqual(acceptAd, app.State.SessionStatus?.AccessUsage?.ExpirationTime==null);
// connect
if (acceptAd) {
await app.Connect(clientProfile.ClientProfileId, ConnectPlanId.PremiumByRewardedAd);
Assert.IsNull(app.State.SessionStatus?.AccessUsage?.ExpirationTime);
}
else {
var ex = await Assert.ThrowsExceptionAsync<SessionException>(()=>app.Connect(clientProfile.ClientProfileId, ConnectPlanId.PremiumByRewardedAd));
Assert.AreEqual(SessionErrorCode.RewardedAdRejected, ex.SessionResponse.ErrorCode);
}
}

[TestMethod]
Expand Down Expand Up @@ -161,8 +166,16 @@ public async Task RewardedAd_expiration_must_be_increased_by_user(bool acceptAd)
Assert.IsNotNull(app.State.SessionStatus?.AccessUsage?.ExpirationTime);

// show ad
await app.ExtendByRewardedAd(CancellationToken.None);
Assert.AreEqual(acceptAd, app.State.SessionStatus?.AccessUsage?.ExpirationTime == null);
if (acceptAd) {
await app.ExtendByRewardedAd(CancellationToken.None);
Assert.IsNull(app.State.SessionStatus?.AccessUsage?.ExpirationTime);
}
else {
var ex = await Assert.ThrowsExceptionAsync<SessionException>(() => app.ExtendByRewardedAd(CancellationToken.None));
Assert.AreEqual(SessionErrorCode.RewardedAdRejected, ex.SessionResponse.ErrorCode);
await Task.Delay(500);
await TestHelper.WaitForAppState(app, AppConnectionState.Connected);
}
}

[TestMethod]
Expand Down
Binary file modified VpnHood.Client.App/Resources/IpLocations.zip
Binary file not shown.
6 changes: 5 additions & 1 deletion VpnHood.Client.App/Services/AppCompositeAdService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,13 @@ public async Task LoadAd(IUiContext uiContext, string? countryCode, bool forceRe
_loadedAdProviderItem = adProviderItem;
return;
}
catch (Exception ex) when (ex is UiContextNotAvailableException || ActiveUiContext.Context != uiContext) {
catch (UiContextNotAvailableException){
throw new ShowAdNoUiException();
}

// do not catch if parent cancel the operation
catch (Exception ex) {
await VerifyActiveUi();
VhLogger.Instance.LogWarning(ex, "Could not load any ad. ProviderName: {ProviderName}.", adProviderItem.Name);
}
}
Expand Down Expand Up @@ -119,6 +120,9 @@ public async Task<string> ShowLoadedAd(IUiContext uiContext, string? customData,

return _loadedAdProviderItem.Name;
}
catch (UiContextNotAvailableException){
throw new ShowAdNoUiException();
}
catch (ShowAdNoUiException) {
throw;
}
Expand Down
2 changes: 1 addition & 1 deletion VpnHood.Client.App/VpnHoodApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,7 @@ public Task ExtendByRewardedAd(CancellationToken cancellationToken)
if (State.SessionStatus?.AccessUsage?.CanExtendByRewardedAd != true)
throw new InvalidOperationException("Can not extend session by a rewarded ad at this time.");

return _client.ShowAd(true, cancellationToken);
return _client.ShowRewardedAd(cancellationToken);
}

internal Task RefreshAccount(string[] accountAccessKeys, bool updateCurrentClientProfile)
Expand Down
52 changes: 40 additions & 12 deletions VpnHood.Client/VpnHoodClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -797,8 +797,10 @@ private async Task ConnectInternal(CancellationToken cancellationToken, bool all

// show ad
string? adNetworkName = null;
if (sessionResponse.AdRequirement is not AdRequirement.None)
adNetworkName = await ShowAd(sessionResponse.AdRequirement is AdRequirement.Rewarded, cancellationToken).VhConfigureAwait();
if (sessionResponse.AdRequirement is AdRequirement.Flexible)
adNetworkName = await ShowNormalAd(cancellationToken).VhConfigureAwait();
if (sessionResponse.AdRequirement is AdRequirement.Rewarded)
adNetworkName = await ShowRewardedAd(cancellationToken).VhConfigureAwait();

// usage trackers
if (_allowAnonymousTracker) {
Expand Down Expand Up @@ -911,7 +913,8 @@ internal async Task<ConnectorRequestResult<T>> SendRequest<T>(ClientRequest requ

// close session if server has ended the session
if (ex.SessionResponse.ErrorCode != SessionErrorCode.GeneralError &&
ex.SessionResponse.ErrorCode != SessionErrorCode.RedirectHost)
ex.SessionResponse.ErrorCode != SessionErrorCode.RedirectHost &&
ex.SessionResponse.ErrorCode != SessionErrorCode.RewardedAdRejected)
_ = DisposeAsync(ex);

throw;
Expand Down Expand Up @@ -980,8 +983,7 @@ private async Task SendByeRequest(CancellationToken cancellationToken)
}
}

/// <returns>NetworkName</returns>
public async Task<string?> ShowAd(bool rewarded, CancellationToken cancellationToken)
public async Task<string?> ShowRewardedAd(CancellationToken cancellationToken)
{
try {
if (_adService == null)
Expand All @@ -992,21 +994,47 @@ private async Task SendByeRequest(CancellationToken cancellationToken)

_isWaitingForAd = true;
_clientHost.PassthruInProcessPackets = true;
var adResult = rewarded
? await _adService.ShowRewarded(ActiveUiContext.RequiredContext, SessionId.ToString(), cancellationToken).VhConfigureAwait()
: await _adService.ShowInterstitial(ActiveUiContext.RequiredContext, SessionId.ToString(), cancellationToken).VhConfigureAwait();
var adResult = await _adService
.ShowRewarded(ActiveUiContext.RequiredContext, SessionId.ToString(), cancellationToken)
.VhConfigureAwait();

if (!string.IsNullOrEmpty(adResult.AdData) && rewarded)
if (!string.IsNullOrEmpty(adResult.AdData))
await SendRewardedAd(adResult.AdData, cancellationToken);

return adResult.NetworkName;
}
catch (UiContextNotAvailableException) {
throw new ShowAdNoUiException();
}
finally {
_isWaitingForAd = false;
_clientHost.PassthruInProcessPackets = false;
}
}


/// <returns>NetworkName</returns>
public async Task<string?> ShowNormalAd(CancellationToken cancellationToken)
{
try {
if (_adService == null)
throw new InvalidOperationException("Client's AdService has not been initialized.");

if (SessionId == 0)
throw new InvalidOperationException("Session has not been established.");

_isWaitingForAd = true;
_clientHost.PassthruInProcessPackets = true;
var adResult = await _adService
.ShowInterstitial(ActiveUiContext.RequiredContext, SessionId.ToString(), cancellationToken)
.VhConfigureAwait();

return adResult.NetworkName;
}
catch (UiContextNotAvailableException) {
throw new ShowAdNoUiException();
}
catch (LoadAdException ex) {
if (rewarded)
throw new LoadAdException("Could not load any rewarded ad.", ex);

VhLogger.Instance.LogInformation(ex, "Could not load any ad.");
// ignore exception for flexible ad if load failed
Expand Down Expand Up @@ -1045,7 +1073,7 @@ private async Task SendRewardedAd(string adData, CancellationToken cancellationT
}
catch (Exception ex) {
VhLogger.LogError(GeneralEventId.Session, ex, "Could not send the RewardedAd request.");
throw new AdException("This server requires a display ad, but AdService has not been initialized.");
throw;
}
}

Expand Down
5 changes: 2 additions & 3 deletions VpnHood.Server.Access.FileAccessManager/FileAccessManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,10 +376,9 @@ private async Task<SessionResponse> Session_AddUsage(SessionUsage sessionUsage)
SessionService.CloseSession(sessionId);

// manage adData for simulation
if (IsValidAd(sessionUsage.AdData) && SessionService.Sessions.TryGetValue(sessionId, out var session))
session.ExpirationTime = null;
var isValidAd = string.IsNullOrEmpty(sessionUsage.AdData) ? (bool?)null : IsValidAd(sessionUsage.AdData);

var res = SessionService.GetSessionResponse(sessionId, accessTokenData, null);
var res = SessionService.GetSessionResponse(sessionId, accessTokenData, null, isValidAd: isValidAd);
var ret = new SessionResponse {
ErrorCode = res.ErrorCode,
AccessUsage = res.AccessUsage,
Expand Down
24 changes: 19 additions & 5 deletions VpnHood.Server.Access.FileAccessManager/Services/SessionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ private AdRequirement ProcessPlanId(Session session, AccessTokenData accessToken
}

public SessionResponseEx GetSessionResponse(ulong sessionId, AccessTokenData accessTokenData,
IPEndPoint? hostEndPoint)
IPEndPoint? hostEndPoint, bool? isValidAd = null)
{
// check existence
if (!Sessions.TryGetValue(sessionId, out var session))
Expand All @@ -170,7 +170,7 @@ public SessionResponseEx GetSessionResponse(ulong sessionId, AccessTokenData acc
session.HostEndPoint = hostEndPoint;

// create response
var ret = BuildSessionResponse(session, accessTokenData);
var ret = BuildSessionResponse(session, accessTokenData, isValidAd);
return ret;
}

Expand All @@ -182,8 +182,13 @@ public ulong[] ResetUpdatedSessions()
return sessionIds;
}
}
private SessionResponseEx BuildSessionResponse(Session session, AccessTokenData accessTokenData)
private SessionResponseEx BuildSessionResponse(Session session, AccessTokenData accessTokenData, bool? isValidAd = null)
{
// check if the ad is valid. MUST before access usage
if (isValidAd == true)
session.ExpirationTime = null;

// build access usage
var accessToken = accessTokenData.AccessToken;
var accessUsage = new AccessUsage {
ActiveClientCount = 0,
Expand All @@ -194,7 +199,16 @@ private SessionResponseEx BuildSessionResponse(Session session, AccessTokenData
IsPremium = true, // token is always premium in File Access Manager
};


if (isValidAd == false)
return new SessionResponseEx {
ErrorCode = SessionErrorCode.RewardedAdRejected,
ErrorMessage = "Could not validate the RewardedAd.",
AccessUsage = accessUsage
};

// validate session status
// ReSharper disable once InvertIf
if (session.ErrorCode == SessionErrorCode.Ok) {
// check token expiration
if (accessToken.ExpirationTime != null && accessToken.ExpirationTime < DateTime.UtcNow)
Expand Down Expand Up @@ -261,9 +275,9 @@ private SessionResponseEx BuildSessionResponse(Session session, AccessTokenData
}

// set to session expiration time if session expiration time is shorter than accessUsage.ExpirationTime
if (session.ExpirationTime != null && (accessUsage.ExpirationTime == null ||
session.ExpirationTime < accessUsage.ExpirationTime))
if (session.ExpirationTime != null && (accessUsage.ExpirationTime == null || session.ExpirationTime < accessUsage.ExpirationTime)) {
accessUsage.ExpirationTime = session.ExpirationTime.Value;
}

accessUsage.ActiveClientCount = otherSessions
.GroupBy(x => x.ClientInfo.ClientId)
Expand Down

0 comments on commit d3fde9d

Please sign in to comment.