@sellwild/react-native-sdk 1.4.0 → 1.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.
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/sellwild/rnsdk/RnGeo.kt +31 -0
- package/android/src/main/java/com/sellwild/rnsdk/SellwildBannerViewManager.kt +15 -0
- package/android/src/main/java/com/sellwild/rnsdk/SellwildFeedViewManager.kt +2 -0
- package/android/src/main/java/com/sellwild/rnsdk/SellwildModule.kt +67 -0
- package/android/src/main/java/com/sellwild/rnsdk/SellwildSdkPackage.kt +2 -1
- package/ios/SellwildBannerViewManager.m +1 -0
- package/ios/SellwildBannerViewManager.swift +14 -2
- package/ios/SellwildFeedViewManager.swift +2 -0
- package/ios/SellwildRNModule.m +10 -0
- package/ios/SellwildRNModule.swift +42 -0
- package/package.json +1 -1
- package/src/SellwildBanner.tsx +64 -1
- package/src/SellwildFeed.tsx +2 -0
- package/src/commands.ts +37 -0
- package/src/index.ts +6 -0
package/android/build.gradle
CHANGED
|
@@ -76,7 +76,7 @@ dependencies {
|
|
|
76
76
|
// The native SDK that owns SellwildAdView, SellwildPrebidMobile, etc.
|
|
77
77
|
// Resolves to the published AAR off https://maven.sellwild.com/releases
|
|
78
78
|
// for npm consumers, or to mavenLocal in the SDK monorepo dev workflow.
|
|
79
|
-
implementation "com.sellwild:sdk:1.4.
|
|
79
|
+
implementation "com.sellwild:sdk:1.4.1"
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// Force kotlin-stdlib down to the bridge's declared Kotlin version. The
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package com.sellwild.rnsdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
import com.sellwild.sdk.SellwildGeo
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Marshals a bridged JS geo object (React `ReadableMap`) into the native
|
|
8
|
+
* [SellwildGeo]. Lives in the RN module (not core) because `ReadableMap` is a
|
|
9
|
+
* React type — the core SDK must not depend on React. Returns null when nothing
|
|
10
|
+
* usable is present, so callers can clear geo by passing an empty object.
|
|
11
|
+
*/
|
|
12
|
+
internal object RnGeo {
|
|
13
|
+
fun readableMapToGeo(map: ReadableMap?): SellwildGeo? {
|
|
14
|
+
if (map == null) return null
|
|
15
|
+
fun str(k: String) = if (map.hasKey(k) && !map.isNull(k)) map.getString(k) else null
|
|
16
|
+
fun dbl(k: String) = if (map.hasKey(k) && !map.isNull(k)) map.getDouble(k) else null
|
|
17
|
+
fun int(k: String) = if (map.hasKey(k) && !map.isNull(k)) map.getInt(k) else null
|
|
18
|
+
val geo = SellwildGeo(
|
|
19
|
+
country = str("country"),
|
|
20
|
+
state = str("state"),
|
|
21
|
+
city = str("city"),
|
|
22
|
+
zip = str("zip"),
|
|
23
|
+
metro = str("metro"),
|
|
24
|
+
lat = dbl("lat"),
|
|
25
|
+
lon = dbl("lon"),
|
|
26
|
+
type = int("type"),
|
|
27
|
+
)
|
|
28
|
+
// toOrtbGeo() returns null when every field is empty — treat that as "no geo".
|
|
29
|
+
return if (geo.toOrtbGeo() != null) geo else null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -87,6 +87,14 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
|
|
|
87
87
|
}
|
|
88
88
|
emit(reactContext, adView, "onAdFailed", payload)
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
override fun onAdResize(adView: SellwildAdView, width: Int, height: Int) {
|
|
92
|
+
val payload = com.facebook.react.bridge.Arguments.createMap().apply {
|
|
93
|
+
putInt("width", width)
|
|
94
|
+
putInt("height", height)
|
|
95
|
+
}
|
|
96
|
+
emit(reactContext, adView, "onAdResize", payload)
|
|
97
|
+
}
|
|
90
98
|
}
|
|
91
99
|
return view
|
|
92
100
|
}
|
|
@@ -148,6 +156,7 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
|
|
|
148
156
|
.put("onAdImpression", MapBuilder.of("registrationName", "onAdImpression"))
|
|
149
157
|
.put("onAdClicked", MapBuilder.of("registrationName", "onAdClicked"))
|
|
150
158
|
.put("onAdFailed", MapBuilder.of("registrationName", "onAdFailed"))
|
|
159
|
+
.put("onAdResize", MapBuilder.of("registrationName", "onAdResize"))
|
|
151
160
|
.build()
|
|
152
161
|
}
|
|
153
162
|
|
|
@@ -186,6 +195,10 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
|
|
|
186
195
|
val appStoreUrl = if (map.hasKey("appStoreUrl")) map.getString("appStoreUrl") else null
|
|
187
196
|
val gamTag = if (map.hasKey("gamTag")) map.getString("gamTag") else null
|
|
188
197
|
val debug = map.hasKey("debug") && map.getBoolean("debug")
|
|
198
|
+
val pbsDebug = map.hasKey("pbsDebug") && map.getBoolean("pbsDebug")
|
|
199
|
+
val geo = RnGeo.readableMapToGeo(
|
|
200
|
+
if (map.hasKey("geo") && !map.isNull("geo")) map.getMap("geo") else null
|
|
201
|
+
)
|
|
189
202
|
val adRefreshMax = if (map.hasKey("adRefreshMax")) map.getInt("adRefreshMax") else 0
|
|
190
203
|
val adRefreshMaxMobile = if (map.hasKey("adRefreshMaxMobile")) map.getInt("adRefreshMaxMobile") else 0
|
|
191
204
|
val adRefreshIntervalMs = if (map.hasKey("adRefreshIntervalMs")) map.getDouble("adRefreshIntervalMs").toLong() else 30_000L
|
|
@@ -202,6 +215,8 @@ class SellwildBannerViewManager : SimpleViewManager<SellwildAdView>() {
|
|
|
202
215
|
appStoreUrl = appStoreUrl,
|
|
203
216
|
gamTag = gamTag,
|
|
204
217
|
debug = debug,
|
|
218
|
+
pbsDebug = pbsDebug,
|
|
219
|
+
geo = geo,
|
|
205
220
|
adRefreshMax = adRefreshMax,
|
|
206
221
|
adRefreshMaxMobile = adRefreshMaxMobile,
|
|
207
222
|
adRefreshIntervalMs = adRefreshIntervalMs,
|
|
@@ -162,6 +162,8 @@ class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
|
162
162
|
if (map.hasKey("appStoreUrl")) config = config.copy(appStoreUrl = map.getString("appStoreUrl"))
|
|
163
163
|
if (map.hasKey("gamTag")) config = config.copy(gamTag = map.getString("gamTag"))
|
|
164
164
|
if (map.hasKey("debug")) config = config.copy(debug = map.getBoolean("debug"))
|
|
165
|
+
if (map.hasKey("pbsDebug")) config = config.copy(pbsDebug = map.getBoolean("pbsDebug"))
|
|
166
|
+
if (map.hasKey("geo") && !map.isNull("geo")) config = config.copy(geo = RnGeo.readableMapToGeo(map.getMap("geo")))
|
|
165
167
|
if (map.hasKey("adRefreshMax")) config = config.copy(adRefreshMax = map.getInt("adRefreshMax"))
|
|
166
168
|
if (map.hasKey("adRefreshMaxMobile")) config = config.copy(adRefreshMaxMobile = map.getInt("adRefreshMaxMobile"))
|
|
167
169
|
if (map.hasKey("adRefreshIntervalMs")) config = config.copy(adRefreshIntervalMs = map.getDouble("adRefreshIntervalMs").toLong())
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package com.sellwild.rnsdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
+
import com.facebook.react.bridge.ReactMethod
|
|
6
|
+
import com.facebook.react.bridge.ReadableArray
|
|
7
|
+
import com.facebook.react.bridge.ReadableMap
|
|
8
|
+
import com.sellwild.sdk.SellwildEid
|
|
9
|
+
import com.sellwild.sdk.SellwildEidUid
|
|
10
|
+
import com.sellwild.sdk.SellwildPrebidMobile
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* React Native method module for the native Sellwild SDK's runtime setters.
|
|
14
|
+
*
|
|
15
|
+
* The RN ad surface is otherwise view-manager-only (config flows as a prop);
|
|
16
|
+
* this module is the one callable bridge for imperative, session-scoped calls
|
|
17
|
+
* like [setGeo]. Registered in [SellwildSdkPackage.createNativeModules]. The
|
|
18
|
+
* module name ("SellwildRNModule") matches the iOS `@objc(SellwildRNModule)` so
|
|
19
|
+
* JS resolves one `NativeModules.SellwildRNModule` on both platforms.
|
|
20
|
+
*/
|
|
21
|
+
class SellwildModule(reactContext: ReactApplicationContext) :
|
|
22
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
23
|
+
|
|
24
|
+
override fun getName(): String = "SellwildRNModule"
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* JS: `SellwildRNModule.setGeo({ state: "NY", zip: "10001", ... })`.
|
|
28
|
+
* Pass an empty object to clear. Mirrors [SellwildPrebidMobile.setGeo] —
|
|
29
|
+
* updates the Prebid auction geo AND the shared SellwildGeoStore.
|
|
30
|
+
*/
|
|
31
|
+
@ReactMethod
|
|
32
|
+
fun setGeo(geo: ReadableMap?) {
|
|
33
|
+
SellwildPrebidMobile.setGeo(RnGeo.readableMapToGeo(geo))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* JS: `SellwildRNModule.setExternalUserIds([{ source, uids: [{ id, atype, ext? }] }])`.
|
|
38
|
+
* Pass `[]` to clear. Mirrors [SellwildPrebidMobile.setExternalUserIds].
|
|
39
|
+
*/
|
|
40
|
+
@ReactMethod
|
|
41
|
+
fun setExternalUserIds(eids: ReadableArray?) {
|
|
42
|
+
SellwildPrebidMobile.setExternalUserIds(toEids(eids))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private fun toEids(arr: ReadableArray?): List<SellwildEid> {
|
|
46
|
+
if (arr == null) return emptyList()
|
|
47
|
+
val out = ArrayList<SellwildEid>()
|
|
48
|
+
for (i in 0 until arr.size()) {
|
|
49
|
+
val eid = arr.getMap(i) ?: continue
|
|
50
|
+
val source = if (eid.hasKey("source")) eid.getString("source") else null
|
|
51
|
+
val uidsArr = if (eid.hasKey("uids") && !eid.isNull("uids")) eid.getArray("uids") else null
|
|
52
|
+
if (source == null || uidsArr == null) continue
|
|
53
|
+
val uids = ArrayList<SellwildEidUid>()
|
|
54
|
+
for (j in 0 until uidsArr.size()) {
|
|
55
|
+
val u = uidsArr.getMap(j) ?: continue
|
|
56
|
+
val id = (if (u.hasKey("id")) u.getString("id") else null) ?: continue
|
|
57
|
+
val atype = if (u.hasKey("atype")) u.getInt("atype") else 0
|
|
58
|
+
@Suppress("UNCHECKED_CAST")
|
|
59
|
+
val ext = if (u.hasKey("ext") && !u.isNull("ext"))
|
|
60
|
+
u.getMap("ext")?.toHashMap() as? Map<String, Any> else null
|
|
61
|
+
uids.add(SellwildEidUid(id, atype, ext))
|
|
62
|
+
}
|
|
63
|
+
out.add(SellwildEid(source, uids))
|
|
64
|
+
}
|
|
65
|
+
return out
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -8,7 +8,8 @@ import com.facebook.react.uimanager.ReactShadowNode
|
|
|
8
8
|
import com.facebook.react.uimanager.ViewManager
|
|
9
9
|
|
|
10
10
|
class SellwildSdkPackage : ReactPackage {
|
|
11
|
-
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
11
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
12
|
+
listOf(SellwildModule(reactContext))
|
|
12
13
|
|
|
13
14
|
override fun createViewManagers(
|
|
14
15
|
reactContext: ReactApplicationContext,
|
|
@@ -21,5 +21,6 @@ RCT_EXPORT_VIEW_PROPERTY(onAdLoaded, RCTDirectEventBlock)
|
|
|
21
21
|
RCT_EXPORT_VIEW_PROPERTY(onAdImpression, RCTDirectEventBlock)
|
|
22
22
|
RCT_EXPORT_VIEW_PROPERTY(onAdClicked, RCTDirectEventBlock)
|
|
23
23
|
RCT_EXPORT_VIEW_PROPERTY(onAdFailed, RCTDirectEventBlock)
|
|
24
|
+
RCT_EXPORT_VIEW_PROPERTY(onAdResize, RCTDirectEventBlock)
|
|
24
25
|
|
|
25
26
|
@end
|
|
@@ -49,6 +49,8 @@ final class SellwildBannerHostView: UIView, SellwildAdViewDelegate {
|
|
|
49
49
|
@objc var onAdImpression: RCTDirectEventBlock?
|
|
50
50
|
@objc var onAdClicked: RCTDirectEventBlock?
|
|
51
51
|
@objc var onAdFailed: RCTDirectEventBlock?
|
|
52
|
+
/// Emitted with the rendered creative size so JS can resize the slot.
|
|
53
|
+
@objc var onAdResize: RCTDirectEventBlock?
|
|
52
54
|
|
|
53
55
|
// MARK: Internals
|
|
54
56
|
|
|
@@ -98,11 +100,15 @@ final class SellwildBannerHostView: UIView, SellwildAdViewDelegate {
|
|
|
98
100
|
view.delegate = self
|
|
99
101
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
100
102
|
addSubview(view)
|
|
103
|
+
// Fill the RN-sized host rather than hard-pinning to the primary size —
|
|
104
|
+
// the host's dimensions are driven by JS style, which the JS component
|
|
105
|
+
// updates from the onAdResize event so the slot tracks the actual
|
|
106
|
+
// rendered creative (multi-size fallback, video, capped native).
|
|
101
107
|
NSLayoutConstraint.activate([
|
|
102
108
|
view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
103
109
|
view.topAnchor.constraint(equalTo: topAnchor),
|
|
104
|
-
view.
|
|
105
|
-
view.
|
|
110
|
+
view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
111
|
+
view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
106
112
|
])
|
|
107
113
|
adView = view
|
|
108
114
|
view.load()
|
|
@@ -126,6 +132,10 @@ final class SellwildBannerHostView: UIView, SellwildAdViewDelegate {
|
|
|
126
132
|
onAdFailed?(["message": error.localizedDescription])
|
|
127
133
|
}
|
|
128
134
|
|
|
135
|
+
func sellwildAdView(_ adView: SellwildAdView, didRenderWithSize size: CGSize) {
|
|
136
|
+
onAdResize?(["width": size.width, "height": size.height])
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
// MARK: Config marshalling
|
|
130
140
|
|
|
131
141
|
/// Build a minimal SellwildConfig from a JS dictionary. Only the
|
|
@@ -139,8 +149,10 @@ final class SellwildBannerHostView: UIView, SellwildAdViewDelegate {
|
|
|
139
149
|
|
|
140
150
|
if let v = map["appBundleId"] as? String { cfg.appBundleId = v }
|
|
141
151
|
if let v = map["appStoreUrl"] as? String { cfg.appStoreUrl = v }
|
|
152
|
+
if let geoMap = map["geo"] as? [String: Any] { cfg.geo = SellwildGeo(bridged: geoMap) }
|
|
142
153
|
if let v = map["gamTag"] as? String { cfg.gamTag = v }
|
|
143
154
|
if let v = map["debug"] as? Bool { cfg.debug = v }
|
|
155
|
+
if let v = map["pbsDebug"] as? Bool { cfg.pbsDebug = v }
|
|
144
156
|
if let v = map["adRefreshMax"] as? Int { cfg.adRefreshMax = v }
|
|
145
157
|
if let v = map["adRefreshMaxMobile"] as? Int { cfg.adRefreshMaxMobile = v }
|
|
146
158
|
|
|
@@ -149,8 +149,10 @@ final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
|
149
149
|
if let v = map["slug"] as? String { cfg.slug = v }
|
|
150
150
|
if let v = map["appBundleId"] as? String { cfg.appBundleId = v }
|
|
151
151
|
if let v = map["appStoreUrl"] as? String { cfg.appStoreUrl = v }
|
|
152
|
+
if let geoMap = map["geo"] as? [String: Any] { cfg.geo = SellwildGeo(bridged: geoMap) }
|
|
152
153
|
if let v = map["gamTag"] as? String { cfg.gamTag = v }
|
|
153
154
|
if let v = map["debug"] as? Bool { cfg.debug = v }
|
|
155
|
+
if let v = map["pbsDebug"] as? Bool { cfg.pbsDebug = v }
|
|
154
156
|
if let v = map["adRefreshMax"] as? Int { cfg.adRefreshMax = v }
|
|
155
157
|
if let v = map["adRefreshMaxMobile"] as? Int { cfg.adRefreshMaxMobile = v }
|
|
156
158
|
if let v = map["listingsUrl"] as? String { cfg.listingsUrl = v }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
// Exposes the Swift SellwildRNModule to React Native. Method signatures must
|
|
4
|
+
// match the @objc selectors in SellwildRNModule.swift.
|
|
5
|
+
@interface RCT_EXTERN_MODULE(SellwildRNModule, NSObject)
|
|
6
|
+
|
|
7
|
+
RCT_EXTERN_METHOD(setGeo:(NSDictionary *)geo)
|
|
8
|
+
RCT_EXTERN_METHOD(setExternalUserIds:(NSArray *)eids)
|
|
9
|
+
|
|
10
|
+
@end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import SellwildSDK
|
|
3
|
+
|
|
4
|
+
/// React Native method module for the native Sellwild SDK's runtime setters.
|
|
5
|
+
///
|
|
6
|
+
/// The RN ad surface is otherwise view-manager-only (config flows as a prop);
|
|
7
|
+
/// this module is the one callable bridge for imperative, session-scoped calls
|
|
8
|
+
/// like `setGeo`. Registered on iOS via `RCT_EXTERN_MODULE` (see the paired
|
|
9
|
+
/// `SellwildRNModule.m`); no manual package wiring is needed.
|
|
10
|
+
@objc(SellwildRNModule)
|
|
11
|
+
final class SellwildRNModule: NSObject {
|
|
12
|
+
|
|
13
|
+
/// Off-main is fine — the setters just update process-wide state.
|
|
14
|
+
@objc static func requiresMainQueueSetup() -> Bool { false }
|
|
15
|
+
|
|
16
|
+
/// JS: `SellwildRNModule.setGeo({ state: "NY", zip: "10001", ... })`.
|
|
17
|
+
/// Pass an empty object to clear. Mirrors the native
|
|
18
|
+
/// `SellwildPrebidMobile.setGeo(_:)` — updates the Prebid auction geo AND the
|
|
19
|
+
/// shared `SellwildGeoStore`.
|
|
20
|
+
@objc(setGeo:)
|
|
21
|
+
func setGeo(_ geo: NSDictionary) {
|
|
22
|
+
let map = geo as? [String: Any] ?? [:]
|
|
23
|
+
SellwildPrebidMobile.setGeo(SellwildGeo(bridged: map))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// JS: `SellwildRNModule.setExternalUserIds([{ source, uids: [{ id, atype, ext? }] }])`.
|
|
27
|
+
/// Pass `[]` to clear. Mirrors `SellwildPrebidMobile.setExternalUserIds(_:)`.
|
|
28
|
+
@objc(setExternalUserIds:)
|
|
29
|
+
func setExternalUserIds(_ eids: NSArray) {
|
|
30
|
+
let mapped: [SellwildEid] = (eids as? [[String: Any]] ?? []).compactMap { dict in
|
|
31
|
+
guard let source = dict["source"] as? String,
|
|
32
|
+
let rawUids = dict["uids"] as? [[String: Any]] else { return nil }
|
|
33
|
+
let uids: [SellwildEidUID] = rawUids.compactMap { u in
|
|
34
|
+
guard let id = u["id"] as? String else { return nil }
|
|
35
|
+
let atype = (u["atype"] as? NSNumber)?.intValue ?? 0
|
|
36
|
+
return SellwildEidUID(id: id, atype: atype, ext: u["ext"] as? [String: Any])
|
|
37
|
+
}
|
|
38
|
+
return SellwildEid(source: source, uids: uids)
|
|
39
|
+
}
|
|
40
|
+
SellwildPrebidMobile.setExternalUserIds(mapped)
|
|
41
|
+
}
|
|
42
|
+
}
|
package/package.json
CHANGED
package/src/SellwildBanner.tsx
CHANGED
|
@@ -13,6 +13,29 @@ const AD_DIMENSIONS: Record<AdSize, { width: number; height: number }> = {
|
|
|
13
13
|
'1x1': { width: 1, height: 1 },
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// Parse a BANNER_SIZES value — `["300x250","320x50"]` or `[[300,250],…]`,
|
|
17
|
+
// possibly a JSON string — into {width,height}[]. Mirrors the native
|
|
18
|
+
// SellwildAdSizes parser so the RN slot reasons about the same size set the
|
|
19
|
+
// auction requests.
|
|
20
|
+
function parseSizeList(raw: unknown): Array<{ width: number; height: number }> {
|
|
21
|
+
let arr: unknown = raw
|
|
22
|
+
if (typeof raw === 'string') {
|
|
23
|
+
try { arr = JSON.parse(raw) } catch { arr = [raw] }
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(arr)) return []
|
|
26
|
+
const out: Array<{ width: number; height: number }> = []
|
|
27
|
+
for (const e of arr) {
|
|
28
|
+
if (typeof e === 'string') {
|
|
29
|
+
const [w, h] = e.toLowerCase().split('x').map((s) => Number(s.trim()))
|
|
30
|
+
if (w > 0 && h > 0) out.push({ width: w, height: h })
|
|
31
|
+
} else if (Array.isArray(e) && e.length === 2) {
|
|
32
|
+
const w = Number(e[0]); const h = Number(e[1])
|
|
33
|
+
if (w > 0 && h > 0) out.push({ width: w, height: h })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out
|
|
37
|
+
}
|
|
38
|
+
|
|
16
39
|
// ─── Native component bridge ─────────────────────────────────────────────────
|
|
17
40
|
//
|
|
18
41
|
// As of 1.3.0 the React Native banner is backed by a real native ad view —
|
|
@@ -41,6 +64,7 @@ interface NativeBannerProps {
|
|
|
41
64
|
onAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
42
65
|
onAdClicked?: (e: NativeSyntheticEvent<{}>) => void
|
|
43
66
|
onAdFailed?: (e: NativeSyntheticEvent<{ message: string }>) => void
|
|
67
|
+
onAdResize?: (e: NativeSyntheticEvent<{ width: number; height: number }>) => void
|
|
44
68
|
}
|
|
45
69
|
|
|
46
70
|
const NativeBanner = (() => {
|
|
@@ -76,7 +100,40 @@ export function SellwildBanner({
|
|
|
76
100
|
onError,
|
|
77
101
|
}: SellwildBannerProps) {
|
|
78
102
|
const dim = AD_DIMENSIONS[size]
|
|
79
|
-
|
|
103
|
+
|
|
104
|
+
// The widest/tallest size the auction may return for this placement: the
|
|
105
|
+
// primary plus any BANNER_SIZES / BANNER_SIZES_BY_ZONE fallbacks. We reserve
|
|
106
|
+
// this as the slot's baseline so a wider or taller fallback creative — e.g. a
|
|
107
|
+
// 320x50 winning a 300-wide MREC request, where 320 > 300 — never clips
|
|
108
|
+
// before, or without, the onAdResize callback. This also covers the Android
|
|
109
|
+
// prebidOnly path, whose rendering BannerView doesn't surface the winning
|
|
110
|
+
// creative size (so onAdResize can't shrink it back down there).
|
|
111
|
+
const baseline = React.useMemo(() => {
|
|
112
|
+
const remote = (config.remote ?? {}) as Record<string, unknown>
|
|
113
|
+
const byZone = remote['BANNER_SIZES_BY_ZONE']
|
|
114
|
+
const zoned = byZone && typeof byZone === 'object'
|
|
115
|
+
? (byZone as Record<string, unknown>)[String(zoneId)]
|
|
116
|
+
: undefined
|
|
117
|
+
const sizes = [dim, ...parseSizeList(zoned ?? remote['BANNER_SIZES'])]
|
|
118
|
+
return {
|
|
119
|
+
width: Math.max(...sizes.map((s) => s.width)),
|
|
120
|
+
height: Math.max(...sizes.map((s) => s.height)),
|
|
121
|
+
}
|
|
122
|
+
}, [dim, zoneId, config.remote])
|
|
123
|
+
|
|
124
|
+
// The slot starts at the reserved baseline, then tracks whatever the native
|
|
125
|
+
// side actually renders (onAdResize): a multi-size fallback creative, an
|
|
126
|
+
// outstream video, or the capped native template. Where the actual size is
|
|
127
|
+
// reported it shrinks the slot to fit; where it isn't (Android prebidOnly)
|
|
128
|
+
// the baseline reservation prevents a clip.
|
|
129
|
+
const [rendered, setRendered] = React.useState<{ width: number; height: number } | null>(null)
|
|
130
|
+
// Reset to the baseline when the placement identity changes.
|
|
131
|
+
React.useEffect(() => { setRendered(null) }, [size, zoneId])
|
|
132
|
+
|
|
133
|
+
const containerStyle: ViewStyle = {
|
|
134
|
+
width: rendered?.width ?? baseline.width,
|
|
135
|
+
height: rendered?.height ?? baseline.height,
|
|
136
|
+
}
|
|
80
137
|
|
|
81
138
|
if (!NativeBanner) {
|
|
82
139
|
// Native module not registered. Most common cause: iOS bridge not yet
|
|
@@ -102,8 +159,10 @@ export function SellwildBanner({
|
|
|
102
159
|
partnerCode: config.partnerCode,
|
|
103
160
|
appBundleId: config.appBundleId,
|
|
104
161
|
appStoreUrl: config.appStoreUrl,
|
|
162
|
+
geo: config.geo,
|
|
105
163
|
gamTag: config.gamTag,
|
|
106
164
|
debug: config.debug,
|
|
165
|
+
pbsDebug: config.pbsDebug,
|
|
107
166
|
adRefreshMax: config.adRefreshMax,
|
|
108
167
|
adRefreshMaxMobile: config.adRefreshMaxMobile,
|
|
109
168
|
adRefreshIntervalMs: config.adRefreshInterval,
|
|
@@ -127,6 +186,10 @@ export function SellwildBanner({
|
|
|
127
186
|
const msg = e.nativeEvent?.message ?? 'Ad failed'
|
|
128
187
|
onError?.(new Error(msg))
|
|
129
188
|
}}
|
|
189
|
+
onAdResize={(e: NativeSyntheticEvent<{ width: number; height: number }>) => {
|
|
190
|
+
const { width, height } = e.nativeEvent ?? { width: 0, height: 0 }
|
|
191
|
+
if (width > 0 && height > 0) setRendered({ width, height })
|
|
192
|
+
}}
|
|
130
193
|
/>
|
|
131
194
|
)
|
|
132
195
|
}
|
package/src/SellwildFeed.tsx
CHANGED
|
@@ -117,8 +117,10 @@ export function SellwildFeed({
|
|
|
117
117
|
slug: config.slug,
|
|
118
118
|
appBundleId: config.appBundleId,
|
|
119
119
|
appStoreUrl: config.appStoreUrl,
|
|
120
|
+
geo: config.geo,
|
|
120
121
|
gamTag: config.gamTag,
|
|
121
122
|
debug: config.debug,
|
|
123
|
+
pbsDebug: config.pbsDebug,
|
|
122
124
|
adRefreshMax: config.adRefreshMax,
|
|
123
125
|
adRefreshMaxMobile: config.adRefreshMaxMobile,
|
|
124
126
|
adRefreshIntervalMs: config.adRefreshInterval,
|
package/src/commands.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NativeModules } from 'react-native'
|
|
2
|
+
import type { SellwildEid, SellwildGeo } from '@sellwild/sdk-core'
|
|
3
|
+
|
|
4
|
+
// Imperative bridge to the native Sellwild method module. The ad surface is
|
|
5
|
+
// otherwise view-manager-only (config flows as a prop on <SellwildBanner> /
|
|
6
|
+
// <SellwildFeed>); this file exposes the session-scoped native setters.
|
|
7
|
+
const SellwildRNModule = NativeModules.SellwildRNModule as
|
|
8
|
+
| {
|
|
9
|
+
setGeo?: (geo: Record<string, unknown>) => void
|
|
10
|
+
setExternalUserIds?: (eids: SellwildEid[]) => void
|
|
11
|
+
}
|
|
12
|
+
| undefined
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Set or update partner-supplied geo at runtime (OpenRTB `device.geo`) for
|
|
16
|
+
* native Prebid auctions, and seed the shared native geo store read by other
|
|
17
|
+
* surfaces (e.g. the listings feed). Pass `null` to clear.
|
|
18
|
+
*
|
|
19
|
+
* Prefer `config.geo` for the value known at configure time; use `setGeo` when
|
|
20
|
+
* location resolves or changes mid-session. No-op if the native module isn't
|
|
21
|
+
* linked (e.g. before autolinking, or on an unsupported platform).
|
|
22
|
+
*/
|
|
23
|
+
export function setGeo(geo: SellwildGeo | null): void {
|
|
24
|
+
// Native maps an empty object to "clear", so send {} for null.
|
|
25
|
+
SellwildRNModule?.setGeo?.((geo ?? {}) as Record<string, unknown>)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Set partner-supplied external/extended user IDs, emitted as OpenRTB
|
|
30
|
+
* `user.ext.eids` on every native Prebid auction. Pass `[]` to clear.
|
|
31
|
+
*
|
|
32
|
+
* Re-set on each launch (Prebid Mobile does not persist eids across restarts).
|
|
33
|
+
* No-op if the native module isn't linked.
|
|
34
|
+
*/
|
|
35
|
+
export function setExternalUserIds(eids: SellwildEid[]): void {
|
|
36
|
+
SellwildRNModule?.setExternalUserIds?.(eids ?? [])
|
|
37
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,9 @@ export type { SellwildListingCardProps } from './SellwildListingCard'
|
|
|
13
13
|
export { useSellwildListings } from './useSellwildListings'
|
|
14
14
|
export type { UseSellwildListingsResult } from './useSellwildListings'
|
|
15
15
|
|
|
16
|
+
// Imperative native setters (runtime, session-scoped).
|
|
17
|
+
export { setGeo, setExternalUserIds } from './commands'
|
|
18
|
+
|
|
16
19
|
// Re-export core types for convenience
|
|
17
20
|
export type {
|
|
18
21
|
SellwildConfig,
|
|
@@ -22,6 +25,9 @@ export type {
|
|
|
22
25
|
SellwildPhoto,
|
|
23
26
|
SellwildUser,
|
|
24
27
|
PrebidServerConfig,
|
|
28
|
+
SellwildGeo,
|
|
29
|
+
SellwildEid,
|
|
30
|
+
SellwildEidUid,
|
|
25
31
|
AdSize,
|
|
26
32
|
AdPlacement,
|
|
27
33
|
AdPlacementType,
|