@sellwild/react-native-sdk 1.7.0 → 1.7.2

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.
@@ -152,6 +152,10 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
152
152
  }
153
153
 
154
154
  override fun onDropViewInstance(view: SellwildAdView) {
155
+ // Tear down the native ad view on unmount so its GMA / Prebid banners are
156
+ // released deterministically. Without this the views leak until GC (iOS
157
+ // RN gets this for free via ARC deinit). Mirrors SellwildFeedView cleanup.
158
+ view.destroy()
155
159
  pending.remove(view)
156
160
  super.onDropViewInstance(view)
157
161
  }
@@ -11,6 +11,7 @@ import com.facebook.react.uimanager.annotations.ReactProp
11
11
  import com.facebook.react.uimanager.events.RCTEventEmitter
12
12
  import com.sellwild.sdk.SellwildConfig
13
13
  import com.sellwild.sdk.SellwildFeedView
14
+ import com.sellwild.sdk.SellwildGrowthCodeConfig
14
15
  import com.sellwild.sdk.SellwildListing
15
16
  import com.sellwild.sdk.SellwildLocalizedListingsConfig
16
17
  import com.sellwild.sdk.SellwildSDK
@@ -82,6 +83,11 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
82
83
  emit(reactContext, view, "onFeedLoaded", null)
83
84
  }
84
85
 
