@advergic-ads/react-native-sdk 1.0.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.
Files changed (29) hide show
  1. package/AdvergicReactNative.podspec +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +329 -0
  4. package/android/advergic-bridge/build.gradle +58 -0
  5. package/android/advergic-bridge/libs/advergic-sdk.aar +0 -0
  6. package/android/advergic-bridge/src/main/java/com/advergic/reactnative/AdvergicBannerViewManager.kt +194 -0
  7. package/android/advergic-bridge/src/main/java/com/advergic/reactnative/AdvergicReactNativeModule.java +401 -0
  8. package/android/advergic-bridge/src/main/java/com/advergic/reactnative/AdvergicReactNativePackage.java +25 -0
  9. package/android/app/build.gradle +144 -0
  10. package/android/app/libs/advergic-sdk.aar +0 -0
  11. package/android/app/src/main/AndroidManifest.xml +30 -0
  12. package/android/app/src/main/java/com/advergic/reactnative/AdvergicReactNativePackage.java +26 -0
  13. package/android/app/src/main/java/com/facebook/react/viewmanagers/AdvergicBannerViewManagerInterface.java +8 -0
  14. package/android/libs/advergic-sdk.aar +0 -0
  15. package/android/settings.gradle +7 -0
  16. package/index.d.ts +135 -0
  17. package/index.js +27 -0
  18. package/install.js +158 -0
  19. package/ios/AdvergicBannerView.swift +59 -0
  20. package/ios/AdvergicBannerViewManager.m +18 -0
  21. package/ios/AdvergicBannerViewManager.swift +24 -0
  22. package/ios/AdvergicReactNativeModule.m +36 -0
  23. package/ios/AdvergicReactNativeModule.swift +363 -0
  24. package/package.json +57 -0
  25. package/react-native.config.js +18 -0
  26. package/src/advergic/AdvergicBannerAd.tsx +51 -0
  27. package/src/advergic/AdvergicSDK.ts +318 -0
  28. package/src/advergic/index.ts +11 -0
  29. package/src/advergic/types.ts +23 -0
