setupad-prebid-react-native 0.1.3 → 0.1.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.
@@ -25,17 +25,23 @@ Pod::Spec.new do |s|
25
25
  s.dependency "Google-Mobile-Ads-SDK", "12.3.0"
26
26
  s.dependency "VeonPrebidMobileGAMEventHandlers", "0.0.4"
27
27
  s.dependency "VeonPrebidMobile", "0.0.4"
28
-
28
+
29
29
  s.static_framework = true
30
30
  s.swift_version = "5.0"
31
-
31
+
32
+ common_xcconfig = {
33
+ 'DEFINES_MODULE' => 'YES',
34
+ 'SWIFT_OBJC_INTERFACE_HEADER_NAME' => 'VeonPrebidReactNative-Swift.h',
35
+ 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
36
+ 'OTHER_SWIFT_FLAGS' => '$(inherited) -no-verify-emitted-module-interface',
37
+ }
38
+
32
39
  # When consumer uses `use_frameworks!`, headers are inside .framework bundles
33
40
  # and need explicit search paths for React Native internal C++ headers
34
41
  if ENV['USE_FRAMEWORKS']
35
- s.pod_target_xcconfig = {
36
- 'DEFINES_MODULE' => 'YES',
37
- 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
42
+ s.pod_target_xcconfig = common_xcconfig.merge({
38
43
  'HEADER_SEARCH_PATHS' => [
44
+ '"$(PODS_ROOT)/Headers/Private/Yoga"',
39
45
  '"$(PODS_CONFIGURATION_BUILD_DIR)/React-utils/React_utils.framework/Headers"',
40
46
  '"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers"',
41
47
  '"$(PODS_CONFIGURATION_BUILD_DIR)/React-featureflags/React_featureflags.framework/Headers"',
@@ -49,11 +55,10 @@ Pod::Spec.new do |s|
49
55
  '"$(PODS_ROOT)/DoubleConversion"',
50
56
  '"$(PODS_ROOT)/RCT-Folly"',
51
57
  ].join(' ')
52
- }
58
+ })
53
59
  else
54
- s.pod_target_xcconfig = {
55
- 'DEFINES_MODULE' => 'YES',
56
- 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
57
- }
60
+ s.pod_target_xcconfig = common_xcconfig.merge({
61
+ 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_ROOT}/Headers/Private/Yoga"',
62
+ })
58
63
  end
59
64
  end
@@ -1,5 +1,7 @@
1
1
  package com.setupadprebidreactnative
2
2
 
3
+ import android.os.Handler
4
+ import android.os.Looper
3
5
  import android.util.Log
4
6
  import android.view.Gravity
5
7
  import android.view.View
@@ -41,6 +43,9 @@ class VeonPrebidReactNativeView(private val reactContext: ReactContext) : FrameL
41
43
  // Track if parameters are complete
42
44
  private var paramsComplete = false
43
45
 
46
+ // Main thread handler to ensure all Prebid/WebView operations run on UI thread
47
+ private val mainHandler = Handler(Looper.getMainLooper())
48
+
44
49
  // Runnable for forcing layout after programmatic view changes
45
50
  private val measureAndLayout = Runnable {
46
51
  measure(
@@ -108,13 +113,18 @@ class VeonPrebidReactNativeView(private val reactContext: ReactContext) : FrameL
108
113
  return
109
114
  }
110
115
 
111
- if (bannerLoader == null) {
112
- Log.d(TAG, "Banner loader is null, creating new one")
113
- createBannerLoader()
114
- }
116
+ // Ensure banner creation and loading happens on the main thread.
117
+ // Prebid OMID SDK registers a ContentObserver on the calling thread's Looper;
118
+ // if that thread is not main, WebView calls from the observer will crash.
119
+ runOnMainThread {
120
+ if (bannerLoader == null) {
121
+ Log.d(TAG, "Banner loader is null, creating new one")
122
+ createBannerLoader()
123
+ }
115
124
 
116
- Log.d(TAG, "Calling bannerLoader.loadAd()")
117
- bannerLoader?.loadAd()
125
+ Log.d(TAG, "Calling bannerLoader.loadAd()")
126
+ bannerLoader?.loadAd()
127
+ }
118
128
  }
