@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.
@@ -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.0"
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> = emptyList()
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.widthAnchor.constraint(equalToConstant: adSize.cgSize.width),
105
- view.heightAnchor.constraint(equalToConstant: adSize.cgSize.height),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellwild/react-native-sdk",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Sellwild mobile advertising SDK for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -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
- const containerStyle: ViewStyle = { width: dim.width, height: dim.height }
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
  }
@@ -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,
@@ -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,