@sellwild/react-native-sdk 1.5.0 → 1.7.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/src/main/java/com/sellwild/rnsdk/RnPrebidServer.kt +36 -0
- package/android/src/main/java/com/sellwild/rnsdk/SellwildBannerViewManager.kt +53 -0
- package/android/src/main/java/com/sellwild/rnsdk/SellwildFeedViewManager.kt +41 -0
- package/ios/SellwildBannerViewManager.m +1 -0
- package/ios/SellwildBannerViewManager.swift +28 -0
- package/ios/SellwildFeedViewManager.m +3 -0
- package/ios/SellwildFeedViewManager.swift +41 -2
- package/package.json +1 -1
- package/src/SellwildBanner.tsx +12 -0
- package/src/SellwildFeed.tsx +54 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.sellwild.rnsdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
import com.sellwild.sdk.PrebidServerConfig
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Marshals a bridged JS `prebidServer` object (React `ReadableMap`) into the
|
|
8
|
+
* native [PrebidServerConfig]. Lives in the RN module because `ReadableMap` is a
|
|
9
|
+
* React type. Mirrors the iOS bridge (`configFromMap`) so an RN partner's custom
|
|
10
|
+
* Prebid Server (accountId / endpoint / bidders / timeout) routes to the same
|
|
11
|
+
* auction endpoint on both platforms. Returns null when `accountId`/`endpoint`
|
|
12
|
+
* are absent, so `bootstrap()` falls back to `S2S_CONFIG` / the hosted default.
|
|
13
|
+
*/
|
|
14
|
+
internal object RnPrebidServer {
|
|
15
|
+
fun fromMap(map: ReadableMap?): PrebidServerConfig? {
|
|
16
|
+
if (map == null) return null
|
|
17
|
+
fun str(k: String) = if (map.hasKey(k) && !map.isNull(k)) map.getString(k) else null
|
|
18
|
+
val accountId = str("accountId") ?: return null
|
|
19
|
+
val endpoint = str("endpoint") ?: return null
|
|
20
|
+
val bidders = if (map.hasKey("bidders") && !map.isNull("bidders")) {
|
|
21
|
+
map.getArray("bidders")?.let { arr ->
|
|
22
|
+
(0 until arr.size()).mapNotNull { arr.getString(it) }
|
|
23
|
+
} ?: emptyList()
|
|
24
|
+
} else {
|
|
25
|
+
emptyList()
|
|
26
|
+
}
|
|
27
|
+
val timeout = if (map.hasKey("timeout") && !map.isNull("timeout")) map.getInt("timeout") else 1500
|
|
28
|
+
return PrebidServerConfig(
|
|
29
|
+
accountId = accountId,
|
|
30
|
+
endpoint = endpoint,
|
|
31
|
+
bidders = bidders,
|
|
32
|
+
timeout = timeout,
|
|
33
|
+
syncEndpoint = str("syncEndpoint"),
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -11,6 +11,8 @@ import com.sellwild.sdk.AdSize
|
|
|
11
11
|
import com.sellwild.sdk.SellwildAdStack
|
|
12
12
|
import com.sellwild.sdk.SellwildAdView
|
|
13
13
|
import com.sellwild.sdk.SellwildConfig
|
|
14
|
+
import com.sellwild.sdk.SellwildGrowthCodeConfig
|
|
15
|
+
import com.sellwild.sdk.SellwildLocalizedListingsConfig
|
|
14
16
|
import org.json.JSONObject
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -77,6 +79,13 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
|
|
|
77
79
|
emit(reactContext, adView, "onAdImpression", payload)
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
override fun onHouseAdImpression(adView: SellwildAdView, zoneId: String) {
|
|
83
|
+
val payload = com.facebook.react.bridge.Arguments.createMap().apply {
|
|
84
|
+
putString("zoneId", zoneId)
|
|
85
|
+
}
|
|
86
|
+
emit(reactContext, adView, "onHouseAdImpression", payload)
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
override fun onAdClicked(adView: SellwildAdView) {
|
|
81
90
|
emit(reactContext, adView, "onAdClicked", null)
|
|
82
91
|
}
|
|
@@ -154,6 +163,7 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
|
|
|
154
163
|
return MapBuilder.builder<String, Any>()
|
|
155
164
|
.put("onAdLoaded", MapBuilder.of("registrationName", "onAdLoaded"))
|
|
156
165
|
.put("onAdImpression", MapBuilder.of("registrationName", "onAdImpression"))
|
|
166
|
+
.put("onHouseAdImpression", MapBuilder.of("registrationName", "onHouseAdImpression"))
|
|
157
167
|
.put("onAdClicked", MapBuilder.of("registrationName", "onAdClicked"))
|
|
158
168
|
.put("onAdFailed", MapBuilder.of("registrationName", "onAdFailed"))
|
|
159
169
|
.put("onAdResize", MapBuilder.of("registrationName", "onAdResize"))
|
|
@@ -209,6 +219,46 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
|
|
|
209
219
|
null
|
|
210
220
|
}
|
|
211
221
|
|
|
222
|
+
val growthCode = if (map.hasKey("growthCode") && !map.isNull("growthCode")) {
|
|
223
|
+
val gc = map.getMap("growthCode")!!
|
|
224
|
+
fun bool(k: String): Boolean? = if (gc.hasKey(k) && !gc.isNull(k)) gc.getBoolean(k) else null
|
|
225
|
+
fun str(k: String): String? = if (gc.hasKey(k) && !gc.isNull(k)) gc.getString(k) else null
|
|
226
|
+
fun int(k: String): Int? = if (gc.hasKey(k) && !gc.isNull(k)) gc.getInt(k) else null
|
|
227
|
+
SellwildGrowthCodeConfig(
|
|
228
|
+
enabled = bool("enabled"),
|
|
229
|
+
partnerId = str("partnerId"),
|
|
230
|
+
endpoint = str("endpoint"),
|
|
231
|
+
syncUrl = str("syncUrl"),
|
|
232
|
+
sendMaid = bool("sendMaid"),
|
|
233
|
+
ttlHours = int("ttlHours"),
|
|
234
|
+
)
|
|
235
|
+
} else {
|
|
236
|
+
null
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
val localizedListings = if (map.hasKey("localizedListings") && !map.isNull("localizedListings")) {
|
|
240
|
+
val ll = map.getMap("localizedListings")!!
|
|
241
|
+
fun bool(k: String): Boolean? = if (ll.hasKey(k) && !ll.isNull(k)) ll.getBoolean(k) else null
|
|
242
|
+
fun str(k: String): String? = if (ll.hasKey(k) && !ll.isNull(k)) ll.getString(k) else null
|
|
243
|
+
fun int(k: String): Int? = if (ll.hasKey(k) && !ll.isNull(k)) ll.getInt(k) else null
|
|
244
|
+
SellwildLocalizedListingsConfig(
|
|
245
|
+
enabled = bool("enabled"),
|
|
246
|
+
source = str("source"),
|
|
247
|
+
baseUrl = str("baseUrl"),
|
|
248
|
+
urlTemplate = str("urlTemplate"),
|
|
249
|
+
frequency = int("frequency"),
|
|
250
|
+
forceState = str("forceState"),
|
|
251
|
+
)
|
|
252
|
+
} else {
|
|
253
|
+
null
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Custom Prebid Server (S2S) config — mirror the iOS bridge so the
|
|
257
|
+
// same RN app auctions against the partner's PBS on both platforms.
|
|
258
|
+
val prebidServer = RnPrebidServer.fromMap(
|
|
259
|
+
if (map.hasKey("prebidServer") && !map.isNull("prebidServer")) map.getMap("prebidServer") else null
|
|
260
|
+
)
|
|
261
|
+
|
|
212
262
|
return SellwildConfig(
|
|
213
263
|
partnerCode = partnerCode ?: "",
|
|
214
264
|
appBundleId = appBundleId,
|
|
@@ -221,6 +271,9 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
|
|
|
221
271
|
adRefreshMaxMobile = adRefreshMaxMobile,
|
|
222
272
|
adRefreshIntervalMs = adRefreshIntervalMs,
|
|
223
273
|
remoteJson = remoteJson,
|
|
274
|
+
growthCode = growthCode,
|
|
275
|
+
localizedListings = localizedListings,
|
|
276
|
+
prebidServer = prebidServer,
|
|
224
277
|
)
|
|
225
278
|
}
|
|
226
279
|
}
|
|
@@ -12,6 +12,7 @@ import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
|
12
12
|
import com.sellwild.sdk.SellwildConfig
|
|
13
13
|
import com.sellwild.sdk.SellwildFeedView
|
|
14
14
|
import com.sellwild.sdk.SellwildListing
|
|
15
|
+
import com.sellwild.sdk.SellwildLocalizedListingsConfig
|
|
15
16
|
import com.sellwild.sdk.SellwildSDK
|
|
16
17
|
import org.json.JSONObject
|
|
17
18
|
|
|
@@ -67,6 +68,11 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
|
67
68
|
emit(reactContext, view, "onAdImpression", payload)
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
override fun onHouseAdImpression(zoneId: String) {
|
|
72
|
+
val payload = Arguments.createMap().apply { putString("zoneId", zoneId) }
|
|
73
|
+
emit(reactContext, view, "onHouseAdImpression", payload)
|
|
74
|
+
}
|
|
75
|
+
|
|
70
76
|
override fun onAdClicked(zoneId: String) {
|
|
71
77
|
val payload = Arguments.createMap().apply { putString("zoneId", zoneId) }
|
|
72
78
|
emit(reactContext, view, "onAdClicked", payload)
|
|
@@ -80,6 +86,11 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
|
80
86
|
val payload = Arguments.createMap().apply { putString("message", message) }
|
|
81
87
|
emit(reactContext, view, "onFeedError", payload)
|
|
82
88
|
}
|
|
89
|
+
|
|
90
|
+
override fun onContentHeightChanged(feedView: SellwildFeedView, heightDp: Int) {
|
|
91
|
+
val payload = Arguments.createMap().apply { putInt("height", heightDp) }
|
|
92
|
+
emit(reactContext, view, "onContentSizeChange", payload)
|
|
93
|
+
}
|
|
83
94
|
}
|
|
84
95
|
return view
|
|
85
96
|
}
|
|
@@ -89,6 +100,11 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
|
89
100
|
pendingFor(view).config = value
|
|
90
101
|
}
|
|
91
102
|
|
|
103
|
+
@ReactProp(name = "scrollEnabled", defaultBoolean = true)
|
|
104
|
+
fun setScrollEnabled(view: SellwildFeedView, value: Boolean) {
|
|
105
|
+
view.scrollEnabled = value
|
|
106
|
+
}
|
|
107
|
+
|
|
92
108
|
override fun onAfterUpdateTransaction(view: SellwildFeedView) {
|
|
93
109
|
super.onAfterUpdateTransaction(view)
|
|
94
110
|
|
|
@@ -117,8 +133,10 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
|
117
133
|
.put("onFeedLoaded", MapBuilder.of("registrationName", "onFeedLoaded"))
|
|
118
134
|
.put("onListingTap", MapBuilder.of("registrationName", "onListingTap"))
|
|
119
135
|
.put("onAdImpression", MapBuilder.of("registrationName", "onAdImpression"))
|
|
136
|
+
.put("onHouseAdImpression", MapBuilder.of("registrationName", "onHouseAdImpression"))
|
|
120
137
|
.put("onAdClicked", MapBuilder.of("registrationName", "onAdClicked"))
|
|
121
138
|
.put("onFeedError", MapBuilder.of("registrationName", "onFeedError"))
|
|
139
|
+
.put("onContentSizeChange", MapBuilder.of("registrationName", "onContentSizeChange"))
|
|
122
140
|
.build()
|
|
123
141
|
}
|
|
124
142
|
|
|
@@ -167,6 +185,29 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
|
167
185
|
if (map.hasKey("adRefreshMax")) config = config.copy(adRefreshMax = map.getInt("adRefreshMax"))
|
|
168
186
|
if (map.hasKey("adRefreshMaxMobile")) config = config.copy(adRefreshMaxMobile = map.getInt("adRefreshMaxMobile"))
|
|
169
187
|
if (map.hasKey("adRefreshIntervalMs")) config = config.copy(adRefreshIntervalMs = map.getDouble("adRefreshIntervalMs").toLong())
|
|
188
|
+
// Custom Prebid Server (S2S) config — mirror the iOS feed bridge.
|
|
189
|
+
if (map.hasKey("prebidServer") && !map.isNull("prebidServer")) {
|
|
190
|
+
config = config.copy(prebidServer = RnPrebidServer.fromMap(map.getMap("prebidServer")))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Local override for the localized (geo-based) secondary-listings
|
|
194
|
+
// integration; the remote LOCALIZED_LISTINGS object rides `remote`.
|
|
195
|
+
if (map.hasKey("localizedListings") && !map.isNull("localizedListings")) {
|
|
196
|
+
val ll = map.getMap("localizedListings")!!
|
|
197
|
+
fun bool(k: String): Boolean? = if (ll.hasKey(k) && !ll.isNull(k)) ll.getBoolean(k) else null
|
|
198
|
+
fun str(k: String): String? = if (ll.hasKey(k) && !ll.isNull(k)) ll.getString(k) else null
|
|
199
|
+
fun int(k: String): Int? = if (ll.hasKey(k) && !ll.isNull(k)) ll.getInt(k) else null
|
|
200
|
+
config = config.copy(
|
|
201
|
+
localizedListings = SellwildLocalizedListingsConfig(
|
|
202
|
+
enabled = bool("enabled"),
|
|
203
|
+
source = str("source"),
|
|
204
|
+
baseUrl = str("baseUrl"),
|
|
205
|
+
urlTemplate = str("urlTemplate"),
|
|
206
|
+
frequency = int("frequency"),
|
|
207
|
+
forceState = str("forceState"),
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
}
|
|
170
211
|
|
|
171
212
|
return config
|
|
172
213
|
}
|
|
@@ -19,6 +19,7 @@ RCT_EXPORT_VIEW_PROPERTY(adStack, NSString)
|
|
|
19
19
|
// Direct events emitted to JS callbacks.
|
|
20
20
|
RCT_EXPORT_VIEW_PROPERTY(onAdLoaded, RCTDirectEventBlock)
|
|
21
21
|
RCT_EXPORT_VIEW_PROPERTY(onAdImpression, RCTDirectEventBlock)
|
|
22
|
+
RCT_EXPORT_VIEW_PROPERTY(onHouseAdImpression, RCTDirectEventBlock)
|
|
22
23
|
RCT_EXPORT_VIEW_PROPERTY(onAdClicked, RCTDirectEventBlock)
|
|
23
24
|
RCT_EXPORT_VIEW_PROPERTY(onAdFailed, RCTDirectEventBlock)
|
|
24
25
|
RCT_EXPORT_VIEW_PROPERTY(onAdResize, RCTDirectEventBlock)
|
|
@@ -47,6 +47,8 @@ final class SellwildBannerHostView: UIView, SellwildAdViewDelegate {
|
|
|
47
47
|
|
|
48
48
|
@objc var onAdLoaded: RCTDirectEventBlock?
|
|
49
49
|
@objc var onAdImpression: RCTDirectEventBlock?
|
|
50
|
+
/// A house ad backfilled an empty slot (no-fill). Not a paid impression.
|
|
51
|
+
@objc var onHouseAdImpression: RCTDirectEventBlock?
|
|
50
52
|
@objc var onAdClicked: RCTDirectEventBlock?
|
|
51
53
|
@objc var onAdFailed: RCTDirectEventBlock?
|
|
52
54
|
/// Emitted with the rendered creative size so JS can resize the slot.
|
|
@@ -124,6 +126,10 @@ final class SellwildBannerHostView: UIView, SellwildAdViewDelegate {
|
|
|
124
126
|
onAdImpression?(["zoneId": zoneId])
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
func sellwildAdView(_ adView: SellwildAdView, didRecordHouseImpressionForZoneId zoneId: String) {
|
|
130
|
+
onHouseAdImpression?(["zoneId": zoneId])
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
func sellwildAdViewDidRecordClick(_ adView: SellwildAdView) {
|
|
128
134
|
onAdClicked?([:])
|
|
129
135
|
}
|
|
@@ -175,6 +181,28 @@ final class SellwildBannerHostView: UIView, SellwildAdViewDelegate {
|
|
|
175
181
|
)
|
|
176
182
|
}
|
|
177
183
|
|
|
184
|
+
if let gc = map["growthCode"] as? NSDictionary {
|
|
185
|
+
cfg.growthCode = SellwildGrowthCodeConfig(
|
|
186
|
+
enabled: gc["enabled"] as? Bool,
|
|
187
|
+
partnerId: gc["partnerId"] as? String,
|
|
188
|
+
endpoint: gc["endpoint"] as? String,
|
|
189
|
+
syncUrl: gc["syncUrl"] as? String,
|
|
190
|
+
sendMaid: gc["sendMaid"] as? Bool,
|
|
191
|
+
ttlHours: gc["ttlHours"] as? Int
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if let ll = map["localizedListings"] as? NSDictionary {
|
|
196
|
+
cfg.localizedListings = SellwildLocalizedListingsConfig(
|
|
197
|
+
enabled: ll["enabled"] as? Bool,
|
|
198
|
+
source: ll["source"] as? String,
|
|
199
|
+
baseUrl: ll["baseUrl"] as? String,
|
|
200
|
+
urlTemplate: ll["urlTemplate"] as? String,
|
|
201
|
+
frequency: ll["frequency"] as? Int,
|
|
202
|
+
forceState: ll["forceState"] as? String
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
178
206
|
if let remote = map["remote"] as? NSDictionary,
|
|
179
207
|
let data = try? JSONSerialization.data(withJSONObject: remote, options: []) {
|
|
180
208
|
cfg.remoteJSON = data
|
|
@@ -12,12 +12,15 @@
|
|
|
12
12
|
// Props set from JS — these forward to @objc properties on
|
|
13
13
|
// SellwildFeedHostView.
|
|
14
14
|
RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary)
|
|
15
|
+
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
|
|
15
16
|
|
|
16
17
|
// Direct events emitted to JS callbacks.
|
|
17
18
|
RCT_EXPORT_VIEW_PROPERTY(onFeedLoaded, RCTDirectEventBlock)
|
|
18
19
|
RCT_EXPORT_VIEW_PROPERTY(onListingTap, RCTDirectEventBlock)
|
|
19
20
|
RCT_EXPORT_VIEW_PROPERTY(onAdImpression, RCTDirectEventBlock)
|
|
21
|
+
RCT_EXPORT_VIEW_PROPERTY(onHouseAdImpression, RCTDirectEventBlock)
|
|
20
22
|
RCT_EXPORT_VIEW_PROPERTY(onAdClicked, RCTDirectEventBlock)
|
|
21
23
|
RCT_EXPORT_VIEW_PROPERTY(onFeedError, RCTDirectEventBlock)
|
|
24
|
+
RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
|
|
22
25
|
|
|
23
26
|
@end
|
|
@@ -38,14 +38,22 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
38
38
|
// MARK: RN-set props
|
|
39
39
|
|
|
40
40
|
@objc var config: NSDictionary? { didSet { needsApply = true } }
|
|
41
|
+
/// Disable the feed's internal scrolling so it can be embedded inside a
|
|
42
|
+
/// parent scroll view. Applied live and on feed creation.
|
|
43
|
+
@objc var scrollEnabled: Bool = true { didSet { feedView?.scrollEnabled = scrollEnabled } }
|
|
41
44
|
|
|
42
45
|
// MARK: RN events
|
|
43
46
|
|
|
44
47
|
@objc var onFeedLoaded: RCTDirectEventBlock?
|
|
45
48
|
@objc var onListingTap: RCTDirectEventBlock?
|
|
46
49
|
@objc var onAdImpression: RCTDirectEventBlock?
|
|
50
|
+
/// A house ad backfilled an empty feed slot (no-fill). Not a paid impression.
|
|
51
|
+
@objc var onHouseAdImpression: RCTDirectEventBlock?
|
|
47
52
|
@objc var onAdClicked: RCTDirectEventBlock?
|
|
48
53
|
@objc var onFeedError: RCTDirectEventBlock?
|
|
54
|
+
/// Emitted whenever the feed's content height changes so JS can size the
|
|
55
|
+
/// host container when embedding with `scrollEnabled={false}`.
|
|
56
|
+
@objc var onContentSizeChange: RCTDirectEventBlock?
|
|
49
57
|
|
|
50
58
|
// MARK: Internals
|
|
51
59
|
|
|
@@ -83,6 +91,7 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
83
91
|
|
|
84
92
|
let view = SellwildFeedView(config: sellwildConfig)
|
|
85
93
|
view.delegate = self
|
|
94
|
+
view.scrollEnabled = scrollEnabled
|
|
86
95
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
87
96
|
addSubview(view)
|
|
88
97
|
NSLayoutConstraint.activate([
|
|
@@ -118,6 +127,10 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
118
127
|
onAdImpression?(["zoneId": zoneId])
|
|
119
128
|
}
|
|
120
129
|
|
|
130
|
+
func sellwildFeed(_ feed: SellwildFeedView, didRecordHouseAdImpressionForZoneId zoneId: String) {
|
|
131
|
+
onHouseAdImpression?(["zoneId": zoneId])
|
|
132
|
+
}
|
|
133
|
+
|
|
121
134
|
func sellwildFeed(_ feed: SellwildFeedView, didRecordAdClickForZoneId zoneId: String) {
|
|
122
135
|
onAdClicked?(["zoneId": zoneId])
|
|
123
136
|
}
|
|
@@ -126,6 +139,10 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
126
139
|
onFeedError?(["message": message])
|
|
127
140
|
}
|
|
128
141
|
|
|
142
|
+
func sellwildFeed(_ feed: SellwildFeedView, didChangeContentHeight height: CGFloat) {
|
|
143
|
+
onContentSizeChange?(["height": height])
|
|
144
|
+
}
|
|
145
|
+
|
|
129
146
|
// MARK: Config marshalling
|
|
130
147
|
|
|
131
148
|
/// Build a full SellwildConfig from a JS dictionary. The JS side
|
|
@@ -159,8 +176,17 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
159
176
|
if let v = map["priceColor"] as? String { cfg.priceColor = v }
|
|
160
177
|
if let v = map["bannerZid"] as? String { cfg.bannerZid = v }
|
|
161
178
|
if let v = map["bottomBannerZid"] as? String { cfg.bottomBannerZid = v }
|
|
162
|
-
|
|
163
|
-
|
|
179
|
+
|
|
180
|
+
// mobileZids / mobileBannerZid are resolved per-platform (iOS here) by
|
|
181
|
+
// SellwildSDK.apply(_:to:) above, which reads the OS-suffixed CDN keys
|
|
182
|
+
// (MOBILE_ZID_IOS / MOBILE_BANNER_ZID_IOS) out of `remote`. Only fall
|
|
183
|
+
// back to the flat JS-passed values when there was no `remote` payload
|
|
184
|
+
// to resolve from — otherwise the iOS-resolved zones would be clobbered
|
|
185
|
+
// by the unsuffixed values the JS core mapped (which are OS-agnostic).
|
|
186
|
+
if map["remote"] == nil {
|
|
187
|
+
if let v = map["mobileBannerZid"] as? String { cfg.mobileBannerZid = v }
|
|
188
|
+
if let v = map["mobileZids"] as? [String] { cfg.mobileZids = v }
|
|
189
|
+
}
|
|
164
190
|
|
|
165
191
|
// JS bridge passes ms; iOS API is seconds (TimeInterval).
|
|
166
192
|
if let v = map["adRefreshIntervalMs"] as? NSNumber {
|
|
@@ -181,6 +207,19 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
181
207
|
)
|
|
182
208
|
}
|
|
183
209
|
|
|
210
|
+
// Local override for the localized (geo-based) secondary-listings
|
|
211
|
+
// integration; remote LOCALIZED_LISTINGS rides `remote` verbatim.
|
|
212
|
+
if let ll = map["localizedListings"] as? NSDictionary {
|
|
213
|
+
cfg.localizedListings = SellwildLocalizedListingsConfig(
|
|
214
|
+
enabled: ll["enabled"] as? Bool,
|
|
215
|
+
source: ll["source"] as? String,
|
|
216
|
+
baseUrl: ll["baseUrl"] as? String,
|
|
217
|
+
urlTemplate: ll["urlTemplate"] as? String,
|
|
218
|
+
frequency: ll["frequency"] as? Int,
|
|
219
|
+
forceState: ll["forceState"] as? String
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
184
223
|
return cfg
|
|
185
224
|
}
|
|
186
225
|
|
package/package.json
CHANGED
package/src/SellwildBanner.tsx
CHANGED
|
@@ -62,6 +62,7 @@ interface NativeBannerProps {
|
|
|
62
62
|
style?: ViewStyle
|
|
63
63
|
onAdLoaded?: (e: NativeSyntheticEvent<{}>) => void
|
|
64
64
|
onAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
65
|
+
onHouseAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
65
66
|
onAdClicked?: (e: NativeSyntheticEvent<{}>) => void
|
|
66
67
|
onAdFailed?: (e: NativeSyntheticEvent<{ message: string }>) => void
|
|
67
68
|
onAdResize?: (e: NativeSyntheticEvent<{ width: number; height: number }>) => void
|
|
@@ -86,6 +87,11 @@ export interface SellwildBannerProps {
|
|
|
86
87
|
zoneId: number | string
|
|
87
88
|
style?: ViewStyle
|
|
88
89
|
onImpression?: () => void
|
|
90
|
+
/**
|
|
91
|
+
* Fired when a house ad backfilled an empty slot (a no-fill). NOT a paid
|
|
92
|
+
* impression — track it separately. See the `MOBILE_HOUSE_AD_*` config keys.
|
|
93
|
+
*/
|
|
94
|
+
onHouseImpression?: (zoneId: string) => void
|
|
89
95
|
onClick?: () => void
|
|
90
96
|
onError?: (error: Error) => void
|
|
91
97
|
}
|
|
@@ -96,6 +102,7 @@ export function SellwildBanner({
|
|
|
96
102
|
zoneId,
|
|
97
103
|
style,
|
|
98
104
|
onImpression,
|
|
105
|
+
onHouseImpression,
|
|
99
106
|
onClick,
|
|
100
107
|
onError,
|
|
101
108
|
}: SellwildBannerProps) {
|
|
@@ -167,6 +174,10 @@ export function SellwildBanner({
|
|
|
167
174
|
adRefreshMaxMobile: config.adRefreshMaxMobile,
|
|
168
175
|
adRefreshIntervalMs: config.adRefreshInterval,
|
|
169
176
|
prebidServer: config.prebidServer,
|
|
177
|
+
// Local GrowthCode overrides (remote GROWTHCODE_* keys ride `remote`).
|
|
178
|
+
growthCode: config.growthCode,
|
|
179
|
+
// Local localized-listings overrides (remote LOCALIZED_LISTINGS rides `remote`).
|
|
180
|
+
localizedListings: config.localizedListings,
|
|
170
181
|
remote: config.remote,
|
|
171
182
|
}
|
|
172
183
|
|
|
@@ -181,6 +192,7 @@ export function SellwildBanner({
|
|
|
181
192
|
// No-op event hook today; surfaced for future fill metrics.
|
|
182
193
|
}}
|
|
183
194
|
onAdImpression={() => onImpression?.()}
|
|
195
|
+
onHouseAdImpression={(e: NativeSyntheticEvent<{ zoneId: string }>) => onHouseImpression?.(e.nativeEvent?.zoneId)}
|
|
184
196
|
onAdClicked={() => onClick?.()}
|
|
185
197
|
onAdFailed={(e: NativeSyntheticEvent<{ message: string }>) => {
|
|
186
198
|
const msg = e.nativeEvent?.message ?? 'Ad failed'
|
package/src/SellwildFeed.tsx
CHANGED
|
@@ -29,12 +29,15 @@ const NATIVE_NAME = 'SellwildFeedView'
|
|
|
29
29
|
|
|
30
30
|
interface NativeFeedProps {
|
|
31
31
|
config: object
|
|
32
|
+
scrollEnabled?: boolean
|
|
32
33
|
style?: StyleProp<ViewStyle>
|
|
33
34
|
onFeedLoaded?: (e: NativeSyntheticEvent<{}>) => void
|
|
34
35
|
onListingTap?: (e: NativeSyntheticEvent<{ listing: SellwildListing }>) => void
|
|
35
36
|
onAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
37
|
+
onHouseAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
36
38
|
onAdClicked?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
37
39
|
onFeedError?: (e: NativeSyntheticEvent<{ message: string }>) => void
|
|
40
|
+
onContentSizeChange?: (e: NativeSyntheticEvent<{ width?: number; height: number }>) => void
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
const NativeFeed = (() => {
|
|
@@ -60,6 +63,24 @@ export interface SellwildFeedProps {
|
|
|
60
63
|
/** Optional style override. The feed expands to its container by default. */
|
|
61
64
|
style?: ViewStyle
|
|
62
65
|
|
|
66
|
+
/**
|
|
67
|
+
* When `false`, the feed's own scrolling is disabled and the component
|
|
68
|
+
* sizes its own container from the reported content height, so it can be
|
|
69
|
+
* embedded inside a parent `ScrollView` (single-scroll pages, e.g.
|
|
70
|
+
* alongside a Taboola feed). The feed then renders every row (no
|
|
71
|
+
* virtualization) and pull-to-refresh is disabled — the host drives
|
|
72
|
+
* refresh. Defaults to `true`; existing full-screen integrations are
|
|
73
|
+
* unaffected.
|
|
74
|
+
*/
|
|
75
|
+
scrollEnabled?: boolean
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Fired whenever the feed's content height changes. Use it to size the
|
|
79
|
+
* feed's container when embedding with `scrollEnabled={false}`; when
|
|
80
|
+
* scroll is disabled the component also applies the height itself.
|
|
81
|
+
*/
|
|
82
|
+
onContentSizeChange?: (e: { width?: number; height: number }) => void
|
|
83
|
+
|
|
63
84
|
/** Fired once after the initial listings fetch resolves successfully. */
|
|
64
85
|
onLoad?: () => void
|
|
65
86
|
|
|
@@ -73,6 +94,12 @@ export interface SellwildFeedProps {
|
|
|
73
94
|
/** Fired when a native ad row records an impression. */
|
|
74
95
|
onAdImpression?: (zoneId: string) => void
|
|
75
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Fired when a house ad backfilled an empty ad row (a no-fill). NOT a paid
|
|
99
|
+
* impression — track it separately. See the `MOBILE_HOUSE_AD_*` config keys.
|
|
100
|
+
*/
|
|
101
|
+
onHouseAdImpression?: (zoneId: string) => void
|
|
102
|
+
|
|
76
103
|
/** Fired when a native ad row is clicked. */
|
|
77
104
|
onAdClicked?: (zoneId: string) => void
|
|
78
105
|
|
|
@@ -83,18 +110,30 @@ export interface SellwildFeedProps {
|
|
|
83
110
|
export function SellwildFeed({
|
|
84
111
|
config,
|
|
85
112
|
style,
|
|
113
|
+
scrollEnabled = true,
|
|
114
|
+
onContentSizeChange,
|
|
86
115
|
onLoad,
|
|
87
116
|
onListingTap,
|
|
88
117
|
onAdImpression,
|
|
118
|
+
onHouseAdImpression,
|
|
89
119
|
onAdClicked,
|
|
90
120
|
onError,
|
|
91
121
|
}: SellwildFeedProps) {
|
|
122
|
+
// When embedded (scrollEnabled === false) the parent ScrollView owns
|
|
123
|
+
// scrolling, so we size our own container from the native-reported content
|
|
124
|
+
// height rather than filling with flex:1.
|
|
125
|
+
const [contentHeight, setContentHeight] = React.useState<number | null>(null)
|
|
126
|
+
const embedded = scrollEnabled === false
|
|
127
|
+
const containerStyle: StyleProp<ViewStyle> = embedded
|
|
128
|
+
? [contentHeight != null ? { height: contentHeight } : undefined, style]
|
|
129
|
+
: [styles.fill, style]
|
|
130
|
+
|
|
92
131
|
if (!NativeFeed) {
|
|
93
132
|
// Native module not registered. Most common cause: the host app was
|
|
94
133
|
// built before the @sellwild/react-native-sdk autolink ran, or this
|
|
95
134
|
// is being rendered in a JS-only test environment.
|
|
96
135
|
return (
|
|
97
|
-
<View style={[
|
|
136
|
+
<View style={[containerStyle, __DEV__ ? styles.devPlaceholder : undefined]}>
|
|
98
137
|
{__DEV__ ? (
|
|
99
138
|
<Text style={styles.devText}>
|
|
100
139
|
Sellwild native feed not available on {Platform.OS} (yet)
|
|
@@ -125,6 +164,8 @@ export function SellwildFeed({
|
|
|
125
164
|
adRefreshMaxMobile: config.adRefreshMaxMobile,
|
|
126
165
|
adRefreshIntervalMs: config.adRefreshInterval,
|
|
127
166
|
prebidServer: config.prebidServer,
|
|
167
|
+
// Local localized-listings overrides (remote LOCALIZED_LISTINGS rides `remote`).
|
|
168
|
+
localizedListings: config.localizedListings,
|
|
128
169
|
remote: config.remote,
|
|
129
170
|
listingsUrl: config.listingsUrl,
|
|
130
171
|
priceColor: config.priceColor,
|
|
@@ -136,8 +177,16 @@ export function SellwildFeed({
|
|
|
136
177
|
|
|
137
178
|
return (
|
|
138
179
|
<NativeFeed
|
|
139
|
-
style={
|
|
180
|
+
style={containerStyle}
|
|
140
181
|
config={nativeConfig}
|
|
182
|
+
scrollEnabled={scrollEnabled}
|
|
183
|
+
onContentSizeChange={(e: NativeSyntheticEvent<{ width?: number; height: number }>) => {
|
|
184
|
+
const { width, height } = e.nativeEvent ?? { height: 0 }
|
|
185
|
+
// Only size our own container when embedded; a scrolling feed fills
|
|
186
|
+
// its parent via flex:1 and doesn't need a measured height.
|
|
187
|
+
if (embedded && height > 0) setContentHeight(height)
|
|
188
|
+
onContentSizeChange?.({ width, height })
|
|
189
|
+
}}
|
|
141
190
|
onFeedLoaded={() => onLoad?.()}
|
|
142
191
|
onListingTap={(e: NativeSyntheticEvent<{ listing: SellwildListing }>) => {
|
|
143
192
|
onListingTap?.(e.nativeEvent.listing)
|
|
@@ -145,6 +194,9 @@ export function SellwildFeed({
|
|
|
145
194
|
onAdImpression={(e: NativeSyntheticEvent<{ zoneId: string }>) => {
|
|
146
195
|
onAdImpression?.(e.nativeEvent.zoneId)
|
|
147
196
|
}}
|
|
197
|
+
onHouseAdImpression={(e: NativeSyntheticEvent<{ zoneId: string }>) => {
|
|
198
|
+
onHouseAdImpression?.(e.nativeEvent.zoneId)
|
|
199
|
+
}}
|
|
148
200
|
onAdClicked={(e: NativeSyntheticEvent<{ zoneId: string }>) => {
|
|
149
201
|
onAdClicked?.(e.nativeEvent.zoneId)
|
|
150
202
|
}}
|