@bigcrunch/react-native-ads 0.11.0 → 0.14.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/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchAds.kt +21 -4
- package/android/bigcrunch-ads/com/bigcrunch/ads/adapters/GoogleAdsAdapter.kt +8 -1
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/AdOrchestrator.kt +37 -12
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/AnalyticsClient.kt +213 -46
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/BidRequestClient.kt +52 -17
- package/android/bigcrunch-ads/com/bigcrunch/ads/core/DeviceContext.kt +1 -1
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/AdEvent.kt +81 -2
- package/android/bigcrunch-ads/com/bigcrunch/ads/models/PlacementConfig.kt +4 -1
- package/android/src/main/java/com/bigcrunch/ads/react/BigCrunchAdsModule.kt +57 -2
- package/ios/BigCrunchAds/Sources/Adapters/GoogleAdsAdapter.swift +7 -0
- package/ios/BigCrunchAds/Sources/BigCrunchAds.swift +29 -6
- package/ios/BigCrunchAds/Sources/BigCrunchRewarded.swift +10 -2
- package/ios/BigCrunchAds/Sources/Core/AdOrchestrator.swift +20 -4
- package/ios/BigCrunchAds/Sources/Core/AnalyticsClient.swift +193 -84
- package/ios/BigCrunchAds/Sources/Core/BidRequestClient.swift +54 -17
- package/ios/BigCrunchAds/Sources/Core/DeviceContext.swift +1 -1
- package/ios/BigCrunchAds/Sources/Models/AdEvent.swift +111 -3
- package/ios/BigCrunchAds/Sources/Models/PlacementConfig.swift +4 -1
- package/ios/BigCrunchAdsModule.m +6 -0
- package/ios/BigCrunchAdsModule.swift +39 -2
- package/ios/BigCrunchBannerViewManager.swift +1 -0
- package/lib/BigCrunchAds.d.ts +33 -2
- package/lib/BigCrunchAds.d.ts.map +1 -1
- package/lib/BigCrunchAds.js +35 -2
- package/lib/NativeBigCrunchAds.d.ts +3 -2
- package/lib/NativeBigCrunchAds.d.ts.map +1 -1
- package/lib/types/index.d.ts +40 -0
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-bigcrunch-ads.podspec +1 -1
- package/src/BigCrunchAds.ts +41 -2
- package/src/NativeBigCrunchAds.ts +5 -2
- package/src/types/index.ts +43 -0
|
@@ -12,6 +12,7 @@ import com.bigcrunch.ads.internal.PrivacyStore
|
|
|
12
12
|
import com.bigcrunch.ads.internal.SharedPreferencesStore
|
|
13
13
|
import com.bigcrunch.ads.models.AppConfig
|
|
14
14
|
import com.bigcrunch.ads.models.DeviceData
|
|
15
|
+
import com.bigcrunch.ads.models.ScreenViewOptions
|
|
15
16
|
import com.bigcrunch.ads.models.SessionInfo
|
|
16
17
|
import com.google.android.gms.ads.MobileAds
|
|
17
18
|
import com.google.android.gms.ads.RequestConfiguration
|
|
@@ -231,13 +232,14 @@ object BigCrunchAds {
|
|
|
231
232
|
* Track screen view for analytics
|
|
232
233
|
*
|
|
233
234
|
* @param screenName Name of the screen being viewed
|
|
235
|
+
* @param options Optional overrides for page URL, content metadata, and custom dimensions
|
|
234
236
|
*/
|
|
235
|
-
fun trackScreen(screenName: String) = runSafely {
|
|
237
|
+
fun trackScreen(screenName: String, options: ScreenViewOptions? = null) = runSafely {
|
|
236
238
|
if (!initialized) {
|
|
237
239
|
BCLogger.w(TAG, "trackScreen called before initialization, ignoring")
|
|
238
240
|
return@runSafely
|
|
239
241
|
}
|
|
240
|
-
analyticsClient.trackScreenView(screenName)
|
|
242
|
+
analyticsClient.trackScreenView(screenName, options)
|
|
241
243
|
SessionManager.incrementScreenViewCount()
|
|
242
244
|
}
|
|
243
245
|
|
|
@@ -245,9 +247,10 @@ object BigCrunchAds {
|
|
|
245
247
|
* Track screen view for analytics (alias for React Native compatibility)
|
|
246
248
|
*
|
|
247
249
|
* @param screenName Name of the screen being viewed
|
|
250
|
+
* @param options Optional overrides for page URL, content metadata, and custom dimensions
|
|
248
251
|
*/
|
|
249
|
-
fun trackScreenView(screenName: String) = runSafely {
|
|
250
|
-
trackScreen(screenName)
|
|
252
|
+
fun trackScreenView(screenName: String, options: ScreenViewOptions? = null) = runSafely {
|
|
253
|
+
trackScreen(screenName, options)
|
|
251
254
|
}
|
|
252
255
|
|
|
253
256
|
/**
|
|
@@ -338,6 +341,20 @@ object BigCrunchAds {
|
|
|
338
341
|
DeviceHelper.getDeviceData(appContext)
|
|
339
342
|
}
|
|
340
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Set the account type for the current user
|
|
346
|
+
*
|
|
347
|
+
* Included in all analytics events. Defaults to "guest" if not set.
|
|
348
|
+
* Valid values: "guest", "logged_in", "paid", "subscriber", "free"
|
|
349
|
+
*
|
|
350
|
+
* @param accountType The user's account type
|
|
351
|
+
*/
|
|
352
|
+
fun setAccountType(accountType: String) = runSafely {
|
|
353
|
+
requireInitialized()
|
|
354
|
+
analyticsClient.setAccountType(accountType)
|
|
355
|
+
BCLogger.d(TAG, "Account type set to: $accountType")
|
|
356
|
+
}
|
|
357
|
+
|
|
341
358
|
/**
|
|
342
359
|
* Set GDPR consent string for privacy compliance
|
|
343
360
|
*
|
|
@@ -230,12 +230,17 @@ internal class GoogleAdsAdapter(
|
|
|
230
230
|
|
|
231
231
|
// Extract GAM metadata from ResponseInfo
|
|
232
232
|
val responseInfo = adView.responseInfo
|
|
233
|
-
val advertiserId = responseInfo?.loadedAdapterResponseInfo?.adSourceId
|
|
234
233
|
val adMetadata = extractAdMetadata(responseInfo)
|
|
235
234
|
|
|
235
|
+
// Extract ad size from the loaded banner
|
|
236
|
+
val loadedAdSize = adView.adSize
|
|
237
|
+
val adSizeString = if (loadedAdSize != null) "${loadedAdSize.width}x${loadedAdSize.height}" else ""
|
|
238
|
+
|
|
236
239
|
analyticsClient.trackAdImpression(
|
|
237
240
|
placementId = placementConfig.placementId,
|
|
238
241
|
format = placementConfig.format,
|
|
242
|
+
gamAdUnit = placementConfig.gamAdUnit,
|
|
243
|
+
adSize = adSizeString,
|
|
239
244
|
advertiserId = adMetadata["advertiser_id"] as? String,
|
|
240
245
|
campaignId = adMetadata["campaign_id"] as? String,
|
|
241
246
|
lineItemId = adMetadata["line_item_id"] as? String,
|
|
@@ -394,6 +399,7 @@ internal class GoogleAdsAdapter(
|
|
|
394
399
|
analyticsClient.trackAdImpression(
|
|
395
400
|
placementId = placementConfig.placementId,
|
|
396
401
|
format = placementConfig.format,
|
|
402
|
+
gamAdUnit = placementConfig.gamAdUnit,
|
|
397
403
|
advertiserId = adMetadata["advertiser_id"] as? String,
|
|
398
404
|
campaignId = adMetadata["campaign_id"] as? String,
|
|
399
405
|
lineItemId = adMetadata["line_item_id"] as? String,
|
|
@@ -501,6 +507,7 @@ internal class GoogleAdsAdapter(
|
|
|
501
507
|
analyticsClient.trackAdImpression(
|
|
502
508
|
placementId = placementConfig.placementId,
|
|
503
509
|
format = placementConfig.format,
|
|
510
|
+
gamAdUnit = placementConfig.gamAdUnit,
|
|
504
511
|
advertiserId = adMetadata["advertiser_id"] as? String,
|
|
505
512
|
campaignId = adMetadata["campaign_id"] as? String,
|
|
506
513
|
lineItemId = adMetadata["line_item_id"] as? String,
|
|
@@ -8,6 +8,7 @@ import com.bigcrunch.ads.adapters.GoogleAdsAdapter
|
|
|
8
8
|
import com.bigcrunch.ads.adapters.InterstitialAdCallback
|
|
9
9
|
import com.bigcrunch.ads.adapters.RewardedAdCallback
|
|
10
10
|
import com.bigcrunch.ads.internal.BCLogger
|
|
11
|
+
import com.bigcrunch.ads.models.AuctionData
|
|
11
12
|
import com.bigcrunch.ads.models.PlacementConfig
|
|
12
13
|
import com.google.android.gms.ads.admanager.AdManagerAdRequest
|
|
13
14
|
import com.google.android.gms.ads.admanager.AdManagerAdView
|
|
@@ -164,16 +165,24 @@ internal class AdOrchestrator(
|
|
|
164
165
|
val adRequestBuilder = AdManagerAdRequest.Builder()
|
|
165
166
|
|
|
166
167
|
// 3. Fetch S2S demand (even if it fails, continue with Google)
|
|
167
|
-
val
|
|
168
|
-
if (
|
|
169
|
-
for ((key, value) in targeting) {
|
|
168
|
+
val bidResponse = bidRequestClient.fetchDemand(placement)
|
|
169
|
+
if (bidResponse != null && bidResponse.targeting.isNotEmpty()) {
|
|
170
|
+
for ((key, value) in bidResponse.targeting) {
|
|
170
171
|
adRequestBuilder.addCustomTargeting(key, value)
|
|
171
172
|
}
|
|
172
|
-
BCLogger.d(TAG, "S2S demand fetched for: ${placement.placementId} (${targeting.size} keys)")
|
|
173
|
+
BCLogger.d(TAG, "S2S demand fetched for: ${placement.placementId} (${bidResponse.targeting.size} keys)")
|
|
173
174
|
} else {
|
|
174
175
|
BCLogger.w(TAG, "S2S demand fetch returned no targeting, continuing with Google only")
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
// Store auction data for analytics (or default to GAM if no S2S)
|
|
179
|
+
val auctionData = bidResponse?.auctionData ?: AuctionData(
|
|
180
|
+
bidder = "google_ad_exchange",
|
|
181
|
+
floorPrice = placement.floorPrice,
|
|
182
|
+
demandChannel = "Google Ad Exchange"
|
|
183
|
+
)
|
|
184
|
+
analyticsClient.setAuctionData(placement.placementId, auctionData)
|
|
185
|
+
|
|
177
186
|
// 4. Load Google ad with the (possibly enriched) ad request
|
|
178
187
|
val adRequest = adRequestBuilder.build()
|
|
179
188
|
val adapterCallback = object : BannerAdCallback {
|
|
@@ -292,16 +301,24 @@ internal class AdOrchestrator(
|
|
|
292
301
|
val adRequestBuilder = AdManagerAdRequest.Builder()
|
|
293
302
|
|
|
294
303
|
// 3. Fetch S2S demand
|
|
295
|
-
val
|
|
296
|
-
if (
|
|
297
|
-
for ((key, value) in targeting) {
|
|
304
|
+
val bidResponse = bidRequestClient.fetchDemand(placement)
|
|
305
|
+
if (bidResponse != null && bidResponse.targeting.isNotEmpty()) {
|
|
306
|
+
for ((key, value) in bidResponse.targeting) {
|
|
298
307
|
adRequestBuilder.addCustomTargeting(key, value)
|
|
299
308
|
}
|
|
300
|
-
BCLogger.d(TAG, "S2S demand fetched for: ${placement.placementId} (${targeting.size} keys)")
|
|
309
|
+
BCLogger.d(TAG, "S2S demand fetched for: ${placement.placementId} (${bidResponse.targeting.size} keys)")
|
|
301
310
|
} else {
|
|
302
311
|
BCLogger.w(TAG, "S2S demand fetch returned no targeting, continuing with Google only")
|
|
303
312
|
}
|
|
304
313
|
|
|
314
|
+
// Store auction data for analytics (or default to GAM if no S2S)
|
|
315
|
+
val auctionData = bidResponse?.auctionData ?: AuctionData(
|
|
316
|
+
bidder = "google_ad_exchange",
|
|
317
|
+
floorPrice = placement.floorPrice,
|
|
318
|
+
demandChannel = "Google Ad Exchange"
|
|
319
|
+
)
|
|
320
|
+
analyticsClient.setAuctionData(placement.placementId, auctionData)
|
|
321
|
+
|
|
305
322
|
// Get test ads flag from config
|
|
306
323
|
val useTestAds = configManager.shouldUseTestAds()
|
|
307
324
|
|
|
@@ -466,16 +483,24 @@ internal class AdOrchestrator(
|
|
|
466
483
|
val adRequestBuilder = AdManagerAdRequest.Builder()
|
|
467
484
|
|
|
468
485
|
// 3. Fetch S2S demand
|
|
469
|
-
val
|
|
470
|
-
if (
|
|
471
|
-
for ((key, value) in targeting) {
|
|
486
|
+
val bidResponse = bidRequestClient.fetchDemand(placement)
|
|
487
|
+
if (bidResponse != null && bidResponse.targeting.isNotEmpty()) {
|
|
488
|
+
for ((key, value) in bidResponse.targeting) {
|
|
472
489
|
adRequestBuilder.addCustomTargeting(key, value)
|
|
473
490
|
}
|
|
474
|
-
BCLogger.d(TAG, "S2S demand fetched for: ${placement.placementId} (${targeting.size} keys)")
|
|
491
|
+
BCLogger.d(TAG, "S2S demand fetched for: ${placement.placementId} (${bidResponse.targeting.size} keys)")
|
|
475
492
|
} else {
|
|
476
493
|
BCLogger.w(TAG, "S2S demand fetch returned no targeting, continuing with Google only")
|
|
477
494
|
}
|
|
478
495
|
|
|
496
|
+
// Store auction data for analytics (or default to GAM if no S2S)
|
|
497
|
+
val auctionData = bidResponse?.auctionData ?: AuctionData(
|
|
498
|
+
bidder = "google_ad_exchange",
|
|
499
|
+
floorPrice = placement.floorPrice,
|
|
500
|
+
demandChannel = "Google Ad Exchange"
|
|
501
|
+
)
|
|
502
|
+
analyticsClient.setAuctionData(placement.placementId, auctionData)
|
|
503
|
+
|
|
479
504
|
// Get test ads flag from config
|
|
480
505
|
val useTestAds = configManager.shouldUseTestAds()
|
|
481
506
|
|
|
@@ -50,16 +50,37 @@ internal class AnalyticsClient(
|
|
|
50
50
|
/** Track impression contexts for correlating events */
|
|
51
51
|
private val impressionContexts = ConcurrentHashMap<String, ImpressionContext>()
|
|
52
52
|
|
|
53
|
+
/** Per-placement auction data from S2S responses (set by AdOrchestrator, consumed by trackAdImpression) */
|
|
54
|
+
private val placementAuctionData = ConcurrentHashMap<String, AuctionData>()
|
|
55
|
+
|
|
56
|
+
/** Batching for impression events */
|
|
57
|
+
private val impressionBatch = mutableListOf<ImpressionBatchEvent>()
|
|
58
|
+
private var impressionBatchHandler: android.os.Handler? = null
|
|
59
|
+
private val impressionBatchLock = Any()
|
|
60
|
+
|
|
53
61
|
/** Batching for viewability events */
|
|
54
62
|
private val viewabilityBatch = mutableListOf<ViewabilityEvent>()
|
|
55
63
|
private var viewabilityBatchHandler: android.os.Handler? = null
|
|
56
|
-
private val
|
|
64
|
+
private val viewabilityBatchLock = Any()
|
|
65
|
+
|
|
57
66
|
private val BATCH_DELAY_MS = 250L
|
|
58
67
|
|
|
59
68
|
/** Previous screen name for referrer tracking */
|
|
60
69
|
@Volatile
|
|
61
70
|
private var previousScreenName: String? = null
|
|
62
71
|
|
|
72
|
+
/** Current page URL (set by trackScreenView, used by all events) */
|
|
73
|
+
@Volatile
|
|
74
|
+
private var currentPageUrl: String = ""
|
|
75
|
+
|
|
76
|
+
/** Current custom dimensions (set by trackScreenView, used by all events) */
|
|
77
|
+
@Volatile
|
|
78
|
+
private var currentCustomDimensions: Map<String, String> = emptyMap()
|
|
79
|
+
|
|
80
|
+
/** Current account type (set by setAccountType, used by all events) */
|
|
81
|
+
@Volatile
|
|
82
|
+
private var currentAcctType: String = "guest"
|
|
83
|
+
|
|
63
84
|
// MARK: - Session Context
|
|
64
85
|
|
|
65
86
|
private val sessionManager: SessionManager
|
|
@@ -177,15 +198,37 @@ internal class AnalyticsClient(
|
|
|
177
198
|
|
|
178
199
|
// MARK: - Page View Tracking
|
|
179
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Generate the default page URL from screen name
|
|
203
|
+
*/
|
|
204
|
+
private fun generatePageUrl(screenName: String): String {
|
|
205
|
+
val appName = try {
|
|
206
|
+
BigCrunchAds.getAppConfig()?.appName ?: "app"
|
|
207
|
+
} catch (e: Exception) {
|
|
208
|
+
"app"
|
|
209
|
+
}
|
|
210
|
+
val sanitizedAppName = appName.lowercase()
|
|
211
|
+
.replace(" ", "-")
|
|
212
|
+
.let { java.net.URLEncoder.encode(it, "UTF-8") }
|
|
213
|
+
val encodedScreen = java.net.URLEncoder.encode(screenName, "UTF-8")
|
|
214
|
+
return "https://$sanitizedAppName.mobile.app/$encodedScreen"
|
|
215
|
+
}
|
|
216
|
+
|
|
180
217
|
/**
|
|
181
218
|
* Track a screen view
|
|
182
219
|
*
|
|
183
220
|
* @param screenName Name of the screen being viewed
|
|
221
|
+
* @param options Optional overrides for page URL, metadata, and custom dimensions
|
|
184
222
|
*/
|
|
185
|
-
fun trackScreenView(screenName: String) {
|
|
223
|
+
fun trackScreenView(screenName: String, options: ScreenViewOptions? = null) {
|
|
186
224
|
// Start new page view and get IDs (pass screen name for impression tracking)
|
|
187
225
|
val pageId = sessionManager.startPageView(screenName)
|
|
188
226
|
|
|
227
|
+
// Resolve page URL: explicit override > auto-generated
|
|
228
|
+
val pageUrl = options?.pageUrl ?: generatePageUrl(screenName)
|
|
229
|
+
currentPageUrl = pageUrl
|
|
230
|
+
currentCustomDimensions = options?.customDimensions ?: emptyMap()
|
|
231
|
+
|
|
189
232
|
// Get common event fields
|
|
190
233
|
val common = getCommonEventFields()
|
|
191
234
|
|
|
@@ -201,7 +244,7 @@ internal class AnalyticsClient(
|
|
|
201
244
|
|
|
202
245
|
val event = PageViewEvent(
|
|
203
246
|
payloadVersion = "1.0.0",
|
|
204
|
-
configVersion = 1,
|
|
247
|
+
configVersion = 1,
|
|
205
248
|
browserTimestamp = sessionManager.getCurrentTimestamp(),
|
|
206
249
|
sessionId = sessionManager.sessionId,
|
|
207
250
|
userId = sessionManager.userId,
|
|
@@ -209,9 +252,9 @@ internal class AnalyticsClient(
|
|
|
209
252
|
newUser = sessionManager.isNewUser,
|
|
210
253
|
pageId = pageId,
|
|
211
254
|
sessionDepth = sessionManager.sessionDepth,
|
|
212
|
-
pageUrl =
|
|
213
|
-
pageSearch = "",
|
|
214
|
-
pageReferrer = "",
|
|
255
|
+
pageUrl = pageUrl,
|
|
256
|
+
pageSearch = "",
|
|
257
|
+
pageReferrer = "",
|
|
215
258
|
browser = common.browser,
|
|
216
259
|
device = common.device,
|
|
217
260
|
os = common.os,
|
|
@@ -226,11 +269,12 @@ internal class AnalyticsClient(
|
|
|
226
269
|
utmContent = common.utmContent ?: "",
|
|
227
270
|
gclid = "",
|
|
228
271
|
fbclid = "",
|
|
229
|
-
acctType =
|
|
272
|
+
acctType = currentAcctType,
|
|
230
273
|
diiSource = "",
|
|
231
274
|
gamNetworkCode = gamNetworkCode,
|
|
232
|
-
amznPubId = NIL_UUID,
|
|
233
|
-
customDimensions =
|
|
275
|
+
amznPubId = NIL_UUID,
|
|
276
|
+
customDimensions = currentCustomDimensions,
|
|
277
|
+
pageMetaData = options?.pageMeta
|
|
234
278
|
)
|
|
235
279
|
|
|
236
280
|
sendEvent(event, pageViewAdapter, "pageviews")
|
|
@@ -286,6 +330,36 @@ internal class AnalyticsClient(
|
|
|
286
330
|
return impressionContexts[impressionId]
|
|
287
331
|
}
|
|
288
332
|
|
|
333
|
+
// MARK: - Auction Data Storage
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Store auction data for a placement (called by AdOrchestrator after S2S demand fetch)
|
|
337
|
+
*/
|
|
338
|
+
fun setAccountType(accountType: String) {
|
|
339
|
+
currentAcctType = accountType
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fun setAuctionData(placementId: String, auctionData: AuctionData) {
|
|
343
|
+
placementAuctionData[placementId] = auctionData
|
|
344
|
+
BCLogger.d(TAG, "Stored auction data for placement: $placementId (channel: ${auctionData.demandChannel ?: "unknown"})")
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Consume auction data for a placement (returns and removes stored data)
|
|
349
|
+
*/
|
|
350
|
+
private fun consumeAuctionData(placementId: String): AuctionData? {
|
|
351
|
+
return placementAuctionData.remove(placementId)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Compute min_bid_to_win: $0.01 above the higher of second-highest bid or floor price
|
|
356
|
+
*/
|
|
357
|
+
private fun computeMinBidToWin(auctionData: AuctionData?): Double {
|
|
358
|
+
if (auctionData == null) return 0.0
|
|
359
|
+
val baseline = maxOf(auctionData.secondHighestBid ?: 0.0, auctionData.floorPrice ?: 0.0)
|
|
360
|
+
return if (baseline > 0) baseline + 0.01 else 0.0
|
|
361
|
+
}
|
|
362
|
+
|
|
289
363
|
// MARK: - Enhanced Impression Tracking
|
|
290
364
|
|
|
291
365
|
/**
|
|
@@ -316,16 +390,25 @@ internal class AnalyticsClient(
|
|
|
316
390
|
val auctionId = context.auctionData.auctionId?.takeIf { it.isNotEmpty() }
|
|
317
391
|
?: UUID.randomUUID().toString()
|
|
318
392
|
|
|
319
|
-
//
|
|
393
|
+
// Look up stored auction data (may have been set by AdOrchestrator)
|
|
394
|
+
val storedAuctionData = consumeAuctionData(context.placementId)
|
|
395
|
+
val effectiveAuctionData = storedAuctionData ?: context.auctionData
|
|
396
|
+
|
|
397
|
+
// Create impression record with auction data fields populated
|
|
320
398
|
val impressionRecord = ImpressionRecord(
|
|
321
399
|
slotId = context.placementId,
|
|
322
400
|
gamUnit = context.gamAdUnit,
|
|
401
|
+
gamPriceBucket = effectiveAuctionData.gamPriceBucket ?: "",
|
|
323
402
|
impressionId = context.impressionId,
|
|
324
403
|
auctionId = auctionId,
|
|
325
|
-
adBidder =
|
|
404
|
+
adBidder = effectiveAuctionData.bidder ?: "",
|
|
326
405
|
adSize = adSize,
|
|
327
|
-
adPrice =
|
|
328
|
-
|
|
406
|
+
adPrice = effectiveAuctionData.bidPriceCpm ?: 0.0,
|
|
407
|
+
adFloorPrice = effectiveAuctionData.floorPrice ?: 0.0,
|
|
408
|
+
minBidToWin = computeMinBidToWin(effectiveAuctionData),
|
|
409
|
+
creativeId = effectiveAuctionData.creativeId ?: "",
|
|
410
|
+
adDemandType = context.format,
|
|
411
|
+
demandChannel = effectiveAuctionData.demandChannel ?: "Google Ad Exchange"
|
|
329
412
|
)
|
|
330
413
|
|
|
331
414
|
// region must be 2 chars or empty per backend validation
|
|
@@ -342,7 +425,7 @@ internal class AnalyticsClient(
|
|
|
342
425
|
newUser = sessionManager.isNewUser,
|
|
343
426
|
pageId = sessionManager.getOrCreatePageId(),
|
|
344
427
|
sessionDepth = sessionManager.sessionDepth,
|
|
345
|
-
pageUrl =
|
|
428
|
+
pageUrl = currentPageUrl,
|
|
346
429
|
pageSearch = "",
|
|
347
430
|
pageReferrer = "",
|
|
348
431
|
browser = common.browser,
|
|
@@ -359,24 +442,25 @@ internal class AnalyticsClient(
|
|
|
359
442
|
utmContent = common.utmContent ?: "",
|
|
360
443
|
gclid = "",
|
|
361
444
|
fbclid = "",
|
|
362
|
-
acctType =
|
|
445
|
+
acctType = currentAcctType,
|
|
363
446
|
diiSource = "",
|
|
364
447
|
gamNetworkCode = getGamNetworkCode(),
|
|
365
|
-
amznPubId = NIL_UUID,
|
|
366
|
-
customDimensions =
|
|
448
|
+
amznPubId = NIL_UUID,
|
|
449
|
+
customDimensions = currentCustomDimensions,
|
|
367
450
|
impressions = listOf(impressionRecord)
|
|
368
451
|
)
|
|
369
452
|
|
|
370
|
-
|
|
453
|
+
batchImpressionEvent(batchEvent)
|
|
371
454
|
}
|
|
372
455
|
|
|
373
456
|
/**
|
|
374
|
-
* Track an ad impression (simple version
|
|
457
|
+
* Track an ad impression (simple version with auction data lookup)
|
|
375
458
|
*/
|
|
376
459
|
fun trackAdImpression(
|
|
377
460
|
placementId: String,
|
|
378
461
|
format: String,
|
|
379
462
|
gamAdUnit: String = "",
|
|
463
|
+
adSize: String = "",
|
|
380
464
|
advertiserId: String? = null,
|
|
381
465
|
campaignId: String? = null,
|
|
382
466
|
lineItemId: String? = null,
|
|
@@ -389,19 +473,31 @@ internal class AnalyticsClient(
|
|
|
389
473
|
// Get common event fields
|
|
390
474
|
val common = getCommonEventFields()
|
|
391
475
|
|
|
392
|
-
//
|
|
393
|
-
val
|
|
476
|
+
// Look up stored auction data from S2S response
|
|
477
|
+
val auctionData = consumeAuctionData(placementId)
|
|
394
478
|
|
|
395
|
-
//
|
|
479
|
+
// auction_id: use S2S auction ID if available, otherwise generate one
|
|
480
|
+
val auctionId = auctionData?.auctionId?.takeIf { it.isNotEmpty() }
|
|
481
|
+
?: UUID.randomUUID().toString()
|
|
482
|
+
|
|
483
|
+
// Create impression record with auction data fields populated
|
|
396
484
|
val impressionRecord = ImpressionRecord(
|
|
397
485
|
slotId = placementId,
|
|
398
486
|
gamUnit = gamAdUnit,
|
|
487
|
+
gamPriceBucket = auctionData?.gamPriceBucket ?: "",
|
|
399
488
|
impressionId = UUID.randomUUID().toString(),
|
|
400
489
|
auctionId = auctionId,
|
|
490
|
+
adBidder = auctionData?.bidder ?: "",
|
|
491
|
+
adSize = adSize,
|
|
492
|
+
adPrice = auctionData?.bidPriceCpm ?: 0.0,
|
|
493
|
+
adFloorPrice = auctionData?.floorPrice ?: 0.0,
|
|
494
|
+
minBidToWin = computeMinBidToWin(auctionData),
|
|
401
495
|
advertiserId = advertiserId ?: "",
|
|
402
496
|
campaignId = campaignId ?: "",
|
|
403
497
|
lineItemId = lineItemId ?: "",
|
|
404
|
-
creativeId = creativeId ?: "",
|
|
498
|
+
creativeId = auctionData?.creativeId ?: creativeId ?: "",
|
|
499
|
+
adDemandType = format,
|
|
500
|
+
demandChannel = auctionData?.demandChannel ?: "Google Ad Exchange",
|
|
405
501
|
refreshCount = refreshCount
|
|
406
502
|
)
|
|
407
503
|
|
|
@@ -419,7 +515,7 @@ internal class AnalyticsClient(
|
|
|
419
515
|
newUser = sessionManager.isNewUser,
|
|
420
516
|
pageId = sessionManager.getOrCreatePageId(),
|
|
421
517
|
sessionDepth = sessionManager.sessionDepth,
|
|
422
|
-
pageUrl =
|
|
518
|
+
pageUrl = currentPageUrl,
|
|
423
519
|
pageSearch = "",
|
|
424
520
|
pageReferrer = "",
|
|
425
521
|
browser = common.browser,
|
|
@@ -436,15 +532,15 @@ internal class AnalyticsClient(
|
|
|
436
532
|
utmContent = common.utmContent ?: "",
|
|
437
533
|
gclid = "",
|
|
438
534
|
fbclid = "",
|
|
439
|
-
acctType =
|
|
535
|
+
acctType = currentAcctType,
|
|
440
536
|
diiSource = "",
|
|
441
537
|
gamNetworkCode = getGamNetworkCode(),
|
|
442
|
-
amznPubId = NIL_UUID,
|
|
443
|
-
customDimensions =
|
|
538
|
+
amznPubId = NIL_UUID,
|
|
539
|
+
customDimensions = currentCustomDimensions,
|
|
444
540
|
impressions = listOf(impressionRecord)
|
|
445
541
|
)
|
|
446
542
|
|
|
447
|
-
|
|
543
|
+
batchImpressionEvent(batchEvent)
|
|
448
544
|
}
|
|
449
545
|
|
|
450
546
|
// MARK: - Click Tracking
|
|
@@ -480,7 +576,7 @@ internal class AnalyticsClient(
|
|
|
480
576
|
newUser = sessionManager.isNewUser,
|
|
481
577
|
pageId = sessionManager.getOrCreatePageId(),
|
|
482
578
|
sessionDepth = sessionManager.sessionDepth,
|
|
483
|
-
pageUrl =
|
|
579
|
+
pageUrl = currentPageUrl,
|
|
484
580
|
pageSearch = "",
|
|
485
581
|
pageReferrer = "",
|
|
486
582
|
browser = common.browser,
|
|
@@ -497,11 +593,11 @@ internal class AnalyticsClient(
|
|
|
497
593
|
utmContent = common.utmContent ?: "",
|
|
498
594
|
gclid = "",
|
|
499
595
|
fbclid = "",
|
|
500
|
-
acctType =
|
|
596
|
+
acctType = currentAcctType,
|
|
501
597
|
diiSource = "",
|
|
502
598
|
gamNetworkCode = getGamNetworkCode(),
|
|
503
|
-
amznPubId = NIL_UUID,
|
|
504
|
-
customDimensions =
|
|
599
|
+
amznPubId = NIL_UUID,
|
|
600
|
+
customDimensions = currentCustomDimensions,
|
|
505
601
|
click = clickData
|
|
506
602
|
)
|
|
507
603
|
|
|
@@ -555,7 +651,7 @@ internal class AnalyticsClient(
|
|
|
555
651
|
newUser = sessionManager.isNewUser,
|
|
556
652
|
pageId = sessionManager.getOrCreatePageId(),
|
|
557
653
|
sessionDepth = sessionManager.sessionDepth,
|
|
558
|
-
pageUrl =
|
|
654
|
+
pageUrl = currentPageUrl,
|
|
559
655
|
pageSearch = "",
|
|
560
656
|
pageReferrer = "",
|
|
561
657
|
browser = common.browser,
|
|
@@ -572,11 +668,11 @@ internal class AnalyticsClient(
|
|
|
572
668
|
utmContent = common.utmContent ?: "",
|
|
573
669
|
gclid = "",
|
|
574
670
|
fbclid = "",
|
|
575
|
-
acctType =
|
|
671
|
+
acctType = currentAcctType,
|
|
576
672
|
diiSource = "",
|
|
577
673
|
gamNetworkCode = getGamNetworkCode(),
|
|
578
|
-
amznPubId = NIL_UUID,
|
|
579
|
-
customDimensions =
|
|
674
|
+
amznPubId = NIL_UUID,
|
|
675
|
+
customDimensions = currentCustomDimensions,
|
|
580
676
|
viewability = listOf(viewabilityData)
|
|
581
677
|
)
|
|
582
678
|
|
|
@@ -616,7 +712,7 @@ internal class AnalyticsClient(
|
|
|
616
712
|
newUser = sessionManager.isNewUser,
|
|
617
713
|
pageId = sessionManager.getOrCreatePageId(),
|
|
618
714
|
sessionDepth = sessionManager.sessionDepth,
|
|
619
|
-
pageUrl =
|
|
715
|
+
pageUrl = currentPageUrl,
|
|
620
716
|
pageSearch = "",
|
|
621
717
|
pageReferrer = "",
|
|
622
718
|
browser = common.browser,
|
|
@@ -633,11 +729,11 @@ internal class AnalyticsClient(
|
|
|
633
729
|
utmContent = common.utmContent ?: "",
|
|
634
730
|
gclid = "",
|
|
635
731
|
fbclid = "",
|
|
636
|
-
acctType =
|
|
732
|
+
acctType = currentAcctType,
|
|
637
733
|
diiSource = "",
|
|
638
734
|
gamNetworkCode = getGamNetworkCode(),
|
|
639
|
-
amznPubId = NIL_UUID,
|
|
640
|
-
customDimensions = emptyMap(),
|
|
735
|
+
amznPubId = NIL_UUID,
|
|
736
|
+
customDimensions = emptyMap(), // Engagement uses array-valued custom dimensions per backend schema
|
|
641
737
|
engagedTime = engagedTime,
|
|
642
738
|
timeOnPage = timeOnPage,
|
|
643
739
|
scrollDepth = scrollDepth
|
|
@@ -678,11 +774,66 @@ internal class AnalyticsClient(
|
|
|
678
774
|
|
|
679
775
|
// MARK: - Batching Helpers
|
|
680
776
|
|
|
777
|
+
/**
|
|
778
|
+
* Batch an impression event (250ms delay)
|
|
779
|
+
*/
|
|
780
|
+
private fun batchImpressionEvent(event: ImpressionBatchEvent) {
|
|
781
|
+
synchronized(impressionBatchLock) {
|
|
782
|
+
impressionBatch.add(event)
|
|
783
|
+
|
|
784
|
+
// Cancel existing timer
|
|
785
|
+
impressionBatchHandler?.removeCallbacksAndMessages(null)
|
|
786
|
+
|
|
787
|
+
// Create new handler if needed
|
|
788
|
+
if (impressionBatchHandler == null) {
|
|
789
|
+
impressionBatchHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Schedule flush after 250ms
|
|
793
|
+
impressionBatchHandler?.postDelayed({
|
|
794
|
+
flushImpressionBatch()
|
|
795
|
+
}, BATCH_DELAY_MS)
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Flush impression batch to backend
|
|
801
|
+
*
|
|
802
|
+
* Merges events with the same pageId into a single object with combined impressions array.
|
|
803
|
+
*/
|
|
804
|
+
private fun flushImpressionBatch() {
|
|
805
|
+
synchronized(impressionBatchLock) {
|
|
806
|
+
if (impressionBatch.isEmpty()) return
|
|
807
|
+
|
|
808
|
+
val events = impressionBatch.toList()
|
|
809
|
+
impressionBatch.clear()
|
|
810
|
+
|
|
811
|
+
// Group by pageId and merge impressions into single objects
|
|
812
|
+
val merged = mutableMapOf<String, ImpressionBatchEvent>()
|
|
813
|
+
for (event in events) {
|
|
814
|
+
val existing = merged[event.pageId]
|
|
815
|
+
if (existing != null) {
|
|
816
|
+
merged[event.pageId] = existing.copy(
|
|
817
|
+
impressions = existing.impressions + event.impressions
|
|
818
|
+
)
|
|
819
|
+
} else {
|
|
820
|
+
merged[event.pageId] = event
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
val mergedEvents = merged.values.toList()
|
|
825
|
+
|
|
826
|
+
BCLogger.d(TAG, "Flushing ${mergedEvents.size} impression groups (${events.size} total impressions)")
|
|
827
|
+
|
|
828
|
+
sendEventBatch(mergedEvents, "impressions")
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
681
832
|
/**
|
|
682
833
|
* Batch a viewability event (250ms delay)
|
|
683
834
|
*/
|
|
684
835
|
private fun batchViewabilityEvent(event: ViewabilityEvent) {
|
|
685
|
-
synchronized(
|
|
836
|
+
synchronized(viewabilityBatchLock) {
|
|
686
837
|
viewabilityBatch.add(event)
|
|
687
838
|
|
|
688
839
|
// Cancel existing timer
|
|
@@ -702,18 +853,34 @@ internal class AnalyticsClient(
|
|
|
702
853
|
|
|
703
854
|
/**
|
|
704
855
|
* Flush viewability batch to backend
|
|
856
|
+
*
|
|
857
|
+
* Merges events with the same pageId into a single object with combined viewability array.
|
|
705
858
|
*/
|
|
706
859
|
private fun flushViewabilityBatch() {
|
|
707
|
-
synchronized(
|
|
860
|
+
synchronized(viewabilityBatchLock) {
|
|
708
861
|
if (viewabilityBatch.isEmpty()) return
|
|
709
862
|
|
|
710
|
-
val
|
|
863
|
+
val events = viewabilityBatch.toList()
|
|
711
864
|
viewabilityBatch.clear()
|
|
712
865
|
|
|
713
|
-
|
|
866
|
+
// Group by pageId and merge viewability into single objects
|
|
867
|
+
val merged = mutableMapOf<String, ViewabilityEvent>()
|
|
868
|
+
for (event in events) {
|
|
869
|
+
val existing = merged[event.pageId]
|
|
870
|
+
if (existing != null) {
|
|
871
|
+
merged[event.pageId] = existing.copy(
|
|
872
|
+
viewability = existing.viewability + event.viewability
|
|
873
|
+
)
|
|
874
|
+
} else {
|
|
875
|
+
merged[event.pageId] = event
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
val mergedEvents = merged.values.toList()
|
|
880
|
+
|
|
881
|
+
BCLogger.d(TAG, "Flushing ${mergedEvents.size} viewability groups (${events.size} total)")
|
|
714
882
|
|
|
715
|
-
|
|
716
|
-
sendEventBatch(eventsToSend, "viewability")
|
|
883
|
+
sendEventBatch(mergedEvents, "viewability")
|
|
717
884
|
}
|
|
718
885
|
}
|
|
719
886
|
}
|