@sellwild/react-native-sdk 1.3.0 → 1.3.5
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/README.md +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/sellwild/rnsdk/SellwildFeedViewManager.kt +183 -0
- package/android/src/main/java/com/sellwild/rnsdk/SellwildSdkPackage.kt +1 -0
- package/ios/SellwildFeedViewManager.m +23 -0
- package/ios/SellwildFeedViewManager.swift +200 -0
- package/ios/SellwildSDK-RN.podspec +6 -4
- package/package.json +7 -3
- package/src/SellwildFeed.tsx +174 -0
- package/src/htmlBuilder.ts +4 -2
- package/src/index.ts +6 -0
- package/android/build/.transforms/055edc6990905b0d0e7b2a38f451ea8f/results.bin +0 -1
- package/android/build/.transforms/055edc6990905b0d0e7b2a38f451ea8f/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/9adcec60aa21840716976bd52bc3bd4a/results.bin +0 -1
- package/android/build/.transforms/9adcec60aa21840716976bd52bc3bd4a/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/f6cdcb296ca6520e70a1cebbdcbe808e/results.bin +0 -1
- package/android/build/.transforms/f6cdcb296ca6520e70a1cebbdcbe808e/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/generated/source/buildConfig/debug/com/sellwild/rnsdk/BuildConfig.java +0 -10
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +0 -7
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +0 -18
- package/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +0 -6
- package/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +0 -1
- package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
- package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +0 -2
- package/android/build/intermediates/java_res/debug/out/META-INF/sellwild-react-native-sdk_debug.kotlin_module +0 -0
- package/android/build/intermediates/javac/debug/classes/com/sellwild/rnsdk/BuildConfig.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +0 -2
- package/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +0 -7
- package/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +0 -7
- package/android/build/intermediates/navigation_json/debug/navigation.json +0 -1
- package/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +0 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +0 -2
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -16
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/META-INF/sellwild-react-native-sdk_debug.kotlin_module +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/sellwild/rnsdk/SellwildBannerViewManager$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/sellwild/rnsdk/SellwildBannerViewManager$PendingProps.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/sellwild/rnsdk/SellwildBannerViewManager$createViewInstance$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/sellwild/rnsdk/SellwildBannerViewManager.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/sellwild/rnsdk/SellwildSdkPackage.class +0 -0
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ import { SellwildBanner, buildConfig } from '@sellwild/react-native-sdk';
|
|
|
18
18
|
|
|
19
19
|
const config = buildConfig({
|
|
20
20
|
partnerCode: 'your-partner',
|
|
21
|
-
listingsUrl: 'https://
|
|
21
|
+
listingsUrl: 'https://your-cms-or-cache.example.com/listings.json',
|
|
22
22
|
appBundleId: 'com.yourcompany.yourapp',
|
|
23
23
|
appStoreUrl: 'https://play.google.com/store/apps/details?id=com.yourcompany.yourapp',
|
|
24
24
|
prebidServer: {
|
package/android/build.gradle
CHANGED
|
@@ -64,5 +64,5 @@ dependencies {
|
|
|
64
64
|
// The native SDK that owns SellwildAdView, SellwildPrebidMobile, etc.
|
|
65
65
|
// Resolves to the published AAR off https://maven.sellwild.com/releases
|
|
66
66
|
// for npm consumers, or to mavenLocal in the SDK monorepo dev workflow.
|
|
67
|
-
implementation "com.sellwild:sdk:1.3.
|
|
67
|
+
implementation "com.sellwild:sdk:1.3.5"
|
|
68
68
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
package com.sellwild.rnsdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.ReactContext
|
|
5
|
+
import com.facebook.react.bridge.ReadableMap
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.facebook.react.common.MapBuilder
|
|
8
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
9
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
11
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
12
|
+
import com.sellwild.sdk.SellwildConfig
|
|
13
|
+
import com.sellwild.sdk.SellwildFeedView
|
|
14
|
+
import com.sellwild.sdk.SellwildListing
|
|
15
|
+
import com.sellwild.sdk.SellwildSDK
|
|
16
|
+
import org.json.JSONObject
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Bridges the JS <SellwildFeed> component to the native
|
|
20
|
+
* com.sellwild.sdk.SellwildFeedView (all-in-one native feed: COL1-scheduled
|
|
21
|
+
* listing cards + Prebid + GAM ads, no WebView).
|
|
22
|
+
*
|
|
23
|
+
* Props (set from JS):
|
|
24
|
+
* - config: object — the resolved SellwildConfig (from configure()).
|
|
25
|
+
* The bridge re-runs the CDN decoder against `config.remote` so
|
|
26
|
+
* feed-specific fields (COL1, bgColor, mobileZids, listingsUrl, …)
|
|
27
|
+
* are populated identically to a native [SellwildSDK.configure] call.
|
|
28
|
+
*
|
|
29
|
+
* Events emitted to JS:
|
|
30
|
+
* - onFeedLoaded
|
|
31
|
+
* - onListingTap { listing }
|
|
32
|
+
* - onAdImpression { zoneId }
|
|
33
|
+
* - onAdClicked { zoneId }
|
|
34
|
+
* - onFeedError { message }
|
|
35
|
+
*/
|
|
36
|
+
class SellwildFeedViewManager : SimpleViewManager<SellwildFeedView>() {
|
|
37
|
+
|
|
38
|
+
override fun getName(): String = REACT_CLASS
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Per-view scratch state. SellwildFeedView.setup() kicks off a network
|
|
42
|
+
* fetch + auctions for every COL1 ad slot, so we defer it to
|
|
43
|
+
* onAfterUpdateTransaction and skip re-applying on JS re-renders.
|
|
44
|
+
*/
|
|
45
|
+
private val pending = java.util.WeakHashMap<SellwildFeedView, PendingProps>()
|
|
46
|
+
|
|
47
|
+
private data class PendingProps(
|
|
48
|
+
var config: ReadableMap? = null,
|
|
49
|
+
var lastAppliedKey: String? = null,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
override fun createViewInstance(reactContext: ThemedReactContext): SellwildFeedView {
|
|
53
|
+
val view = SellwildFeedView(reactContext)
|
|
54
|
+
pending[view] = PendingProps()
|
|
55
|
+
view.listener = object : SellwildFeedView.Listener {
|
|
56
|
+
override fun onListingTap(listing: SellwildListing): Boolean {
|
|
57
|
+
val payload = Arguments.createMap().apply {
|
|
58
|
+
putMap("listing", listingPayload(listing))
|
|
59
|
+
}
|
|
60
|
+
emit(reactContext, view, "onListingTap", payload)
|
|
61
|
+
// SDK still owns navigation (Custom Tabs); JS just observes.
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun onAdImpression(zoneId: String) {
|
|
66
|
+
val payload = Arguments.createMap().apply { putString("zoneId", zoneId) }
|
|
67
|
+
emit(reactContext, view, "onAdImpression", payload)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun onAdClicked(zoneId: String) {
|
|
71
|
+
val payload = Arguments.createMap().apply { putString("zoneId", zoneId) }
|
|
72
|
+
emit(reactContext, view, "onAdClicked", payload)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun onLoad() {
|
|
76
|
+
emit(reactContext, view, "onFeedLoaded", null)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun onError(message: String) {
|
|
80
|
+
val payload = Arguments.createMap().apply { putString("message", message) }
|
|
81
|
+
emit(reactContext, view, "onFeedError", payload)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return view
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@ReactProp(name = "config")
|
|
88
|
+
fun setConfig(view: SellwildFeedView, value: ReadableMap?) {
|
|
89
|
+
pendingFor(view).config = value
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override fun onAfterUpdateTransaction(view: SellwildFeedView) {
|
|
93
|
+
super.onAfterUpdateTransaction(view)
|
|
94
|
+
|
|
95
|
+
val p = pending[view] ?: return
|
|
96
|
+
val configMap = p.config ?: return
|
|
97
|
+
|
|
98
|
+
val key = "${configMap.hashCode()}"
|
|
99
|
+
if (p.lastAppliedKey == key) return
|
|
100
|
+
p.lastAppliedKey = key
|
|
101
|
+
|
|
102
|
+
val config = configFromMap(configMap)
|
|
103
|
+
view.setup(config)
|
|
104
|
+
view.load()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
override fun onDropViewInstance(view: SellwildFeedView) {
|
|
108
|
+
pending.remove(view)
|
|
109
|
+
super.onDropViewInstance(view)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private fun pendingFor(view: SellwildFeedView): PendingProps =
|
|
113
|
+
pending.getOrPut(view) { PendingProps() }
|
|
114
|
+
|
|
115
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
|
|
116
|
+
return MapBuilder.builder<String, Any>()
|
|
117
|
+
.put("onFeedLoaded", MapBuilder.of("registrationName", "onFeedLoaded"))
|
|
118
|
+
.put("onListingTap", MapBuilder.of("registrationName", "onListingTap"))
|
|
119
|
+
.put("onAdImpression", MapBuilder.of("registrationName", "onAdImpression"))
|
|
120
|
+
.put("onAdClicked", MapBuilder.of("registrationName", "onAdClicked"))
|
|
121
|
+
.put("onFeedError", MapBuilder.of("registrationName", "onFeedError"))
|
|
122
|
+
.build()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private fun emit(
|
|
126
|
+
context: ReactContext,
|
|
127
|
+
view: SellwildFeedView,
|
|
128
|
+
name: String,
|
|
129
|
+
payload: WritableMap?,
|
|
130
|
+
) {
|
|
131
|
+
context.getJSModule(RCTEventEmitter::class.java)
|
|
132
|
+
.receiveEvent(view.id, name, payload)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
companion object {
|
|
136
|
+
const val REACT_CLASS = "SellwildFeedView"
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build a full SellwildConfig from a JS map. The JS side passes the
|
|
140
|
+
* resolved CDN payload under `remote`; we re-run the canonical CDN
|
|
141
|
+
* decoder ([SellwildSDK.apply]) against it so feed-specific fields
|
|
142
|
+
* (COL1, bgColor, mobileZids, listingsUrl, …) land identically to a
|
|
143
|
+
* native [SellwildSDK.configure] call. Explicit JS overrides
|
|
144
|
+
* (e.g. appBundleId from the host app) win.
|
|
145
|
+
*/
|
|
146
|
+
internal fun configFromMap(map: ReadableMap): SellwildConfig {
|
|
147
|
+
val partnerCode = if (map.hasKey("partnerCode")) map.getString("partnerCode") ?: "" else ""
|
|
148
|
+
|
|
149
|
+
var config = SellwildConfig(partnerCode = partnerCode)
|
|
150
|
+
|
|
151
|
+
// Apply the raw CDN payload first.
|
|
152
|
+
if (map.hasKey("remote") && !map.isNull("remote")) {
|
|
153
|
+
val remoteMap = map.getMap("remote")!!.toHashMap()
|
|
154
|
+
val remoteJson = JSONObject(remoteMap)
|
|
155
|
+
config = SellwildSDK.apply(remoteJson, config).copy(
|
|
156
|
+
remoteJson = remoteJson.toString()
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// JS-side overrides.
|
|
161
|
+
if (map.hasKey("appBundleId")) config = config.copy(appBundleId = map.getString("appBundleId"))
|
|
162
|
+
if (map.hasKey("appStoreUrl")) config = config.copy(appStoreUrl = map.getString("appStoreUrl"))
|
|
163
|
+
if (map.hasKey("gamTag")) config = config.copy(gamTag = map.getString("gamTag"))
|
|
164
|
+
if (map.hasKey("debug")) config = config.copy(debug = map.getBoolean("debug"))
|
|
165
|
+
if (map.hasKey("adRefreshMax")) config = config.copy(adRefreshMax = map.getInt("adRefreshMax"))
|
|
166
|
+
if (map.hasKey("adRefreshMaxMobile")) config = config.copy(adRefreshMaxMobile = map.getInt("adRefreshMaxMobile"))
|
|
167
|
+
if (map.hasKey("adRefreshIntervalMs")) config = config.copy(adRefreshIntervalMs = map.getDouble("adRefreshIntervalMs").toLong())
|
|
168
|
+
|
|
169
|
+
return config
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Surface the listing payload to JS. Mirrors `SellwildListing` in @sellwild/sdk-core. */
|
|
173
|
+
internal fun listingPayload(listing: SellwildListing): WritableMap = Arguments.createMap().apply {
|
|
174
|
+
putString("id", listing.id)
|
|
175
|
+
putString("title", listing.title)
|
|
176
|
+
listing.url?.let { putString("url", it) }
|
|
177
|
+
listing.currency?.let { putString("currency", it) }
|
|
178
|
+
listing.price?.let { putString("price", it) }
|
|
179
|
+
listing.remoteUrl?.let { putString("remoteUrl", it) }
|
|
180
|
+
listing.primaryPhotoUrl?.let { putString("photoUrl", it) }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#import <React/RCTViewManager.h>
|
|
2
|
+
#import <React/RCTUIManager.h>
|
|
3
|
+
#import <React/RCTBridgeModule.h>
|
|
4
|
+
|
|
5
|
+
// Register the view manager class with React Native. We export it under
|
|
6
|
+
// the JS name "SellwildFeedView" to match the Android side
|
|
7
|
+
// (com.sellwild.rnsdk.SellwildFeedViewManager.REACT_CLASS).
|
|
8
|
+
@interface RCT_EXTERN_REMAP_MODULE(SellwildFeedView,
|
|
9
|
+
SellwildFeedViewManager,
|
|
10
|
+
RCTViewManager)
|
|
11
|
+
|
|
12
|
+
// Props set from JS — these forward to @objc properties on
|
|
13
|
+
// SellwildFeedHostView.
|
|
14
|
+
RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary)
|
|
15
|
+
|
|
16
|
+
// Direct events emitted to JS callbacks.
|
|
17
|
+
RCT_EXPORT_VIEW_PROPERTY(onFeedLoaded, RCTDirectEventBlock)
|
|
18
|
+
RCT_EXPORT_VIEW_PROPERTY(onListingTap, RCTDirectEventBlock)
|
|
19
|
+
RCT_EXPORT_VIEW_PROPERTY(onAdImpression, RCTDirectEventBlock)
|
|
20
|
+
RCT_EXPORT_VIEW_PROPERTY(onAdClicked, RCTDirectEventBlock)
|
|
21
|
+
RCT_EXPORT_VIEW_PROPERTY(onFeedError, RCTDirectEventBlock)
|
|
22
|
+
|
|
23
|
+
@end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
import SellwildSDK
|
|
4
|
+
import UIKit
|
|
5
|
+
|
|
6
|
+
/// Bridges the JS `<SellwildFeed>` component to the native
|
|
7
|
+
/// `SellwildFeedView` (all-in-one native feed: COL1-scheduled listing
|
|
8
|
+
/// cards + Prebid + GAM ads, zero WebView). Mirrors the Android
|
|
9
|
+
/// `SellwildFeedViewManager` shape.
|
|
10
|
+
///
|
|
11
|
+
/// Props (set from JS):
|
|
12
|
+
/// - config: object — the resolved SellwildConfig (from configure()).
|
|
13
|
+
/// The bridge re-runs the CDN decoder against `config.remote` so
|
|
14
|
+
/// feed-specific fields (COL1, bgColor, mobileZids, …) are
|
|
15
|
+
/// populated even though they're not all typed on the JS side.
|
|
16
|
+
///
|
|
17
|
+
/// Events emitted to JS:
|
|
18
|
+
/// - onFeedLoaded
|
|
19
|
+
/// - onListingTap { listing }
|
|
20
|
+
/// - onAdImpression { zoneId }
|
|
21
|
+
/// - onAdClicked { zoneId }
|
|
22
|
+
/// - onFeedError { message }
|
|
23
|
+
@objc(SellwildFeedViewManager)
|
|
24
|
+
public final class SellwildFeedViewManager: RCTViewManager {
|
|
25
|
+
|
|
26
|
+
public override static func requiresMainQueueSetup() -> Bool { true }
|
|
27
|
+
|
|
28
|
+
public override func view() -> UIView! {
|
|
29
|
+
return SellwildFeedHostView()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Hosts a `SellwildFeedView`. We can't construct the feed until the
|
|
34
|
+
/// `config` prop has arrived, so we cache it and apply on
|
|
35
|
+
/// `didSetProps:` — same pattern as the banner host view.
|
|
36
|
+
final class SellwildFeedHostView: UIView, SellwildFeedViewDelegate {
|
|
37
|
+
|
|
38
|
+
// MARK: RN-set props
|
|
39
|
+
|
|
40
|
+
@objc var config: NSDictionary? { didSet { needsApply = true } }
|
|
41
|
+
|
|
42
|
+
// MARK: RN events
|
|
43
|
+
|
|
44
|
+
@objc var onFeedLoaded: RCTDirectEventBlock?
|
|
45
|
+
@objc var onListingTap: RCTDirectEventBlock?
|
|
46
|
+
@objc var onAdImpression: RCTDirectEventBlock?
|
|
47
|
+
@objc var onAdClicked: RCTDirectEventBlock?
|
|
48
|
+
@objc var onFeedError: RCTDirectEventBlock?
|
|
49
|
+
|
|
50
|
+
// MARK: Internals
|
|
51
|
+
|
|
52
|
+
private var feedView: SellwildFeedView?
|
|
53
|
+
private var lastAppliedKey: String?
|
|
54
|
+
private var needsApply: Bool = false
|
|
55
|
+
|
|
56
|
+
override init(frame: CGRect) {
|
|
57
|
+
super.init(frame: frame)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) not used") }
|
|
61
|
+
|
|
62
|
+
override func didSetProps(_ changedProps: [String]) {
|
|
63
|
+
guard needsApply else { return }
|
|
64
|
+
needsApply = false
|
|
65
|
+
applyIfReady()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private func applyIfReady() {
|
|
69
|
+
guard let cfgMap = config else { return }
|
|
70
|
+
|
|
71
|
+
// Skip if the props identity hasn't changed since last apply.
|
|
72
|
+
// Feed refresh is driven by user pull-to-refresh, not JS
|
|
73
|
+
// re-renders.
|
|
74
|
+
let key = "\(cfgMap.hash)"
|
|
75
|
+
if lastAppliedKey == key { return }
|
|
76
|
+
lastAppliedKey = key
|
|
77
|
+
|
|
78
|
+
let sellwildConfig = Self.configFromMap(cfgMap)
|
|
79
|
+
|
|
80
|
+
// Tear down any previous feed view; SellwildFeedView holds its
|
|
81
|
+
// own table state and we want a clean fetch on identity change.
|
|
82
|
+
feedView?.removeFromSuperview()
|
|
83
|
+
|
|
84
|
+
let view = SellwildFeedView(config: sellwildConfig)
|
|
85
|
+
view.delegate = self
|
|
86
|
+
view.translatesAutoresizingMaskIntoConstraints = false
|
|
87
|
+
addSubview(view)
|
|
88
|
+
NSLayoutConstraint.activate([
|
|
89
|
+
view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
90
|
+
view.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
91
|
+
view.topAnchor.constraint(equalTo: topAnchor),
|
|
92
|
+
view.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
93
|
+
])
|
|
94
|
+
feedView = view
|
|
95
|
+
view.load()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// MARK: SellwildFeedViewDelegate
|
|
99
|
+
|
|
100
|
+
func sellwildFeedDidLoad(_ feed: SellwildFeedView) {
|
|
101
|
+
onFeedLoaded?([:])
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func sellwildFeed(_ feed: SellwildFeedView, didTapListing listing: SellwildListing) -> Bool {
|
|
105
|
+
// Forward the listing payload to JS as a plain dictionary. JS
|
|
106
|
+
// can't return a value back through a direct event block, so
|
|
107
|
+
// the SDK still owns whether to open the URL in
|
|
108
|
+
// SFSafariViewController. Partners who want to fully consume
|
|
109
|
+
// the tap can subclass via `useNativeNavigation` later;
|
|
110
|
+
// current behaviour matches the WebView widget (always opens
|
|
111
|
+
// in in-app browser) so there's no regression.
|
|
112
|
+
let payload = Self.listingPayload(listing)
|
|
113
|
+
onListingTap?(["listing": payload])
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func sellwildFeed(_ feed: SellwildFeedView, didRecordAdImpressionForZoneId zoneId: String) {
|
|
118
|
+
onAdImpression?(["zoneId": zoneId])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func sellwildFeed(_ feed: SellwildFeedView, didRecordAdClickForZoneId zoneId: String) {
|
|
122
|
+
onAdClicked?(["zoneId": zoneId])
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func sellwildFeed(_ feed: SellwildFeedView, didFailWithError message: String) {
|
|
126
|
+
onFeedError?(["message": message])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MARK: Config marshalling
|
|
130
|
+
|
|
131
|
+
/// Build a full SellwildConfig from a JS dictionary. The JS side
|
|
132
|
+
/// passes the raw CDN payload under `remote`; we re-run the
|
|
133
|
+
/// canonical CDN decoder against it so feed-specific fields (COL1,
|
|
134
|
+
/// bgColor, mobileZids, listingsUrl, …) are populated identically
|
|
135
|
+
/// to a native `SellwildSDK.configure(...)` call.
|
|
136
|
+
static func configFromMap(_ map: NSDictionary) -> SellwildConfig {
|
|
137
|
+
let partnerCode = (map["partnerCode"] as? String) ?? ""
|
|
138
|
+
var cfg = SellwildConfig(partnerCode: partnerCode)
|
|
139
|
+
|
|
140
|
+
// Apply the raw CDN payload first so explicit JS overrides
|
|
141
|
+
// (e.g. appBundleId from the host app) win.
|
|
142
|
+
if let remote = map["remote"] as? [String: Any] {
|
|
143
|
+
cfg = SellwildSDK.apply(remote, to: cfg)
|
|
144
|
+
if let data = try? JSONSerialization.data(withJSONObject: remote, options: []) {
|
|
145
|
+
cfg.remoteJSON = data
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if let v = map["slug"] as? String { cfg.slug = v }
|
|
150
|
+
if let v = map["appBundleId"] as? String { cfg.appBundleId = v }
|
|
151
|
+
if let v = map["appStoreUrl"] as? String { cfg.appStoreUrl = v }
|
|
152
|
+
if let v = map["gamTag"] as? String { cfg.gamTag = v }
|
|
153
|
+
if let v = map["debug"] as? Bool { cfg.debug = v }
|
|
154
|
+
if let v = map["adRefreshMax"] as? Int { cfg.adRefreshMax = v }
|
|
155
|
+
if let v = map["adRefreshMaxMobile"] as? Int { cfg.adRefreshMaxMobile = v }
|
|
156
|
+
if let v = map["listingsUrl"] as? String { cfg.listingsUrl = v }
|
|
157
|
+
if let v = map["priceColor"] as? String { cfg.priceColor = v }
|
|
158
|
+
if let v = map["bannerZid"] as? String { cfg.bannerZid = v }
|
|
159
|
+
if let v = map["bottomBannerZid"] as? String { cfg.bottomBannerZid = v }
|
|
160
|
+
if let v = map["mobileBannerZid"] as? String { cfg.mobileBannerZid = v }
|
|
161
|
+
if let v = map["mobileZids"] as? [String] { cfg.mobileZids = v }
|
|
162
|
+
|
|
163
|
+
// JS bridge passes ms; iOS API is seconds (TimeInterval).
|
|
164
|
+
if let v = map["adRefreshIntervalMs"] as? NSNumber {
|
|
165
|
+
cfg.adRefreshInterval = v.doubleValue / 1000.0
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if let prebid = map["prebidServer"] as? NSDictionary,
|
|
169
|
+
let accountId = prebid["accountId"] as? String,
|
|
170
|
+
let endpoint = prebid["endpoint"] as? String {
|
|
171
|
+
let bidders = (prebid["bidders"] as? [String]) ?? []
|
|
172
|
+
let timeout = (prebid["timeout"] as? Int) ?? 1500
|
|
173
|
+
cfg.prebidServer = PrebidServerConfig(
|
|
174
|
+
accountId: accountId,
|
|
175
|
+
endpoint: endpoint,
|
|
176
|
+
bidders: bidders,
|
|
177
|
+
timeout: timeout,
|
|
178
|
+
syncEndpoint: prebid["syncEndpoint"] as? String
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return cfg
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Serialize a SellwildListing to a JS-friendly dictionary. Surfaces
|
|
186
|
+
/// the fields the JS callback is most likely to act on; the rest
|
|
187
|
+
/// can be added on demand.
|
|
188
|
+
static func listingPayload(_ listing: SellwildListing) -> [String: Any] {
|
|
189
|
+
var dict: [String: Any] = [
|
|
190
|
+
"id": listing.id,
|
|
191
|
+
"title": listing.title,
|
|
192
|
+
]
|
|
193
|
+
if let v = listing.url { dict["url"] = v }
|
|
194
|
+
if let v = listing.currency { dict["currency"] = v }
|
|
195
|
+
if let v = listing.price { dict["price"] = v }
|
|
196
|
+
if let v = listing.remoteUrl { dict["remoteUrl"] = v }
|
|
197
|
+
if let firstPhoto = listing.photos?.first?.url { dict["photoUrl"] = firstPhoto }
|
|
198
|
+
return dict
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -7,9 +7,11 @@ Pod::Spec.new do |s|
|
|
|
7
7
|
s.version = package['version']
|
|
8
8
|
s.summary = 'React Native bridge for the Sellwild iOS SDK'
|
|
9
9
|
s.description = <<-DESC
|
|
10
|
-
Bridges
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Bridges the @sellwild/react-native-sdk components to native iOS:
|
|
11
|
+
- <SellwildBanner> → SellwildAdView (Prebid Mobile + AdManagerBannerView)
|
|
12
|
+
- <SellwildFeed> → SellwildFeedView (all-in-one COL1-scheduled
|
|
13
|
+
feed of native listing cards + native ads)
|
|
14
|
+
There is no WebView in either path.
|
|
13
15
|
DESC
|
|
14
16
|
|
|
15
17
|
s.homepage = 'https://github.com/Antengo/sellwild-sdk'
|
|
@@ -26,6 +28,6 @@ Pod::Spec.new do |s|
|
|
|
26
28
|
# The host app must already pull in SellwildSDK (the iOS SDK pod). We
|
|
27
29
|
# depend on it explicitly so `pod install` resolves both with one
|
|
28
30
|
# declaration in the host Podfile.
|
|
29
|
-
s.dependency 'SellwildSDK', '>= 1.3.
|
|
31
|
+
s.dependency 'SellwildSDK', '>= 1.3.5'
|
|
30
32
|
s.dependency 'React-Core'
|
|
31
33
|
end
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sellwild/react-native-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "Sellwild mobile advertising SDK for React Native",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"src",
|
|
9
|
-
"android",
|
|
10
|
-
"
|
|
9
|
+
"android/build.gradle",
|
|
10
|
+
"android/src/**/*",
|
|
11
|
+
"ios/*.swift",
|
|
12
|
+
"ios/*.m",
|
|
13
|
+
"ios/*.h",
|
|
14
|
+
"ios/SellwildSDK-RN.podspec"
|
|
11
15
|
],
|
|
12
16
|
"keywords": [
|
|
13
17
|
"react-native",
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
requireNativeComponent,
|
|
5
|
+
StyleProp,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
UIManager,
|
|
9
|
+
View,
|
|
10
|
+
ViewStyle,
|
|
11
|
+
NativeSyntheticEvent,
|
|
12
|
+
} from 'react-native'
|
|
13
|
+
import type { SellwildConfig, SellwildListing } from '@sellwild/sdk-core'
|
|
14
|
+
|
|
15
|
+
// ─── Native component bridge ─────────────────────────────────────────────────
|
|
16
|
+
//
|
|
17
|
+
// SellwildFeed is the "all-in-one" native surface: a single-column scroll
|
|
18
|
+
// of native listing cards interleaved with native Prebid + GAM ads,
|
|
19
|
+
// scheduled by the CDN-published COL1 token string. There is **no
|
|
20
|
+
// WebView** in this surface — every row is native on both platforms.
|
|
21
|
+
//
|
|
22
|
+
// iOS: SellwildFeedView (UITableView-backed)
|
|
23
|
+
// Android: com.sellwild.sdk.SellwildFeedView (RecyclerView-backed)
|
|
24
|
+
//
|
|
25
|
+
// This component is the drop-in native replacement for the WebView-based
|
|
26
|
+
// <SellwildWidget>. Same one-component integration shape; native rails.
|
|
27
|
+
|
|
28
|
+
const NATIVE_NAME = 'SellwildFeedView'
|
|
29
|
+
|
|
30
|
+
interface NativeFeedProps {
|
|
31
|
+
config: object
|
|
32
|
+
style?: StyleProp<ViewStyle>
|
|
33
|
+
onFeedLoaded?: (e: NativeSyntheticEvent<{}>) => void
|
|
34
|
+
onListingTap?: (e: NativeSyntheticEvent<{ listing: SellwildListing }>) => void
|
|
35
|
+
onAdImpression?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
36
|
+
onAdClicked?: (e: NativeSyntheticEvent<{ zoneId: string }>) => void
|
|
37
|
+
onFeedError?: (e: NativeSyntheticEvent<{ message: string }>) => void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const NativeFeed = (() => {
|
|
41
|
+
// requireNativeComponent crashes loudly if the view manager isn't
|
|
42
|
+
// registered. Probe first so we can render a friendly fallback on
|
|
43
|
+
// platforms where the bridge isn't in this build yet.
|
|
44
|
+
const config = UIManager.getViewManagerConfig?.(NATIVE_NAME)
|
|
45
|
+
if (!config) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
return requireNativeComponent<NativeFeedProps>(NATIVE_NAME)
|
|
49
|
+
})()
|
|
50
|
+
|
|
51
|
+
// ─── Public component ────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export interface SellwildFeedProps {
|
|
54
|
+
/**
|
|
55
|
+
* Resolved Sellwild config from `configure()`. The native feed reads
|
|
56
|
+
* the COL1 schedule, listings URL, ad zones, and theme out of this.
|
|
57
|
+
*/
|
|
58
|
+
config: SellwildConfig
|
|
59
|
+
|
|
60
|
+
/** Optional style override. The feed expands to its container by default. */
|
|
61
|
+
style?: ViewStyle
|
|
62
|
+
|
|
63
|
+
/** Fired once after the initial listings fetch resolves successfully. */
|
|
64
|
+
onLoad?: () => void
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fired when a listing card is tapped. Return `true` to consume the
|
|
68
|
+
* event; return `false` (or omit) to let the SDK open `listing.url`
|
|
69
|
+
* in the platform in-app browser (Custom Tabs / SFSafariViewController).
|
|
70
|
+
*/
|
|
71
|
+
onListingTap?: (listing: SellwildListing) => boolean | void
|
|
72
|
+
|
|
73
|
+
/** Fired when a native ad row records an impression. */
|
|
74
|
+
onAdImpression?: (zoneId: string) => void
|
|
75
|
+
|
|
76
|
+
/** Fired when a native ad row is clicked. */
|
|
77
|
+
onAdClicked?: (zoneId: string) => void
|
|
78
|
+
|
|
79
|
+
/** Fired when the listings fetch fails or a row fails to render. */
|
|
80
|
+
onError?: (error: Error) => void
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function SellwildFeed({
|
|
84
|
+
config,
|
|
85
|
+
style,
|
|
86
|
+
onLoad,
|
|
87
|
+
onListingTap,
|
|
88
|
+
onAdImpression,
|
|
89
|
+
onAdClicked,
|
|
90
|
+
onError,
|
|
91
|
+
}: SellwildFeedProps) {
|
|
92
|
+
if (!NativeFeed) {
|
|
93
|
+
// Native module not registered. Most common cause: the host app was
|
|
94
|
+
// built before the @sellwild/react-native-sdk autolink ran, or this
|
|
95
|
+
// is being rendered in a JS-only test environment.
|
|
96
|
+
return (
|
|
97
|
+
<View style={[styles.fill, style, __DEV__ ? styles.devPlaceholder : undefined]}>
|
|
98
|
+
{__DEV__ ? (
|
|
99
|
+
<Text style={styles.devText}>
|
|
100
|
+
Sellwild native feed not available on {Platform.OS} (yet)
|
|
101
|
+
</Text>
|
|
102
|
+
) : null}
|
|
103
|
+
</View>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Pass the fields the native feed reads, plus the raw CDN payload
|
|
108
|
+
// under `remote`. The native bridge re-runs the CDN decoder against
|
|
109
|
+
// `remote` to populate feed-specific fields (COL1 schedule, bgColor,
|
|
110
|
+
// mobileZids, mobileBannerZid, listingsUrl), so we don't need to
|
|
111
|
+
// mirror every field as a typed property on the JS side.
|
|
112
|
+
//
|
|
113
|
+
// Note: TS core uses `adRefreshInterval` (ms); the native side
|
|
114
|
+
// reads it as `adRefreshIntervalMs`. The bridge translates.
|
|
115
|
+
const nativeConfig: Record<string, unknown> = {
|
|
116
|
+
partnerCode: config.partnerCode,
|
|
117
|
+
slug: config.slug,
|
|
118
|
+
appBundleId: config.appBundleId,
|
|
119
|
+
appStoreUrl: config.appStoreUrl,
|
|
120
|
+
gamTag: config.gamTag,
|
|
121
|
+
debug: config.debug,
|
|
122
|
+
adRefreshMax: config.adRefreshMax,
|
|
123
|
+
adRefreshMaxMobile: config.adRefreshMaxMobile,
|
|
124
|
+
adRefreshIntervalMs: config.adRefreshInterval,
|
|
125
|
+
prebidServer: config.prebidServer,
|
|
126
|
+
remote: config.remote,
|
|
127
|
+
listingsUrl: config.listingsUrl,
|
|
128
|
+
priceColor: config.priceColor,
|
|
129
|
+
bannerZid: config.bannerZid,
|
|
130
|
+
bottomBannerZid: config.bottomBannerZid,
|
|
131
|
+
mobileBannerZid: config.mobileBannerZid,
|
|
132
|
+
mobileZids: config.mobileZids,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<NativeFeed
|
|
137
|
+
style={[styles.fill, style]}
|
|
138
|
+
config={nativeConfig}
|
|
139
|
+
onFeedLoaded={() => onLoad?.()}
|
|
140
|
+
onListingTap={(e: NativeSyntheticEvent<{ listing: SellwildListing }>) => {
|
|
141
|
+
onListingTap?.(e.nativeEvent.listing)
|
|
142
|
+
}}
|
|
143
|
+
onAdImpression={(e: NativeSyntheticEvent<{ zoneId: string }>) => {
|
|
144
|
+
onAdImpression?.(e.nativeEvent.zoneId)
|
|
145
|
+
}}
|
|
146
|
+
onAdClicked={(e: NativeSyntheticEvent<{ zoneId: string }>) => {
|
|
147
|
+
onAdClicked?.(e.nativeEvent.zoneId)
|
|
148
|
+
}}
|
|
149
|
+
onFeedError={(e: NativeSyntheticEvent<{ message: string }>) => {
|
|
150
|
+
const msg = e.nativeEvent?.message ?? 'Feed failed'
|
|
151
|
+
onError?.(new Error(msg))
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const styles = StyleSheet.create({
|
|
158
|
+
fill: {
|
|
159
|
+
flex: 1,
|
|
160
|
+
},
|
|
161
|
+
devPlaceholder: {
|
|
162
|
+
backgroundColor: '#FEE2E2',
|
|
163
|
+
borderWidth: 1,
|
|
164
|
+
borderColor: '#FCA5A5',
|
|
165
|
+
alignItems: 'center',
|
|
166
|
+
justifyContent: 'center',
|
|
167
|
+
padding: 12,
|
|
168
|
+
},
|
|
169
|
+
devText: {
|
|
170
|
+
color: '#991B1B',
|
|
171
|
+
fontSize: 12,
|
|
172
|
+
textAlign: 'center',
|
|
173
|
+
},
|
|
174
|
+
})
|
package/src/htmlBuilder.ts
CHANGED
|
@@ -286,10 +286,12 @@ export function buildWidgetHtml(config: SellwildConfig & { widgetJsUrl?: string
|
|
|
286
286
|
} catch(e) {}
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
// partner/index.tsx calls window.open() on listing tap — intercept
|
|
289
|
+
// partner/index.tsx calls window.open() on listing tap — intercept ALL
|
|
290
|
+
// calls. Listings link to external sites (eBay, Amazon, dealer sites, etc.)
|
|
291
|
+
// so we can't filter by domain. The widget only uses window.open for listings.
|
|
290
292
|
var _open = window.open;
|
|
291
293
|
window.open = function(url) {
|
|
292
|
-
if (url
|
|
294
|
+
if (url) {
|
|
293
295
|
send('LISTING_CLICK', { url: url });
|
|
294
296
|
return null;
|
|
295
297
|
}
|