@bigcrunch/react-native-ads 0.3.1 → 0.5.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.
Files changed (64) hide show
  1. package/README.md +5 -5
  2. package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchAds.kt +434 -0
  3. package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchBannerView.kt +484 -0
  4. package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchInterstitial.kt +403 -0
  5. package/android/bigcrunch-ads/com/bigcrunch/ads/BigCrunchRewarded.kt +409 -0
  6. package/android/bigcrunch-ads/com/bigcrunch/ads/adapters/GoogleAdsAdapter.kt +592 -0
  7. package/android/bigcrunch-ads/com/bigcrunch/ads/core/AdOrchestrator.kt +623 -0
  8. package/android/bigcrunch-ads/com/bigcrunch/ads/core/AnalyticsClient.kt +719 -0
  9. package/android/bigcrunch-ads/com/bigcrunch/ads/core/BidRequestClient.kt +364 -0
  10. package/android/bigcrunch-ads/com/bigcrunch/ads/core/ConfigManager.kt +301 -0
  11. package/android/bigcrunch-ads/com/bigcrunch/ads/core/DeviceContext.kt +385 -0
  12. package/android/bigcrunch-ads/com/bigcrunch/ads/core/RewardedCallback.kt +42 -0
  13. package/android/bigcrunch-ads/com/bigcrunch/ads/core/SessionManager.kt +330 -0
  14. package/android/bigcrunch-ads/com/bigcrunch/ads/internal/DeviceHelper.kt +60 -0
  15. package/android/bigcrunch-ads/com/bigcrunch/ads/internal/HttpClient.kt +114 -0
  16. package/android/bigcrunch-ads/com/bigcrunch/ads/internal/Logger.kt +71 -0
  17. package/android/bigcrunch-ads/com/bigcrunch/ads/internal/PrivacyStore.kt +125 -0
  18. package/android/bigcrunch-ads/com/bigcrunch/ads/internal/Storage.kt +88 -0
  19. package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/BannerAdListener.kt +55 -0
  20. package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/InterstitialAdListener.kt +55 -0
  21. package/android/bigcrunch-ads/com/bigcrunch/ads/listeners/RewardedAdListener.kt +58 -0
  22. package/android/bigcrunch-ads/com/bigcrunch/ads/models/AdEvent.kt +880 -0
  23. package/android/bigcrunch-ads/com/bigcrunch/ads/models/AppConfig.kt +90 -0
  24. package/android/bigcrunch-ads/com/bigcrunch/ads/models/DeviceData.kt +18 -0
  25. package/android/bigcrunch-ads/com/bigcrunch/ads/models/PlacementConfig.kt +70 -0
  26. package/android/bigcrunch-ads/com/bigcrunch/ads/models/SessionInfo.kt +21 -0
  27. package/android/build.gradle +22 -10
  28. package/android/settings.gradle +2 -6
  29. package/android/src/main/java/com/bigcrunch/ads/react/BigCrunchAdsModule.kt +0 -23
  30. package/ios/BigCrunchAds/Sources/Adapters/GoogleAdsAdapter.swift +512 -0
  31. package/ios/BigCrunchAds/Sources/BigCrunchAds.swift +387 -0
  32. package/ios/BigCrunchAds/Sources/BigCrunchBannerView.swift +448 -0
  33. package/ios/BigCrunchAds/Sources/BigCrunchInterstitial.swift +412 -0
  34. package/ios/BigCrunchAds/Sources/BigCrunchRewarded.swift +523 -0
  35. package/ios/BigCrunchAds/Sources/Core/AdOrchestrator.swift +514 -0
  36. package/ios/BigCrunchAds/Sources/Core/AnalyticsClient.swift +874 -0
  37. package/ios/BigCrunchAds/Sources/Core/BidRequestClient.swift +344 -0
  38. package/ios/BigCrunchAds/Sources/Core/ConfigManager.swift +306 -0
  39. package/ios/BigCrunchAds/Sources/Core/DeviceContext.swift +284 -0
  40. package/ios/BigCrunchAds/Sources/Core/SessionManager.swift +392 -0
  41. package/ios/BigCrunchAds/Sources/Internal/HTTPClient.swift +146 -0
  42. package/ios/BigCrunchAds/Sources/Internal/Logger.swift +62 -0
  43. package/ios/BigCrunchAds/Sources/Internal/PrivacyStore.swift +129 -0
  44. package/ios/BigCrunchAds/Sources/Internal/Storage.swift +73 -0
  45. package/ios/BigCrunchAds/Sources/Models/AdEvent.swift +784 -0
  46. package/ios/BigCrunchAds/Sources/Models/AppConfig.swift +100 -0
  47. package/ios/BigCrunchAds/Sources/Models/DeviceData.swift +68 -0
  48. package/ios/BigCrunchAds/Sources/Models/PlacementConfig.swift +137 -0
  49. package/ios/BigCrunchAds/Sources/Models/SessionInfo.swift +48 -0
  50. package/ios/BigCrunchAdsModule.swift +5 -14
  51. package/ios/BigCrunchBannerViewManager.swift +0 -1
  52. package/lib/index.d.ts +1 -1
  53. package/lib/index.d.ts.map +1 -1
  54. package/lib/index.js +3 -2
  55. package/lib/types/config.d.ts +22 -9
  56. package/lib/types/config.d.ts.map +1 -1
  57. package/lib/types/events.d.ts +4 -4
  58. package/lib/types/events.d.ts.map +1 -1
  59. package/package.json +11 -4
  60. package/react-native-bigcrunch-ads.podspec +1 -3
  61. package/scripts/inject-version.js +55 -0
  62. package/src/index.ts +3 -2
  63. package/src/types/config.ts +23 -9
  64. package/src/types/events.ts +4 -4
