@bigcrunch/react-native-ads 0.14.0 → 0.15.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.
@@ -238,6 +238,7 @@ internal class GoogleAdsAdapter(
238
238
 
239
239
  analyticsClient.trackAdImpression(
240
240
  placementId = placementConfig.placementId,
241
+ slotId = placementConfig.id,
241
242
  format = placementConfig.format,
242
243
  gamAdUnit = placementConfig.gamAdUnit,
243
244
  adSize = adSizeString,
@@ -257,7 +258,7 @@ internal class GoogleAdsAdapter(
257
258
 
258
259
  override fun onAdClicked() {
259
260
  BCLogger.d(TAG, "Banner ad clicked: ${placementConfig.placementId}")
260
- analyticsClient.trackAdClick(placementConfig.placementId, placementConfig.format)
261
+ analyticsClient.trackAdClick(placementConfig.placementId, placementConfig.id, placementConfig.format)
261
262
  safeCallback { callback.onAdClicked() }
262
263
  }
263
264
 
@@ -398,6 +399,7 @@ internal class GoogleAdsAdapter(
398
399
 
399
400
  analyticsClient.trackAdImpression(
400
401
  placementId = placementConfig.placementId,
402
+ slotId = placementConfig.id,
401
403
  format = placementConfig.format,
402
404
  gamAdUnit = placementConfig.gamAdUnit,
403
405
  advertiserId = adMetadata["advertiser_id"] as? String,
@@ -420,7 +422,7 @@ internal class GoogleAdsAdapter(
420
422
 
421
423
  override fun onAdClicked() {
422
424
  BCLogger.d(TAG, "Interstitial ad clicked: ${placementConfig.placementId}")
423
- analyticsClient.trackAdClick(placementConfig.placementId, placementConfig.format)
425
+ analyticsClient.trackAdClick(placementConfig.placementId, placementConfig.id, placementConfig.format)
424
426
  safeCallback { callback.onAdClicked() }
425
427
  }
426
428
 
@@ -506,6 +508,7 @@ internal class GoogleAdsAdapter(
506
508
 
507
509
  analyticsClient.trackAdImpression(
508
510
  placementId = placementConfig.placementId,
511
+ slotId = placementConfig.id,
509
512
  format = placementConfig.format,
510
513
  gamAdUnit = placementConfig.gamAdUnit,
511
514
  advertiserId = adMetadata["advertiser_id"] as? String,
@@ -528,7 +531,7 @@ internal class GoogleAdsAdapter(
528
531
 
529
532
  override fun onAdClicked() {
530
533
  BCLogger.d(TAG, "Rewarded ad clicked: ${placementConfig.placementId}")
531
- analyticsClient.trackAdClick(placementConfig.placementId, placementConfig.format)
534
+ analyticsClient.trackAdClick(placementConfig.placementId, placementConfig.id, placementConfig.format)
532
535
  safeCallback { callback.onAdClicked() }
533
536
  }
534
537
 
@@ -293,6 +293,7 @@ internal class AnalyticsClient(
293
293
  */
294
294
  fun createImpressionContext(
295
295
  placementId: String,
296
+ slotId: String,
296
297
  gamAdUnit: String,
297
298
  format: String,
298
299
  width: Int? = null,
@@ -300,6 +301,7 @@ internal class AnalyticsClient(
300
301
  ): ImpressionContext {
301
302
  val context = ImpressionContext(
302
303
  placementId = placementId,
304
+ slotId = slotId,
303
305
  gamAdUnit = gamAdUnit,
304
306
  format = format,
305
307
  width = width,
@@ -396,7 +398,7 @@ internal class AnalyticsClient(
396
398
 
397
399
  // Create impression record with auction data fields populated
398
400
  val impressionRecord = ImpressionRecord(
399
- slotId = context.placementId,
401
+ slotId = context.slotId,
400
402
  gamUnit = context.gamAdUnit,
401
403
  gamPriceBucket = effectiveAuctionData.gamPriceBucket ?: "",
402
404
  impressionId = context.impressionId,
@@ -458,6 +460,7 @@ internal class AnalyticsClient(
458
460
  */
459
461
  fun trackAdImpression(
460
462
  placementId: String,
463
+ slotId: String,
461
464
  format: String,
462
465
  gamAdUnit: String = "",
463
466
  adSize: String = "",
@@ -482,7 +485,7 @@ internal class AnalyticsClient(
482
485
 
483
486
  // Create impression record with auction data fields populated
484
487
  val impressionRecord = ImpressionRecord(
485
- slotId = placementId,
488
+ slotId = slotId,
486
489
  gamUnit = gamAdUnit,
487
490
  gamPriceBucket = auctionData?.gamPriceBucket ?: "",
488
491
  impressionId = UUID.randomUUID().toString(),
@@ -552,7 +555,7 @@ internal class AnalyticsClient(
552
555
  * @param placementId Placement ID
553
556
  * @param format Ad format
554
557
  */
555
- fun trackAdClick(impressionId: String, placementId: String, format: String) {
558
+ fun trackAdClick(impressionId: String, placementId: String, slotId: String, format: String) {
556
559
  // Get common event fields
557
560
  val common = getCommonEventFields()
558
561
 
@@ -562,7 +565,7 @@ internal class AnalyticsClient(
562
565
  // Create nested click data - impressionId must be valid UUID
563
566
  val clickData = ClickData(
564
567
  clickId = UUID.randomUUID().toString(),
565
- slotId = placementId,
568
+ slotId = slotId,
566
569
  impressionId = impressionId.takeIf { it.isNotEmpty() } ?: UUID.randomUUID().toString()
567
570
  )
568
571
 
@@ -607,8 +610,8 @@ internal class AnalyticsClient(
607
610
  /**
608
611
  * Track an ad click (simple version without impression ID)
609
612
  */
610
- fun trackAdClick(placementId: String, format: String) {
611
- trackAdClick("", placementId, format)
613
+ fun trackAdClick(placementId: String, slotId: String, format: String) {
614
+ trackAdClick("", placementId, slotId, format)
612
615
  }
613
616
 
614
617
  // MARK: - Viewability Tracking
@@ -625,6 +628,7 @@ internal class AnalyticsClient(
625
628
  fun trackAdViewable(
626
629
  impressionId: String,
627
630
  placementId: String,
631
+ slotId: String,
628
632
  format: String,
629
633
  viewableTimeMs: Long,
630
634
  percentVisible: Int
@@ -637,7 +641,7 @@ internal class AnalyticsClient(
637
641
 
638
642
  // Create nested viewability data - impressionId must be valid UUID
639
643
  val viewabilityData = ViewabilityData(
640
- slotId = placementId,
644
+ slotId = slotId,
641
645
  impressionId = impressionId.takeIf { it.isNotEmpty() } ?: UUID.randomUUID().toString()
642
646
  )
643
647
 
@@ -682,8 +686,8 @@ internal class AnalyticsClient(
682
686
  /**
683
687
  * Track ad viewability (simple version without metrics)
684
688
  */
685
- fun trackAdViewable(placementId: String, format: String) {
686
- trackAdViewable("", placementId, format, 0, 0)
689
+ fun trackAdViewable(placementId: String, slotId: String, format: String) {
690
+ trackAdViewable("", placementId, slotId, format, 0, 0)
687
691
  }
688
692
 
689
693
  // MARK: - Engagement Tracking
@@ -188,7 +188,7 @@ internal class BidRequestClient(
188
188
 
189
189
  private fun buildImp(placement: PlacementConfig): JSONObject {
190
190
  val imp = JSONObject()
191
- imp.put("id", placement.placementId)
191
+ imp.put("id", placement.id)
192
192
 
193
193
  val deviceContext = DeviceContext.getInstance()
194
194
 
@@ -268,7 +268,8 @@ internal class BidRequestClient(
268
268
  */
269
269
  private fun buildBiddersExt(placements: List<PlacementConfig>): JSONObject {
270
270
  val bidders = configManager.getCachedConfig()?.bidders ?: return JSONObject()
271
- val placementIds = placements.map { it.placementId }.toSet()
271
+ val placementIdToUuid = placements.associate { it.placementId to it.id }
272
+ val placementIds = placementIdToUuid.keys
272
273
  val biddersExt = JSONObject()
273
274
 
274
275
  for ((bidderName, bidderEntry) in bidders) {
@@ -285,7 +286,7 @@ internal class BidRequestClient(
285
286
  for (placementId in placementIds) {
286
287
  bidderPlacements[placementId]?.let { impParams ->
287
288
  impArray.put(JSONObject().apply {
288
- put("impid", placementId)
289
+ put("impid", placementIdToUuid[placementId])
289
290
  put("params", mapToJson(impParams))
290
291
  })
291
292
  }
@@ -316,8 +317,9 @@ internal class BidRequestClient(
316
317
 
317
318
  val auctionId = json.optString("id", "")
318
319
 
319
- // Build floor price lookup from placement configs
320
- val floorPrices = placements.associate { it.placementId to it.floorPrice }
320
+ // Build floor price lookup and UUID-to-placementId mapping
321
+ val floorPrices = placements.associate { it.id to it.floorPrice }
322
+ val uuidToPlacementId = placements.associate { it.id to it.placementId }
321
323
 
322
324
  // Collect all bids grouped by impid
323
325
  data class BidInfo(
@@ -376,8 +378,10 @@ internal class BidRequestClient(
376
378
  demandChannel = "S2S"
377
379
  )
378
380
 
379
- results[impid] = BidResponse(winner.targeting, auctionData)
380
- BCLogger.d(TAG, "Winner for $impid: $${winner.price}")
381
+ // Re-key from UUID to placementId for internal use
382
+ val placementId = uuidToPlacementId[impid] ?: impid
383
+ results[placementId] = BidResponse(winner.targeting, auctionData)
384
+ BCLogger.d(TAG, "Winner for $placementId: $${winner.price}")
381
385
  }
382
386
 
383
387
  BCLogger.i(TAG, "Parsed ${results.size} winning bids")
@@ -5,10 +5,14 @@ import com.bigcrunch.ads.internal.HttpClient
5
5
  import com.bigcrunch.ads.internal.KeyValueStore
6
6
  import com.bigcrunch.ads.models.AppConfig
7
7
  import com.bigcrunch.ads.models.PlacementConfig
8
+ import com.squareup.moshi.JsonDataException
9
+ import com.squareup.moshi.JsonEncodingException
8
10
  import com.squareup.moshi.Moshi
9
11
  import kotlinx.coroutines.sync.Mutex
10
12
  import kotlinx.coroutines.sync.withLock
11
13
 
14
+ class ConfigParseException(message: String, cause: Throwable? = null) : Exception(message, cause)
15
+
12
16
  /**
13
17
  * Manages application configuration fetching, caching, and access
14
18
  *
@@ -61,12 +65,14 @@ internal class ConfigManager(
61
65
  refresh = com.bigcrunch.ads.models.RefreshConfig(enabled = true, intervalMs = 30000, maxRefreshes = 20),
62
66
  placements = listOf(
63
67
  PlacementConfig(
68
+ id = "00000000-0000-0000-0000-000000000001",
64
69
  placementId = "test_banner_320x50",
65
70
  format = "banner",
66
71
  gamAdUnit = "ca-app-pub-3940256099942544/6300978111",
67
72
  sizes = listOf(com.bigcrunch.ads.models.AdSize(width = 320, height = 50))
68
73
  ),
69
74
  PlacementConfig(
75
+ id = "00000000-0000-0000-0000-000000000002",
70
76
  placementId = "test_mrec",
71
77
  format = "banner",
72
78
  gamAdUnit = "ca-app-pub-3940256099942544/6300978111",
@@ -74,18 +80,21 @@ internal class ConfigManager(
74
80
  refresh = com.bigcrunch.ads.models.RefreshConfig(enabled = true, intervalMs = 15000, maxRefreshes = 40)
75
81
  ),
76
82
  PlacementConfig(
83
+ id = "00000000-0000-0000-0000-000000000003",
77
84
  placementId = "test_adaptive_banner",
78
85
  format = "banner",
79
86
  gamAdUnit = "ca-app-pub-3940256099942544/6300978111",
80
87
  sizes = listOf(com.bigcrunch.ads.models.AdSize.adaptive())
81
88
  ),
82
89
  PlacementConfig(
90
+ id = "00000000-0000-0000-0000-000000000004",
83
91
  placementId = "test_interstitial",
84
92
  format = "interstitial",
85
93
  gamAdUnit = "ca-app-pub-3940256099942544/1033173712",
86
94
  sizes = null
87
95
  ),
88
96
  PlacementConfig(
97
+ id = "00000000-0000-0000-0000-000000000005",
89
98
  placementId = "test_rewarded",
90
99
  format = "rewarded",
91
100
  gamAdUnit = "ca-app-pub-3940256099942544/5224354917",
@@ -150,8 +159,9 @@ internal class ConfigManager(
150
159
  val config = try {
151
160
  adapter.fromJson(json)
152
161
  } catch (e: Exception) {
153
- BCLogger.e("ConfigManager", "Failed to parse config JSON from $url", e)
154
- null
162
+ val message = describeJsonError(e)
163
+ BCLogger.e("ConfigManager", "Failed to parse config JSON from $url: $message", e)
164
+ return Result.failure(ConfigParseException(message, e))
155
165
  }
156
166
 
157
167
  if (config != null) {
@@ -163,7 +173,7 @@ internal class ConfigManager(
163
173
  Result.success(config)
164
174
  } else {
165
175
  BCLogger.e("ConfigManager", "Invalid config JSON")
166
- Result.failure(Exception("Invalid config JSON"))
176
+ Result.failure(ConfigParseException("Config JSON parsed to null"))
167
177
  }
168
178
  }
169
179
  storedConfig != null -> {
@@ -288,11 +298,22 @@ internal class ConfigManager(
288
298
  val json = storage.getString(CONFIG_STORAGE_KEY) ?: return null
289
299
  adapter.fromJson(json)
290
300
  } catch (e: Exception) {
291
- BCLogger.e("ConfigManager", "Failed to load config from storage", e)
301
+ BCLogger.e("ConfigManager", "Failed to load config from storage: ${describeJsonError(e)}", e)
292
302
  null
293
303
  }
294
304
  }
295
305
 
306
+ /**
307
+ * Produce a human-readable description from a Moshi parse error
308
+ */
309
+ private fun describeJsonError(e: Exception): String {
310
+ return when (e) {
311
+ is JsonDataException -> e.message ?: "Invalid JSON data"
312
+ is JsonEncodingException -> e.message ?: "Malformed JSON"
313
+ else -> e.message ?: e.toString()
314
+ }
315
+ }
316
+
296
317
  /**
297
318
  * Save config to persistent storage
298
319
  */
@@ -28,7 +28,7 @@ internal class DeviceContext private constructor(context: Context) {
28
28
 
29
29
  companion object {
30
30
  private const val TAG = "DeviceContext"
31
- internal const val SDK_VERSION = "0.14.0"
31
+ internal const val SDK_VERSION = "0.15.0"
32
32
 
33
33
  @Volatile
34
34
  private var instance: DeviceContext? = null
@@ -944,6 +944,8 @@ internal data class ImpressionContext(
944
944
 
945
945
  /** Placement configuration */
946
946
  val placementId: String,
947
+ /** UUID for backend reporting (slot_id) */
948
+ val slotId: String,
947
949
  val gamAdUnit: String,
948
950
  val format: String,
949
951
 
@@ -12,6 +12,9 @@ import com.squareup.moshi.JsonClass
12
12
  */
13
13
  @JsonClass(generateAdapter = true)
14
14
  data class PlacementConfig(
15
+ @Json(name = "id")
16
+ val id: String,
17
+
15
18
  @Json(name = "placementId")
16
19
  val placementId: String,
17
20
 
@@ -46,7 +46,7 @@ class BigCrunchAdsModule(reactContext: ReactApplicationContext) :
46
46
  BigCrunchAds.initialize(
47
47
  context = context,
48
48
  propertyId = propertyId,
49
- env = if (environment == "staging")
49
+ env = if (environment == "sandbox" || environment == "staging")
50
50
  BigCrunchAds.Environment.Staging
51
51
  else
52
52
  BigCrunchAds.Environment.Prod,
@@ -367,6 +367,7 @@ private class BannerDelegateWrapper: NSObject, GoogleMobileAds.BannerViewDelegat
367
367
 
368
368
  analyticsClient.trackAdImpression(
369
369
  placementId: placementConfig.placementId,
370
+ slotId: placementConfig.id,
370
371
  format: placementConfig.format,
371
372
  gamAdUnit: placementConfig.gamAdUnit,
372
373
  adSize: adSizeString,
@@ -388,6 +389,7 @@ private class BannerDelegateWrapper: NSObject, GoogleMobileAds.BannerViewDelegat
388
389
  BCLogger.debug("GoogleAdsAdapter: Banner ad clicked: \(placementConfig.placementId)")
389
390
  analyticsClient.trackAdClick(
390
391
  placementId: placementConfig.placementId,
392
+ slotId: placementConfig.id,
391
393
  format: placementConfig.format
392
394
  )
393
395
  delegate?.bannerAdDidRecordClick(bannerView)
@@ -431,6 +433,7 @@ private class InterstitialDelegateWrapper: NSObject, GoogleMobileAds.FullScreenC
431
433
 
432
434
  analyticsClient.trackAdImpression(
433
435
  placementId: placementConfig.placementId,
436
+ slotId: placementConfig.id,
434
437
  format: placementConfig.format,
435
438
  gamAdUnit: placementConfig.gamAdUnit,
436
439
  advertiserId: adMetadata["advertiser_id"] as? String,
@@ -457,6 +460,7 @@ private class InterstitialDelegateWrapper: NSObject, GoogleMobileAds.FullScreenC
457
460
  BCLogger.debug("GoogleAdsAdapter: Interstitial ad clicked: \(placementConfig.placementId)")
458
461
  analyticsClient.trackAdClick(
459
462
  placementId: placementConfig.placementId,
463
+ slotId: placementConfig.id,
460
464
  format: placementConfig.format
461
465
  )
462
466
  delegate?.interstitialAdDidRecordClick(interstitialAd)
@@ -465,6 +465,7 @@ private class RewardedDelegateWrapper: NSObject, FullScreenContentDelegate {
465
465
 
466
466
  analyticsClient.trackAdImpression(
467
467
  placementId: placementConfig.placementId,
468
+ slotId: placementConfig.id,
468
469
  format: placementConfig.format,
469
470
  advertiserId: adMetadata["advertiser_id"] as? String,
470
471
  campaignId: adMetadata["campaign_id"] as? String,
@@ -492,6 +493,7 @@ private class RewardedDelegateWrapper: NSObject, FullScreenContentDelegate {
492
493
  BCLogger.debug("BigCrunchRewarded: Rewarded ad clicked: \(placementId)")
493
494
  analyticsClient.trackAdClick(
494
495
  placementId: placementConfig.placementId,
496
+ slotId: placementConfig.id,
495
497
  format: placementConfig.format
496
498
  )
497
499
  delegate?.rewardedDidClick(placementId: placementId)
@@ -239,6 +239,7 @@ internal class AnalyticsClient {
239
239
  */
240
240
  func createImpressionContext(
241
241
  placementId: String,
242
+ slotId: String,
242
243
  gamAdUnit: String,
243
244
  format: String,
244
245
  width: Int? = nil,
@@ -246,6 +247,7 @@ internal class AnalyticsClient {
246
247
  ) -> ImpressionContext {
247
248
  let context = ImpressionContext(
248
249
  placementId: placementId,
250
+ slotId: slotId,
249
251
  gamAdUnit: gamAdUnit,
250
252
  format: format,
251
253
  width: width,
@@ -351,7 +353,7 @@ internal class AnalyticsClient {
351
353
 
352
354
  // Create the impression record
353
355
  let impressionRecord = ImpressionRecord(
354
- slotId: context.placementId,
356
+ slotId: context.slotId,
355
357
  gamUnit: context.gamAdUnit,
356
358
  gamPriceBucket: effectiveAuctionData.gamPriceBucket ?? "",
357
359
  impressionId: context.impressionId,
@@ -417,6 +419,7 @@ internal class AnalyticsClient {
417
419
  */
418
420
  func trackAdImpression(
419
421
  placementId: String,
422
+ slotId: String,
420
423
  format: String,
421
424
  gamAdUnit: String = "",
422
425
  adSize: String = "",
@@ -445,7 +448,7 @@ internal class AnalyticsClient {
445
448
 
446
449
  // Create the impression record with auction data fields populated
447
450
  let impressionRecord = ImpressionRecord(
448
- slotId: placementId,
451
+ slotId: slotId,
449
452
  gamUnit: gamAdUnit,
450
453
  gamPriceBucket: auctionData?.gamPriceBucket ?? "",
451
454
  impressionId: UUID().uuidString,
@@ -516,7 +519,7 @@ internal class AnalyticsClient {
516
519
  * - placementId: Placement ID
517
520
  * - format: Ad format
518
521
  */
519
- func trackAdClick(impressionId: String, placementId: String, format: String) {
522
+ func trackAdClick(impressionId: String, placementId: String, slotId: String, format: String) {
520
523
  // Get common event fields
521
524
  let common = getCommonEventFields()
522
525
 
@@ -530,7 +533,7 @@ internal class AnalyticsClient {
530
533
  // Note: customDimensions values must be string arrays per backend validation
531
534
  let clickData = ClickData(
532
535
  clickId: UUID().uuidString,
533
- slotId: placementId,
536
+ slotId: slotId,
534
537
  impressionId: impressionId.isEmpty ? UUID().uuidString : impressionId, // Must be valid UUID
535
538
  refreshCount: 0,
536
539
  adAmznp: "",
@@ -586,8 +589,8 @@ internal class AnalyticsClient {
586
589
  /**
587
590
  * Track an ad click (simple version without impression ID)
588
591
  */
589
- func trackAdClick(placementId: String, format: String) {
590
- trackAdClick(impressionId: "", placementId: placementId, format: format)
592
+ func trackAdClick(placementId: String, slotId: String, format: String) {
593
+ trackAdClick(impressionId: "", placementId: placementId, slotId: slotId, format: format)
591
594
  }
592
595
 
593
596
  // MARK: - Viewability Tracking
@@ -605,6 +608,7 @@ internal class AnalyticsClient {
605
608
  func trackAdViewable(
606
609
  impressionId: String,
607
610
  placementId: String,
611
+ slotId: String,
608
612
  format: String,
609
613
  viewableTimeMs: Int64,
610
614
  percentVisible: Int
@@ -620,7 +624,7 @@ internal class AnalyticsClient {
620
624
 
621
625
  // Create viewability data with nested structure per backend schema
622
626
  let viewabilityData = ViewabilityData(
623
- slotId: placementId,
627
+ slotId: slotId,
624
628
  impressionId: impressionId.isEmpty ? UUID().uuidString : impressionId, // Must be valid UUID
625
629
  refreshCount: 0,
626
630
  adAmznp: "",
@@ -675,7 +679,7 @@ internal class AnalyticsClient {
675
679
  /**
676
680
  * Track ad viewability (simple version without metrics)
677
681
  */
678
- func trackAdViewable(placementId: String, format: String) {
682
+ func trackAdViewable(placementId: String, slotId: String, format: String) {
679
683
  // Get common event fields
680
684
  let common = getCommonEventFields()
681
685
 
@@ -687,7 +691,7 @@ internal class AnalyticsClient {
687
691
 
688
692
  // Create viewability data with nested structure per backend schema
689
693
  let viewabilityData = ViewabilityData(
690
- slotId: placementId,
694
+ slotId: slotId,
691
695
  impressionId: UUID().uuidString, // Generate UUID since none provided
692
696
  refreshCount: 0,
693
697
  adAmznp: "",
@@ -189,7 +189,7 @@ internal class BidRequestClient {
189
189
 
190
190
  private func buildImp(_ placement: PlacementConfig) -> [String: Any] {
191
191
  var imp: [String: Any] = [
192
- "id": placement.placementId
192
+ "id": placement.id
193
193
  ]
194
194
 
195
195
  switch placement.format {
@@ -262,7 +262,8 @@ internal class BidRequestClient {
262
262
  return [:]
263
263
  }
264
264
 
265
- let placementIds = Set(placements.map { $0.placementId })
265
+ let placementIdToUuid = Dictionary(uniqueKeysWithValues: placements.map { ($0.placementId, $0.id) })
266
+ let placementIds = Set(placementIdToUuid.keys)
266
267
  var biddersExt: [String: Any] = [:]
267
268
 
268
269
  for (bidderName, bidderEntry) in bidders {
@@ -279,7 +280,7 @@ internal class BidRequestClient {
279
280
  for placementId in placementIds {
280
281
  if let impParams = bidderPlacements[placementId] {
281
282
  impEntries.append([
282
- "impid": placementId,
283
+ "impid": placementIdToUuid[placementId] ?? placementId,
283
284
  "params": impParams.mapValues { $0.value }
284
285
  ])
285
286
  }
@@ -326,8 +327,9 @@ internal class BidRequestClient {
326
327
 
327
328
  let auctionId = json["id"] as? String
328
329
 
329
- // Build floor price lookup from placement configs
330
- let floorPrices = Dictionary(uniqueKeysWithValues: placements.map { ($0.placementId, $0.floorPrice) })
330
+ // Build floor price lookup (keyed by UUID) and UUID-to-placementId mapping
331
+ let floorPrices = Dictionary(uniqueKeysWithValues: placements.map { ($0.id, $0.floorPrice) })
332
+ let uuidToPlacementId = Dictionary(uniqueKeysWithValues: placements.map { ($0.id, $0.placementId) })
331
333
 
332
334
  // Collect all bids grouped by impid
333
335
  struct BidInfo {
@@ -376,8 +378,10 @@ internal class BidRequestClient {
376
378
  demandChannel: "S2S"
377
379
  )
378
380
 
379
- results[impid] = BidResponse(targeting: winner.targeting, auctionData: auctionData)
380
- BCLogger.debug("BidRequestClient: Winner for \(impid): $\(winner.price)")
381
+ // Re-key from UUID to placementId for internal use
382
+ let placementId = uuidToPlacementId[impid] ?? impid
383
+ results[placementId] = BidResponse(targeting: winner.targeting, auctionData: auctionData)
384
+ BCLogger.debug("BidRequestClient: Winner for \(placementId): $\(winner.price)")
381
385
  }
382
386
 
383
387
  BCLogger.info("BidRequestClient: Parsed \(results.count) winning bids")
@@ -12,6 +12,17 @@ import Foundation
12
12
  *
13
13
  * The config is fetched once at SDK initialization and cached for the app session.
14
14
  */
15
+ enum ConfigError: LocalizedError {
16
+ case parseError(String)
17
+
18
+ var errorDescription: String? {
19
+ switch self {
20
+ case .parseError(let message):
21
+ return message
22
+ }
23
+ }
24
+ }
25
+
15
26
  internal class ConfigManager {
16
27
 
17
28
  private let httpClient: HTTPClient
@@ -55,12 +66,14 @@ internal class ConfigManager {
55
66
  refresh: RefreshConfig(enabled: true, intervalMs: 30000, maxRefreshes: 20),
56
67
  placements: [
57
68
  PlacementConfig(
69
+ id: "00000000-0000-0000-0000-000000000001",
58
70
  placementId: "test_banner_320x50",
59
71
  format: "banner",
60
72
  gamAdUnit: "ca-app-pub-3940256099942544/2435281174",
61
73
  sizes: [AdSize(width: 320, height: 50)]
62
74
  ),
63
75
  PlacementConfig(
76
+ id: "00000000-0000-0000-0000-000000000002",
64
77
  placementId: "test_mrec",
65
78
  format: "banner",
66
79
  gamAdUnit: "ca-app-pub-3940256099942544/2435281174",
@@ -68,18 +81,21 @@ internal class ConfigManager {
68
81
  refresh: RefreshConfig(enabled: true, intervalMs: 15000, maxRefreshes: 40)
69
82
  ),
70
83
  PlacementConfig(
84
+ id: "00000000-0000-0000-0000-000000000003",
71
85
  placementId: "test_adaptive_banner",
72
86
  format: "banner",
73
87
  gamAdUnit: "ca-app-pub-3940256099942544/2435281174",
74
88
  sizes: [AdSize.adaptive()]
75
89
  ),
76
90
  PlacementConfig(
91
+ id: "00000000-0000-0000-0000-000000000004",
77
92
  placementId: "test_interstitial",
78
93
  format: "interstitial",
79
94
  gamAdUnit: "ca-app-pub-3940256099942544/4411468910",
80
95
  sizes: nil
81
96
  ),
82
97
  PlacementConfig(
98
+ id: "00000000-0000-0000-0000-000000000005",
83
99
  placementId: "test_rewarded",
84
100
  format: "rewarded",
85
101
  gamAdUnit: "ca-app-pub-3940256099942544/1712485313",
@@ -152,8 +168,9 @@ internal class ConfigManager {
152
168
  BCLogger.verbose("Config JSON: \(json)")
153
169
  return .success(config)
154
170
  } catch {
155
- BCLogger.error("Failed to parse config JSON from \(url): \(error)")
156
- return .failure(error)
171
+ let message = Self.describeDecodingError(error)
172
+ BCLogger.error("Failed to parse config JSON from \(url): \(message)")
173
+ return .failure(ConfigError.parseError(message))
157
174
  }
158
175
 
159
176
  case .failure(let error):
@@ -295,11 +312,51 @@ internal class ConfigManager {
295
312
  do {
296
313
  return try JSONDecoder().decode(AppConfig.self, from: data)
297
314
  } catch {
298
- BCLogger.error("Failed to load config from storage: \(error)")
315
+ BCLogger.error("Failed to load config from storage: \(Self.describeDecodingError(error))")
299
316
  return nil
300
317
  }
301
318
  }
302
319
 
320
+ /**
321
+ * Produce a human-readable description from a DecodingError
322
+ */
323
+ private static func describeDecodingError(_ error: Error) -> String {
324
+ guard let decodingError = error as? DecodingError else {
325
+ return error.localizedDescription
326
+ }
327
+
328
+ let path: String
329
+ let detail: String
330
+
331
+ switch decodingError {
332
+ case .keyNotFound(let key, let context):
333
+ path = Self.formatCodingPath(context.codingPath)
334
+ detail = "missing required field '\(key.stringValue)'"
335
+ case .typeMismatch(let type, let context):
336
+ path = Self.formatCodingPath(context.codingPath)
337
+ detail = "expected \(type)"
338
+ case .valueNotFound(let type, let context):
339
+ path = Self.formatCodingPath(context.codingPath)
340
+ detail = "null value for \(type)"
341
+ case .dataCorrupted(let context):
342
+ path = Self.formatCodingPath(context.codingPath)
343
+ detail = context.debugDescription
344
+ @unknown default:
345
+ return error.localizedDescription
346
+ }
347
+
348
+ return path.isEmpty ? detail : "\(path): \(detail)"
349
+ }
350
+
351
+ private static func formatCodingPath(_ codingPath: [CodingKey]) -> String {
352
+ return codingPath.map { key in
353
+ if let index = key.intValue {
354
+ return "[\(index)]"
355
+ }
356
+ return key.stringValue
357
+ }.joined(separator: ".")
358
+ }
359
+
303
360
  /**
304
361
  * Save config to persistent storage
305
362
  */
@@ -74,7 +74,7 @@ internal final class DeviceContext {
74
74
  // MARK: - SDK Properties
75
75
 
76
76
  /// SDK version
77
- static let SDK_VERSION = "0.14.0"
77
+ static let SDK_VERSION = "0.15.0"
78
78
  let sdkVersion: String = SDK_VERSION
79
79
 
80
80
  /// SDK platform (always "ios")
@@ -860,6 +860,8 @@ internal struct ImpressionContext {
860
860
 
861
861
  /// Placement configuration
862
862
  let placementId: String
863
+ /// UUID for backend reporting (slot_id)
864
+ let slotId: String
863
865
  let gamAdUnit: String
864
866
  let format: String
865
867
 
@@ -875,6 +877,7 @@ internal struct ImpressionContext {
875
877
 
876
878
  init(
877
879
  placementId: String,
880
+ slotId: String,
878
881
  gamAdUnit: String,
879
882
  format: String,
880
883
  width: Int? = nil,
@@ -882,6 +885,7 @@ internal struct ImpressionContext {
882
885
  ) {
883
886
  self.impressionId = UUID().uuidString
884
887
  self.placementId = placementId
888
+ self.slotId = slotId
885
889
  self.gamAdUnit = gamAdUnit
886
890
  self.format = format
887
891
  self.width = width
@@ -8,6 +8,7 @@ import Foundation
8
8
  * in the top-level `AppConfig.bidders` dictionary.
9
9
  */
10
10
  public struct PlacementConfig: Codable {
11
+ public let id: String
11
12
  public let placementId: String
12
13
  public let format: String // "banner", "interstitial", "rewarded"
13
14
  public let gamAdUnit: String
@@ -16,6 +17,7 @@ public struct PlacementConfig: Codable {
16
17
  public let floorPrice: Double?
17
18
 
18
19
  public init(
20
+ id: String,
19
21
  placementId: String,
20
22
  format: String,
21
23
  gamAdUnit: String,
@@ -23,6 +25,7 @@ public struct PlacementConfig: Codable {
23
25
  refresh: RefreshConfig? = nil,
24
26
  floorPrice: Double? = nil
25
27
  ) {
28
+ self.id = id
26
29
  self.placementId = placementId
27
30
  self.format = format
28
31
  self.gamAdUnit = gamAdUnit
@@ -27,7 +27,9 @@ export interface AppConfig {
27
27
  * Individual placement configuration
28
28
  */
29
29
  export interface PlacementConfig {
30
- /** Unique placement ID */
30
+ /** Unique placement UUID for backend reporting */
31
+ id: string;
32
+ /** Developer-facing placement identifier */
31
33
  placementId: string;
32
34
  /** Placement name for reporting */
33
35
  placementName?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigcrunch/react-native-ads",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "BigCrunch Mobile Ads SDK for React Native - Simplified in-app advertising with S2S demand and Google Ad Manager",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -30,7 +30,9 @@ export interface AppConfig {
30
30
  * Individual placement configuration
31
31
  */
32
32
  export interface PlacementConfig {
33
- /** Unique placement ID */
33
+ /** Unique placement UUID for backend reporting */
34
+ id: string;
35
+ /** Developer-facing placement identifier */
34
36
  placementId: string;
35
37
  /** Placement name for reporting */
36
38
  placementName?: string;