Skip to content

Instantly share code, notes, and snippets.

@thomasmso
Created March 23, 2020 21:05
Show Gist options
  • Save thomasmso/9084fb7d15df601b01d3dba3fee37912 to your computer and use it in GitHub Desktop.
Save thomasmso/9084fb7d15df601b01d3dba3fee37912 to your computer and use it in GitHub Desktop.
MAX Tapjoy Adapter - iOS
//
// ALTapjoyMediationAdapter.m
// Solitaire
//
// Created by Thomas So on 10/19/18.
// Copyright © 2019 AppLovin Corporation. All rights reserved.
//
#import "ALTapjoyMediationAdapter.h"
#import <Tapjoy/Tapjoy.h>
#import "NSDictionary+ALUtils.h"
#import "ALUtils.h"
#define ADAPTER_VERSION @"12.4.1.0"
@interface ALTapjoyMediationAdapterInterstitialDelegate : NSObject<TJPlacementDelegate, TJPlacementVideoDelegate>
@property (nonatomic, weak) ALTapjoyMediationAdapter *parentAdapter;
@property (nonatomic, strong) id<MAInterstitialAdapterDelegate> delegate;
- (instancetype)initWithParentAdapter:(ALTapjoyMediationAdapter *)parentAdapter andNotify:(id<MAInterstitialAdapterDelegate>)delegate;
@end
@interface ALTapjoyMediationAdapterRewardedDelegate : NSObject<TJPlacementDelegate, TJPlacementVideoDelegate>
@property (nonatomic, weak) ALTapjoyMediationAdapter *parentAdapter;
@property (nonatomic, strong) id<MARewardedAdapterDelegate> delegate;
@property (nonatomic, assign, getter=hasGrantedReward) BOOL grantedReward;
- (instancetype)initWithParentAdapter:(ALTapjoyMediationAdapter *)parentAdapter andNotify:(id<MARewardedAdapterDelegate>)delegate;
@end
@interface ALTapjoyMediationAdapter()
// Initialization
@property (nonatomic, copy, nullable) void(^oldCompletionHandler)(void);
@property (nonatomic, copy, nullable) void(^completionHandler)(MAAdapterInitializationStatus, NSString * _Nullable);
// Interstitial
@property (nonatomic, strong) TJPlacement *interstitialPlacement;
@property (nonatomic, strong) ALTapjoyMediationAdapterInterstitialDelegate *interstitialDelegate;
// Rewarded
@property (nonatomic, strong) TJPlacement *rewardedPlacement;
@property (nonatomic, strong) ALTapjoyMediationAdapterRewardedDelegate *rewardedDelegate;
@end
@implementation ALTapjoyMediationAdapter
#pragma mark - MAAdapter Methods
- (void)initializeWithParameters:(id<MAAdapterInitializationParameters>)parameters withCompletionHandler:(void(^)(void))completionHandler
{
[self log: @"Initializing Tapjoy SDK..."];
if ( ![Tapjoy isConnected] )
{
self.oldCompletionHandler = completionHandler;
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(tapjoyConnectCompleted:)
name: TJC_CONNECT_SUCCESS
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(tapjoyConnectCompleted:)
name: TJC_CONNECT_FAILED
object: nil];
[Tapjoy setDebugEnabled: [parameters isTesting]];
// Update GDPR settings before initialization
[self updateConsentWithParameters: parameters];
NSString *sdkKey = [parameters.serverParameters al_stringForKey: @"sdk_key"];
[Tapjoy connect: sdkKey
options: @{TJC_OPTION_ENABLE_LOGGING : @([parameters isTesting])}];
}
else
{
[self updateConsentWithParameters: parameters];
completionHandler();
}
}
- (void)initializeWithParameters:(id<MAAdapterInitializationParameters>)parameters completionHandler:(void (^)(MAAdapterInitializationStatus, NSString * _Nullable))completionHandler
{
[self log: @"Initializing Tapjoy SDK..."];
if ( ![Tapjoy isConnected] )
{
self.completionHandler = completionHandler;
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(tapjoyConnectCompleted:)
name: TJC_CONNECT_SUCCESS
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(tapjoyConnectCompleted:)
name: TJC_CONNECT_FAILED
object: nil];
[Tapjoy setDebugEnabled: [parameters isTesting]];
// Update GDPR settings before initialization
[self updateConsentWithParameters: parameters];
NSString *sdkKey = [parameters.serverParameters al_stringForKey: @"sdk_key"];
[Tapjoy connect: sdkKey
options: @{TJC_OPTION_ENABLE_LOGGING : @([parameters isTesting])}];
}
else
{
[self updateConsentWithParameters: parameters];
completionHandler(MAAdapterInitializationStatusInitializedSuccess, nil);
}
}
- (void)tapjoyConnectCompleted:(NSNotification *)notification
{
if ( [notification.name isEqualToString: TJC_CONNECT_SUCCESS] )
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
[self log: @"Tapjoy SDK initialized"];
if ( self.completionHandler )
{
self.completionHandler(MAAdapterInitializationStatusInitializedSuccess, nil);
self.completionHandler = nil;
}
}
else
{
// Tapjoy will attempt to re-connect, so wait for "SUCCESS"
[[NSNotificationCenter defaultCenter] removeObserver: self
name: TJC_CONNECT_FAILED
object: nil];
[self log: @"Tapjoy SDK failed to initialized"];
if ( self.completionHandler )
{
self.completionHandler(MAAdapterInitializationStatusInitializedFailure, nil);
self.completionHandler = nil;
}
}
if ( self.oldCompletionHandler )
{
self.oldCompletionHandler();
self.oldCompletionHandler = nil;
}
}
- (NSString *)SDKVersion
{
return [Tapjoy getVersion];
}
- (NSString *)adapterVersion
{
return ADAPTER_VERSION;
}
- (void)destroy {}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
#pragma mark - MASignalProvider Methods
- (void)collectSignalWithParameters:(id<MASignalCollectionParameters>)parameters andNotify:(id<MASignalCollectionDelegate>)delegate
{
// Update GDPR settings
[self updateConsentWithParameters: parameters];
NSString *token = [Tapjoy getUserToken];
[delegate didCollectSignal: token];
}
#pragma mark - MAInterstitialAdapter Methods
- (void)loadInterstitialAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MAInterstitialAdapterDelegate>)delegate
{
[self log: @"Loading interstitial..."];
if ( ![Tapjoy isConnected] )
{
[self log: @"Tapjoy SDK is not initialized"];
[delegate didFailToLoadInterstitialAdWithError: MAAdapterError.notInitialized];
return;
}
// Update GDPR settings
[self updateConsentWithParameters: parameters];
self.interstitialDelegate = [[ALTapjoyMediationAdapterInterstitialDelegate alloc] initWithParentAdapter: self andNotify: delegate];
self.interstitialPlacement = [self placementWithParameters: parameters andNotify: self.interstitialDelegate];
if ( self.interstitialPlacement )
{
[self.interstitialPlacement requestContent];
}
else
{
[delegate didFailToLoadInterstitialAdWithError: MAAdapterError.badRequest];
}
}
- (void)showInterstitialAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MAInterstitialAdapterDelegate>)delegate
{
[self log: @"Showing interstitial..."];
if ( [self.interstitialPlacement isContentReady] )
{
[self.interstitialPlacement showContentWithViewController: [ALUtils topViewControllerFromKeyWindow]];
}
else
{
[self log: @"Interstitial ad not ready"];
[delegate didFailToDisplayInterstitialAdWithError: MAAdapterError.adNotReady];
}
}
#pragma mark - MARewardedAdapter Methods
- (void)loadRewardedAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MARewardedAdapterDelegate>)delegate
{
[self log: @"Loading rewarded ad..."];
if ( ![Tapjoy isConnected] )
{
[self log: @"Tapjoy SDK is not initialized"];
[delegate didFailToLoadRewardedAdWithError: MAAdapterError.notInitialized];
return;
}
// Update GDPR settings
[self updateConsentWithParameters: parameters];
self.rewardedDelegate = [[ALTapjoyMediationAdapterRewardedDelegate alloc] initWithParentAdapter: self andNotify: delegate];
self.rewardedPlacement = [self placementWithParameters: parameters andNotify: self.rewardedDelegate];
if ( self.rewardedPlacement )
{
[self.rewardedPlacement requestContent];
}
else
{
[delegate didFailToLoadRewardedAdWithError: MAAdapterError.badRequest];
}
}
- (void)showRewardedAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MARewardedAdapterDelegate>)delegate
{
[self log: @"Showing rewarded ad..."];
if ( [self.rewardedPlacement isContentReady] )
{
// Configure reward from server.
[self configureRewardForParameters: parameters];
[self.rewardedPlacement showContentWithViewController: [ALUtils topViewControllerFromKeyWindow]];
}
else
{
[self log: @"Rewarded ad not ready"];
[delegate didFailToDisplayRewardedAdWithError: MAAdapterError.adNotReady];
}
}
#pragma mark - Utility Methods
- (void)updateConsentWithParameters:(id<MAAdapterParameters>)parameters
{
// https://dev.tapjoy.com/sdk-integration/#sdk11122_gdpr_release
[Tapjoy setUserConsent: [parameters hasUserConsent] ? @"1" : @"0"];
[Tapjoy belowConsentAge: [parameters isAgeRestrictedUser]];
}
- (nullable TJPlacement *)placementWithParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<TJPlacementDelegate>)delegate
{
TJPlacement *placement = [TJPlacement placementWithName: parameters.thirdPartyAdPlacementIdentifier
mediationAgent: @"applovin"
mediationId: nil
delegate: delegate];
placement.adapterVersion = self.adapterVersion;
if ( [delegate conformsToProtocol: @protocol(TJPlacementVideoDelegate)] )
{
placement.videoDelegate = (id<TJPlacementVideoDelegate>) delegate;
}
if ( parameters.bidResponse )
{
NSData *jsonData = [parameters.bidResponse dataUsingEncoding: NSUTF8StringEncoding];
// Attempt to deserialize
NSError *error = nil;
NSDictionary *auctionData = [NSJSONSerialization JSONObjectWithData: jsonData
options: NSJSONReadingAllowFragments
error: &error];
if ( !error && auctionData )
{
placement.auctionData = auctionData;
}
else
{
[self log: @"Failed to load ad due to JSON deserialization error: %@", error];
return nil;
}
}
return placement;
}
+ (MAAdapterError *)maxErrorFromTapjoyError:(NSError *)error
{
NSInteger errorCode;
if ( error.code == 204 )
{
errorCode = MAAdapterError.errorCodeNoFill;
}
else if ( error.code == -1011 )
{
errorCode = MAAdapterError.errorCodeNotInitialized;
}
else
{
errorCode = MAAdapterError.errorCodeUnspecified;
}
return [MAAdapterError errorWithCode: errorCode adapterErrorCode: error.code];
}
@end
@implementation ALTapjoyMediationAdapterInterstitialDelegate
- (instancetype)initWithParentAdapter:(ALTapjoyMediationAdapter *)parentAdapter andNotify:(id<MAInterstitialAdapterDelegate>)delegate
{
self = [super init];
if ( self )
{
self.parentAdapter = parentAdapter;
self.delegate = delegate;
}
return self;
}
- (void)requestDidSucceed:(TJPlacement *)placement
{
if ( [placement isContentAvailable] )
{
[self.parentAdapter log: @"Interstitial request succeeded"];
}
else
{
[self.parentAdapter log: @"Interstitial request failed"];
[self.delegate didFailToLoadInterstitialAdWithError: MAAdapterError.noFill];
}
}
- (void)contentIsReady:(TJPlacement *)placement
{
[self.parentAdapter log: @"Interstitial loaded"];
[self.delegate didLoadInterstitialAd];
}
- (void)requestDidFail:(TJPlacement *)placement error:(NSError *)error
{
[self.parentAdapter log: @"Interstitial failed to load with error: %@", error];
MAAdapterError *adapterError = [ALTapjoyMediationAdapter maxErrorFromTapjoyError: error];
[self.delegate didFailToLoadInterstitialAdWithError: adapterError];
}
- (void)contentDidAppear:(TJPlacement *)placement
{
[self.parentAdapter log: @"Interstitial shown"];
}
- (void)didClick:(TJPlacement *)placement
{
[self.parentAdapter log: @"Interstitial clicked"];
[self.delegate didClickInterstitialAd];
}
- (void)contentDidDisappear:(TJPlacement *)placement
{
[self.parentAdapter log: @"Interstitial hidden"];
[self.delegate didHideInterstitialAd];
}
- (void)videoDidStart:(TJPlacement *)placement
{
[self.parentAdapter log: @"Interstitial video began"];
[self.delegate didDisplayInterstitialAd];
}
- (void)videoDidComplete:(TJPlacement *)placement
{
[self.parentAdapter log: @"Interstitial video completed"];
}
- (void)videoDidFail:(TJPlacement *)placement error:(NSString *)errorMessage
{
[self.parentAdapter log: @"Interstitial failed with error message: %@", errorMessage];
MAAdapterError *adapterError = [MAAdapterError errorWithCode: MAAdapterError.errorCodeUnspecified errorString: errorMessage];
[self.delegate didFailToDisplayInterstitialAdWithError: adapterError];
}
@end
@implementation ALTapjoyMediationAdapterRewardedDelegate
- (instancetype)initWithParentAdapter:(ALTapjoyMediationAdapter *)parentAdapter andNotify:(id<MARewardedAdapterDelegate>)delegate
{
self = [super init];
if ( self )
{
self.parentAdapter = parentAdapter;
self.delegate = delegate;
}
return self;
}
- (void)requestDidSucceed:(TJPlacement *)placement
{
if ( [placement isContentAvailable] )
{
[self.parentAdapter log: @"Rewarded request succeeded"];
}
else
{
[self.parentAdapter log: @"Rewarded request failed"];
[self.delegate didFailToLoadRewardedAdWithError: MAAdapterError.noFill];
}
}
- (void)contentIsReady:(TJPlacement *)placement
{
[self.parentAdapter log: @"Rewarded loaded"];
[self.delegate didLoadRewardedAd];
}
- (void)requestDidFail:(TJPlacement *)placement error:(NSError *)error
{
[self.parentAdapter log: @"Rewarded failed to load with error: %@", error];
MAAdapterError *adapterError = [ALTapjoyMediationAdapter maxErrorFromTapjoyError: error];
[self.delegate didFailToLoadRewardedAdWithError: adapterError];
}
- (void)contentDidAppear:(TJPlacement *)placement
{
[self.parentAdapter log: @"Rewarded shown"];
[self.delegate didDisplayRewardedAd];
}
- (void)didClick:(TJPlacement *)placement
{
[self.parentAdapter log: @"Rewarded clicked"];
[self.delegate didClickRewardedAd];
}
- (void)contentDidDisappear:(TJPlacement *)placement
{
if ( [self hasGrantedReward] || [self.parentAdapter shouldAlwaysRewardUser] )
{
MAReward *reward = [self.parentAdapter reward];
[self.parentAdapter log: @"Rewarded user with reward: %@", reward];
[self.delegate didRewardUserWithReward: reward];
}
[self.parentAdapter log: @"Rewarded hidden"];
[self.delegate didHideRewardedAd];
}
- (void)videoDidStart:(TJPlacement *)placement
{
[self.parentAdapter log: @"Rewarded video began"];
[self.delegate didStartRewardedAdVideo];
}
- (void)videoDidComplete:(TJPlacement *)placement
{
[self.parentAdapter log: @"Rewarded video completed"];
[self.delegate didCompleteRewardedAdVideo];
self.grantedReward = YES;
}
- (void)videoDidFail:(TJPlacement *)placement error:(NSString *)errorMessage
{
[self.parentAdapter log: @"Rewarded failed with error message: %@", errorMessage];
MAAdapterError *adapterError = [MAAdapterError errorWithCode: MAAdapterError.errorCodeUnspecified errorString: errorMessage];
[self.delegate didFailToDisplayRewardedAdWithError: adapterError];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment