@ezoic/react-native-sdk 1.0.0 → 1.2.0
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/EzoicReactNativeSdk.podspec +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/ezoic/reactnative/EzoicAdsModule.kt +289 -0
- package/ios/EzoicAdsImpl.swift +287 -23
- package/ios/EzoicReactNativeSdk.h +4 -1
- package/ios/EzoicReactNativeSdk.mm +62 -0
- package/lib/module/EzoicInterstitialAd.js +108 -0
- package/lib/module/EzoicInterstitialAd.js.map +1 -0
- package/lib/module/EzoicRewardedAd.js +115 -0
- package/lib/module/EzoicRewardedAd.js.map +1 -0
- package/lib/module/NativeEzoicAds.js +8 -0
- package/lib/module/NativeEzoicAds.js.map +1 -1
- package/lib/module/helpers.js +13 -0
- package/lib/module/helpers.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/EzoicInterstitialAd.d.ts +51 -0
- package/lib/typescript/src/EzoicInterstitialAd.d.ts.map +1 -0
- package/lib/typescript/src/EzoicRewardedAd.d.ts +55 -0
- package/lib/typescript/src/EzoicRewardedAd.d.ts.map +1 -0
- package/lib/typescript/src/NativeEzoicAds.d.ts +17 -0
- package/lib/typescript/src/NativeEzoicAds.d.ts.map +1 -1
- package/lib/typescript/src/helpers.d.ts +13 -0
- package/lib/typescript/src/helpers.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/EzoicInterstitialAd.ts +126 -0
- package/src/EzoicRewardedAd.ts +143 -0
- package/src/NativeEzoicAds.ts +19 -0
- package/src/helpers.ts +19 -0
- package/src/index.tsx +9 -0
|
@@ -20,6 +20,6 @@ Pod::Spec.new do |s|
|
|
|
20
20
|
|
|
21
21
|
# Native Ezoic Ads SDK (vends the `EzoicAdsSDKBinary` module). Brings in
|
|
22
22
|
# PrebidMobile + Google-Mobile-Ads-SDK transitively.
|
|
23
|
-
s.dependency "EzoicAdsSDK", "~> 1.
|
|
23
|
+
s.dependency "EzoicAdsSDK", "~> 1.2"
|
|
24
24
|
s.swift_version = "5.9"
|
|
25
25
|
end
|
package/android/build.gradle
CHANGED
|
@@ -68,5 +68,5 @@ dependencies {
|
|
|
68
68
|
// Native Ezoic Ads SDK (resolved from Maven Central). Brings in Google Mobile
|
|
69
69
|
// Ads + Prebid transitively. Requires mavenCentral() + google() in the
|
|
70
70
|
// consuming app's repositories (the RN template provides both).
|
|
71
|
-
implementation "com.ezoic.sdk:ezoic-ads-sdk:1.
|
|
71
|
+
implementation "com.ezoic.sdk:ezoic-ads-sdk:1.2.0"
|
|
72
72
|
}
|
|
@@ -1,17 +1,56 @@
|
|
|
1
1
|
package com.ezoic.reactnative
|
|
2
2
|
|
|
3
3
|
import android.app.Application
|
|
4
|
+
import com.facebook.react.bridge.Arguments
|
|
4
5
|
import com.facebook.react.bridge.Promise
|
|
5
6
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
7
|
import com.facebook.react.bridge.ReadableMap
|
|
8
|
+
import com.facebook.react.bridge.WritableMap
|
|
9
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
|
+
import com.ezoic.ads.sdk.adunits.EzoicInterstitialAd
|
|
11
|
+
import com.ezoic.ads.sdk.adunits.EzoicInterstitialAdListener
|
|
12
|
+
import com.ezoic.ads.sdk.adunits.EzoicInterstitialAdListenerAdapter
|
|
13
|
+
import com.ezoic.ads.sdk.adunits.EzoicReward
|
|
14
|
+
import com.ezoic.ads.sdk.adunits.EzoicRewardedAd
|
|
15
|
+
import com.ezoic.ads.sdk.adunits.EzoicRewardedAdListener
|
|
16
|
+
import com.ezoic.ads.sdk.adunits.EzoicRewardedAdListenerAdapter
|
|
7
17
|
import com.ezoic.ads.sdk.core.EzoicAds
|
|
8
18
|
import com.ezoic.ads.sdk.core.EzoicConfiguration
|
|
19
|
+
import com.ezoic.ads.sdk.core.EzoicError
|
|
20
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
9
21
|
|
|
10
22
|
class EzoicAdsModule(reactContext: ReactApplicationContext) :
|
|
11
23
|
NativeEzoicAdsSpec(reactContext) {
|
|
12
24
|
|
|
13
25
|
override fun getName() = NAME
|
|
14
26
|
|
|
27
|
+
/** Loaded rewarded ads awaiting `show`, keyed by ad unit id. */
|
|
28
|
+
private val rewardedAds = ConcurrentHashMap<Int, EzoicRewardedAd>()
|
|
29
|
+
|
|
30
|
+
/** In-flight `show` calls, keyed by ad unit id. */
|
|
31
|
+
private val pendingShows = ConcurrentHashMap<Int, RewardShow>()
|
|
32
|
+
|
|
33
|
+
private class RewardShow(val promise: Promise) {
|
|
34
|
+
@Volatile var settled = false
|
|
35
|
+
@Volatile var reward: EzoicReward? = null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Loaded interstitial ads awaiting `show`, keyed by ad unit id. */
|
|
39
|
+
private val interstitialAds = ConcurrentHashMap<Int, EzoicInterstitialAd>()
|
|
40
|
+
|
|
41
|
+
/** In-flight interstitial `show` calls, keyed by ad unit id. */
|
|
42
|
+
private val pendingInterstitialShows = ConcurrentHashMap<Int, InterstitialShow>()
|
|
43
|
+
|
|
44
|
+
private class InterstitialShow(val promise: Promise) {
|
|
45
|
+
@Volatile var settled = false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Ad unit ids with an in-flight rewarded `load`. */
|
|
49
|
+
private val loadingRewarded = ConcurrentHashMap.newKeySet<Int>()
|
|
50
|
+
|
|
51
|
+
/** Ad unit ids with an in-flight interstitial `load`. */
|
|
52
|
+
private val loadingInterstitial = ConcurrentHashMap.newKeySet<Int>()
|
|
53
|
+
|
|
15
54
|
override fun initialize(config: ReadableMap, promise: Promise) {
|
|
16
55
|
val domain = if (config.hasKey("domain")) config.getString("domain") else null
|
|
17
56
|
if (domain.isNullOrEmpty()) {
|
|
@@ -53,10 +92,260 @@ class EzoicAdsModule(reactContext: ReactApplicationContext) :
|
|
|
53
92
|
EzoicAds.instance.trackPageview { success -> promise.resolve(success) }
|
|
54
93
|
}
|
|
55
94
|
|
|
95
|
+
override fun loadRewardedAd(adUnitIdentifier: String, promise: Promise) {
|
|
96
|
+
val id = adUnitIdentifier.toIntOrNull()
|
|
97
|
+
if (id == null) {
|
|
98
|
+
promise.reject("EzoicAds", "Invalid adUnitIdentifier: $adUnitIdentifier")
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
if (rewardedAds.containsKey(id) || !loadingRewarded.add(id)) {
|
|
102
|
+
promise.reject("EzoicAds", "An ad is already loaded/loading for ad unit $adUnitIdentifier")
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
EzoicRewardedAd.load(reactApplicationContext, id) { result ->
|
|
106
|
+
loadingRewarded.remove(id)
|
|
107
|
+
result.onSuccess { ad ->
|
|
108
|
+
ad.listener = makeListener(adUnitIdentifier)
|
|
109
|
+
rewardedAds[id] = ad
|
|
110
|
+
promise.resolve(null)
|
|
111
|
+
}.onFailure { e ->
|
|
112
|
+
promise.reject("EzoicAds", e.message ?: "Rewarded ad failed to load", e)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override fun showRewardedAd(adUnitIdentifier: String, promise: Promise) {
|
|
118
|
+
val id = adUnitIdentifier.toIntOrNull()
|
|
119
|
+
val ad = if (id != null) rewardedAds[id] else null
|
|
120
|
+
if (id == null || ad == null) {
|
|
121
|
+
promise.reject("EzoicAds", "Rewarded ad not loaded for $adUnitIdentifier")
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
if (pendingShows.containsKey(id)) {
|
|
125
|
+
promise.reject("EzoicAds", "A show is already in progress for ad unit $adUnitIdentifier")
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
val activity = currentActivity
|
|
129
|
+
if (activity == null) {
|
|
130
|
+
promise.reject("EzoicAds", "No current Activity to present the rewarded ad")
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
val show = RewardShow(promise)
|
|
135
|
+
pendingShows[id] = show
|
|
136
|
+
|
|
137
|
+
// Replace the load-time listener with one that also settles the promise on
|
|
138
|
+
// terminal events (dismiss = resolve, failed-to-show = reject).
|
|
139
|
+
ad.listener = makeListener(
|
|
140
|
+
adUnitIdentifier,
|
|
141
|
+
onDismiss = {
|
|
142
|
+
rewardedAds.remove(id)
|
|
143
|
+
val pending = pendingShows.remove(id)
|
|
144
|
+
if (pending != null && !pending.settled) {
|
|
145
|
+
pending.settled = true
|
|
146
|
+
val reward = pending.reward
|
|
147
|
+
val map = Arguments.createMap()
|
|
148
|
+
map.putBoolean("earned", reward != null)
|
|
149
|
+
map.putString("type", reward?.type ?: "")
|
|
150
|
+
map.putDouble("amount", (reward?.amount ?: 0).toDouble())
|
|
151
|
+
pending.promise.resolve(map)
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
onFailedToShow = { message ->
|
|
155
|
+
rewardedAds.remove(id)
|
|
156
|
+
val pending = pendingShows.remove(id)
|
|
157
|
+
if (pending != null && !pending.settled) {
|
|
158
|
+
pending.settled = true
|
|
159
|
+
pending.promise.reject("EzoicAds", message)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
activity.runOnUiThread {
|
|
165
|
+
ad.show(activity) { reward -> show.reward = reward }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
override fun loadInterstitialAd(adUnitIdentifier: String, promise: Promise) {
|
|
170
|
+
val id = adUnitIdentifier.toIntOrNull()
|
|
171
|
+
if (id == null) {
|
|
172
|
+
promise.reject("EzoicAds", "Invalid adUnitIdentifier: $adUnitIdentifier")
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
if (interstitialAds.containsKey(id) || !loadingInterstitial.add(id)) {
|
|
176
|
+
promise.reject("EzoicAds", "An ad is already loaded/loading for ad unit $adUnitIdentifier")
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
EzoicInterstitialAd.load(reactApplicationContext, id) { result ->
|
|
180
|
+
loadingInterstitial.remove(id)
|
|
181
|
+
result.onSuccess { ad ->
|
|
182
|
+
ad.listener = makeInterstitialListener(adUnitIdentifier)
|
|
183
|
+
interstitialAds[id] = ad
|
|
184
|
+
promise.resolve(null)
|
|
185
|
+
}.onFailure { e ->
|
|
186
|
+
promise.reject("EzoicAds", e.message ?: "Interstitial ad failed to load", e)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
override fun showInterstitialAd(adUnitIdentifier: String, promise: Promise) {
|
|
192
|
+
val id = adUnitIdentifier.toIntOrNull()
|
|
193
|
+
val ad = if (id != null) interstitialAds[id] else null
|
|
194
|
+
if (id == null || ad == null) {
|
|
195
|
+
promise.reject("EzoicAds", "Interstitial ad not loaded for $adUnitIdentifier")
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
if (pendingInterstitialShows.containsKey(id)) {
|
|
199
|
+
promise.reject("EzoicAds", "A show is already in progress for ad unit $adUnitIdentifier")
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
val activity = currentActivity
|
|
203
|
+
if (activity == null) {
|
|
204
|
+
promise.reject("EzoicAds", "No current Activity to present the interstitial ad")
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
pendingInterstitialShows[id] = InterstitialShow(promise)
|
|
209
|
+
|
|
210
|
+
// Native show(activity) has no completion lambda, so replace the load-time
|
|
211
|
+
// listener with one that settles the promise on terminal events
|
|
212
|
+
// (dismiss = resolve, failed-to-show = reject).
|
|
213
|
+
ad.listener = makeInterstitialListener(
|
|
214
|
+
adUnitIdentifier,
|
|
215
|
+
onDismiss = {
|
|
216
|
+
interstitialAds.remove(id)
|
|
217
|
+
val pending = pendingInterstitialShows.remove(id)
|
|
218
|
+
if (pending != null && !pending.settled) {
|
|
219
|
+
pending.settled = true
|
|
220
|
+
pending.promise.resolve(null)
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
onFailedToShow = { message ->
|
|
224
|
+
interstitialAds.remove(id)
|
|
225
|
+
val pending = pendingInterstitialShows.remove(id)
|
|
226
|
+
if (pending != null && !pending.settled) {
|
|
227
|
+
pending.settled = true
|
|
228
|
+
pending.promise.reject("EzoicAds", message)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
activity.runOnUiThread {
|
|
234
|
+
ad.show(activity)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
override fun addListener(eventName: String) {
|
|
239
|
+
// No-op: required by the React Native NativeEventEmitter contract.
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
override fun removeListeners(count: Double) {
|
|
243
|
+
// No-op: required by the React Native NativeEventEmitter contract.
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private fun makeListener(
|
|
247
|
+
adUnitIdentifier: String,
|
|
248
|
+
onDismiss: (() -> Unit)? = null,
|
|
249
|
+
onFailedToShow: ((String) -> Unit)? = null
|
|
250
|
+
): EzoicRewardedAdListener = object : EzoicRewardedAdListenerAdapter() {
|
|
251
|
+
override fun onRewardedAdShown(rewardedAd: EzoicRewardedAd) {
|
|
252
|
+
emitRewardedEvent(adUnitIdentifier, "shown")
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
override fun onRewardedAdFailedToShow(rewardedAd: EzoicRewardedAd, error: EzoicError) {
|
|
256
|
+
emitRewardedEvent(adUnitIdentifier, "failedToShow") {
|
|
257
|
+
putString("message", error.message)
|
|
258
|
+
putInt("code", error.code)
|
|
259
|
+
}
|
|
260
|
+
onFailedToShow?.invoke(error.message)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
override fun onRewardedAdImpression(rewardedAd: EzoicRewardedAd) {
|
|
264
|
+
emitRewardedEvent(adUnitIdentifier, "impression")
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
override fun onRewardedAdClicked(rewardedAd: EzoicRewardedAd) {
|
|
268
|
+
emitRewardedEvent(adUnitIdentifier, "clicked")
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
override fun onUserEarnedReward(rewardedAd: EzoicRewardedAd, reward: EzoicReward) {
|
|
272
|
+
emitRewardedEvent(adUnitIdentifier, "reward") {
|
|
273
|
+
putString("rewardType", reward.type)
|
|
274
|
+
putDouble("rewardAmount", reward.amount.toDouble())
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
override fun onRewardedAdDismissed(rewardedAd: EzoicRewardedAd) {
|
|
279
|
+
emitRewardedEvent(adUnitIdentifier, "dismissed")
|
|
280
|
+
onDismiss?.invoke()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private fun emitRewardedEvent(
|
|
285
|
+
adUnitIdentifier: String,
|
|
286
|
+
type: String,
|
|
287
|
+
extra: (WritableMap.() -> Unit)? = null
|
|
288
|
+
) {
|
|
289
|
+
val map = Arguments.createMap()
|
|
290
|
+
map.putString("adUnitIdentifier", adUnitIdentifier)
|
|
291
|
+
map.putString("type", type)
|
|
292
|
+
extra?.invoke(map)
|
|
293
|
+
reactApplicationContext
|
|
294
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
295
|
+
.emit(REWARDED_EVENT, map)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private fun makeInterstitialListener(
|
|
299
|
+
adUnitIdentifier: String,
|
|
300
|
+
onDismiss: (() -> Unit)? = null,
|
|
301
|
+
onFailedToShow: ((String) -> Unit)? = null
|
|
302
|
+
): EzoicInterstitialAdListener = object : EzoicInterstitialAdListenerAdapter() {
|
|
303
|
+
override fun onInterstitialAdShown(interstitialAd: EzoicInterstitialAd) {
|
|
304
|
+
emitInterstitialEvent(adUnitIdentifier, "shown")
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
override fun onInterstitialAdFailedToShow(interstitialAd: EzoicInterstitialAd, error: EzoicError) {
|
|
308
|
+
emitInterstitialEvent(adUnitIdentifier, "failedToShow") {
|
|
309
|
+
putString("message", error.message)
|
|
310
|
+
putInt("code", error.code)
|
|
311
|
+
}
|
|
312
|
+
onFailedToShow?.invoke(error.message)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
override fun onInterstitialAdImpression(interstitialAd: EzoicInterstitialAd) {
|
|
316
|
+
emitInterstitialEvent(adUnitIdentifier, "impression")
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
override fun onInterstitialAdClicked(interstitialAd: EzoicInterstitialAd) {
|
|
320
|
+
emitInterstitialEvent(adUnitIdentifier, "clicked")
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
override fun onInterstitialAdDismissed(interstitialAd: EzoicInterstitialAd) {
|
|
324
|
+
emitInterstitialEvent(adUnitIdentifier, "dismissed")
|
|
325
|
+
onDismiss?.invoke()
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private fun emitInterstitialEvent(
|
|
330
|
+
adUnitIdentifier: String,
|
|
331
|
+
type: String,
|
|
332
|
+
extra: (WritableMap.() -> Unit)? = null
|
|
333
|
+
) {
|
|
334
|
+
val map = Arguments.createMap()
|
|
335
|
+
map.putString("adUnitIdentifier", adUnitIdentifier)
|
|
336
|
+
map.putString("type", type)
|
|
337
|
+
extra?.invoke(map)
|
|
338
|
+
reactApplicationContext
|
|
339
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
340
|
+
.emit(INTERSTITIAL_EVENT, map)
|
|
341
|
+
}
|
|
342
|
+
|
|
56
343
|
private fun ReadableMap.optBool(key: String, default: Boolean): Boolean =
|
|
57
344
|
if (hasKey(key) && !isNull(key)) getBoolean(key) else default
|
|
58
345
|
|
|
59
346
|
companion object {
|
|
60
347
|
const val NAME = NativeEzoicAdsSpec.NAME
|
|
348
|
+
private const val REWARDED_EVENT = "EzoicRewardedAdEvent"
|
|
349
|
+
private const val INTERSTITIAL_EVENT = "EzoicInterstitialAdEvent"
|
|
61
350
|
}
|
|
62
351
|
}
|
package/ios/EzoicAdsImpl.swift
CHANGED
|
@@ -3,46 +3,310 @@ import EzoicAdsSDKBinary
|
|
|
3
3
|
|
|
4
4
|
@objc public class EzoicAdsImpl: NSObject {
|
|
5
5
|
|
|
6
|
+
/// Set by the Obj-C module to forward rewarded lifecycle events to JS.
|
|
7
|
+
@objc public var eventEmitter: ((String, [String: Any]) -> Void)?
|
|
8
|
+
|
|
9
|
+
/// Loaded rewarded ads awaiting `show`, keyed by ad unit id.
|
|
10
|
+
private var rewardedAds: [Int: EzoicRewardedAd] = [:]
|
|
11
|
+
|
|
12
|
+
/// In-flight `show` calls, keyed by ad unit id.
|
|
13
|
+
private var pendingShows: [Int: PendingRewardShow] = [:]
|
|
14
|
+
|
|
15
|
+
private final class PendingRewardShow {
|
|
16
|
+
let resolve: (Any?) -> Void
|
|
17
|
+
let reject: (String, String, NSError?) -> Void
|
|
18
|
+
var reward: EzoicReward?
|
|
19
|
+
init(resolve: @escaping (Any?) -> Void, reject: @escaping (String, String, NSError?) -> Void) {
|
|
20
|
+
self.resolve = resolve
|
|
21
|
+
self.reject = reject
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Loaded interstitial ads awaiting `show`, keyed by ad unit id.
|
|
26
|
+
private var interstitialAds: [Int: EzoicInterstitialAd] = [:]
|
|
27
|
+
|
|
28
|
+
/// In-flight interstitial `show` calls, keyed by ad unit id.
|
|
29
|
+
private var pendingInterstitialShows: [Int: PendingInterstitialShow] = [:]
|
|
30
|
+
|
|
31
|
+
private final class PendingInterstitialShow {
|
|
32
|
+
let resolve: (Any?) -> Void
|
|
33
|
+
let reject: (String, String, NSError?) -> Void
|
|
34
|
+
init(resolve: @escaping (Any?) -> Void, reject: @escaping (String, String, NSError?) -> Void) {
|
|
35
|
+
self.resolve = resolve
|
|
36
|
+
self.reject = reject
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Ad unit ids with an in-flight rewarded `load`.
|
|
41
|
+
private var loadingRewarded: Set<Int> = []
|
|
42
|
+
|
|
43
|
+
/// Ad unit ids with an in-flight interstitial `load`.
|
|
44
|
+
private var loadingInterstitial: Set<Int> = []
|
|
45
|
+
|
|
46
|
+
/// Runs `work` on the main thread. The ad/pending/loading dictionaries and
|
|
47
|
+
/// every native load/show call touch UIKit and this shared state, so they must
|
|
48
|
+
/// only run on main. Delegate callbacks already arrive on main.
|
|
49
|
+
private func onMain(_ work: @escaping () -> Void) {
|
|
50
|
+
if Thread.isMainThread {
|
|
51
|
+
work()
|
|
52
|
+
} else {
|
|
53
|
+
DispatchQueue.main.async(execute: work)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
6
57
|
@objc public func initialize(_ config: NSDictionary,
|
|
7
58
|
resolve: @escaping (Any?) -> Void,
|
|
8
59
|
reject: @escaping (String, String, NSError?) -> Void) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
60
|
+
onMain {
|
|
61
|
+
guard let domain = config["domain"] as? String, !domain.isEmpty else {
|
|
62
|
+
reject("EzoicAds", "initialize requires a non-empty `domain`.", nil)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
let configuration = EzoicConfiguration(
|
|
66
|
+
domain: domain,
|
|
67
|
+
autoReadConsent: (config["autoReadConsent"] as? Bool) ?? true,
|
|
68
|
+
subjectToCOPPA: (config["subjectToCOPPA"] as? Bool) ?? false,
|
|
69
|
+
requestATTBeforeAds: (config["requestATTBeforeAds"] as? Bool) ?? true,
|
|
70
|
+
debugEnabled: (config["debugEnabled"] as? Bool) ?? false,
|
|
71
|
+
testMode: (config["testMode"] as? Bool) ?? false
|
|
72
|
+
)
|
|
73
|
+
EzoicAds.shared.initialize(with: configuration) { result in
|
|
74
|
+
switch result {
|
|
75
|
+
case .success:
|
|
76
|
+
resolve(nil)
|
|
77
|
+
case .failure(let error):
|
|
78
|
+
reject("EzoicAds", error.localizedDescription, error as NSError)
|
|
79
|
+
}
|
|
27
80
|
}
|
|
28
81
|
}
|
|
29
82
|
}
|
|
30
83
|
|
|
31
84
|
@objc public func setGDPRConsent(_ applies: Bool, consentString: String?) {
|
|
32
|
-
|
|
85
|
+
onMain {
|
|
86
|
+
EzoicAds.shared.setGDPRConsent(applies: applies, consentString: consentString)
|
|
87
|
+
}
|
|
33
88
|
}
|
|
34
89
|
|
|
35
90
|
@objc public func setGPPConsent(_ gppString: String?, sectionIds: String?) {
|
|
36
|
-
|
|
91
|
+
onMain {
|
|
92
|
+
EzoicAds.shared.setGPPConsent(gppString: gppString, sectionIds: sectionIds)
|
|
93
|
+
}
|
|
37
94
|
}
|
|
38
95
|
|
|
39
96
|
@objc public func setSubjectToCOPPA(_ value: Bool) {
|
|
40
|
-
|
|
97
|
+
onMain {
|
|
98
|
+
EzoicAds.shared.setSubjectToCOPPA(value)
|
|
99
|
+
}
|
|
41
100
|
}
|
|
42
101
|
|
|
43
102
|
@objc public func trackPageview(_ resolve: @escaping (Any?) -> Void) {
|
|
44
|
-
|
|
45
|
-
|
|
103
|
+
onMain {
|
|
104
|
+
EzoicAds.shared.trackPageview { success in
|
|
105
|
+
resolve(NSNumber(value: success))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@objc public func loadRewardedAd(_ adUnitIdentifier: String,
|
|
111
|
+
resolve: @escaping (Any?) -> Void,
|
|
112
|
+
reject: @escaping (String, String, NSError?) -> Void) {
|
|
113
|
+
onMain { [weak self] in
|
|
114
|
+
guard let self = self else { return }
|
|
115
|
+
guard let id = Int(adUnitIdentifier) else {
|
|
116
|
+
reject("EzoicAds", "Invalid adUnitIdentifier: \(adUnitIdentifier)", nil)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
if self.rewardedAds[id] != nil || self.loadingRewarded.contains(id) {
|
|
120
|
+
reject("EzoicAds", "An ad is already loaded/loading for ad unit \(adUnitIdentifier)", nil)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
self.loadingRewarded.insert(id)
|
|
124
|
+
EzoicRewardedAd.load(adUnitIdentifier: id) { [weak self] result in
|
|
125
|
+
guard let self = self else { return }
|
|
126
|
+
self.onMain {
|
|
127
|
+
self.loadingRewarded.remove(id)
|
|
128
|
+
switch result {
|
|
129
|
+
case .success(let ad):
|
|
130
|
+
ad.delegate = self
|
|
131
|
+
self.rewardedAds[id] = ad
|
|
132
|
+
resolve(nil)
|
|
133
|
+
case .failure(let error):
|
|
134
|
+
reject("EzoicAds", error.localizedDescription, error as NSError)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@objc public func showRewardedAd(_ adUnitIdentifier: String,
|
|
142
|
+
resolve: @escaping (Any?) -> Void,
|
|
143
|
+
reject: @escaping (String, String, NSError?) -> Void) {
|
|
144
|
+
onMain { [weak self] in
|
|
145
|
+
guard let self = self else { return }
|
|
146
|
+
guard let id = Int(adUnitIdentifier), let ad = self.rewardedAds[id] else {
|
|
147
|
+
reject("EzoicAds", "Rewarded ad not loaded for \(adUnitIdentifier)", nil)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
if self.pendingShows[id] != nil {
|
|
151
|
+
reject("EzoicAds", "A show is already in progress for ad unit \(adUnitIdentifier)", nil)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
self.pendingShows[id] = PendingRewardShow(resolve: resolve, reject: reject)
|
|
155
|
+
// Presenting from nil lets GMA use the application's top view controller.
|
|
156
|
+
ad.show(from: nil) { [weak self] reward in
|
|
157
|
+
self?.onMain { self?.pendingShows[id]?.reward = reward }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private func emit(_ ad: EzoicRewardedAd, _ type: String, _ extra: [String: Any] = [:]) {
|
|
163
|
+
var body: [String: Any] = [
|
|
164
|
+
"adUnitIdentifier": String(ad.adUnitIdentifier),
|
|
165
|
+
"type": type
|
|
166
|
+
]
|
|
167
|
+
for (key, value) in extra { body[key] = value }
|
|
168
|
+
eventEmitter?("EzoicRewardedAdEvent", body)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@objc public func loadInterstitialAd(_ adUnitIdentifier: String,
|
|
172
|
+
resolve: @escaping (Any?) -> Void,
|
|
173
|
+
reject: @escaping (String, String, NSError?) -> Void) {
|
|
174
|
+
onMain { [weak self] in
|
|
175
|
+
guard let self = self else { return }
|
|
176
|
+
guard let id = Int(adUnitIdentifier) else {
|
|
177
|
+
reject("EzoicAds", "Invalid adUnitIdentifier: \(adUnitIdentifier)", nil)
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
if self.interstitialAds[id] != nil || self.loadingInterstitial.contains(id) {
|
|
181
|
+
reject("EzoicAds", "An ad is already loaded/loading for ad unit \(adUnitIdentifier)", nil)
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
self.loadingInterstitial.insert(id)
|
|
185
|
+
EzoicInterstitialAd.load(adUnitIdentifier: id) { [weak self] result in
|
|
186
|
+
guard let self = self else { return }
|
|
187
|
+
self.onMain {
|
|
188
|
+
self.loadingInterstitial.remove(id)
|
|
189
|
+
switch result {
|
|
190
|
+
case .success(let ad):
|
|
191
|
+
ad.delegate = self
|
|
192
|
+
self.interstitialAds[id] = ad
|
|
193
|
+
resolve(nil)
|
|
194
|
+
case .failure(let error):
|
|
195
|
+
reject("EzoicAds", error.localizedDescription, error as NSError)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@objc public func showInterstitialAd(_ adUnitIdentifier: String,
|
|
203
|
+
resolve: @escaping (Any?) -> Void,
|
|
204
|
+
reject: @escaping (String, String, NSError?) -> Void) {
|
|
205
|
+
onMain { [weak self] in
|
|
206
|
+
guard let self = self else { return }
|
|
207
|
+
guard let id = Int(adUnitIdentifier), let ad = self.interstitialAds[id] else {
|
|
208
|
+
reject("EzoicAds", "Interstitial ad not loaded for \(adUnitIdentifier)", nil)
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
if self.pendingInterstitialShows[id] != nil {
|
|
212
|
+
reject("EzoicAds", "A show is already in progress for ad unit \(adUnitIdentifier)", nil)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
self.pendingInterstitialShows[id] = PendingInterstitialShow(resolve: resolve, reject: reject)
|
|
216
|
+
// Native show(from:) has no completion handler, so the show promise is
|
|
217
|
+
// settled from the delegate (dismiss = resolve, failed-to-present = reject).
|
|
218
|
+
// Presenting from nil lets GMA use the application's top view controller.
|
|
219
|
+
ad.show(from: nil)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private func emitInterstitial(_ ad: EzoicInterstitialAd, _ type: String, _ extra: [String: Any] = [:]) {
|
|
224
|
+
var body: [String: Any] = [
|
|
225
|
+
"adUnitIdentifier": String(ad.adUnitIdentifier),
|
|
226
|
+
"type": type
|
|
227
|
+
]
|
|
228
|
+
for (key, value) in extra { body[key] = value }
|
|
229
|
+
eventEmitter?("EzoicInterstitialAdEvent", body)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// MARK: - EzoicRewardedAdDelegate
|
|
234
|
+
|
|
235
|
+
extension EzoicAdsImpl: EzoicRewardedAdDelegate {
|
|
236
|
+
|
|
237
|
+
public func rewardedAdDidPresent(_ rewardedAd: EzoicRewardedAd) {
|
|
238
|
+
emit(rewardedAd, "shown")
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public func rewardedAd(_ rewardedAd: EzoicRewardedAd, didFailToPresentWithError error: EzoicError) {
|
|
242
|
+
emit(rewardedAd, "failedToShow", ["message": error.localizedDescription, "code": error.code])
|
|
243
|
+
let id = rewardedAd.adUnitIdentifier
|
|
244
|
+
rewardedAds.removeValue(forKey: id)
|
|
245
|
+
if let pending = pendingShows.removeValue(forKey: id) {
|
|
246
|
+
pending.reject("EzoicAds", error.localizedDescription, error as NSError)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public func rewardedAdDidRecordImpression(_ rewardedAd: EzoicRewardedAd) {
|
|
251
|
+
emit(rewardedAd, "impression")
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
public func rewardedAdDidRecordClick(_ rewardedAd: EzoicRewardedAd) {
|
|
255
|
+
emit(rewardedAd, "clicked")
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public func rewardedAd(_ rewardedAd: EzoicRewardedAd, userDidEarn reward: EzoicReward) {
|
|
259
|
+
emit(rewardedAd, "reward", ["rewardType": reward.type, "rewardAmount": reward.amount])
|
|
260
|
+
pendingShows[rewardedAd.adUnitIdentifier]?.reward = reward
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
public func rewardedAdDidDismiss(_ rewardedAd: EzoicRewardedAd) {
|
|
264
|
+
emit(rewardedAd, "dismissed")
|
|
265
|
+
let id = rewardedAd.adUnitIdentifier
|
|
266
|
+
rewardedAds.removeValue(forKey: id)
|
|
267
|
+
if let pending = pendingShows.removeValue(forKey: id) {
|
|
268
|
+
let reward = pending.reward
|
|
269
|
+
let result: [String: Any] = [
|
|
270
|
+
"earned": reward != nil,
|
|
271
|
+
"type": reward?.type ?? "",
|
|
272
|
+
"amount": reward?.amount ?? 0
|
|
273
|
+
]
|
|
274
|
+
pending.resolve(result)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// MARK: - EzoicInterstitialAdDelegate
|
|
280
|
+
|
|
281
|
+
extension EzoicAdsImpl: EzoicInterstitialAdDelegate {
|
|
282
|
+
|
|
283
|
+
public func interstitialAdDidPresent(_ interstitialAd: EzoicInterstitialAd) {
|
|
284
|
+
emitInterstitial(interstitialAd, "shown")
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
public func interstitialAd(_ interstitialAd: EzoicInterstitialAd, didFailToPresentWithError error: EzoicError) {
|
|
288
|
+
emitInterstitial(interstitialAd, "failedToShow", ["message": error.localizedDescription, "code": error.code])
|
|
289
|
+
let id = interstitialAd.adUnitIdentifier
|
|
290
|
+
interstitialAds.removeValue(forKey: id)
|
|
291
|
+
if let pending = pendingInterstitialShows.removeValue(forKey: id) {
|
|
292
|
+
pending.reject("EzoicAds", error.localizedDescription, error as NSError)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
public func interstitialAdDidRecordImpression(_ interstitialAd: EzoicInterstitialAd) {
|
|
297
|
+
emitInterstitial(interstitialAd, "impression")
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public func interstitialAdDidRecordClick(_ interstitialAd: EzoicInterstitialAd) {
|
|
301
|
+
emitInterstitial(interstitialAd, "clicked")
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
public func interstitialAdDidDismiss(_ interstitialAd: EzoicInterstitialAd) {
|
|
305
|
+
emitInterstitial(interstitialAd, "dismissed")
|
|
306
|
+
let id = interstitialAd.adUnitIdentifier
|
|
307
|
+
interstitialAds.removeValue(forKey: id)
|
|
308
|
+
if let pending = pendingInterstitialShows.removeValue(forKey: id) {
|
|
309
|
+
pending.resolve(nil)
|
|
46
310
|
}
|
|
47
311
|
}
|
|
48
312
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#import <EzoicReactNativeSdkSpec/EzoicReactNativeSdkSpec.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// Subclasses RCTEventEmitter so the rewarded ad lifecycle can be surfaced to
|
|
5
|
+
// JS via NativeEventEmitter, while still vending the codegen'd TurboModule.
|
|
6
|
+
@interface EzoicReactNativeSdk : RCTEventEmitter <NativeEzoicAdsSpec>
|
|
4
7
|
|
|
5
8
|
@end
|