@@ -0,0 +1,363 @@
1
+ //
2
+ // AdvergicReactNativeModule.swift
3
+ // AdvergicReactNative
4
+ //
5
+ // React Native bridge for the Advergic iOS SDK.
6
+ // Exposed to JavaScript as `NativeModules.AdvergicSDK`, mirroring the
7
+ // Android `AdvergicReactNativeModule` so the shared TypeScript layer works
8
+ // identically on both platforms.
9
+ //
10
+
11
+ import Foundation
12
+ import UIKit
13
+ import React
14
+ import AdvergicSDK
15
+
16
+ @objc(AdvergicSDK)
17
+ class AdvergicReactNativeModule: RCTEventEmitter {
18
+
19
+ // The native iOS SDK exposes a single delegate per instance, so we keep one
20
+ // shared instance and act as its delegate, fanning callbacks out to JS.
21
+ static weak var shared: AdvergicReactNativeModule?
22
+
23
+ private let sdk = AdvergicSdk()
24
+ private var hasListeners = false
25
+
26
+ // The iOS SDK callbacks don't echo back the ad unit id, so we remember the
27
+ // last requested id per format to attach it to emitted events.
28
+ private var lastInterstitialAdUnitId = ""
29
+ private var lastRewardedAdUnitId = ""
30
+ private var lastAppOpenAdUnitId = ""
31
+
32
+ // The currently mounted banner view, used to forward click / impression
33
+ // delegate callbacks (which are not tied to a specific banner by the SDK).
34
+ private weak var activeBannerView: AdvergicBannerView?
35
+
36
+ override init() {
37
+ super.init()
38
+ sdk.advergicDelegate = self
39
+ AdvergicReactNativeModule.shared = self
40
+ }
41
+
42
+ @objc
43
+ override static func requiresMainQueueSetup() -> Bool {
44
+ return true
45
+ }
46
+
47
+ override func supportedEvents() -> [String]! {
48
+ return [
49
+ "OnSDKInitialized",
50
+ "OnBotDetectionResult",
51
+ "OnBannerAdLoaded",
52
+ "OnBannerAdFailedToLoad",
53
+ "OnBannerAdClicked",
54
+ "OnBannerAdImpression",
55
+ "OnInterstitialAdLoaded",
56
+ "OnInterstitialAdFailedToLoad",
57
+ "OnInterstitialAdShowed",
58
+ "OnInterstitialAdDismissed",
59
+ "OnInterstitialAdFailedToShow",
60
+ "OnInterstitialAdClicked",
61
+ "OnInterstitialAdImpression",
62
+ "OnRewardedAdLoaded",
63
+ "OnRewardedAdFailedToLoad",
64
+ "OnRewardedAdShowed",
65
+ "OnRewardedAdFailedToShow",
66
+ "OnRewardedAdOpened",
67
+ "OnRewardedAdClicked",
68
+ "OnRewardedAdClosed",
69
+ "OnUserEarnedReward",
70
+ "OnAppOpenAdLoaded",
71
+ "OnAppOpenAdFailedToLoad",
72
+ "OnAppOpenAdShowed",
73
+ "OnAppOpenAdDismissed",
74
+ "OnAppOpenAdFailedToShow",
75
+ "OnAppOpenAdClicked",
76
+ "OnAppOpenAdImpression",
77
+ "OnNativeAdLoaded",
78
+ "OnNativeAdFailedToLoad",
79
+ "OnNativeAdClicked",
80
+ "OnNativeAdImpression",
81
+ "OnNativePrebidAdNotFound",
82
+ "OnNativePrebidAdNotValid",
83
+ ]
84
+ }
85
+
86
+ override func startObserving() {
87
+ hasListeners = true
88
+ }
89
+
90
+ override func stopObserving() {
91
+ hasListeners = false
92
+ }
93
+
94
+ private func emit(_ name: String, _ body: [String: Any]) {
95
+ guard hasListeners else { return }
96
+ sendEvent(withName: name, body: body)
97
+ }
98
+
99
+ private func adUnitBody(_ adUnitId: String) -> [String: Any] {
100
+ return ["adUnitId": adUnitId]
101
+ }
102
+
103
+ private func errorBody(_ adUnitId: String, _ error: String) -> [String: Any] {
104
+ return ["adUnitId": adUnitId, "error": error]
105
+ }
106
+
107
+ // MARK: - View controller helper
108
+
109
+ static func topViewController() -> UIViewController? {
110
+ var keyWindow: UIWindow?
111
+ keyWindow = UIApplication.shared.connectedScenes
112
+ .compactMap { $0 as? UIWindowScene }
113
+ .flatMap { $0.windows }
114
+ .first { $0.isKeyWindow }
115
+ if keyWindow == nil {
116
+ keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
117
+ }
118
+ var top = keyWindow?.rootViewController
119
+ while let presented = top?.presentedViewController {
120
+ top = presented
121
+ }
122
+ return top
123
+ }
124
+
125
+ // MARK: - Initialization
126
+
127
+ @objc(initialize:resolver:rejecter:)
128
+ func initialize(_ apiKey: String,
129
+ resolver resolve: @escaping RCTPromiseResolveBlock,
130
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
131
+ DispatchQueue.main.async {
132
+ self.sdk.Init(apiKey: apiKey)
133
+ // The iOS SDK loads its config asynchronously and exposes no completion
134
+ // callback, so we poll for ad units to mirror the Android contract of
135
+ // resolving `true` once the SDK is ready to serve.
136
+ self.waitForInit(attempt: 0, resolve: resolve)
137
+ }
138
+ }
139
+
140
+ private func waitForInit(attempt: Int, resolve: @escaping RCTPromiseResolveBlock) {
141
+ let units = sdk.getAdvergicAdUnits()
142
+ if !units.isEmpty {
143
+ emit("OnSDKInitialized", ["initialized": true, "adUnitCount": units.count])
144
+ resolve(true)
145
+ return
146
+ }
147
+ // ~12s budget (40 * 300ms) before giving up and resolving anyway.
148
+ if attempt >= 40 {
149
+ resolve(true)
150
+ return
151
+ }
152
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
153
+ self.waitForInit(attempt: attempt + 1, resolve: resolve)
154
+ }
155
+ }
156
+
157
+ @objc(canServeAds:rejecter:)
158
+ func canServeAds(_ resolve: @escaping RCTPromiseResolveBlock,
159
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
160
+ resolve(!sdk.getAdvergicAdUnits().isEmpty)
161
+ }
162
+
163
+ @objc(getAdUnits:rejecter:)
164
+ func getAdUnits(_ resolve: @escaping RCTPromiseResolveBlock,
165
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
166
+ let result: [[String: Any]] = sdk.getAdvergicAdUnits().map { unit in
167
+ return [
168
+ "id": unit.configID,
169
+ "name": unit.configID,
170
+ "type": unit.adUnitType,
171
+ ]
172
+ }
173
+ resolve(result)
174
+ }
175
+
176
+ @objc(performFollowUpBotDetection)
177
+ func performFollowUpBotDetection() {
178
+ // Bot detection runs automatically inside the iOS SDK after init; there is
179
+ // no public trigger, so this is a no-op for API parity with Android.
180
+ }
181
+
182
+ // MARK: - Banner (called by AdvergicBannerView)
183
+
184
+ func loadBanner(adUnitId: String,
185
+ container: UIView,
186
+ onLoaded: @escaping () -> Void,
187
+ onFailed: @escaping (String) -> Void) {
188
+ activeBannerView = container as? AdvergicBannerView
189
+ attemptLoadBanner(adUnitId: adUnitId, container: container, attempt: 0,
190
+ onLoaded: onLoaded, onFailed: onFailed)
191
+ }
192
+
193
+ private let maxBannerRetries = 10
194
+
195
+ private func attemptLoadBanner(adUnitId: String,
196
+ container: UIView,
197
+ attempt: Int,
198
+ onLoaded: @escaping () -> Void,
199
+ onFailed: @escaping (String) -> Void) {
200
+ // The SDK silently ignores requests before its config is loaded, so retry
201
+ // until ad units are available (mirrors the Android banner retry logic).
202
+ if sdk.getAdvergicAdUnits().isEmpty {
203
+ if attempt >= maxBannerRetries {
204
+ onFailed("AdvergicSDK not initialized")
205
+ return
206
+ }
207
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.3 * Double(attempt + 1)) {
208
+ self.attemptLoadBanner(adUnitId: adUnitId, container: container,
209
+ attempt: attempt + 1, onLoaded: onLoaded, onFailed: onFailed)
210
+ }
211
+ return
212
+ }
213
+
214
+ guard let vc = AdvergicReactNativeModule.topViewController() else {
215
+ onFailed("No view controller available")
216
+ return
217
+ }
218
+
219
+ sdk.showBannerAd(adUnitID: adUnitId, viewController: vc, bannerAdView: container) { bannerView, error in
220
+ if bannerView != nil {
221
+ onLoaded()
222
+ } else {
223
+ onFailed(error?.localizedDescription ?? "Unknown error")
224
+ }
225
+ }
226
+ }
227
+
228
+ // Low-level banner API (parity with Android's showBannerAd(adUnitId,
229
+ // containerId)). The primary path is the `AdvergicBannerAd` component, which
230
+ // loads automatically; this resolves a container by its React tag and loads
231
+ // into it, emitting global banner events.
232
+ @objc(showBannerAd:containerId:)
233
+ func showBannerAd(_ adUnitId: String, containerId: NSNumber) {
234
+ bridge.uiManager.addUIBlock { [weak self] _, viewRegistry in
235
+ guard let self = self else { return }
236
+ guard let view = viewRegistry?[containerId] else {
237
+ self.emit("OnBannerAdFailedToLoad", self.errorBody(adUnitId, "Container not found"))
238
+ return
239
+ }
240
+ self.loadBanner(adUnitId: adUnitId, container: view, onLoaded: {
241
+ self.emit("OnBannerAdLoaded", self.adUnitBody(adUnitId))
242
+ }, onFailed: { error in
243
+ self.emit("OnBannerAdFailedToLoad", self.errorBody(adUnitId, error))
244
+ })
245
+ }
246
+ }
247
+
248
+ // MARK: - Full-screen formats
249
+
250
+ @objc(showInterstitialAd:)
251
+ func showInterstitialAd(_ adUnitId: String) {
252
+ DispatchQueue.main.async {
253
+ guard let vc = AdvergicReactNativeModule.topViewController() else {
254
+ self.emit("OnInterstitialAdFailedToLoad", self.errorBody(adUnitId, "No view controller available"))
255
+ return
256
+ }
257
+ self.lastInterstitialAdUnitId = adUnitId
258
+ self.sdk.showInterstitialAd(adUnitID: adUnitId, viewController: vc)
259
+ }
260
+ }
261
+
262
+ @objc(showRewardedAd:)
263
+ func showRewardedAd(_ adUnitId: String) {
264
+ DispatchQueue.main.async {
265
+ guard let vc = AdvergicReactNativeModule.topViewController() else {
266
+ self.emit("OnRewardedAdFailedToLoad", self.errorBody(adUnitId, "No view controller available"))
267
+ return
268
+ }
269
+ self.lastRewardedAdUnitId = adUnitId
270
+ self.sdk.showRewardedAd(adUnitID: adUnitId, viewController: vc)
271
+ }
272
+ }
273
+
274
+ @objc(showAppOpenAd:)
275
+ func showAppOpenAd(_ adUnitId: String) {
276
+ DispatchQueue.main.async {
277
+ guard let vc = AdvergicReactNativeModule.topViewController() else {
278
+ self.emit("OnAppOpenAdFailedToLoad", self.errorBody(adUnitId, "No view controller available"))
279
+ return
280
+ }
281
+ self.lastAppOpenAdUnitId = adUnitId
282
+ self.sdk.showAppOpenAd(adUnitID: adUnitId, viewController: vc)
283
+ }
284
+ }
285
+
286
+ @objc(showNativeAd:containerId:)
287
+ func showNativeAd(_ adUnitId: String, containerId: NSNumber) {
288
+ // Native ads are not supported by the iOS SDK (Android-only). Emit a
289
+ // failure so callers using the shared JS API get a clear signal.
290
+ emit("OnNativeAdFailedToLoad", errorBody(adUnitId, "Native ads are not supported on iOS"))
291
+ }
292
+ }
293
+
294
+ // MARK: - AdvergicSdkProtocols
295
+
296
+ extension AdvergicReactNativeModule: AdvergicSdkProtocols {
297
+
298
+ // Banner — loaded / failed are handled via the showBannerAd completion in the
299
+ // banner view; click / impression are only delivered through this delegate,
300
+ // so we forward them to the active banner view's bubbling events.
301
+ func advergicBannerDidRecordImpression() {
302
+ activeBannerView?.dispatchImpression()
303
+ }
304
+
305
+ func advergicBannerAdDidClick() {
306
+ activeBannerView?.dispatchClicked()
307
+ }
308
+
309
+ // Interstitial
310
+ func advergicInterstitialDidDissmissAd() {
311
+ emit("OnInterstitialAdDismissed", adUnitBody(lastInterstitialAdUnitId))
312
+ }
313
+
314
+ func advergicInterstitialFailToShowFullScreenContent(adError: String) {
315
+ emit("OnInterstitialAdFailedToShow", errorBody(lastInterstitialAdUnitId, adError))
316
+ }
317
+
318
+ func advergicInterstitialDidClickAd() {
319
+ emit("OnInterstitialAdClicked", adUnitBody(lastInterstitialAdUnitId))
320
+ }
321
+
322
+ func advergicInterstitialDidRecordImpressionAd() {
323
+ emit("OnInterstitialAdImpression", adUnitBody(lastInterstitialAdUnitId))
324
+ }
325
+
326
+ // Rewarded
327
+ func advergicRewardReceived() {
328
+ emit("OnUserEarnedReward", adUnitBody(lastRewardedAdUnitId))
329
+ }
330
+
331
+ func advergicRewardDidDissmissFullScreen() {
332
+ emit("OnRewardedAdClosed", adUnitBody(lastRewardedAdUnitId))
333
+ }
334
+
335
+ func advergicRewardFailToPresentFullScreenContent(adError: String) {
336
+ emit("OnRewardedAdFailedToShow", errorBody(lastRewardedAdUnitId, adError))
337
+ }
338
+
339
+ func advergicRewardDidRecordImpression() {
340
+ emit("OnRewardedAdShowed", adUnitBody(lastRewardedAdUnitId))
341
+ }
342
+
343
+ func advergicRewardDidRecordClick() {
344
+ emit("OnRewardedAdClicked", adUnitBody(lastRewardedAdUnitId))
345
+ }
346
+
347
+ // App Open
348
+ func advergicAppOpenDidDissmissFullScreen() {
349
+ emit("OnAppOpenAdDismissed", adUnitBody(lastAppOpenAdUnitId))
350
+ }
351
+
352
+ func advergicAppOpenFailToPresentFullScreenContent(adError: String) {
353
+ emit("OnAppOpenAdFailedToShow", errorBody(lastAppOpenAdUnitId, adError))
354
+ }
355
+
356
+ func advergicAppOpenDidRecordImpression() {
357
+ emit("OnAppOpenAdImpression", adUnitBody(lastAppOpenAdUnitId))
358
+ }
359
+
360
+ func advergicAppOpenDidRecordClick() {
361
+ emit("OnAppOpenAdClicked", adUnitBody(lastAppOpenAdUnitId))
362
+ }
363
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@advergic-ads/react-native-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Advergic React Native SDK - Integrate Prebid header bidding and mobile advertising into your React Native apps",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "scripts": {
8
+ "install": "node install.js"
9
+ },
10
+ "keywords": [
11
+ "react-native",
12
+ "advergic",
13
+ "advertising",
14
+ "ads",
15
+ "banner",
16
+ "interstitial",
17
+ "rewarded",
18
+ "mobile-ads",
19
+ "admob",
20
+ "prebid"
21
+ ],
22
+ "author": "Advergic <hello@advergic.com>",
23
+ "license": "MIT",
24
+ "homepage": "https://advergic.com",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/advergic/react-native-sdk.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/advergic/react-native-sdk/issues"
31
+ },
32
+ "peerDependencies": {
33
+ "react-native": "^0.85.3"
34
+ },
35
+ "dependencies": {
36
+ "react-native-safe-area-context": "^4.12.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/react-native": "^0.73.0"
40
+ },
41
+ "files": [
42
+ "src/",
43
+ "android/",
44
+ "ios/",
45
+ "install.js",
46
+ "index.js",
47
+ "index.d.ts",
48
+ "react-native.config.js",
49
+ "AdvergicReactNative.podspec",
50
+ "SPEC.md",
51
+ "README.md",
52
+ "CHANGELOG.md"
53
+ ],
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ }
57
+ }
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ project: {
3
+ ios: {},
4
+ android: {},
5
+ },
6
+ dependency: {
7
+ platforms: {
8
+ android: {
9
+ sourceDir: "android/advergic-bridge",
10
+ packageImportPath: "import com.advergic.reactnative.AdvergicReactNativePackage;",
11
+ packageInstance: "new AdvergicReactNativePackage()",
12
+ },
13
+ ios: {
14
+ podspecPath: "AdvergicReactNative.podspec",
15
+ },
16
+ },
17
+ },
18
+ };
@@ -0,0 +1,51 @@
1
+ import type { NativeSyntheticEvent, ViewStyle } from 'react-native';
2
+ import React from 'react';
3
+ import { requireNativeComponent } from 'react-native';
4
+
5
+ interface NativeProps {
6
+ adUnitId?: string;
7
+ style?: ViewStyle;
8
+ onAdLoaded?: () => void;
9
+ onAdFailedToLoad?: (event: NativeSyntheticEvent<{ error: string }>) => void;
10
+ onAdClicked?: () => void;
11
+ onAdImpression?: () => void;
12
+ }
13
+
14
+ const NativeAdvergicBannerView =
15
+ requireNativeComponent<NativeProps>('AdvergicBannerView');
16
+
17
+ interface AdvergicBannerAdProps {
18
+ adUnitId: string;
19
+ style?: ViewStyle;
20
+ onAdLoaded?: () => void;
21
+ onAdFailedToLoad?: (error: string) => void;
22
+ onAdClicked?: () => void;
23
+ onAdImpression?: () => void;
24
+ }
25
+
26
+ export function AdvergicBannerAd({
27
+ adUnitId,
28
+ style,
29
+ onAdLoaded,
30
+ onAdFailedToLoad,
31
+ onAdClicked,
32
+ onAdImpression,
33
+ }: AdvergicBannerAdProps) {
34
+ const handleFailedToLoad = React.useCallback(
35
+ (event: NativeSyntheticEvent<{ error: string }>) => {
36
+ onAdFailedToLoad?.(event.nativeEvent.error);
37
+ },
38
+ [onAdFailedToLoad],
39
+ );
40
+
41
+ return (
42
+ <NativeAdvergicBannerView
43
+ adUnitId={adUnitId}
44
+ style={style}
45
+ onAdLoaded={onAdLoaded}
46
+ onAdFailedToLoad={handleFailedToLoad}
47
+ onAdClicked={onAdClicked}
48
+ onAdImpression={onAdImpression}
49
+ />
50
+ );
51
+ }