From 385033df9fb93c4d4292da23fbce0cf8de16c9d6 Mon Sep 17 00:00:00 2001 From: Alex Rice Date: Tue, 12 Dec 2023 11:42:13 -0800 Subject: [PATCH] Release/4.2.4.0.0.0 (#2) * Lay foundation * Fix version number * Fix interstitial ad loading * Add rewarded ad * Banner ads now work * Connect privacy methods * Remove comment * Cleanup * Prevent multiple loads * Suggestions from PR * Support adaptive banners * Privacy changes discussed in PR * Move privacy settings * Return loaded banner size * Remove 'show' logs from banner ad * Update smoke-test.yml * Change copyright year * Explicit self to silence warning * Info doesn't need to be populated pre-init * cleanup * 'support' rewarded_interstitial * Update CHANGELOG.md * Use BidMachine 4.2.0.0 --- .github/workflows/smoke-test.yml | 2 +- CHANGELOG.md | 4 +- ChartboostMediationAdapterBidMachine.podspec | 8 +- README.md | 10 +- Source/BidMachineAdapter.swift | 155 ++++++++++++++++ Source/BidMachineAdapterAd.swift | 37 ++++ Source/BidMachineAdapterBannerAd.swift | 175 +++++++++++++++++++ Source/BidMachineAdapterConfig.swift | 26 +++ Source/BidMachineAdapterInterstitialAd.swift | 130 ++++++++++++++ Source/BidMachineAdapterRewardedAd.swift | 136 ++++++++++++++ 10 files changed, 671 insertions(+), 12 deletions(-) create mode 100644 Source/BidMachineAdapter.swift create mode 100644 Source/BidMachineAdapterAd.swift create mode 100644 Source/BidMachineAdapterBannerAd.swift create mode 100644 Source/BidMachineAdapterConfig.swift create mode 100644 Source/BidMachineAdapterInterstitialAd.swift create mode 100644 Source/BidMachineAdapterRewardedAd.swift diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 764108c..ebbdd32 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -12,4 +12,4 @@ jobs: validate-podspec: runs-on: macos-latest steps: - - uses: chartboost/chartboost-mediation-ios-actions/adapter-smoke-test@v1 + - uses: chartboost/chartboost-ios-adapter-actions/adapter-smoke-test@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b4ccd1..d0544ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,5 +3,5 @@ Note the first digit of every adapter version corresponds to the major version of the Chartboost Mediation SDK compatible with that adapter. Adapters are compatible with any Chartboost Mediation SDK version within that major version. -### {Adapter Version} -- Say something clever. \ No newline at end of file +### 4.2.4.0.0.0 +- This version of the adapter has been certified with BidMachine 2.4.0.0. diff --git a/ChartboostMediationAdapterBidMachine.podspec b/ChartboostMediationAdapterBidMachine.podspec index 33c4fb1..cd3fe15 100644 --- a/ChartboostMediationAdapterBidMachine.podspec +++ b/ChartboostMediationAdapterBidMachine.podspec @@ -1,10 +1,10 @@ Pod::Spec.new do |spec| spec.name = 'ChartboostMediationAdapterBidMachine' - spec.version = '4.2.3.0.0' + spec.version = '4.2.4.0.0.0' spec.license = { :type => 'MIT', :file => 'LICENSE.md' } spec.homepage = 'https://github.com/ChartBoost/chartboost-mediation-ios-adapter-bidmachine' spec.authors = { 'Chartboost' => 'https://www.chartboost.com/' } - spec.summary = 'Chartboost Mediation iOS SDK Reference adapter.' + spec.summary = 'Chartboost Mediation iOS SDK BidMachine adapter.' spec.description = 'BidMachine Adapters for mediating through Chartboost Mediation. Supported ad formats: banner, interstitial, rewarded.' # Source @@ -13,7 +13,7 @@ Pod::Spec.new do |spec| spec.source_files = 'Source/**/*.{swift}' # Minimum supported versions - spec.swift_version = '5.0' + spec.swift_version = '5.1' spec.ios.deployment_target = '12.0' # System frameworks used @@ -23,7 +23,7 @@ Pod::Spec.new do |spec| spec.dependency 'ChartboostMediationSDK', '~> 4.0' # Partner network SDK and version that this adapter is certified to work with. - spec.dependency 'BidMachine', '2.3.0' + spec.dependency 'BidMachine', '~> 2.4.0.0' # Indicates, that if use_frameworks! is specified, the pod should include a static library framework. spec.static_framework = true diff --git a/README.md b/README.md index 27f5763..5ae128d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Chartboost Mediation {Partner} Adapter +# Chartboost Mediation BidMachine Adapter -The Chartboost Mediation {Partner} adapter mediates {Partner} via the Chartboost Mediation SDK. +The Chartboost Mediation BidMachine adapter mediates BidMachine via the Chartboost Mediation SDK. ## Minimum Requirements @@ -8,14 +8,14 @@ The Chartboost Mediation {Partner} adapter mediates {Partner} via the Chartboost | ------ | ------ | | Chartboost Mediation SDK | 4.0.0+ | | Cocoapods | 1.11.3+ | -| iOS | {Partner's minimum iOS version} | +| iOS | 12 | | Xcode | 14.1+ | ## Integration In your `Podfile`, add the following entry: ``` -pod 'ChartboostMediationAdapter{Partner}' +pod 'ChartboostMediationAdapterBidMachine' ``` ## Contributions @@ -26,4 +26,4 @@ Refer to our [CONTRIBUTING](CONTRIBUTING.md) file for more information on how to ## License -Refer to our [LICENSE](LICENSE.md) file for more information. \ No newline at end of file +Refer to our [LICENSE](LICENSE.md) file for more information. diff --git a/Source/BidMachineAdapter.swift b/Source/BidMachineAdapter.swift new file mode 100644 index 0000000..5b6be7a --- /dev/null +++ b/Source/BidMachineAdapter.swift @@ -0,0 +1,155 @@ +// Copyright 2023-2023 Chartboost, Inc. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +import ChartboostMediationSDK +import Foundation +import UIKit +import BidMachine + +final class BidMachineAdapter: PartnerAdapter { + private let SOURCE_ID_KEY = "source_id" + + /// The version of the partner SDK. + let partnerSDKVersion = BidMachineSdk.sdkVersion + + /// The version of the adapter. + /// It should have either 5 or 6 digits separated by periods, where the first digit is Chartboost Mediation SDK's major version, the last digit is the adapter's build version, and intermediate digits are the partner SDK's version. + /// Format: `.....` where `.` is optional. + let adapterVersion = "4.2.4.0.0.0" + + /// The partner's unique identifier. + let partnerIdentifier = "bidmachine" + + /// The human-friendly partner name. + let partnerDisplayName = "BidMachine" + + /// Ad storage managed by Chartboost Mediation SDK. + let storage: PartnerAdapterStorage + + /// The designated initializer for the adapter. + /// Chartboost Mediation SDK will use this constructor to create instances of conforming types. + /// - parameter storage: An object that exposes storage managed by the Chartboost Mediation SDK to the adapter. + /// It includes a list of created `PartnerAd` instances. You may ignore this parameter if you don't need it. + init(storage: PartnerAdapterStorage) { + self.storage = storage + } + + /// Does any setup needed before beginning to load ads. + /// - parameter configuration: Configuration data for the adapter to set up. + /// - parameter completion: Closure to be performed by the adapter when it's done setting up. It should include an error indicating the cause for failure or `nil` if the operation finished successfully. + func setUp(with configuration: PartnerConfiguration, completion: @escaping (Error?) -> Void) { + log(.setUpStarted) + + BidMachineSdk.shared.populate { + $0.withTestMode(BidMachineAdapterConfiguration.testMode) + .withLoggingMode(BidMachineAdapterConfiguration.logging) + .withBidLoggingMode(BidMachineAdapterConfiguration.bidLogging) + .withEventLoggingMode(BidMachineAdapterConfiguration.eventLogging) + } + + guard let sourceID = configuration.credentials[SOURCE_ID_KEY] as? String else { + let error = error(.initializationFailureInvalidCredentials, description: "The 'source ID' was invalid") + log(.setUpFailed(error)) + completion(error) + return + } + // Initialize the SDK + BidMachineSdk.shared.initializeSdk(sourceID) + guard BidMachineSdk.shared.isInitialized == true else { + let error = error(.initializationFailureUnknown) + log(.setUpFailed(error)) + completion(error) + return + } + log(.setUpSucceded) + completion(nil) + } + + /// Fetches bidding tokens needed for the partner to participate in an auction. + /// - parameter request: Information about the ad load request. + /// - parameter completion: Closure to be performed with the fetched info. + func fetchBidderInformation(request: PreBidRequest, completion: @escaping ([String : String]?) -> Void) { + log(.fetchBidderInfoStarted(request)) + guard let token = BidMachineSdk.shared.token else { + let error = error(.prebidFailureInvalidArgument, description: "No bidding token provided by BidMachine SDK") + log(.fetchBidderInfoFailed(request, error: error)) + completion(nil) + return + } + log(.fetchBidderInfoSucceeded(request)) + completion(["token": token]) + } + + /// Indicates if GDPR applies or not and the user's GDPR consent status. + /// - parameter applies: `true` if GDPR applies, `false` if not, `nil` if the publisher has not provided this information. + /// - parameter status: One of the `GDPRConsentStatus` values depending on the user's preference. + func setGDPR(applies: Bool?, status: GDPRConsentStatus) { + if let applies = applies { + log(.privacyUpdated(setting: "gdprZone", value: applies)) + BidMachineSdk.shared.regulationInfo.populate { $0.withGDPRZone(applies) } + } + + // In the case where status == .unknown, we do nothing + if status == .denied { + log(.privacyUpdated(setting: "gdprConsent", value: false)) + BidMachineSdk.shared.regulationInfo.populate { $0.withGDPRConsent(false) } + } else if status == .granted { + log(.privacyUpdated(setting: "gdprConsent", value: true)) + BidMachineSdk.shared.regulationInfo.populate { $0.withGDPRConsent(true) } + } + } + + /// Indicates the CCPA status both as a boolean and as an IAB US privacy string. + /// - parameter hasGivenConsent: A boolean indicating if the user has given consent. + /// - parameter privacyString: An IAB-compliant string indicating the CCPA status. + func setCCPA(hasGivenConsent: Bool, privacyString: String) { + log(.privacyUpdated(setting: "usPrivacyString", value: privacyString)) + BidMachineSdk.shared.regulationInfo.populate { $0.withUSPrivacyString(privacyString) } + } + + /// Indicates if the user is subject to COPPA or not. + /// - parameter isChildDirected: `true` if the user is subject to COPPA, `false` otherwise. + func setCOPPA(isChildDirected: Bool) { + log(.privacyUpdated(setting: "COPPA", value: isChildDirected)) + BidMachineSdk.shared.regulationInfo.populate { $0.withCOPPA(isChildDirected) } + } + + /// Creates a new ad object in charge of communicating with a single partner SDK ad instance. + /// Chartboost Mediation SDK calls this method to create a new ad for each new load request. Ad instances are never reused. + /// Chartboost Mediation SDK takes care of storing and disposing of ad instances so you don't need to. + /// `invalidate()` is called on ads before disposing of them in case partners need to perform any custom logic before the object gets destroyed. + /// If, for some reason, a new ad cannot be provided, an error should be thrown. + /// - parameter request: Information about the ad load request. + /// - parameter delegate: The delegate that will receive ad life-cycle notifications. + func makeAd(request: PartnerAdLoadRequest, delegate: PartnerAdDelegate) throws -> PartnerAd { + // Prevent multiple loads for the same partner placement, since the partner SDK cannot handle them. + // Banner loads are allowed so a banner prefetch can happen during auto-refresh. + // ChartboostMediationSDK 4.x does not support loading more than 2 banners with the same placement, and the partner may or may not support it. + guard !storage.ads.contains(where: { $0.request.partnerPlacement == request.partnerPlacement }) + || request.format == .banner + else { + log("Failed to load ad for already loading placement \(request.partnerPlacement)") + throw error(.loadFailureLoadInProgress) + } + + switch request.format { + case .interstitial: + return BidMachineAdapterInterstitialAd(adapter: self, request: request, delegate: delegate) + case .rewarded: + return BidMachineAdapterRewardedAd(adapter: self, request: request, delegate: delegate) + case .banner: + return BidMachineAdapterBannerAd(adapter: self, request: request, delegate: delegate) + default: + // Not using the `.adaptiveBanner` case directly to maintain backward compatibility with Chartboost Mediation 4.0 + if request.format.rawValue == "adaptive_banner" { + return BidMachineAdapterBannerAd(adapter: self, request: request, delegate: delegate) + } else if request.format.rawValue == "rewarded_interstitial" { + return BidMachineAdapterRewardedAd(adapter: self, request: request, delegate: delegate) + } else { + throw error(.loadFailureUnsupportedAdFormat) + } + } + } +} diff --git a/Source/BidMachineAdapterAd.swift b/Source/BidMachineAdapterAd.swift new file mode 100644 index 0000000..6963ddd --- /dev/null +++ b/Source/BidMachineAdapterAd.swift @@ -0,0 +1,37 @@ +// Copyright 2023-2023 Chartboost, Inc. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +import ChartboostMediationSDK +import Foundation + +class BidMachineAdapterAd: NSObject { + + /// The partner ad view to display inline. E.g. a banner view. + /// Should be nil for full-screen ads. + var inlineView: UIView? + + /// The partner adapter that created this ad. + let adapter: PartnerAdapter + + /// The ad load request associated to the ad. + /// It should be the one provided on `PartnerAdapter.makeAd(request:delegate:)`. + let request: PartnerAdLoadRequest + + /// The partner ad delegate to send ad life-cycle events to. + /// It should be the one provided on `PartnerAdapter.makeAd(request:delegate:)`. + weak var delegate: PartnerAdDelegate? + + /// The completion for the ongoing load operation. + var loadCompletion: ((Result) -> Void)? + + /// The completion for the ongoing show operation. + var showCompletion: ((Result) -> Void)? + + init(adapter: PartnerAdapter, request: PartnerAdLoadRequest, delegate: PartnerAdDelegate) { + self.adapter = adapter + self.request = request + self.delegate = delegate + } +} diff --git a/Source/BidMachineAdapterBannerAd.swift b/Source/BidMachineAdapterBannerAd.swift new file mode 100644 index 0000000..9061107 --- /dev/null +++ b/Source/BidMachineAdapterBannerAd.swift @@ -0,0 +1,175 @@ +// Copyright 2023-2023 Chartboost, Inc. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +import ChartboostMediationSDK +import Foundation +import BidMachine +import BidMachineApiCore // Needed for the PlacementFormat type + +final class BidMachineAdapterBannerAd: BidMachineAdapterAd, PartnerAd { + + /// The BidMachineSDK ad instance. + private var ad: BidMachineBanner? + + /// Loads an ad. + /// - parameter viewController: The view controller on which the ad will be presented on. Needed on load for some banners. + /// - parameter completion: Closure to be performed once the ad has been loaded. + func load(with viewController: UIViewController?, completion: @escaping (Result) -> Void) { + log(.loadStarted) + + guard let size = request.size, + let bannerType = BidMachineApiCore.PlacementFormat.from(size: fixedBannerSize(for: size)) else { + let error = error(.loadFailureInvalidBannerSize) + log(.loadFailed(error)) + completion(.failure(error)) + return + } + + // Make request configuration + let config: BidMachineRequestConfigurationProtocol + do { + config = try BidMachineSdk.shared.requestConfiguration(bannerType) + } catch { + self.log(.loadFailed(error)) + completion(.failure(error)) + return + } + + self.loadCompletion = completion + + // There's no harm in setting the placement ID when loading a bidding ad, but calling + // .withPayload(request.adm ?? "") causes an error when BidMachine parses the empty string + config.populate { $0.withPlacementId(request.partnerPlacement) } + if let adm = request.adm { + config.populate { $0.withPayload(adm) } + } + + BidMachineSdk.shared.banner(config) { [weak self, weak viewController] ad, error in + guard let self else { + return + } + guard let ad else { + let chartboostMediationError = self.error(.loadFailureUnknown, error: error) + self.log(.loadFailed(chartboostMediationError)) + completion(.failure(chartboostMediationError)) + return + } + self.inlineView = ad + ad.controller = viewController + ad.delegate = self + ad.loadAd() + } + } + + /// Shows a loaded ad. + /// It will never get called for banner ads. You may leave the implementation blank for that ad format. + /// - parameter viewController: The view controller on which the ad will be presented on. + /// - parameter completion: Closure to be performed once the ad has been shown. + func show(with viewController: UIViewController, completion: @escaping (Result) -> Void) { + // no-op + } +} + +extension BidMachineAdapterBannerAd: BidMachineAdDelegate { + func didLoadAd(_ ad: BidMachine.BidMachineAdProtocol) { + // Because 'show' isn't a separate step for banners, we don't declare a load success until + // after any show checks are done + guard let bannerAdView = ad as? BidMachineBanner, + bannerAdView.canShow else { + let loadError = error(.loadFailureUnknown) + log(.loadFailed(loadError)) + loadCompletion?(.failure(loadError)) ?? log(.loadResultIgnored) + loadCompletion = nil + return + } + log(.loadSucceeded) + var partnerDetails: [String: String] = [:] + if let loadedSize = fixedBannerSize(for: request.size ?? IABStandardAdSize) { + partnerDetails["bannerWidth"] = "\(loadedSize.width)" + partnerDetails["bannerHeight"] = "\(loadedSize.height)" + partnerDetails["bannerType"] = "0" // Fixed banner + } + loadCompletion?(.success([:])) ?? log(.loadResultIgnored) + loadCompletion = nil + } + + func didFailLoadAd(_ ad: BidMachine.BidMachineAdProtocol, _ error: Error) { + log(.loadFailed(error)) + loadCompletion?(.failure(error)) ?? log(.loadResultIgnored) + loadCompletion = nil + } + + func didPresentAd(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func didFailPresentAd(_ ad: BidMachineAdProtocol, _ error: Error) { + log(.delegateCallIgnored) + } + + func didDismissAd(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func willPresentScreen(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func didDismissScreen(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func didUserInteraction(_ ad: BidMachineAdProtocol) { + log(.didClick(error: nil)) + delegate?.didClick(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didExpired(_ ad: BidMachineAdProtocol) { + log(.didExpire) + delegate?.didExpire(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didTrackImpression(_ ad: BidMachineAdProtocol) { + log(.didTrackImpression) + self.delegate?.didTrackImpression(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didTrackInteraction(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } +} + +extension BidMachineApiCore.PlacementFormat { + static func from(size: CGSize?) -> BidMachineApiCore.PlacementFormat? { + // Translate IAB size to a BidMachine placement format + switch size { + case IABStandardAdSize: + return .banner320x50 + case IABMediumAdSize: + return .banner300x250 + case IABLeaderboardAdSize: + return .banner728x90 + default: + // Not a standard IAB size + return nil + } + } +} + +extension BidMachineAdapterBannerAd { + private func fixedBannerSize(for requestedSize: CGSize) -> CGSize? { + let sizes = [IABLeaderboardAdSize, IABMediumAdSize, IABStandardAdSize] + // Find the largest size that can fit in the requested size. + for size in sizes { + // If height is 0, the pub has requested an ad of any height, so only the width matters. + if requestedSize.width >= size.width && + (size.height == 0 || requestedSize.height >= size.height) { + return size + } + } + // The requested size cannot fit any fixed size banners. + return nil + } +} diff --git a/Source/BidMachineAdapterConfig.swift b/Source/BidMachineAdapterConfig.swift new file mode 100644 index 0000000..9eb4fbc --- /dev/null +++ b/Source/BidMachineAdapterConfig.swift @@ -0,0 +1,26 @@ +// Copyright 2023-2023 Chartboost, Inc. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +import Foundation + + +/// A list of externally configurable properties pertaining to the partner SDK that can be retrieved and set by publishers. +@objc public class BidMachineAdapterConfiguration: NSObject { + /// Init flag for starting up BidMachine SDK in test mode. + /// Default value is 'false'. + @objc public static var testMode: Bool = false + + /// Init flag for turning on BidMachine SDK general logging. + /// Default value is 'false'. + @objc public static var logging: Bool = false + + /// Init flag for turning on BidMachine SDK bidding logging. + /// Default value is 'false'. + @objc public static var bidLogging: Bool = false + + /// Init flag for turning on BidMachine SDK event logging. + /// Default value is 'false'. + @objc public static var eventLogging: Bool = false +} diff --git a/Source/BidMachineAdapterInterstitialAd.swift b/Source/BidMachineAdapterInterstitialAd.swift new file mode 100644 index 0000000..e4fb3d7 --- /dev/null +++ b/Source/BidMachineAdapterInterstitialAd.swift @@ -0,0 +1,130 @@ +// Copyright 2023-2023 Chartboost, Inc. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +import ChartboostMediationSDK +import Foundation +import BidMachine + +final class BidMachineAdapterInterstitialAd: BidMachineAdapterAd, PartnerAd { + + /// The BidMachineSDK ad instance. + private var ad: BidMachineInterstitial? + + /// Loads an ad. + /// - parameter viewController: The view controller on which the ad will be presented on. Needed on load for some banners. + /// - parameter completion: Closure to be performed once the ad has been loaded. + func load(with viewController: UIViewController?, completion: @escaping (Result) -> Void) { + log(.loadStarted) + + // Make request configuration + let config: BidMachineRequestConfigurationProtocol + do { + config = try BidMachineSdk.shared.requestConfiguration(.interstitial) + } catch { + self.log(.loadFailed(error)) + completion(.failure(error)) + return + } + + loadCompletion = completion + + // There's no harm in setting the placement ID when loading a bidding ad, but calling + // .withPayload(request.adm ?? "") causes an error when BidMachine parses the empty string + config.populate { $0.withPlacementId(request.partnerPlacement) } + if let adm = request.adm { + config.populate { $0.withPayload(adm) } + } + + BidMachineSdk.shared.interstitial(config) { [weak self] ad, error in + guard let self = self else { + return + } + guard let ad = ad else { + let chartboostMediationError = self.error(.loadFailureUnknown, error: error) + self.log(.loadFailed(chartboostMediationError)) + completion(.failure(chartboostMediationError)) + return + } + self.ad = ad + ad.delegate = self + ad.loadAd() + } + } + + /// Shows a loaded ad. + /// It will never get called for banner ads. You may leave the implementation blank for that ad format. + /// - parameter viewController: The view controller on which the ad will be presented on. + /// - parameter completion: Closure to be performed once the ad has been shown. + func show(with viewController: UIViewController, completion: @escaping (Result) -> Void) { + log(.showStarted) + ad?.controller = viewController + guard let ad = ad, ad.canShow else { + let error = error(.showFailureAdNotReady) + log(.showFailed(error)) + completion(.failure(error)) + return + } + showCompletion = completion + ad.presentAd() + } +} + +extension BidMachineAdapterInterstitialAd: BidMachineAdDelegate { + func didLoadAd(_ ad: BidMachine.BidMachineAdProtocol) { + log(.loadSucceeded) + loadCompletion?(.success([:])) ?? log(.loadResultIgnored) + loadCompletion = nil + } + + func didFailLoadAd(_ ad: BidMachine.BidMachineAdProtocol, _ error: Error) { + log(.loadFailed(error)) + loadCompletion?(.failure(error)) ?? log(.loadResultIgnored) + loadCompletion = nil + } + + func didPresentAd(_ ad: BidMachineAdProtocol) { + log(.showSucceeded) + showCompletion?(.success([:])) ?? log(.showResultIgnored) + showCompletion = nil + } + + func didFailPresentAd(_ ad: BidMachineAdProtocol, _ error: Error) { + log(.showFailed(error)) + showCompletion?(.failure(error)) ?? log(.showResultIgnored) + showCompletion = nil + } + + func didDismissAd(_ ad: BidMachineAdProtocol) { + log(.didDismiss(error: nil)) + delegate?.didDismiss(self, details: [:], error: nil) ?? log(.delegateUnavailable) + } + + func willPresentScreen(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func didDismissScreen(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func didUserInteraction(_ ad: BidMachineAdProtocol) { + log(.didClick(error: nil)) + delegate?.didClick(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didExpired(_ ad: BidMachineAdProtocol) { + log(.didExpire) + delegate?.didExpire(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didTrackImpression(_ ad: BidMachineAdProtocol) { + log(.didTrackImpression) + self.delegate?.didTrackImpression(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didTrackInteraction(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } +} diff --git a/Source/BidMachineAdapterRewardedAd.swift b/Source/BidMachineAdapterRewardedAd.swift new file mode 100644 index 0000000..8235b84 --- /dev/null +++ b/Source/BidMachineAdapterRewardedAd.swift @@ -0,0 +1,136 @@ +// Copyright 2023-2023 Chartboost, Inc. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +import ChartboostMediationSDK +import Foundation +import BidMachine + +final class BidMachineAdapterRewardedAd: BidMachineAdapterAd, PartnerAd { + + /// The BidMachineSDK ad instance. + private var ad: BidMachineRewarded? + + /// Loads an ad. + /// - parameter viewController: The view controller on which the ad will be presented on. Needed on load for some banners. + /// - parameter completion: Closure to be performed once the ad has been loaded. + func load(with viewController: UIViewController?, completion: @escaping (Result) -> Void) { + log(.loadStarted) + + // Make request configuration + let config: BidMachineRequestConfigurationProtocol + do { + config = try BidMachineSdk.shared.requestConfiguration(.rewarded) + } catch { + self.log(.loadFailed(error)) + completion(.failure(error)) + return + } + + loadCompletion = completion + + // There's no harm in setting the placement ID when loading a bidding ad, but calling + // .withPayload(request.adm ?? "") causes an error when BidMachine parses the empty string + config.populate { $0.withPlacementId(request.partnerPlacement) } + if let adm = request.adm { + config.populate { $0.withPayload(adm) } + } + + BidMachineSdk.shared.rewarded(config) { [weak self] ad, error in + guard let self = self else { + return + } + guard let ad = ad else { + let chartboostMediationError = self.error(.loadFailureUnknown, error: error) + self.log(.loadFailed(chartboostMediationError)) + completion(.failure(chartboostMediationError)) + return + } + self.ad = ad + ad.delegate = self + ad.loadAd() + } + } + + /// Shows a loaded ad. + /// It will never get called for banner ads. You may leave the implementation blank for that ad format. + /// - parameter viewController: The view controller on which the ad will be presented on. + /// - parameter completion: Closure to be performed once the ad has been shown. + func show(with viewController: UIViewController, completion: @escaping (Result) -> Void) { + log(.showStarted) + ad?.controller = viewController + guard let ad = ad, ad.canShow else { + let error = error(.showFailureAdNotReady) + log(.showFailed(error)) + completion(.failure(error)) + return + } + showCompletion = completion + ad.presentAd() + } +} + +extension BidMachineAdapterRewardedAd: BidMachineAdDelegate { + func didLoadAd(_ ad: BidMachine.BidMachineAdProtocol) { + log(.loadSucceeded) + loadCompletion?(.success([:])) ?? log(.loadResultIgnored) + loadCompletion = nil + } + + func didFailLoadAd(_ ad: BidMachine.BidMachineAdProtocol, _ error: Error) { + log(.loadFailed(error)) + loadCompletion?(.failure(error)) ?? log(.loadResultIgnored) + loadCompletion = nil + } + + func didPresentAd(_ ad: BidMachineAdProtocol) { + log(.showSucceeded) + showCompletion?(.success([:])) ?? log(.showResultIgnored) + showCompletion = nil + } + + func didFailPresentAd(_ ad: BidMachineAdProtocol, _ error: Error) { + log(.showFailed(error)) + showCompletion?(.failure(error)) ?? log(.showResultIgnored) + showCompletion = nil + } + + func didDismissAd(_ ad: BidMachineAdProtocol) { + log(.didDismiss(error: nil)) + delegate?.didDismiss(self, details: [:], error: nil) ?? log(.delegateUnavailable) + } + + func willPresentScreen(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func didDismissScreen(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func didUserInteraction(_ ad: BidMachineAdProtocol) { + log(.didClick(error: nil)) + delegate?.didClick(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didExpired(_ ad: BidMachineAdProtocol) { + log(.didExpire) + delegate?.didExpire(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didTrackImpression(_ ad: BidMachineAdProtocol) { + log(.didTrackImpression) + self.delegate?.didTrackImpression(self, details: [:]) ?? log(.delegateUnavailable) + } + + func didTrackInteraction(_ ad: BidMachineAdProtocol) { + log(.delegateCallIgnored) + } + + func didReceiveReward(_ ad: BidMachineAdProtocol) { + log(.didReward) + delegate?.didReward(self, details: [:]) ?? log(.delegateUnavailable) + } +} +