86
+ override fun onFeedReady(listingCount: Int) {
87
+ val payload = Arguments.createMap().apply { putInt("listingCount", listingCount) }
88
+ emit(reactContext, view, "onFeedReady", payload)
89
+ }
90
+
85
91
  override fun onError(message: String) {
86
92
  val payload = Arguments.createMap().apply { putString("message", message) }
87
93
  emit(reactContext, view, "onFeedError", payload)
@@ -131,6 +137,7 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
131
137
  override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
132
138
  return MapBuilder.builder<String, Any>()
133
139
  .put("onFeedLoaded", MapBuilder.of("registrationName", "onFeedLoaded"))
140
+ .put("onFeedReady", MapBuilder.of("registrationName", "onFeedReady"))
134
141
  .put("onListingTap", MapBuilder.of("registrationName", "onListingTap"))
135
142
  .put("onAdImpression", MapBuilder.of("registrationName", "onAdImpression"))
136
143
  .put("onHouseAdImpression", MapBuilder.of("registrationName", "onHouseAdImpression"))
@@ -185,11 +192,52 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
185
192
  if (map.hasKey("adRefreshMax")) config = config.copy(adRefreshMax = map.getInt("adRefreshMax"))
186
193
  if (map.hasKey("adRefreshMaxMobile")) config = config.copy(adRefreshMaxMobile = map.getInt("adRefreshMaxMobile"))
187
194
  if (map.hasKey("adRefreshIntervalMs")) config = config.copy(adRefreshIntervalMs = map.getDouble("adRefreshIntervalMs").toLong())
195
+
196
+ // Flat identity/display/zone fields — mirror the iOS feed bridge so
197
+ // static buildConfig() and overrides land identically. SellwildSDK.apply
198
+ // (above) resolves these from `remote` when present; these overlay on top.
199
+ if (map.hasKey("slug")) config = config.copy(slug = map.getString("slug") ?: config.slug)
200
+ if (map.hasKey("listingsUrl")) config = config.copy(listingsUrl = map.getString("listingsUrl"))
201
+ if (map.hasKey("priceColor")) config = config.copy(priceColor = map.getString("priceColor") ?: config.priceColor)
202
+ if (map.hasKey("bannerZid")) config = config.copy(bannerZid = map.getString("bannerZid"))
203
+ if (map.hasKey("bottomBannerZid")) config = config.copy(bottomBannerZid = map.getString("bottomBannerZid"))
204
+ // mobileZids / mobileBannerZid are OS-suffix-resolved by SellwildSDK.apply
205
+ // when `remote` is present; only fall back to the flat (OS-agnostic) JS
206
+ // values when there was no remote payload to resolve from (parity w/ iOS).
207
+ if (!map.hasKey("remote") || map.isNull("remote")) {
208
+ if (map.hasKey("mobileBannerZid")) config = config.copy(mobileBannerZid = map.getString("mobileBannerZid"))
209
+ if (map.hasKey("mobileZids") && !map.isNull("mobileZids")) {
210
+ val arr = map.getArray("mobileZids")
211
+ if (arr != null) config = config.copy(mobileZids = (0 until arr.size()).mapNotNull { arr.getString(it) })
212
+ }
213
+ }
214
+
188
215
  // Custom Prebid Server (S2S) config — mirror the iOS feed bridge.
189
216
  if (map.hasKey("prebidServer") && !map.isNull("prebidServer")) {
190
217
  config = config.copy(prebidServer = RnPrebidServer.fromMap(map.getMap("prebidServer")))
191
218
  }
192
219
 
220
+ // Local GrowthCode override — parity with the banner bridge and the
221
+ // iOS feed bridge. Without it the feed's ad rows only see remote
222
+ // GROWTHCODE_* keys; a code-supplied partnerId never reaches the
223
+ // auction, so the identity sync never fires for feed ad rows.
224
+ if (map.hasKey("growthCode") && !map.isNull("growthCode")) {
225
+ val gc = map.getMap("growthCode")!!
226
+ fun bool(k: String): Boolean? = if (gc.hasKey(k) && !gc.isNull(k)) gc.getBoolean(k) else null
227
+ fun str(k: String): String? = if (gc.hasKey(k) && !gc.isNull(k)) gc.getString(k) else null
228
+ fun int(k: String): Int? = if (gc.hasKey(k) && !gc.isNull(k)) gc.getInt(k) else null
229
+ config = config.copy(
230
+ growthCode = SellwildGrowthCodeConfig(
231
+ enabled = bool("enabled"),
232
+ partnerId = str("partnerId"),
233
+ endpoint = str("endpoint"),
234
+ syncUrl = str("syncUrl"),
235
+ sendMaid = bool("sendMaid"),
236
+ ttlHours = int("ttlHours"),
237
+ ),
238
+ )
239
+ }
240
+
193
241
  // Local override for the localized (geo-based) secondary-listings
194
242
  // integration; the remote LOCALIZED_LISTINGS object rides `remote`.
195
243
  if (map.hasKey("localizedListings") && !map.isNull("localizedListings")) {
@@ -16,6 +16,7 @@ RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
16
16
 
17
17
  // Direct events emitted to JS callbacks.
18
18
  RCT_EXPORT_VIEW_PROPERTY(onFeedLoaded, RCTDirectEventBlock)
19
+ RCT_EXPORT_VIEW_PROPERTY(onFeedReady, RCTDirectEventBlock)
19
20
  RCT_EXPORT_VIEW_PROPERTY(onListingTap, RCTDirectEventBlock)
20
21
  RCT_EXPORT_VIEW_PROPERTY(onAdImpression, RCTDirectEventBlock)
21
22
  RCT_EXPORT_VIEW_PROPERTY(onHouseAdImpression, RCTDirectEventBlock)
@@ -45,6 +45,9 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
45
45
  // MARK: RN events
46
46
 
47
47
  @objc var onFeedLoaded: RCTDirectEventBlock?
48
+ /// Fires after a successful fetch with the bound listing count
49
+ /// (`listingCount == 0` ⇒ empty / header-only). Reliable "ready" signal.
50
+ @objc var onFeedReady: RCTDirectEventBlock?
48
51
  @objc var onListingTap: RCTDirectEventBlock?
49
52
  @objc var onAdImpression: RCTDirectEventBlock?
50
53
  /// A house ad backfilled an empty feed slot (no-fill). Not a paid impression.
@@ -110,6 +113,10 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
110
113
  onFeedLoaded?([:])
111
114
  }
112
115
 
116
+ func sellwildFeed(_ feed: SellwildFeedView, didBecomeReadyWithListingCount count: Int) {
117
+ onFeedReady?(["listingCount": count])
118
+ }
119
+
113
120
  func sellwildFeed(_ feed: SellwildFeedView, didTapListing listing: SellwildListing) -> Bool {
114
121
  // Forward the listing payload to JS as a plain dictionary. JS
115
122
  // can't return a value back through a direct event block, so
@@ -207,6 +214,21 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
207
214
  )
208
215
  }