119
129
 
120
130
  fun showBanner() {
@@ -250,19 +260,23 @@ class VeonPrebidReactNativeView(private val reactContext: ReactContext) : FrameL
250
260
  return
251
261
  }
252
262
 
253
- if (interstitialLoader == null) {
254
- createInterstitialLoader()
255
- }
263
+ runOnMainThread {
264
+ if (interstitialLoader == null) {
265
+ createInterstitialLoader()
266
+ }
256
267
 
257
- Log.d(TAG, "Calling interstitialLoader.loadAd()")
258
- interstitialLoader?.loadAd()
268
+ Log.d(TAG, "Calling interstitialLoader.loadAd()")
269
+ interstitialLoader?.loadAd()
270
+ }
259
271
  }
260
272
 
261
273
  fun showInterstitial() {
262
274
  Log.d(TAG, "showInterstitial called")
263
- interstitialLoader?.showAd() ?: run {
264
- Log.w(TAG, "No interstitial to show")
265
- sendEvent("onAdFailed", "No interstitial loaded yet")
275
+ runOnMainThread {
276
+ interstitialLoader?.showAd() ?: run {
277
+ Log.w(TAG, "No interstitial to show")
278
+ sendEvent("onAdFailed", "No interstitial loaded yet")
279
+ }
266
280
  }
267
281
  }
268
282
 
@@ -412,9 +426,27 @@ class VeonPrebidReactNativeView(private val reactContext: ReactContext) : FrameL
412
426
 
413
427
  fun destroy() {
414
428
  Log.d(TAG, "Destroying view")
415
- destroyBannerLoader()
416
- destroyInterstitialLoader()
417
- paramsComplete = false
429
+ runOnMainThread {
430
+ destroyBannerLoader()
431
+ destroyInterstitialLoader()
432
+ paramsComplete = false
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Run a block on the main thread. If already on main, execute immediately.
438
+ * This prevents Prebid OMID SDK from binding ContentObservers to background threads,
439
+ * which would cause WebView calls from non-main threads and crash the app.
440
+ */
441
+ private fun runOnMainThread(block: () -> Unit) {
442
+ val isMainThread = Looper.myLooper() == Looper.getMainLooper()
443
+ if (isMainThread) {
444
+ Log.d(TAG, "runOnMainThread: already on main thread, executing directly")
445
+ block()
446
+ } else {
447
+ Log.w(TAG, "runOnMainThread: called from background thread '${Thread.currentThread().name}', dispatching to main")
448
+ mainHandler.post(block)
449
+ }
418
450
  }
419
451
 
420
452
  override fun onDetachedFromWindow() {
package/app.plugin.js ADDED
@@ -0,0 +1,62 @@
1
+ const { withDangerousMod, withPlugins } = require('@expo/config-plugins');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ /**
6
+ * Expo config plugin for setupad-prebid-react-native
7
+ *
8
+ * Automatically configures the consumer's iOS Podfile with:
9
+ * - Explicit VeonPrebid pod dependencies
10
+ * - post_install hooks for DEFINES_MODULE and Swift module resolution
11
+ */
12
+ function withVeonPrebidIOS(config) {
13
+ return withDangerousMod(config, [
14
+ 'ios',
15
+ async (cfg) => {
16
+ const podfilePath = path.join(cfg.modRequest.platformProjectRoot, 'Podfile');
17
+
18
+ if (!fs.existsSync(podfilePath)) {
19
+ return cfg;
20
+ }
21
+
22
+ let podfile = fs.readFileSync(podfilePath, 'utf8');
23
+
24
+ // Add explicit Veon Prebid pod dependencies if not already present
25
+ if (!podfile.includes("pod 'VeonPrebidMobile'")) {
26
+ podfile = podfile.replace(
27
+ /use_react_native!\(/,
28
+ `# Veon Prebid Dependencies\n pod 'Google-Mobile-Ads-SDK', '12.3.0'\n pod 'VeonPrebidMobile', '0.0.4'\n pod 'VeonPrebidMobileGAMEventHandlers', '0.0.4'\n\n use_react_native!(`
29
+ );
30
+ }
31
+
32
+ // Add post_install hook for VeonPrebid module fixes
33
+ if (!podfile.includes('VeonPrebidMobile Swift module')) {
34
+ const postInstallHook = `
35
+ # Fix for VeonPrebidMobile Swift module resolution
36
+ installer.pods_project.targets.each do |target|
37
+ if ['VeonPrebidMobile', 'VeonPrebidMobileGAMEventHandlers'].include?(target.name)
38
+ target.build_configurations.each do |build_config|
39
+ build_config.build_settings['DEFINES_MODULE'] = 'YES'
40
+ build_config.build_settings['SWIFT_VERSION'] = '5.0'
41
+ build_config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
42
+ build_config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -no-verify-emitted-module-interface'
43
+ end
44
+ end
45
+ end`;
46
+
47
+ // Insert before the last `end` of post_install block
48
+ podfile = podfile.replace(
49
+ /(react_native_post_install\([^)]*\)[\s\S]*?\))/,
50
+ `$1\n${postInstallHook}`
51
+ );
52
+ }
53
+
54
+ fs.writeFileSync(podfilePath, podfile);
55
+ return cfg;
56
+ },
57
+ ]);
58
+ }
59
+
60
+ module.exports = function withVeonPrebid(config) {
61
+ return withPlugins(config, [withVeonPrebidIOS]);
62
+ };
@@ -9,7 +9,6 @@
9
9
  #import <react/renderer/components/VeonPrebidReactNativeViewSpec/RCTComponentViewHelpers.h>
10
10
 
11
11
  #import "RCTFabricComponentsPlugins.h"
12
- #import "VeonPrebidReactNative-Swift.h"
13
12
 
14
13
  using namespace facebook::react;
15
14
 
@@ -17,7 +16,7 @@ using namespace facebook::react;
17
16
  @end
18
17
 
19
18
  @implementation VeonPrebidReactNativeViewComponentView {
20
- VeonPrebidReactNativeView *_view;
19
+ UIView *_view;
21
20
  }
22
21
 
23
22
  + (ComponentDescriptorProvider)componentDescriptorProvider
@@ -31,7 +30,12 @@ using namespace facebook::react;
31
30
  static const auto defaultProps = std::make_shared<const VeonPrebidReactNativeViewProps>();
32
31
  _props = defaultProps;
33
32
 
34
- _view = [[VeonPrebidReactNativeView alloc] init];
33
+ Class viewClass = NSClassFromString(@"VeonPrebidReactNativeView");
34
+ if (viewClass) {
35
+ _view = [[viewClass alloc] init];
36
+ } else {
37
+ _view = [[UIView alloc] init];
38
+ }
35
39
  _view.frame = self.bounds;
36
40
  _view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
37
41
 
@@ -46,34 +50,28 @@ using namespace facebook::react;
46
50
  const auto &oldViewProps = *std::static_pointer_cast<const VeonPrebidReactNativeViewProps>(_props);
47
51
  const auto &newViewProps = *std::static_pointer_cast<const VeonPrebidReactNativeViewProps>(props);
48
52
 
49
- // Update adType
50
53
  if (oldViewProps.adType != newViewProps.adType) {
51
- _view.adTypeValue = [NSString stringWithUTF8String:newViewProps.adType.c_str()];
54
+ [_view setValue:[NSString stringWithUTF8String:newViewProps.adType.c_str()] forKey:@"adTypeValue"];
52
55
  }
53
56
 
54
- // Update configId
55
57
  if (oldViewProps.configId != newViewProps.configId) {
56
- _view.configIdValue = [NSString stringWithUTF8String:newViewProps.configId.c_str()];
58
+ [_view setValue:[NSString stringWithUTF8String:newViewProps.configId.c_str()] forKey:@"configIdValue"];
57
59
  }
58
60
 
59
- // Update adUnitId
60
61
  if (oldViewProps.adUnitId != newViewProps.adUnitId) {
61
- _view.adUnitIdValue = [NSString stringWithUTF8String:newViewProps.adUnitId.c_str()];
62
+ [_view setValue:[NSString stringWithUTF8String:newViewProps.adUnitId.c_str()] forKey:@"adUnitIdValue"];
62
63
  }
63
64
 
64
- // Update width
65
65
  if (oldViewProps.width != newViewProps.width) {
66
- _view.widthValue = @(newViewProps.width);
66
+ [_view setValue:@(newViewProps.width) forKey:@"widthValue"];
67
67
  }
68
68
 
69
- // Update height
70
69
  if (oldViewProps.height != newViewProps.height) {
71
- _view.heightValue = @(newViewProps.height);
70
+ [_view setValue:@(newViewProps.height) forKey:@"heightValue"];
72
71
  }
73
72
 
74
- // Update refreshInterval
75
73
  if (oldViewProps.refreshInterval != newViewProps.refreshInterval) {
76
- _view.refreshIntervalValue = @(newViewProps.refreshInterval);
74
+ [_view setValue:@(newViewProps.refreshInterval) forKey:@"refreshIntervalValue"];
77
75
  }
78
76
 
79
77
  [super updateProps:props oldProps:oldProps];
@@ -85,76 +83,70 @@ using namespace facebook::react;
85
83
 
86
84
  __weak __typeof(self) weakSelf = self;
87
85
 
88
- // Set up event handlers
89
- _view.onAdLoaded = ^(NSDictionary *event) {
86
+ void (^onAdLoaded)(NSDictionary *) = ^(NSDictionary *event) {
90
87
  __typeof(self) strongSelf = weakSelf;
91
88
  if (strongSelf && strongSelf->_eventEmitter) {
92
89
  auto emitter = std::static_pointer_cast<const VeonPrebidReactNativeViewEventEmitter>(strongSelf->_eventEmitter);
93
-
94
90
  VeonPrebidReactNativeViewEventEmitter::OnAdLoaded data;
95
91
  if (event[@"adId"]) data.adId = std::string([event[@"adId"] UTF8String]);
96
92
  if (event[@"sdk"]) data.sdk = std::string([event[@"sdk"] UTF8String]);
97
93
  if (event[@"message"]) data.message = std::string([event[@"message"] UTF8String]);
98
-
99
94
  emitter->onAdLoaded(data);
100
95
  }
101
96
  };
97
+ [_view setValue:[onAdLoaded copy] forKey:@"onAdLoaded"];
102
98
 
103
- _view.onAdDisplayed = ^(NSDictionary *event) {
99
+ void (^onAdDisplayed)(NSDictionary *) = ^(NSDictionary *event) {
104
100
  __typeof(self) strongSelf = weakSelf;
105
101
  if (strongSelf && strongSelf->_eventEmitter) {
106
102
  auto emitter = std::static_pointer_cast<const VeonPrebidReactNativeViewEventEmitter>(strongSelf->_eventEmitter);
107
-
108
103
  VeonPrebidReactNativeViewEventEmitter::OnAdDisplayed data;
109
104
  if (event[@"adId"]) data.adId = std::string([event[@"adId"] UTF8String]);
110
105
  if (event[@"sdk"]) data.sdk = std::string([event[@"sdk"] UTF8String]);
111
106
  if (event[@"message"]) data.message = std::string([event[@"message"] UTF8String]);
112
-
113
107
  emitter->onAdDisplayed(data);
114
108
  }
115
109
  };
110
+ [_view setValue:[onAdDisplayed copy] forKey:@"onAdDisplayed"];
116
111
 
117
- _view.onAdFailed = ^(NSDictionary *event) {
112
+ void (^onAdFailed)(NSDictionary *) = ^(NSDictionary *event) {
118
113
  __typeof(self) strongSelf = weakSelf;
119
114
  if (strongSelf && strongSelf->_eventEmitter) {
120
115
  auto emitter = std::static_pointer_cast<const VeonPrebidReactNativeViewEventEmitter>(strongSelf->_eventEmitter);
121
-
122
116
  VeonPrebidReactNativeViewEventEmitter::OnAdFailed data;
123
117
  if (event[@"adId"]) data.adId = std::string([event[@"adId"] UTF8String]);
124
118
  if (event[@"sdk"]) data.sdk = std::string([event[@"sdk"] UTF8String]);
125
119
  if (event[@"message"]) data.message = std::string([event[@"message"] UTF8String]);
126
-
127
120
  emitter->onAdFailed(data);
128
121
  }
129
122
  };
123
+ [_view setValue:[onAdFailed copy] forKey:@"onAdFailed"];
130
124
 
131
- _view.onAdClicked = ^(NSDictionary *event) {
125
+ void (^onAdClicked)(NSDictionary *) = ^(NSDictionary *event) {
132
126
  __typeof(self) strongSelf = weakSelf;
133
127
  if (strongSelf && strongSelf->_eventEmitter) {
134
128
  auto emitter = std::static_pointer_cast<const VeonPrebidReactNativeViewEventEmitter>(strongSelf->_eventEmitter);
135
-
136
129
  VeonPrebidReactNativeViewEventEmitter::OnAdClicked data;
137
130
  if (event[@"adId"]) data.adId = std::string([event[@"adId"] UTF8String]);
138
131
  if (event[@"sdk"]) data.sdk = std::string([event[@"sdk"] UTF8String]);
139
132
  if (event[@"message"]) data.message = std::string([event[@"message"] UTF8String]);
140
-
141
133
  emitter->onAdClicked(data);
142
134
  }
143
135
  };
136
+ [_view setValue:[onAdClicked copy] forKey:@"onAdClicked"];
144
137
 
145
- _view.onAdClosed = ^(NSDictionary *event) {
138
+ void (^onAdClosed)(NSDictionary *) = ^(NSDictionary *event) {
146
139
  __typeof(self) strongSelf = weakSelf;
147
140
  if (strongSelf && strongSelf->_eventEmitter) {
148
141
  auto emitter = std::static_pointer_cast<const VeonPrebidReactNativeViewEventEmitter>(strongSelf->_eventEmitter);
149
-
150
142
  VeonPrebidReactNativeViewEventEmitter::OnAdClosed data;
151
143
  if (event[@"adId"]) data.adId = std::string([event[@"adId"] UTF8String]);
152
144
  if (event[@"sdk"]) data.sdk = std::string([event[@"sdk"] UTF8String]);
153
145
  if (event[@"message"]) data.message = std::string([event[@"message"] UTF8String]);
154
-
155
146
  emitter->onAdClosed(data);
156
147
  }
157
148
  };
149
+ [_view setValue:[onAdClosed copy] forKey:@"onAdClosed"];
158
150
  }
159
151
 
160
152
  Class<RCTComponentViewProtocol> VeonPrebidReactNativeViewCls(void)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "setupad-prebid-react-native",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Prebid SDK for React Native",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -19,6 +19,7 @@
19
19
  "ios",
20
20
  "cpp",
21
21
  "*.podspec",
22
+ "app.plugin.js",
22
23
  "react-native.config.js",
23
24
  "!ios/build",
24
25
  "!android/build",