react-native-liftoffads 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/NitroLiftoffAds.podspec +26 -0
- package/README.md +61 -0
- package/android/CMakeLists.txt +20 -0
- package/android/build.gradle +132 -0
- package/android/consumer-rules.pro +2 -0
- package/android/fix-prefab.gradle +46 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/cpp-adapter.cpp +7 -0
- package/android/src/main/java/com/margelo/nitro/liftoffads/HybridLiftoffAds.kt +121 -0
- package/android/src/main/java/com/margelo/nitro/liftoffads/HybridLiftoffInterstitialAd.kt +96 -0
- package/android/src/main/java/com/margelo/nitro/liftoffads/HybridLiftoffRewardedAd.kt +104 -0
- package/android/src/main/java/com/margelo/nitro/liftoffads/LiftoffAdEventEmitter.kt +51 -0
- package/android/src/main/java/com/margelo/nitro/liftoffads/LiftoffAndroidUtils.kt +54 -0
- package/android/src/main/java/com/margelo/nitro/liftoffads/NitroLiftoffAdsPackage.java +27 -0
- package/ios/HybridLiftoffAds.swift +113 -0
- package/ios/HybridLiftoffInterstitialAd.swift +98 -0
- package/ios/HybridLiftoffRewardedAd.swift +100 -0
- package/ios/LiftoffSupport.swift +106 -0
- package/lib/module/index.js +169 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/index.test.js +205 -0
- package/lib/module/index.test.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/specs/LiftoffAds.nitro.js +4 -0
- package/lib/module/specs/LiftoffAds.nitro.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/index.d.ts +72 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/index.test.d.ts +2 -0
- package/lib/typescript/src/index.test.d.ts.map +1 -0
- package/lib/typescript/src/specs/LiftoffAds.nitro.d.ts +46 -0
- package/lib/typescript/src/specs/LiftoffAds.nitro.d.ts.map +1 -0
- package/nitro.json +18 -0
- package/nitrogen/generated/android/NitroLiftoffAds+autolinking.cmake +85 -0
- package/nitrogen/generated/android/NitroLiftoffAds+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroLiftoffAdsOnLoad.cpp +50 -0
- package/nitrogen/generated/android/NitroLiftoffAdsOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_LiftoffAdEventPayload_.hpp +78 -0
- package/nitrogen/generated/android/c++/JHybridLiftoffAdsSpec.cpp +88 -0
- package/nitrogen/generated/android/c++/JHybridLiftoffAdsSpec.hpp +69 -0
- package/nitrogen/generated/android/c++/JHybridLiftoffInterstitialAdSpec.cpp +89 -0
- package/nitrogen/generated/android/c++/JHybridLiftoffInterstitialAdSpec.hpp +68 -0
- package/nitrogen/generated/android/c++/JHybridLiftoffRewardedAdSpec.cpp +93 -0
- package/nitrogen/generated/android/c++/JHybridLiftoffRewardedAdSpec.hpp +69 -0
- package/nitrogen/generated/android/c++/JLiftoffAdEventPayload.hpp +58 -0
- package/nitrogen/generated/android/c++/JLiftoffAdEventType.hpp +77 -0
- package/nitrogen/generated/android/c++/JLiftoffAdShowOptions.hpp +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/liftoffads/Func_void_std__optional_LiftoffAdEventPayload_.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/liftoffads/HybridLiftoffAdsSpec.kt +72 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/liftoffads/HybridLiftoffInterstitialAdSpec.kt +73 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/liftoffads/HybridLiftoffRewardedAdSpec.kt +77 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/liftoffads/LiftoffAdEventPayload.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/liftoffads/LiftoffAdEventType.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/liftoffads/LiftoffAdShowOptions.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/liftoffads/NitroLiftoffAdsOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroLiftoffAds+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroLiftoffAds-Swift-Cxx-Bridge.cpp +90 -0
- package/nitrogen/generated/ios/NitroLiftoffAds-Swift-Cxx-Bridge.hpp +285 -0
- package/nitrogen/generated/ios/NitroLiftoffAds-Swift-Cxx-Umbrella.hpp +66 -0
- package/nitrogen/generated/ios/NitroLiftoffAdsAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroLiftoffAdsAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridLiftoffAdsSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridLiftoffAdsSpecSwift.hpp +114 -0
- package/nitrogen/generated/ios/c++/HybridLiftoffInterstitialAdSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridLiftoffInterstitialAdSpecSwift.hpp +110 -0
- package/nitrogen/generated/ios/c++/HybridLiftoffRewardedAdSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridLiftoffRewardedAdSpecSwift.hpp +116 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_LiftoffAdEventPayload_.swift +54 -0
- package/nitrogen/generated/ios/swift/HybridLiftoffAdsSpec.swift +54 -0
- package/nitrogen/generated/ios/swift/HybridLiftoffAdsSpec_cxx.swift +193 -0
- package/nitrogen/generated/ios/swift/HybridLiftoffInterstitialAdSpec.swift +53 -0
- package/nitrogen/generated/ios/swift/HybridLiftoffInterstitialAdSpec_cxx.swift +193 -0
- package/nitrogen/generated/ios/swift/HybridLiftoffRewardedAdSpec.swift +54 -0
- package/nitrogen/generated/ios/swift/HybridLiftoffRewardedAdSpec_cxx.swift +204 -0
- package/nitrogen/generated/ios/swift/LiftoffAdEventPayload.swift +77 -0
- package/nitrogen/generated/ios/swift/LiftoffAdEventType.swift +64 -0
- package/nitrogen/generated/ios/swift/LiftoffAdShowOptions.swift +47 -0
- package/nitrogen/generated/shared/c++/HybridLiftoffAdsSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridLiftoffAdsSpec.hpp +74 -0
- package/nitrogen/generated/shared/c++/HybridLiftoffInterstitialAdSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridLiftoffInterstitialAdSpec.hpp +76 -0
- package/nitrogen/generated/shared/c++/HybridLiftoffRewardedAdSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridLiftoffRewardedAdSpec.hpp +78 -0
- package/nitrogen/generated/shared/c++/LiftoffAdEventPayload.hpp +72 -0
- package/nitrogen/generated/shared/c++/LiftoffAdEventType.hpp +100 -0
- package/nitrogen/generated/shared/c++/LiftoffAdShowOptions.hpp +67 -0
- package/package.json +173 -0
- package/react-native.config.js +8 -0
- package/src/index.test.ts +257 -0
- package/src/index.ts +307 -0
- package/src/specs/LiftoffAds.nitro.ts +67 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package com.margelo.nitro.liftoffads
|
|
2
|
+
|
|
3
|
+
internal class LiftoffAdEventEmitter {
|
|
4
|
+
private val lock = Any()
|
|
5
|
+
private val listeners = mutableMapOf<Double, Listener>()
|
|
6
|
+
private var nextSubscriptionId = 1.0
|
|
7
|
+
|
|
8
|
+
fun add(
|
|
9
|
+
eventType: LiftoffAdEventType,
|
|
10
|
+
listener: (payload: LiftoffAdEventPayload?) -> Unit,
|
|
11
|
+
): Double = synchronized(lock) {
|
|
12
|
+
val subscriptionId = nextSubscriptionId++
|
|
13
|
+
listeners[subscriptionId] = Listener(eventType, listener)
|
|
14
|
+
subscriptionId
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fun remove(subscriptionId: Double) {
|
|
18
|
+
synchronized(lock) { listeners.remove(subscriptionId) }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fun removeAll() {
|
|
22
|
+
synchronized(lock) { listeners.clear() }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fun emit(
|
|
26
|
+
eventType: LiftoffAdEventType,
|
|
27
|
+
payload: LiftoffAdEventPayload? = null,
|
|
28
|
+
) {
|
|
29
|
+
val callbacks = synchronized(lock) {
|
|
30
|
+
listeners.values
|
|
31
|
+
.filter { it.eventType == eventType }
|
|
32
|
+
.map { it.callback }
|
|
33
|
+
}
|
|
34
|
+
callbacks.forEach { callback -> runCatching { callback(payload) } }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private data class Listener(
|
|
38
|
+
val eventType: LiftoffAdEventType,
|
|
39
|
+
val callback: (payload: LiftoffAdEventPayload?) -> Unit,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
internal fun Throwable.toLiftoffEventPayload(): LiftoffAdEventPayload {
|
|
44
|
+
val vungleError =
|
|
45
|
+
this as? com.vungle.ads.VungleError
|
|
46
|
+
?: cause as? com.vungle.ads.VungleError
|
|
47
|
+
return LiftoffAdEventPayload(
|
|
48
|
+
errorCode = vungleError?.code?.toDouble(),
|
|
49
|
+
errorMessage = message ?: localizedMessage,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package com.margelo.nitro.liftoffads
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.view.View
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.vungle.ads.VungleError
|
|
7
|
+
import java.lang.ref.WeakReference
|
|
8
|
+
|
|
9
|
+
internal fun ReactApplicationContext.requireCurrentActivity(): Activity =
|
|
10
|
+
currentActivity ?: error("Cannot show Liftoff ad without an active Activity")
|
|
11
|
+
|
|
12
|
+
internal class ImmersiveModeController {
|
|
13
|
+
private var activityReference: WeakReference<Activity>? = null
|
|
14
|
+
private var previousSystemUiVisibility: Int? = null
|
|
15
|
+
|
|
16
|
+
fun enable(activity: Activity) {
|
|
17
|
+
restore()
|
|
18
|
+
|
|
19
|
+
@Suppress("DEPRECATION")
|
|
20
|
+
val decorView = activity.window.decorView
|
|
21
|
+
@Suppress("DEPRECATION")
|
|
22
|
+
previousSystemUiVisibility = decorView.systemUiVisibility
|
|
23
|
+
activityReference = WeakReference(activity)
|
|
24
|
+
|
|
25
|
+
@Suppress("DEPRECATION")
|
|
26
|
+
decorView.systemUiVisibility =
|
|
27
|
+
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
|
28
|
+
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
|
29
|
+
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
|
30
|
+
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
|
31
|
+
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
|
32
|
+
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fun restore() {
|
|
36
|
+
val activity = activityReference?.get()
|
|
37
|
+
val visibility = previousSystemUiVisibility
|
|
38
|
+
activityReference = null
|
|
39
|
+
previousSystemUiVisibility = null
|
|
40
|
+
|
|
41
|
+
if (activity != null && visibility != null) {
|
|
42
|
+
activity.runOnUiThread {
|
|
43
|
+
@Suppress("DEPRECATION")
|
|
44
|
+
activity.window.decorView.systemUiVisibility = visibility
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
internal fun liftoffException(stage: String, error: VungleError) =
|
|
51
|
+
IllegalStateException(
|
|
52
|
+
"Liftoff $stage failed (${error.code}): ${error.localizedMessage}",
|
|
53
|
+
error,
|
|
54
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.margelo.nitro.liftoffads;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.Nullable;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.TurboReactPackage;
|
|
6
|
+
import com.facebook.react.bridge.NativeModule;
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
8
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
9
|
+
|
|
10
|
+
import java.util.HashMap;
|
|
11
|
+
|
|
12
|
+
public class NitroLiftoffAdsPackage extends TurboReactPackage {
|
|
13
|
+
@Nullable
|
|
14
|
+
@Override
|
|
15
|
+
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Override
|
|
20
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
21
|
+
return HashMap::new;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static {
|
|
25
|
+
NitroLiftoffAdsOnLoad.initializeNative();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
import VungleAdsSDK
|
|
4
|
+
|
|
5
|
+
final class HybridLiftoffAds: HybridLiftoffAdsSpec, @unchecked Sendable {
|
|
6
|
+
private let stateLock = NSLock()
|
|
7
|
+
private var initializedAppId: String?
|
|
8
|
+
private var initializingAppId: String?
|
|
9
|
+
private var initializationContinuations: [CheckedContinuation<Void, Error>] = []
|
|
10
|
+
private var isInitializing = false
|
|
11
|
+
|
|
12
|
+
func initialize(appId: String) throws -> Promise<Void> {
|
|
13
|
+
guard !appId.isEmpty else {
|
|
14
|
+
throw liftoffError("Liftoff app ID is empty")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return Promise.async {
|
|
18
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
19
|
+
DispatchQueue.main.async {
|
|
20
|
+
self.stateLock.lock()
|
|
21
|
+
if VungleAds.isInitialized() {
|
|
22
|
+
let existing = self.initializedAppId
|
|
23
|
+
if existing == nil { self.initializedAppId = appId }
|
|
24
|
+
self.stateLock.unlock()
|
|
25
|
+
|
|
26
|
+
if let existing, existing != appId {
|
|
27
|
+
continuation.resume(
|
|
28
|
+
throwing: liftoffError("Liftoff SDK was initialized with another app ID")
|
|
29
|
+
)
|
|
30
|
+
} else {
|
|
31
|
+
continuation.resume()
|
|
32
|
+
}
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if self.isInitializing, self.initializingAppId != appId {
|
|
37
|
+
self.stateLock.unlock()
|
|
38
|
+
continuation.resume(
|
|
39
|
+
throwing: liftoffError("Liftoff SDK is being initialized with another app ID")
|
|
40
|
+
)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
self.initializationContinuations.append(continuation)
|
|
45
|
+
if self.isInitializing {
|
|
46
|
+
self.stateLock.unlock()
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
self.isInitializing = true
|
|
51
|
+
self.initializingAppId = appId
|
|
52
|
+
self.stateLock.unlock()
|
|
53
|
+
|
|
54
|
+
VungleAds.initWithAppId(appId) { error in
|
|
55
|
+
self.stateLock.lock()
|
|
56
|
+
let continuations = self.initializationContinuations
|
|
57
|
+
self.initializationContinuations.removeAll()
|
|
58
|
+
self.isInitializing = false
|
|
59
|
+
self.initializingAppId = nil
|
|
60
|
+
if error == nil { self.initializedAppId = appId }
|
|
61
|
+
self.stateLock.unlock()
|
|
62
|
+
|
|
63
|
+
continuations.forEach { item in
|
|
64
|
+
if let error {
|
|
65
|
+
item.resume(throwing: error)
|
|
66
|
+
} else {
|
|
67
|
+
item.resume()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func setGDPRConsent(optIn: Bool, consentMessageVersion: String) throws {
|
|
77
|
+
runOnMain {
|
|
78
|
+
VunglePrivacySettings.setGDPRStatus(optIn)
|
|
79
|
+
VunglePrivacySettings.setGDPRMessageVersion(consentMessageVersion)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func setCCPAConsent(optIn: Bool) throws {
|
|
84
|
+
runOnMain { VunglePrivacySettings.setCCPAStatus(optIn) }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func setCOPPA(isUserCoppa: Bool) throws {
|
|
88
|
+
runOnMain { VunglePrivacySettings.setCOPPAStatus(isUserCoppa) }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func createInterstitialAd(
|
|
92
|
+
placementId: String
|
|
93
|
+
) throws -> any HybridLiftoffInterstitialAdSpec {
|
|
94
|
+
try requireInitialized(placementId: placementId)
|
|
95
|
+
return HybridLiftoffInterstitialAd(placementId: placementId)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func createRewardedAd(
|
|
99
|
+
placementId: String
|
|
100
|
+
) throws -> any HybridLiftoffRewardedAdSpec {
|
|
101
|
+
try requireInitialized(placementId: placementId)
|
|
102
|
+
return HybridLiftoffRewardedAd(placementId: placementId)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private func requireInitialized(placementId: String) throws {
|
|
106
|
+
guard VungleAds.isInitialized() else {
|
|
107
|
+
throw liftoffError("Liftoff SDK is not initialized")
|
|
108
|
+
}
|
|
109
|
+
guard !placementId.isEmpty else {
|
|
110
|
+
throw liftoffError("Liftoff placement ID is empty")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
import VungleAdsSDK
|
|
4
|
+
|
|
5
|
+
final class HybridLiftoffInterstitialAd:
|
|
6
|
+
HybridLiftoffInterstitialAdSpec, @unchecked Sendable {
|
|
7
|
+
fileprivate let events = LiftoffAdEventEmitter()
|
|
8
|
+
private let ad: VungleInterstitial
|
|
9
|
+
private lazy var delegate = LiftoffInterstitialDelegate(owner: self)
|
|
10
|
+
|
|
11
|
+
init(placementId: String) {
|
|
12
|
+
self.ad = VungleInterstitial(placementId: placementId)
|
|
13
|
+
super.init()
|
|
14
|
+
self.ad.delegate = delegate
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func load() throws {
|
|
18
|
+
DispatchQueue.main.async { self.ad.load() }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func show(options: LiftoffAdShowOptions?) throws -> Promise<Void> {
|
|
22
|
+
Promise.async {
|
|
23
|
+
do {
|
|
24
|
+
let viewController = try await currentViewController()
|
|
25
|
+
try await withCheckedThrowingContinuation {
|
|
26
|
+
(continuation: CheckedContinuation<Void, Error>) in
|
|
27
|
+
DispatchQueue.main.async {
|
|
28
|
+
guard self.ad.canPlayAd() else {
|
|
29
|
+
continuation.resume(
|
|
30
|
+
throwing: liftoffError("Liftoff interstitial ad is not ready")
|
|
31
|
+
)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
self.ad.present(with: viewController)
|
|
35
|
+
continuation.resume()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
self.events.emit(.error, error: error)
|
|
40
|
+
throw error
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func addAdEventListener(
|
|
46
|
+
eventType: LiftoffAdEventType,
|
|
47
|
+
listener: @escaping (LiftoffAdEventPayload?) -> Void
|
|
48
|
+
) throws -> Double {
|
|
49
|
+
events.add(eventType: eventType, listener: listener)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func removeAdEventListener(subscriptionId: Double) throws {
|
|
53
|
+
events.remove(subscriptionId: subscriptionId)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func removeAllListeners() throws {
|
|
57
|
+
events.removeAll()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private final class LiftoffInterstitialDelegate: NSObject, VungleInterstitialDelegate {
|
|
63
|
+
private weak var owner: HybridLiftoffInterstitialAd?
|
|
64
|
+
|
|
65
|
+
init(owner: HybridLiftoffInterstitialAd) {
|
|
66
|
+
self.owner = owner
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func interstitialAdDidLoad(_ interstitial: VungleInterstitial) {
|
|
70
|
+
owner?.events.emit(.loaded)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func interstitialAdDidFailToLoad(
|
|
74
|
+
_ interstitial: VungleInterstitial,
|
|
75
|
+
withError error: NSError
|
|
76
|
+
) {
|
|
77
|
+
owner?.events.emit(.error, error: error)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func interstitialAdDidFailToPresent(
|
|
81
|
+
_ interstitial: VungleInterstitial,
|
|
82
|
+
withError error: NSError
|
|
83
|
+
) {
|
|
84
|
+
owner?.events.emit(.error, error: error)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func interstitialAdDidPresent(_ interstitial: VungleInterstitial) {
|
|
88
|
+
owner?.events.emit(.opened)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func interstitialAdDidClick(_ interstitial: VungleInterstitial) {
|
|
92
|
+
owner?.events.emit(.clicked)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func interstitialAdDidClose(_ interstitial: VungleInterstitial) {
|
|
96
|
+
owner?.events.emit(.closed)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
import VungleAdsSDK
|
|
4
|
+
|
|
5
|
+
final class HybridLiftoffRewardedAd:
|
|
6
|
+
HybridLiftoffRewardedAdSpec, @unchecked Sendable {
|
|
7
|
+
fileprivate let events = LiftoffAdEventEmitter()
|
|
8
|
+
private let ad: VungleRewarded
|
|
9
|
+
private lazy var delegate = LiftoffRewardedDelegate(owner: self)
|
|
10
|
+
|
|
11
|
+
init(placementId: String) {
|
|
12
|
+
self.ad = VungleRewarded(placementId: placementId)
|
|
13
|
+
super.init()
|
|
14
|
+
self.ad.delegate = delegate
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func load() throws {
|
|
18
|
+
DispatchQueue.main.async { self.ad.load() }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func setUserId(userId: String) throws {
|
|
22
|
+
DispatchQueue.main.async { self.ad.setUserId(userId: userId) }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func show(options: LiftoffAdShowOptions?) throws -> Promise<Void> {
|
|
26
|
+
Promise.async {
|
|
27
|
+
do {
|
|
28
|
+
let viewController = try await currentViewController()
|
|
29
|
+
try await withCheckedThrowingContinuation {
|
|
30
|
+
(continuation: CheckedContinuation<Void, Error>) in
|
|
31
|
+
DispatchQueue.main.async {
|
|
32
|
+
guard self.ad.canPlayAd() else {
|
|
33
|
+
continuation.resume(
|
|
34
|
+
throwing: liftoffError("Liftoff rewarded ad is not ready")
|
|
35
|
+
)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
self.ad.present(with: viewController)
|
|
39
|
+
continuation.resume()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
self.events.emit(.error, error: error)
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func addAdEventListener(
|
|
50
|
+
eventType: LiftoffAdEventType,
|
|
51
|
+
listener: @escaping (LiftoffAdEventPayload?) -> Void
|
|
52
|
+
) throws -> Double {
|
|
53
|
+
events.add(eventType: eventType, listener: listener)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func removeAdEventListener(subscriptionId: Double) throws {
|
|
57
|
+
events.remove(subscriptionId: subscriptionId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func removeAllListeners() throws {
|
|
61
|
+
events.removeAll()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private final class LiftoffRewardedDelegate: NSObject, VungleRewardedDelegate {
|
|
67
|
+
private weak var owner: HybridLiftoffRewardedAd?
|
|
68
|
+
|
|
69
|
+
init(owner: HybridLiftoffRewardedAd) {
|
|
70
|
+
self.owner = owner
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func rewardedAdDidLoad(_ rewarded: VungleRewarded) {
|
|
74
|
+
owner?.events.emit(.rewardedLoaded)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func rewardedAdDidFailToLoad(_ rewarded: VungleRewarded, withError error: NSError) {
|
|
78
|
+
owner?.events.emit(.error, error: error)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func rewardedAdDidFailToPresent(_ rewarded: VungleRewarded, withError error: NSError) {
|
|
82
|
+
owner?.events.emit(.error, error: error)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func rewardedAdDidPresent(_ rewarded: VungleRewarded) {
|
|
86
|
+
owner?.events.emit(.opened)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func rewardedAdDidClick(_ rewarded: VungleRewarded) {
|
|
90
|
+
owner?.events.emit(.clicked)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func rewardedAdDidRewardUser(_ rewarded: VungleRewarded) {
|
|
94
|
+
owner?.events.emit(.rewardedEarnedReward)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func rewardedAdDidClose(_ rewarded: VungleRewarded) {
|
|
98
|
+
owner?.events.emit(.closed)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
let liftoffErrorDomain = "NitroLiftoffAds"
|
|
5
|
+
|
|
6
|
+
func liftoffError(_ message: String, code: Int = 1) -> NSError {
|
|
7
|
+
NSError(
|
|
8
|
+
domain: liftoffErrorDomain,
|
|
9
|
+
code: code,
|
|
10
|
+
userInfo: [NSLocalizedDescriptionKey: message]
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func runOnMain(_ body: @escaping () -> Void) {
|
|
15
|
+
if Thread.isMainThread {
|
|
16
|
+
body()
|
|
17
|
+
} else {
|
|
18
|
+
DispatchQueue.main.sync(execute: body)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func currentViewController() async throws -> UIViewController {
|
|
23
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
24
|
+
DispatchQueue.main.async {
|
|
25
|
+
let root = UIApplication.shared.connectedScenes
|
|
26
|
+
.compactMap { $0 as? UIWindowScene }
|
|
27
|
+
.flatMap(\.windows)
|
|
28
|
+
.first(where: \.isKeyWindow)?
|
|
29
|
+
.rootViewController
|
|
30
|
+
|
|
31
|
+
guard let root else {
|
|
32
|
+
continuation.resume(
|
|
33
|
+
throwing: liftoffError("Cannot show Liftoff ad without a view controller")
|
|
34
|
+
)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
continuation.resume(returning: topViewController(root))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private func topViewController(_ viewController: UIViewController) -> UIViewController {
|
|
43
|
+
if let presented = viewController.presentedViewController {
|
|
44
|
+
return topViewController(presented)
|
|
45
|
+
}
|
|
46
|
+
if let navigation = viewController as? UINavigationController,
|
|
47
|
+
let visible = navigation.visibleViewController {
|
|
48
|
+
return topViewController(visible)
|
|
49
|
+
}
|
|
50
|
+
if let tab = viewController as? UITabBarController,
|
|
51
|
+
let selected = tab.selectedViewController {
|
|
52
|
+
return topViewController(selected)
|
|
53
|
+
}
|
|
54
|
+
return viewController
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
final class LiftoffAdEventEmitter {
|
|
58
|
+
typealias Listener = (LiftoffAdEventPayload?) -> Void
|
|
59
|
+
|
|
60
|
+
private let lock = NSLock()
|
|
61
|
+
private var listeners: [Double: (eventType: String, listener: Listener)] = [:]
|
|
62
|
+
private var nextSubscriptionId = 1.0
|
|
63
|
+
|
|
64
|
+
func add(eventType: LiftoffAdEventType, listener: @escaping Listener) -> Double {
|
|
65
|
+
lock.lock()
|
|
66
|
+
defer { lock.unlock() }
|
|
67
|
+
|
|
68
|
+
let subscriptionId = nextSubscriptionId
|
|
69
|
+
nextSubscriptionId += 1
|
|
70
|
+
listeners[subscriptionId] = (eventType.stringValue, listener)
|
|
71
|
+
return subscriptionId
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func remove(subscriptionId: Double) {
|
|
75
|
+
lock.lock()
|
|
76
|
+
listeners.removeValue(forKey: subscriptionId)
|
|
77
|
+
lock.unlock()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func removeAll() {
|
|
81
|
+
lock.lock()
|
|
82
|
+
listeners.removeAll()
|
|
83
|
+
lock.unlock()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func emit(
|
|
87
|
+
_ eventType: LiftoffAdEventType,
|
|
88
|
+
error: Error? = nil,
|
|
89
|
+
payload: LiftoffAdEventPayload? = nil
|
|
90
|
+
) {
|
|
91
|
+
lock.lock()
|
|
92
|
+
let callbacks = listeners.values
|
|
93
|
+
.filter { $0.eventType == eventType.stringValue }
|
|
94
|
+
.map(\.listener)
|
|
95
|
+
lock.unlock()
|
|
96
|
+
|
|
97
|
+
let resolvedPayload = payload ?? error.map { error in
|
|
98
|
+
let nsError = error as NSError
|
|
99
|
+
return LiftoffAdEventPayload(
|
|
100
|
+
errorCode: Double(nsError.code),
|
|
101
|
+
errorMessage: nsError.localizedDescription
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
callbacks.forEach { $0(resolvedPayload) }
|
|
105
|
+
}
|
|
106
|
+
}
|