209
216
 
217
+ // Local GrowthCode override — parity with the banner bridge. Without
218
+ // this the feed's ad rows only see remote GROWTHCODE_* keys (via
219
+ // remoteJSON); a code-supplied partnerId never reaches the auction, so
220
+ // the identity sync never fires and feed ad rows lose eids enrichment.
221
+ if let gc = map["growthCode"] as? NSDictionary {
222
+ cfg.growthCode = SellwildGrowthCodeConfig(
223
+ enabled: gc["enabled"] as? Bool,
224
+ partnerId: gc["partnerId"] as? String,
225
+ endpoint: gc["endpoint"] as? String,
226
+ syncUrl: gc["syncUrl"] as? String,
227
+ sendMaid: gc["sendMaid"] as? Bool,
228
+ ttlHours: gc["ttlHours"] as? Int
229
+ )
230
+ }
231
+
210
232
  // Local override for the localized (geo-based) secondary-listings
211
233
  // integration; remote LOCALIZED_LISTINGS rides `remote` verbatim.
212
234
  if let ll = map["localizedListings"] as? NSDictionary {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellwild/react-native-sdk",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "Sellwild mobile advertising SDK for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -32,6 +32,7 @@ interface NativeFeedProps {
32
32
  scrollEnabled?: boolean
33
33
  style?: StyleProp<ViewStyle>
34
34
  onFeedLoaded?: (e: NativeSyntheticEvent<{}>) => void
35
+ onFeedReady?: (e: NativeSyntheticEvent<{ listingCount: number }>) => void
35
36
  onListingTap?: (e: NativeSyntheticEvent<{ listing: SellwildListing }>) => void
36
37
  onAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
37
38
  onHouseAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
@@ -84,6 +85,14 @@ export interface SellwildFeedProps {
84
85
  /** Fired once after the initial listings fetch resolves successfully. */
85
86
  onLoad?: () => void
86
87
 
88
+ /**
89
+ * Fired after a successful fetch with the number of listings bound to the
90
+ * feed. `listingCount === 0` means an empty / header-only render. Prefer this
91
+ * over `onLoad` when you need to know the feed is actually populated — `onLoad`
92
+ * also fires on an empty result.
93
+ */
94
+ onFeedReady?: (listingCount: number) => void
95
+
87
96
  /**
88
97
  * Fired when a listing card is tapped. Return `true` to consume the
89
98
  * event; return `false` (or omit) to let the SDK open `listing.url`
@@ -113,6 +122,7 @@ export function SellwildFeed({
113
122
  scrollEnabled = true,
114
123
  onContentSizeChange,
115
124
  onLoad,
125
+ onFeedReady,
116
126
  onListingTap,
117
127
  onAdImpression,
118
128
  onHouseAdImpression,
@@ -166,6 +176,9 @@ export function SellwildFeed({
166
176
  prebidServer: config.prebidServer,
167
177
  // Local localized-listings overrides (remote LOCALIZED_LISTINGS rides `remote`).
168
178
  localizedListings: config.localizedListings,
179
+ // Local GrowthCode overrides (remote GROWTHCODE_* rides `remote`) — parity
180
+ // with SellwildBanner so the feed's native auctions honor local overrides too.
181
+ growthCode: config.growthCode,
169
182
  remote: config.remote,
170
183
  listingsUrl: config.listingsUrl,
171
184
  priceColor: config.priceColor,
@@ -188,6 +201,9 @@ export function SellwildFeed({
188
201
  onContentSizeChange?.({ width, height })
189
202
  }}
190
203
  onFeedLoaded={() => onLoad?.()}
204
+ onFeedReady={(e: NativeSyntheticEvent<{ listingCount: number }>) => {
205
+ onFeedReady?.(e.nativeEvent.listingCount)
206
+ }}
191
207
  onListingTap={(e: NativeSyntheticEvent<{ listing: SellwildListing }>) => {
192
208
  onListingTap?.(e.nativeEvent.listing)
193
209
  }}