@@ -0,0 +1,880 @@
1
+ package com.bigcrunch.ads.models
2
+
3
+ import com.bigcrunch.ads.core.DeviceContextData
4
+ import com.squareup.moshi.Json
5
+ import com.squareup.moshi.JsonClass
6
+ import java.util.UUID
7
+
8
+ // MARK: - Legacy Event (keeping for backward compatibility)
9
+
10
+ /**
11
+ * Analytics event sent to BigCrunch backend
12
+ *
13
+ * All ad-related events (impressions, clicks, revenue) are tracked and sent
14
+ * to the analytics endpoint in this format.
15
+ */
16
+ @JsonClass(generateAdapter = true)
17
+ internal data class AdEvent(
18
+ @Json(name = "eventType")
19
+ val eventType: String, // "screen_view", "ad_request", "ad_impression", "ad_revenue", "ad_click", "ad_viewable"
20
+
21
+ @Json(name = "placementId")
22
+ val placementId: String? = null,
23
+
24
+ @Json(name = "format")
25
+ val format: String? = null, // "banner", "interstitial", "rewarded"
26
+
27
+ @Json(name = "timestamp")
28
+ val timestamp: Long = System.currentTimeMillis(),
29
+
30
+ @Json(name = "sessionId")
31
+ val sessionId: String,
32
+
33
+ @Json(name = "deviceInfo")
34
+ val deviceInfo: DeviceInfo,
35
+
36
+ @Json(name = "revenue")
37
+ val revenue: RevenueData? = null
38
+ )
39
+
40
+ /**
41
+ * Device and app information included with every event
42
+ */
43
+ @JsonClass(generateAdapter = true)
44
+ internal data class DeviceInfo(
45
+ @Json(name = "platform")
46
+ val platform: String = "android",
47
+
48
+ @Json(name = "osVersion")
49
+ val osVersion: String,
50
+
51
+ @Json(name = "appVersion")
52
+ val appVersion: String,
53
+
54
+ @Json(name = "deviceId")
55
+ val deviceId: String
56
+ )
57
+
58
+ /**
59
+ * Revenue data from ILRD (Impression-Level Revenue Data)
60
+ *
61
+ * Only present in ad_revenue events. Values are in micros (1/1,000,000 of currency unit).
62
+ */
63
+ @JsonClass(generateAdapter = true)
64
+ internal data class RevenueData(
65
+ @Json(name = "valueMicros")
66
+ val valueMicros: Long,
67
+
68
+ @Json(name = "currency")
69
+ val currency: String
70
+ )
71
+
72
+ // MARK: - Enhanced Analytics Events (matching web SDK data model)
73
+
74
+ /**
75
+ * Screen/Page view event - matches `/pageviews` endpoint
76
+ *
77
+ * Sent when a new screen is viewed. Contains session context and device info.
78
+ */
79
+ @JsonClass(generateAdapter = true)
80
+ internal data class PageViewEvent(
81
+ // Web schema common fields
82
+ @Json(name = "payload_version")
83
+ val payloadVersion: String,
84
+
85
+ @Json(name = "config_version")
86
+ val configVersion: Int = 1,
87
+
88
+ @Json(name = "browser_timestamp")
89
+ val browserTimestamp: String, // ISO 8601
90
+
91
+ // Session/user context
92
+ @Json(name = "session_id")
93
+ val sessionId: String,
94
+
95
+ @Json(name = "user_id")
96
+ val userId: String,
97
+
98
+ @Json(name = "property_id")
99
+ val propertyId: String,
100
+
101
+ @Json(name = "new_user")
102
+ val newUser: Boolean,
103
+
104
+ // Page context
105
+ @Json(name = "page_id")
106
+ val pageId: String,
107
+
108
+ @Json(name = "session_depth")
109
+ val sessionDepth: Int,
110
+
111
+ @Json(name = "page_url")
112
+ val pageUrl: String,
113
+
114
+ @Json(name = "page_search")
115
+ val pageSearch: String = "",
116
+
117
+ @Json(name = "page_referrer")
118
+ val pageReferrer: String = "",
119
+
120
+ // Device context (flattened)
121
+ @Json(name = "browser")
122
+ val browser: String,
123
+
124
+ @Json(name = "device")
125
+ val device: String,
126
+
127
+ @Json(name = "os")
128
+ val os: String,
129
+
130
+ @Json(name = "country")
131
+ val country: String,
132
+
133
+ @Json(name = "region")
134
+ val region: String,
135
+
136
+ // Attribution
137
+ @Json(name = "session_source")
138
+ val sessionSource: String,
139
+
140
+ @Json(name = "session_medium")
141
+ val sessionMedium: String,
142
+
143
+ @Json(name = "utm_source")
144
+ val utmSource: String = "",
145
+
146
+ @Json(name = "utm_medium")
147
+ val utmMedium: String = "",
148
+
149
+ @Json(name = "utm_campaign")
150
+ val utmCampaign: String = "",
151
+
152
+ @Json(name = "utm_term")
153
+ val utmTerm: String = "",
154
+
155
+ @Json(name = "utm_content")
156
+ val utmContent: String = "",
157
+
158
+ // Click IDs for attribution
159
+ @Json(name = "gclid")
160
+ val gclid: String = "",
161
+
162
+ @Json(name = "fbclid")
163
+ val fbclid: String = "",
164
+
165
+ // Account & Identity
166
+ @Json(name = "acct_type")
167
+ val acctType: String = "anonymous",
168
+
169
+ @Json(name = "dii_source")
170
+ val diiSource: String = "",
171
+
172
+ // Ad platform IDs
173
+ @Json(name = "gam_network_code")
174
+ val gamNetworkCode: String = "",
175
+
176
+ @Json(name = "amzn_pub_id")
177
+ val amznPubId: String = "",
178
+
179
+ // Custom dimensions
180
+ @Json(name = "custom_dimensions")
181
+ val customDimensions: Map<String, String> = emptyMap()
182
+ )
183
+
184
+ /**
185
+ * Individual impression record - nested inside ImpressionBatchEvent
186
+ *
187
+ * Represents a single ad impression with auction/bid data.
188
+ * All string fields default to empty string to ensure they're serialized (server requires all fields).
189
+ */
190
+ @JsonClass(generateAdapter = true)
191
+ internal data class ImpressionRecord(
192
+ /** Placement identifier (slot_id in web SDK) */
193
+ @Json(name = "slot_id")
194
+ val slotId: String,
195
+
196
+ /** GAM ad unit path */
197
+ @Json(name = "gam_unit")
198
+ val gamUnit: String,
199
+
200
+ /** GAM price bucket */
201
+ @Json(name = "gam_price_bucket")
202
+ val gamPriceBucket: String = "",
203
+
204
+ /** Unique identifier for this impression */
205
+ @Json(name = "impression_id")
206
+ val impressionId: String,
207
+
208
+ /** Auction ID (from Prebid) */
209
+ @Json(name = "auction_id")
210
+ val auctionId: String = "",
211
+
212
+ /** Refresh count for this placement */
213
+ @Json(name = "refresh_count")
214
+ val refreshCount: Int = 0,
215
+
216
+ /** Winning bidder name */
217
+ @Json(name = "ad_bidder")
218
+ val adBidder: String = "",
219
+
220
+ /** Ad size (e.g., "320x50") */
221
+ @Json(name = "ad_size")
222
+ val adSize: String = "",
223
+
224
+ /** Bid price/revenue in CPM */
225
+ @Json(name = "ad_price")
226
+ val adPrice: Double = 0.0,
227
+
228
+ /** Floor price */
229
+ @Json(name = "ad_floor_price")
230
+ val adFloorPrice: Double = 0.0,
231
+
232
+ /** Minimum bid to win */
233
+ @Json(name = "min_bid_to_win")
234
+ val minBidToWin: Double = 0.0,
235
+
236
+ /** Advertiser ID from GAM */
237
+ @Json(name = "advertiser_id")
238
+ val advertiserId: String = "",
239
+
240
+ /** Campaign ID from GAM */
241
+ @Json(name = "campaign_id")
242
+ val campaignId: String = "",
243
+
244
+ /** Line item ID from GAM */
245
+ @Json(name = "line_item_id")
246
+ val lineItemId: String = "",
247
+
248
+ /** Creative ID from GAM response */
249
+ @Json(name = "creative_id")
250
+ val creativeId: String = "",
251
+
252
+ /** Amazon bid data (if applicable) */
253
+ @Json(name = "ad_amznbid")
254
+ val adAmznbid: String = "",
255
+
256
+ /** Amazon price data (if applicable) */
257
+ @Json(name = "ad_amznp")
258
+ val adAmznp: String = "",
259
+
260
+ /** Demand type (banner, video, etc.) */
261
+ @Json(name = "ad_demand_type")
262
+ val adDemandType: String = "",
263
+
264
+ /** Demand channel (e.g., "Prebid Header", "GAM Direct") */
265
+ @Json(name = "demand_channel")
266
+ val demandChannel: String = "",
267
+
268
+ /** Slot-level custom dimensions */
269
+ @Json(name = "custom_dimensions")
270
+ val customDimensions: Map<String, String> = emptyMap()
271
+ )
272
+
273
+ /**
274
+ * Impression batch event - matches `/impressions` endpoint
275
+ *
276
+ * Contains session/page context with nested array of impression records.
277
+ * This matches the web SDK format where impressions are batched with pageview context.
278
+ */
279
+ @JsonClass(generateAdapter = true)
280
+ internal data class ImpressionBatchEvent(
281
+ // Web schema common fields
282
+ @Json(name = "payload_version")
283
+ val payloadVersion: String,
284
+
285
+ @Json(name = "config_version")
286
+ val configVersion: Int = 1,
287
+
288
+ @Json(name = "browser_timestamp")
289
+ val browserTimestamp: String, // ISO 8601
290
+
291
+ // Session/user context
292
+ @Json(name = "session_id")
293
+ val sessionId: String,
294
+
295
+ @Json(name = "user_id")
296
+ val userId: String,
297
+
298
+ @Json(name = "property_id")
299
+ val propertyId: String,
300
+
301
+ @Json(name = "new_user")
302
+ val newUser: Boolean,
303
+
304
+ @Json(name = "page_id")
305
+ val pageId: String,
306
+
307
+ @Json(name = "session_depth")
308
+ val sessionDepth: Int,
309
+
310
+ @Json(name = "page_url")
311
+ val pageUrl: String = "",
312
+
313
+ @Json(name = "page_search")
314
+ val pageSearch: String = "",
315
+
316
+ @Json(name = "page_referrer")
317
+ val pageReferrer: String = "",
318
+
319
+ // Device context (flattened)
320
+ @Json(name = "browser")
321
+ val browser: String,
322
+
323
+ @Json(name = "device")
324
+ val device: String,
325
+
326
+ @Json(name = "os")
327
+ val os: String,
328
+
329
+ @Json(name = "country")
330
+ val country: String,
331
+
332
+ @Json(name = "region")
333
+ val region: String,
334
+
335
+ // Attribution
336
+ @Json(name = "session_source")
337
+ val sessionSource: String,
338
+
339
+ @Json(name = "session_medium")
340
+ val sessionMedium: String,
341
+
342
+ @Json(name = "utm_source")
343
+ val utmSource: String = "",
344
+
345
+ @Json(name = "utm_medium")
346
+ val utmMedium: String = "",
347
+
348
+ @Json(name = "utm_campaign")
349
+ val utmCampaign: String = "",
350
+
351
+ @Json(name = "utm_term")
352
+ val utmTerm: String = "",
353
+
354
+ @Json(name = "utm_content")
355
+ val utmContent: String = "",
356
+
357
+ // Click IDs for attribution
358
+ @Json(name = "gclid")
359
+ val gclid: String = "",
360
+
361
+ @Json(name = "fbclid")
362
+ val fbclid: String = "",
363
+
364
+ // Account & Identity
365
+ @Json(name = "acct_type")
366
+ val acctType: String = "anonymous",
367
+
368
+ @Json(name = "dii_source")
369
+ val diiSource: String = "",
370
+
371
+ // Ad platform IDs
372
+ @Json(name = "gam_network_code")
373
+ val gamNetworkCode: String = "",
374
+
375
+ @Json(name = "amzn_pub_id")
376
+ val amznPubId: String = "",
377
+
378
+ // Custom dimensions
379
+ @Json(name = "custom_dimensions")
380
+ val customDimensions: Map<String, String> = emptyMap(),
381
+
382
+ // Nested impressions array
383
+ @Json(name = "impressions")
384
+ val impressions: List<ImpressionRecord>
385
+ )
386
+
387
+ /**
388
+ * Ad click event - matches `/clicks` endpoint
389
+ *
390
+ * Sent when a user clicks on an ad.
391
+ */
392
+ @JsonClass(generateAdapter = true)
393
+ internal data class ClickEvent(
394
+ // Web schema common fields
395
+ @Json(name = "payload_version")
396
+ val payloadVersion: String,
397
+
398
+ @Json(name = "config_version")
399
+ val configVersion: Int = 1,
400
+
401
+ @Json(name = "browser_timestamp")
402
+ val browserTimestamp: String, // ISO 8601
403
+
404
+ // Session/user context
405
+ @Json(name = "session_id")
406
+ val sessionId: String,
407
+
408
+ @Json(name = "user_id")
409
+ val userId: String,
410
+
411
+ @Json(name = "property_id")
412
+ val propertyId: String,
413
+
414
+ @Json(name = "new_user")
415
+ val newUser: Boolean,
416
+
417
+ @Json(name = "page_id")
418
+ val pageId: String,
419
+
420
+ @Json(name = "session_depth")
421
+ val sessionDepth: Int,
422
+
423
+ @Json(name = "page_url")
424
+ val pageUrl: String = "",
425
+
426
+ @Json(name = "page_search")
427
+ val pageSearch: String = "",
428
+
429
+ @Json(name = "page_referrer")
430
+ val pageReferrer: String = "",
431
+
432
+ // Device context (flattened)
433
+ @Json(name = "browser")
434
+ val browser: String,
435
+
436
+ @Json(name = "device")
437
+ val device: String,
438
+
439
+ @Json(name = "os")
440
+ val os: String,
441
+
442
+ @Json(name = "country")
443
+ val country: String,
444
+
445
+ @Json(name = "region")
446
+ val region: String,
447
+
448
+ // Attribution
449
+ @Json(name = "session_source")
450
+ val sessionSource: String,
451
+
452
+ @Json(name = "session_medium")
453
+ val sessionMedium: String,
454
+
455
+ @Json(name = "utm_source")
456
+ val utmSource: String = "",
457
+
458
+ @Json(name = "utm_medium")
459
+ val utmMedium: String = "",
460
+
461
+ @Json(name = "utm_campaign")
462
+ val utmCampaign: String = "",
463
+
464
+ @Json(name = "utm_term")
465
+ val utmTerm: String = "",
466
+
467
+ @Json(name = "utm_content")
468
+ val utmContent: String = "",
469
+
470
+ // Click IDs for attribution
471
+ @Json(name = "gclid")
472
+ val gclid: String = "",
473
+
474
+ @Json(name = "fbclid")
475
+ val fbclid: String = "",
476
+
477
+ // Account & Identity
478
+ @Json(name = "acct_type")
479
+ val acctType: String = "anonymous",
480
+
481
+ @Json(name = "dii_source")
482
+ val diiSource: String = "",
483
+
484
+ // Ad platform IDs
485
+ @Json(name = "gam_network_code")
486
+ val gamNetworkCode: String = "",
487
+
488
+ @Json(name = "amzn_pub_id")
489
+ val amznPubId: String = "",
490
+
491
+ // Custom dimensions
492
+ @Json(name = "custom_dimensions")
493
+ val customDimensions: Map<String, String> = emptyMap(),
494
+
495
+ // Click-specific fields (nested click object per schema)
496
+ @Json(name = "click")
497
+ val click: ClickData
498
+ )
499
+
500
+ /**
501
+ * Click data object nested inside ClickEvent
502
+ */
503
+ @JsonClass(generateAdapter = true)
504
+ internal data class ClickData(
505
+ @Json(name = "click_id")
506
+ val clickId: String,
507
+
508
+ @Json(name = "slot_id")
509
+ val slotId: String,
510
+
511
+ @Json(name = "impression_id")
512
+ val impressionId: String,
513
+
514
+ @Json(name = "refresh_count")
515
+ val refreshCount: Int = 0,
516
+
517
+ @Json(name = "ad_amznp")
518
+ val adAmznp: String = "",
519
+
520
+ @Json(name = "ad_bidder")
521
+ val adBidder: String = "",
522
+
523
+ @Json(name = "ad_size")
524
+ val adSize: String = "",
525
+
526
+ @Json(name = "advertiser_id")
527
+ val advertiserId: String = "",
528
+
529
+ @Json(name = "campaign_id")
530
+ val campaignId: String = "",
531
+
532
+ @Json(name = "line_item_id")
533
+ val lineItemId: String = "",
534
+
535
+ @Json(name = "creative_id")
536
+ val creativeId: String = "",
537
+
538
+ @Json(name = "ad_demand_type")
539
+ val adDemandType: String = "",
540
+
541
+ @Json(name = "demand_channel")
542
+ val demandChannel: String = "",
543
+
544
+ /** Custom dimensions - values must be string arrays per backend schema */
545
+ @Json(name = "custom_dimensions")
546
+ val customDimensions: Map<String, List<String>> = emptyMap()
547
+ )
548
+
549
+ /**
550
+ * Viewability event - matches `/viewability` endpoint
551
+ *
552
+ * Sent when an ad meets viewability thresholds (e.g., 50% visible for 1 second).
553
+ */
554
+ @JsonClass(generateAdapter = true)
555
+ internal data class ViewabilityEvent(
556
+ // Web schema common fields
557
+ @Json(name = "payload_version")
558
+ val payloadVersion: String,
559
+
560
+ @Json(name = "config_version")
561
+ val configVersion: Int = 1,
562
+
563
+ @Json(name = "browser_timestamp")
564
+ val browserTimestamp: String, // ISO 8601
565
+
566
+ // Session/user context
567
+ @Json(name = "session_id")
568
+ val sessionId: String,
569
+
570
+ @Json(name = "user_id")
571
+ val userId: String,
572
+
573
+ @Json(name = "property_id")
574
+ val propertyId: String,
575
+
576
+ @Json(name = "new_user")
577
+ val newUser: Boolean,
578
+
579
+ @Json(name = "page_id")
580
+ val pageId: String,
581
+
582
+ @Json(name = "session_depth")
583
+ val sessionDepth: Int,
584
+
585
+ @Json(name = "page_url")
586
+ val pageUrl: String = "",
587
+
588
+ @Json(name = "page_search")
589
+ val pageSearch: String = "",
590
+
591
+ @Json(name = "page_referrer")
592
+ val pageReferrer: String = "",
593
+
594
+ // Device context (flattened)
595
+ @Json(name = "browser")
596
+ val browser: String,
597
+
598
+ @Json(name = "device")
599
+ val device: String,
600
+
601
+ @Json(name = "os")
602
+ val os: String,
603
+
604
+ @Json(name = "country")
605
+ val country: String,
606
+
607
+ @Json(name = "region")
608
+ val region: String,
609
+
610
+ // Attribution
611
+ @Json(name = "session_source")
612
+ val sessionSource: String,
613
+
614
+ @Json(name = "session_medium")
615
+ val sessionMedium: String,
616
+
617
+ @Json(name = "utm_source")
618
+ val utmSource: String = "",
619
+
620
+ @Json(name = "utm_medium")
621
+ val utmMedium: String = "",
622
+
623
+ @Json(name = "utm_campaign")
624
+ val utmCampaign: String = "",
625
+
626
+ @Json(name = "utm_term")
627
+ val utmTerm: String = "",
628
+
629
+ @Json(name = "utm_content")
630
+ val utmContent: String = "",
631
+
632
+ // Click IDs for attribution
633
+ @Json(name = "gclid")
634
+ val gclid: String = "",
635
+
636
+ @Json(name = "fbclid")
637
+ val fbclid: String = "",
638
+
639
+ // Account & Identity
640
+ @Json(name = "acct_type")
641
+ val acctType: String = "anonymous",
642
+
643
+ @Json(name = "dii_source")
644
+ val diiSource: String = "",
645
+
646
+ // Ad platform IDs
647
+ @Json(name = "gam_network_code")
648
+ val gamNetworkCode: String = "",
649
+
650
+ @Json(name = "amzn_pub_id")
651
+ val amznPubId: String = "",
652
+
653
+ // Custom dimensions
654
+ @Json(name = "custom_dimensions")
655
+ val customDimensions: Map<String, String> = emptyMap(),
656
+
657
+ // Viewability-specific fields (nested array per schema)
658
+ @Json(name = "viewability")
659
+ val viewability: List<ViewabilityData>
660
+ )
661
+
662
+ /**
663
+ * Viewability data object nested inside ViewabilityEvent
664
+ */
665
+ @JsonClass(generateAdapter = true)
666
+ internal data class ViewabilityData(
667
+ @Json(name = "slot_id")
668
+ val slotId: String,
669
+
670
+ @Json(name = "impression_id")
671
+ val impressionId: String,
672
+
673
+ @Json(name = "refresh_count")
674
+ val refreshCount: Int = 0,
675
+
676
+ @Json(name = "ad_amznp")
677
+ val adAmznp: String = "",
678
+
679
+ @Json(name = "ad_bidder")
680
+ val adBidder: String = "",
681
+
682
+ @Json(name = "ad_size")
683
+ val adSize: String = "",
684
+
685
+ @Json(name = "advertiser_id")
686
+ val advertiserId: String = "",
687
+
688
+ @Json(name = "campaign_id")
689
+ val campaignId: String = "",
690
+
691
+ @Json(name = "line_item_id")
692
+ val lineItemId: String = "",
693
+
694
+ @Json(name = "creative_id")
695
+ val creativeId: String = "",
696
+
697
+ @Json(name = "ad_demand_type")
698
+ val adDemandType: String = "",
699
+
700
+ @Json(name = "demand_channel")
701
+ val demandChannel: String = "",
702
+
703
+ /** Custom dimensions - values must be string arrays per backend schema */
704
+ @Json(name = "custom_dimensions")
705
+ val customDimensions: Map<String, List<String>> = emptyMap()
706
+ )
707
+
708
+ /**
709
+ * Engagement event - matches `/engagement` endpoint
710
+ *
711
+ * Tracks user engagement with content/screens.
712
+ */
713
+ @JsonClass(generateAdapter = true)
714
+ internal data class EngagementEvent(
715
+ // Web schema common fields
716
+ @Json(name = "payload_version")
717
+ val payloadVersion: String,
718
+
719
+ @Json(name = "config_version")
720
+ val configVersion: Int = 1,
721
+
722
+ @Json(name = "browser_timestamp")
723
+ val browserTimestamp: String, // ISO 8601
724
+
725
+ // Session/user context
726
+ @Json(name = "session_id")
727
+ val sessionId: String,
728
+
729
+ @Json(name = "user_id")
730
+ val userId: String,
731
+
732
+ @Json(name = "property_id")
733
+ val propertyId: String,
734
+
735
+ @Json(name = "new_user")
736
+ val newUser: Boolean,
737
+
738
+ @Json(name = "page_id")
739
+ val pageId: String,
740
+
741
+ @Json(name = "session_depth")
742
+ val sessionDepth: Int,
743
+
744
+ @Json(name = "page_url")
745
+ val pageUrl: String = "",
746
+
747
+ @Json(name = "page_search")
748
+ val pageSearch: String = "",
749
+
750
+ @Json(name = "page_referrer")
751
+ val pageReferrer: String = "",
752
+
753
+ // Device context (flattened)
754
+ @Json(name = "browser")
755
+ val browser: String,
756
+
757
+ @Json(name = "device")
758
+ val device: String,
759
+
760
+ @Json(name = "os")
761
+ val os: String,
762
+
763
+ @Json(name = "country")
764
+ val country: String,
765
+
766
+ @Json(name = "region")
767
+ val region: String,
768
+
769
+ // Attribution
770
+ @Json(name = "session_source")
771
+ val sessionSource: String,
772
+
773
+ @Json(name = "session_medium")
774
+ val sessionMedium: String,
775
+
776
+ @Json(name = "utm_source")
777
+ val utmSource: String = "",
778
+
779
+ @Json(name = "utm_medium")
780
+ val utmMedium: String = "",
781
+
782
+ @Json(name = "utm_campaign")
783
+ val utmCampaign: String = "",
784
+
785
+ @Json(name = "utm_term")
786
+ val utmTerm: String = "",
787
+
788
+ @Json(name = "utm_content")
789
+ val utmContent: String = "",
790
+
791
+ // Click IDs for attribution
792
+ @Json(name = "gclid")
793
+ val gclid: String = "",
794
+
795
+ @Json(name = "fbclid")
796
+ val fbclid: String = "",
797
+
798
+ // Account & Identity
799
+ @Json(name = "acct_type")
800
+ val acctType: String = "anonymous",
801
+
802
+ @Json(name = "dii_source")
803
+ val diiSource: String = "",
804
+
805
+ // Ad platform IDs
806
+ @Json(name = "gam_network_code")
807
+ val gamNetworkCode: String = "",
808
+
809
+ @Json(name = "amzn_pub_id")
810
+ val amznPubId: String = "",
811
+
812
+ /** Custom dimensions - values must be string arrays per backend schema */
813
+ @Json(name = "custom_dimensions")
814
+ val customDimensions: Map<String, List<String>> = emptyMap(),
815
+
816
+ // Engagement-specific fields
817
+ /** Time actively engaged in seconds */
818
+ @Json(name = "engaged_time")
819
+ val engagedTime: Int,
820
+
821
+ /** Total time on page/screen in seconds */
822
+ @Json(name = "time_on_page")
823
+ val timeOnPage: Int,
824
+
825
+ /** Maximum scroll depth percentage (0-100) */
826
+ @Json(name = "scroll_depth")
827
+ val scrollDepth: Int = 0
828
+ )
829
+
830
+ // MARK: - Auction Data
831
+
832
+ /**
833
+ * Auction data from Prebid response
834
+ *
835
+ * Captures bidding information for analytics.
836
+ */
837
+ internal data class AuctionData(
838
+ /** Prebid auction ID */
839
+ val auctionId: String? = null,
840
+
841
+ /** Winning bidder name */
842
+ val bidder: String? = null,
843
+
844
+ /** Winning bid price in CPM */
845
+ val bidPriceCpm: Double? = null,
846
+
847
+ /** Creative ID from winning bid */
848
+ val creativeId: String? = null
849
+ ) {
850
+ companion object {
851
+ val EMPTY = AuctionData()
852
+ }
853
+ }
854
+
855
+ // MARK: - Impression Context
856
+
857
+ /**
858
+ * Context for tracking an ad impression
859
+ *
860
+ * Aggregates all data needed to track an impression through its lifecycle.
861
+ */
862
+ internal data class ImpressionContext(
863
+ /** Unique impression identifier */
864
+ val impressionId: String = UUID.randomUUID().toString(),
865
+
866
+ /** Placement configuration */
867
+ val placementId: String,
868
+ val gamAdUnit: String,
869
+ val format: String,
870
+
871
+ /** Size (for banners) */
872
+ val width: Int? = null,
873
+ val height: Int? = null,
874
+
875
+ /** Auction data (populated after Prebid response) */
876
+ var auctionData: AuctionData = AuctionData.EMPTY,
877
+
878
+ /** Timestamp when impression was created */
879
+ val createdAt: Long = System.currentTimeMillis()
880
+ )