adwhale-sdk-react-native 2.7.204 → 2.7.300

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 (108) hide show
  1. package/AdwhaleSdkReactNative.podspec +22 -0
  2. package/README.md +80 -37
  3. package/android/proguard-rules.pro +56 -0
  4. package/android/src/main/java/com/adwhalesdkreactnative/AdWhaleCustomNativeBinderFactory.java +18 -0
  5. package/android/src/main/java/com/adwhalesdkreactnative/AdwhaleSdkReactNativePackage.java +51 -6
  6. package/android/src/main/java/com/adwhalesdkreactnative/BinderFactory.java +9 -0
  7. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdSettingModule.java +148 -49
  8. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdView.java +46 -27
  9. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAppOpenAd.java +136 -0
  10. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationCustomNativeAdView.java +37 -46
  11. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationExitPopupAd.java +240 -0
  12. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationInterstitialAd.java +189 -0
  13. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationLoggerModule.java +2 -1
  14. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationNativeAdViewListenerBridge.java +83 -0
  15. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationRewardAd.java +255 -6
  16. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTemplateNativeAdView.java +191 -70
  17. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTransitionPopupAd.java +218 -0
  18. package/android/src/main/java/com/adwhalesdkreactnative/RNWrapperView.java +137 -0
  19. package/android/src/main/java/com/adwhalesdkreactnative/SimpleBinderFactory.java +1 -1
  20. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodArgConst.java +112 -0
  21. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodCallConst.java +113 -0
  22. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodResultConst.java +28 -0
  23. package/build/generated/ios/ReactAppDependencyProvider.podspec +34 -0
  24. package/ios/AdWhaleNativeCustomViewFactoryRegistry.swift +56 -0
  25. package/ios/AdwhaleSdkReactNative.mm +96 -5
  26. package/ios/AdwhaleSdkReactNativeModule.swift +148 -0
  27. package/ios/RNAdWhaleMediationAdViewManager.m +38 -0
  28. package/ios/RNAdWhaleMediationAdViewManager.swift +14 -0
  29. package/ios/RNAdWhaleMediationAppOpenAd.m +18 -0
  30. package/ios/RNAdWhaleMediationAppOpenAd.swift +175 -0
  31. package/ios/RNAdWhaleMediationCustomNativeAdViewManager.m +22 -0
  32. package/ios/RNAdWhaleMediationCustomNativeAdViewManager.swift +239 -0
  33. package/ios/RNAdWhaleMediationInterstitialAd.m +18 -0
  34. package/ios/RNAdWhaleMediationInterstitialAd.swift +161 -0
  35. package/ios/RNAdWhaleMediationRewardAd.m +18 -0
  36. package/ios/RNAdWhaleMediationRewardAd.swift +172 -0
  37. package/ios/WhaleMediationBannerContainer.swift +182 -0
  38. package/lib/module/AdWhaleAdView.js +58 -3
  39. package/lib/module/AdWhaleAdView.js.map +1 -1
  40. package/lib/module/AdWhaleAppOpenAd.js +223 -25
  41. package/lib/module/AdWhaleAppOpenAd.js.map +1 -1
  42. package/lib/module/AdWhaleExitPopupAd.js +93 -0
  43. package/lib/module/AdWhaleExitPopupAd.js.map +1 -0
  44. package/lib/module/AdWhaleInterstitialAd.js +146 -22
  45. package/lib/module/AdWhaleInterstitialAd.js.map +1 -1
  46. package/lib/module/AdWhaleMediationAds.js +75 -4
  47. package/lib/module/AdWhaleMediationAds.js.map +1 -1
  48. package/lib/module/AdWhaleNativeCustomView.js +59 -8
  49. package/lib/module/AdWhaleNativeCustomView.js.map +1 -1
  50. package/lib/module/AdWhaleNativeTemplateView.js +37 -19
  51. package/lib/module/AdWhaleNativeTemplateView.js.map +1 -1
  52. package/lib/module/AdWhaleRewardAd.js +169 -24
  53. package/lib/module/AdWhaleRewardAd.js.map +1 -1
  54. package/lib/module/AdWhaleTransitionPopupAd.js +87 -0
  55. package/lib/module/AdWhaleTransitionPopupAd.js.map +1 -0
  56. package/lib/module/NativeAdwhaleSdkReactNative.js +16 -1
  57. package/lib/module/NativeAdwhaleSdkReactNative.js.map +1 -1
  58. package/lib/module/constants/AdWhaleMethodArgConst.js +53 -0
  59. package/lib/module/constants/AdWhaleMethodArgConst.js.map +1 -0
  60. package/lib/module/constants/AdWhaleMethodCallConst.js +56 -0
  61. package/lib/module/constants/AdWhaleMethodCallConst.js.map +1 -0
  62. package/lib/module/constants/AdWhaleMethodResultConst.js +16 -0
  63. package/lib/module/constants/AdWhaleMethodResultConst.js.map +1 -0
  64. package/lib/module/index.js +10 -1
  65. package/lib/module/index.js.map +1 -1
  66. package/lib/typescript/src/AdWhaleAdView.d.ts +20 -2
  67. package/lib/typescript/src/AdWhaleAdView.d.ts.map +1 -1
  68. package/lib/typescript/src/AdWhaleAppOpenAd.d.ts +10 -0
  69. package/lib/typescript/src/AdWhaleAppOpenAd.d.ts.map +1 -1
  70. package/lib/typescript/src/AdWhaleExitPopupAd.d.ts +36 -0
  71. package/lib/typescript/src/AdWhaleExitPopupAd.d.ts.map +1 -0
  72. package/lib/typescript/src/AdWhaleInterstitialAd.d.ts +16 -0
  73. package/lib/typescript/src/AdWhaleInterstitialAd.d.ts.map +1 -1
  74. package/lib/typescript/src/AdWhaleMediationAds.d.ts +13 -5
  75. package/lib/typescript/src/AdWhaleMediationAds.d.ts.map +1 -1
  76. package/lib/typescript/src/AdWhaleNativeCustomView.d.ts +7 -4
  77. package/lib/typescript/src/AdWhaleNativeCustomView.d.ts.map +1 -1
  78. package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts +20 -7
  79. package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts.map +1 -1
  80. package/lib/typescript/src/AdWhaleRewardAd.d.ts +18 -2
  81. package/lib/typescript/src/AdWhaleRewardAd.d.ts.map +1 -1
  82. package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts +33 -0
  83. package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts.map +1 -0
  84. package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts +28 -6
  85. package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts.map +1 -1
  86. package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts +51 -0
  87. package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts.map +1 -0
  88. package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts +53 -0
  89. package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts.map +1 -0
  90. package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts +14 -0
  91. package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts.map +1 -0
  92. package/lib/typescript/src/index.d.ts +9 -1
  93. package/lib/typescript/src/index.d.ts.map +1 -1
  94. package/package.json +1 -1
  95. package/src/AdWhaleAdView.tsx +92 -4
  96. package/src/AdWhaleAppOpenAd.ts +293 -38
  97. package/src/AdWhaleExitPopupAd.ts +183 -0
  98. package/src/AdWhaleInterstitialAd.ts +206 -36
  99. package/src/AdWhaleMediationAds.ts +108 -4
  100. package/src/AdWhaleNativeCustomView.tsx +85 -23
  101. package/src/AdWhaleNativeTemplateView.tsx +79 -42
  102. package/src/AdWhaleRewardAd.ts +245 -43
  103. package/src/AdWhaleTransitionPopupAd.ts +171 -0
  104. package/src/NativeAdwhaleSdkReactNative.ts +30 -6
  105. package/src/constants/AdWhaleMethodArgConst.ts +50 -0
  106. package/src/constants/AdWhaleMethodCallConst.ts +60 -0
  107. package/src/constants/AdWhaleMethodResultConst.ts +13 -0
  108. package/src/index.ts +33 -0
@@ -0,0 +1,148 @@
1
+ import Foundation
2
+ import React
3
+ import AdWhaleSDK
4
+
5
+ /// AdWhale TurboModule 본문 — `AdwhaleSdkReactNative.mm` 브리지가 여기로 위임합니다.
6
+ @objcMembers
7
+ public final class AdwhaleSdkReactNativeModule: NSObject {
8
+
9
+ private static let errNoActivity = "NO_ACTIVITY"
10
+
11
+ private static func mapMaxAdContentRating(_ rating: String?) -> AdWhaleMaxAdContentRating {
12
+ guard let rating, !rating.isEmpty else { return .general }
13
+ switch rating {
14
+ case ".general": return .general
15
+ case ".parentalGuidance": return .parentalGuidance
16
+ case ".teen": return .teen
17
+ case ".matureAudience": return .matureAudience
18
+ default: return .general
19
+ }
20
+ }
21
+
22
+ @objc public func initializeWithResolve(
23
+ _ resolve: @escaping RCTPromiseResolveBlock,
24
+ reject: @escaping RCTPromiseRejectBlock
25
+ ) {
26
+ AdWhaleAds.sharedInstance.initialize("0") {
27
+ DispatchQueue.main.async {
28
+ resolve([
29
+ "statusCode": 100,
30
+ "message": "AdWhale SDK initialization completed",
31
+ ])
32
+ }
33
+ }
34
+ }
35
+
36
+ @objc public func setCoppa(_ isEnabled: Bool) {
37
+ AdWhaleAds.sharedInstance.setTagForChildDirectedTreatment(NSNumber(value: isEnabled))
38
+ }
39
+
40
+ @objc public func setTagForUnderAgeOfConsent(_ tag: Bool) {
41
+ AdWhaleAds.sharedInstance.setTagForUnderAgeOfConsent(NSNumber(value: tag))
42
+ }
43
+
44
+ @objc public func setMaxAdContentRating(_ rating: String?) {
45
+ AdWhaleAds.sharedInstance.maxAdContentRating(Self.mapMaxAdContentRating(rating))
46
+ }
47
+
48
+ @objc public func setTestDeviceIdentifiers(_ testDeviceIds: [Any]?) {
49
+ guard let raw = testDeviceIds, !raw.isEmpty else { return }
50
+ let strings = raw.compactMap { $0 as? String }
51
+ guard !strings.isEmpty else { return }
52
+ AdWhaleAds.sharedInstance.setTestDeviceIdentifiers(strings as NSArray)
53
+ }
54
+
55
+ @objc public func requestGdprConsent(
56
+ _ resolve: @escaping RCTPromiseResolveBlock,
57
+ reject: @escaping RCTPromiseRejectBlock
58
+ ) {
59
+ DispatchQueue.main.async {
60
+ guard let vc = RCTPresentedViewController() else {
61
+ reject(
62
+ Self.errNoActivity,
63
+ "Root view controller is not available. Cannot request GDPR consent.",
64
+ nil
65
+ )
66
+ return
67
+ }
68
+ AdWhaleAds.sharedInstance.gdpr(vc, testDevices: nil) { success in
69
+ DispatchQueue.main.async {
70
+ resolve([
71
+ "success": success,
72
+ "message": success
73
+ ? "Consent flow completed."
74
+ : "Consent not obtained or dismissed.",
75
+ ])
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ @objc public func resetGdprConsentStatus() {
82
+ DispatchQueue.main.async {
83
+ AdWhaleAds.sharedInstance.resetGDPR(completionHandler: {})
84
+ }
85
+ }
86
+
87
+ @objc public func setGdpr(_ consent: Bool) {
88
+ _ = consent
89
+ }
90
+
91
+ @objc public func getConsentStatus(
92
+ _ resolve: @escaping RCTPromiseResolveBlock,
93
+ reject: @escaping RCTPromiseRejectBlock
94
+ ) {
95
+ DispatchQueue.main.async {
96
+ let ads = AdWhaleAds.sharedInstance
97
+ let coppaNum = ads.getTagForChildDirectedTreatment()
98
+ let coppa = coppaNum?.boolValue ?? false
99
+ let gdprConsent = ads.getGdprConsentStatus()
100
+ let personalized = ads.isGDPR()
101
+ let gdprStr = gdprConsent ? "OBTAINED" : "NOT_OBTAINED"
102
+ resolve([
103
+ "coppa": coppa,
104
+ "gdpr": gdprStr,
105
+ "personalizedConsent": personalized,
106
+ ])
107
+ }
108
+ }
109
+
110
+ @objc public func showAdInspector(
111
+ _ resolve: @escaping RCTPromiseResolveBlock,
112
+ reject: @escaping RCTPromiseRejectBlock
113
+ ) {
114
+ DispatchQueue.main.async {
115
+ guard let vc = RCTPresentedViewController() else {
116
+ reject(
117
+ Self.errNoActivity,
118
+ "Root view controller is not available. Cannot open Ad Inspector.",
119
+ nil
120
+ )
121
+ return
122
+ }
123
+ AdWhaleAds.sharedInstance.showAdInspector(viewController: vc) { error in
124
+ DispatchQueue.main.async {
125
+ if let error {
126
+ resolve([
127
+ "statusCode": -1,
128
+ "message": error.localizedDescription,
129
+ ])
130
+ } else {
131
+ resolve([
132
+ "statusCode": 100,
133
+ "message": "Ad Inspector closed",
134
+ ])
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+
141
+ @objc public func setAppMuted(_ isMuted: Bool) {
142
+ AdWhaleAds.sharedInstance.setMuted(isMuted)
143
+ }
144
+
145
+ @objc public func setAppVolume(_ volume: Double) {
146
+ AdWhaleAds.sharedInstance.setVolume(Float(volume))
147
+ }
148
+ }
@@ -0,0 +1,38 @@
1
+ #import <React/RCTViewManager.h>
2
+ #import <React/RCTUIManager.h>
3
+ #import <React/RCTConvert.h>
4
+
5
+ /**
6
+ * Swift `WhaleMediationBannerContainer` + `RNAdWhaleMediationAdViewManager.swift` 와 연결.
7
+ * Swift 생성 헤더를 여기서 import 하지 않아 ScanDependencies 순서 문제를 피합니다 (KVC).
8
+ */
9
+ @interface RCT_EXTERN_MODULE(RNAdWhaleMediationAdViewManager, RCTViewManager)
10
+
11
+ RCT_EXPORT_VIEW_PROPERTY(placementUid, NSString)
12
+
13
+ RCT_CUSTOM_VIEW_PROPERTY(adSize, NSString, UIView)
14
+ {
15
+ [view setValue:(json ? [RCTConvert NSString:json] : @"") forKey:@"adSizeString"];
16
+ }
17
+
18
+ RCT_CUSTOM_VIEW_PROPERTY(loadAd, BOOL, UIView)
19
+ {
20
+ BOOL load = json ? [RCTConvert BOOL:json] : NO;
21
+ [view setValue:@(load) forKey:@"shouldLoadAd"];
22
+ }
23
+
24
+ RCT_CUSTOM_VIEW_PROPERTY(adaptiveAnchorWidth, NSNumber, UIView)
25
+ {
26
+ NSInteger w = json ? [RCTConvert NSInteger:json] : 0;
27
+ [view setValue:@(w) forKey:@"adaptiveAnchorWidth"];
28
+ }
29
+
30
+ RCT_EXPORT_VIEW_PROPERTY(placementName, NSString)
31
+ RCT_EXPORT_VIEW_PROPERTY(region, NSString)
32
+ RCT_EXPORT_VIEW_PROPERTY(gcoder, NSDictionary)
33
+
34
+ RCT_EXPORT_VIEW_PROPERTY(onAdLoaded, RCTDirectEventBlock)
35
+ RCT_EXPORT_VIEW_PROPERTY(onAdLoadFailed, RCTDirectEventBlock)
36
+ RCT_EXPORT_VIEW_PROPERTY(onAdClicked, RCTDirectEventBlock)
37
+
38
+ @end
@@ -0,0 +1,14 @@
1
+ import UIKit
2
+ import React
3
+
4
+ @objc(RNAdWhaleMediationAdViewManager)
5
+ public class RNAdWhaleMediationAdViewManager: RCTViewManager {
6
+
7
+ override public static func requiresMainQueueSetup() -> Bool {
8
+ true
9
+ }
10
+
11
+ override public func view() -> UIView! {
12
+ WhaleMediationBannerContainer()
13
+ }
14
+ }
@@ -0,0 +1,18 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(RNAdWhaleMediationAppOpenAd, NSObject)
4
+
5
+ RCT_EXTERN_METHOD(setPlacementName:(NSString *)placementName)
6
+ RCT_EXTERN_METHOD(setRegion:(NSString *)region)
7
+ RCT_EXTERN_METHOD(setGcoder:(NSDictionary *)gcoder)
8
+
9
+ // Legacy API
10
+ RCT_EXTERN_METHOD(loadAd:(NSString *)placementUid)
11
+ RCT_EXTERN_METHOD(showAd)
12
+
13
+ // Flutter-sync API
14
+ RCT_EXTERN_METHOD(loadAppOpenAd:(NSDictionary *)args)
15
+ RCT_EXTERN_METHOD(showAdWithoutView:(nonnull NSNumber *)adId)
16
+ RCT_EXTERN_METHOD(destroyAd:(nonnull NSNumber *)adId)
17
+
18
+ @end
@@ -0,0 +1,175 @@
1
+ import Foundation
2
+ import React
3
+ import AdWhaleSDK
4
+
5
+ @objc(RNAdWhaleMediationAppOpenAd)
6
+ final class RNAdWhaleMediationAppOpenAd: RCTEventEmitter {
7
+
8
+ private struct Slot {
9
+ let adId: Int
10
+ let placementUid: String
11
+ }
12
+
13
+ private final class DelegateBox: NSObject, AdWhaleAppOpenAdDelegate {
14
+ let adId: Int
15
+ weak var module: RNAdWhaleMediationAppOpenAd?
16
+
17
+ init(adId: Int, module: RNAdWhaleMediationAppOpenAd) {
18
+ self.adId = adId
19
+ self.module = module
20
+ super.init()
21
+ }
22
+
23
+ func adDidReceiveAppOpenAd(_ ad: AdWhaleAppOpenAd) {
24
+ _ = ad
25
+ module?.emit(adId: adId, eventName: "onAppOpenAdLoaded", payload: nil)
26
+ }
27
+
28
+ func adDidFailToReceiveAppOpenAd(error: any Error) {
29
+ module?.emitError(adId: adId, eventName: "onAppOpenAdFailedToLoad", error: error)
30
+ }
31
+
32
+ func adWillPresentAppOpenAd() {
33
+ module?.emit(adId: adId, eventName: "onAppOpenAdShowed", payload: nil)
34
+ }
35
+
36
+ func adDidDismissAppOpenAd() {
37
+ module?.emit(adId: adId, eventName: "onAppOpenAdDismissed", payload: nil)
38
+ module?.destroyAd(NSNumber(value: adId))
39
+ }
40
+
41
+ func adDidFailToPresentAppOpenAd(error: any Error) {
42
+ module?.emitError(adId: adId, eventName: "onAppOpenAdFailedToShow", error: error)
43
+ }
44
+ }
45
+
46
+ /// iOS SDK가 singleton(`AdWhaleAppOpenAd.shared`)이라 동시에 여러 adId를 완전 분리 운용할 수는 없습니다.
47
+ /// 그래도 Flutter/RN 인터페이스를 맞추기 위해, 마지막으로 로드된 adId를 active로 간주합니다.
48
+ private var slotsById: [Int: Slot] = [:]
49
+ private var delegateById: [Int: DelegateBox] = [:]
50
+ private var activeAdId: Int = 0
51
+
52
+ // 레거시 옵션(기존 JS API) — iOS SDK에 대응 API가 없어 저장만 함
53
+ private var legacyPlacementName: String?
54
+ private var legacyRegion: String?
55
+ private var legacyGcoder: (Double, Double)?
56
+ private var legacyLastAdId: Int = 0
57
+
58
+ override static func requiresMainQueueSetup() -> Bool { true }
59
+
60
+ override func supportedEvents() -> [String]! {
61
+ [
62
+ "onAppOpenAdLoaded",
63
+ "onAppOpenAdFailedToLoad",
64
+ "onAppOpenAdShowed",
65
+ "onAppOpenAdFailedToShow",
66
+ "onAppOpenAdDismissed",
67
+ "onAppOpenAdClicked", // iOS delegate에 직접 콜백은 없지만 문자열 유지
68
+ "onAdEvent",
69
+ ]
70
+ }
71
+
72
+ // MARK: - Flutter-sync API (권장)
73
+
74
+ /// loadAppOpenAd({adId, placementUid, ...})
75
+ @objc func loadAppOpenAd(_ args: NSDictionary) {
76
+ let adId = (args["adId"] as? NSNumber)?.intValue ?? 0
77
+ let placementUid = (args["placementUid"] as? String) ?? ""
78
+ if placementUid.isEmpty { return }
79
+ log("RNAdWhaleMediationAppOpenAd.loadAppOpenAd adId=\(adId) adUnitId=\(placementUid)")
80
+
81
+ slotsById[adId] = Slot(adId: adId, placementUid: placementUid)
82
+ activeAdId = adId
83
+
84
+ let delegate = DelegateBox(adId: adId, module: self)
85
+ delegateById[adId] = delegate
86
+
87
+ DispatchQueue.main.async {
88
+ let shared = AdWhaleAppOpenAd.shared
89
+ shared.appOpenAdDelegate = delegate
90
+ shared.adUnitId = placementUid
91
+ shared.loadAd()
92
+ }
93
+ }
94
+
95
+ /// showAdWithoutView(adId)
96
+ @objc func showAdWithoutView(_ adId: NSNumber) {
97
+ let id = adId.intValue
98
+ activeAdId = id
99
+ DispatchQueue.main.async {
100
+ let shared = AdWhaleAppOpenAd.shared
101
+ if #available(iOS 13.0, *) {
102
+ shared.showAdIfAvailable(RCTPresentedViewController())
103
+ } else {
104
+ shared.showAdIfAvailable()
105
+ }
106
+ }
107
+ }
108
+
109
+ /// destroyAd(adId)
110
+ @objc func destroyAd(_ adId: NSNumber) {
111
+ let id = adId.intValue
112
+ slotsById.removeValue(forKey: id)
113
+ delegateById.removeValue(forKey: id)
114
+ if activeAdId == id {
115
+ activeAdId = 0
116
+ }
117
+ DispatchQueue.main.async {
118
+ let shared = AdWhaleAppOpenAd.shared
119
+ shared.clearAd()
120
+ // delegate는 마지막 active를 기준으로만 유지
121
+ shared.appOpenAdDelegate = nil
122
+ }
123
+ }
124
+
125
+ // MARK: - Legacy API (기존 JS와 호환)
126
+
127
+ @objc func setPlacementName(_ placementName: String) { legacyPlacementName = placementName }
128
+ @objc func setRegion(_ region: String) { legacyRegion = region }
129
+ @objc func setGcoder(_ gcoder: NSDictionary) {
130
+ let lt = (gcoder["lt"] as? NSNumber)?.doubleValue ?? 0
131
+ let lng = (gcoder["lng"] as? NSNumber)?.doubleValue ?? 0
132
+ legacyGcoder = (lt, lng)
133
+ }
134
+
135
+ @objc func loadAd(_ placementUid: String) {
136
+ legacyLastAdId += 1
137
+ _ = legacyPlacementName
138
+ _ = legacyRegion
139
+ _ = legacyGcoder
140
+ loadAppOpenAd([
141
+ "adId": legacyLastAdId,
142
+ "placementUid": placementUid,
143
+ ])
144
+ }
145
+
146
+ @objc func showAd() {
147
+ showAdWithoutView(NSNumber(value: legacyLastAdId))
148
+ }
149
+
150
+ // MARK: - Event helpers
151
+
152
+ private func emit(adId: Int, eventName: String, payload: [String: Any]?) {
153
+ log("RNAdWhaleMediationAppOpenAd sendEvent adId=\(adId) event=\(eventName)")
154
+ sendEvent(withName: eventName, body: payload)
155
+
156
+ var body: [String: Any] = ["adId": adId, "eventName": eventName]
157
+ if let payload {
158
+ for (k, v) in payload { body[k] = v }
159
+ }
160
+ sendEvent(withName: "onAdEvent", body: body)
161
+ }
162
+
163
+ private func emitError(adId: Int, eventName: String, error: Error) {
164
+ let ns = error as NSError
165
+ emit(adId: adId, eventName: eventName, payload: [
166
+ "statusCode": ns.code,
167
+ "message": ns.localizedDescription,
168
+ ])
169
+ }
170
+
171
+ private func log(_ message: String) {
172
+ print("[adwhale_sdk_react_native] \(message)")
173
+ }
174
+ }
175
+
@@ -0,0 +1,22 @@
1
+ #import <React/RCTViewManager.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(RNAdWhaleMediationCustomNativeAdViewManager, RCTViewManager)
4
+
5
+ RCT_EXPORT_VIEW_PROPERTY(placementUid, NSString)
6
+ RCT_EXPORT_VIEW_PROPERTY(factoryId, NSString)
7
+ RCT_EXPORT_VIEW_PROPERTY(placementName, NSString)
8
+ RCT_EXPORT_VIEW_PROPERTY(region, NSString)
9
+ RCT_EXPORT_VIEW_PROPERTY(gcoder, NSDictionary)
10
+
11
+ RCT_EXPORT_VIEW_PROPERTY(onNativeAdLoaded, RCTDirectEventBlock)
12
+ RCT_EXPORT_VIEW_PROPERTY(onNativeAdFailedToLoad, RCTDirectEventBlock)
13
+ RCT_EXPORT_VIEW_PROPERTY(onNativeAdShowFailed, RCTDirectEventBlock)
14
+ RCT_EXPORT_VIEW_PROPERTY(onNativeAdClicked, RCTDirectEventBlock)
15
+ RCT_EXPORT_VIEW_PROPERTY(onNativeAdClosed, RCTDirectEventBlock)
16
+
17
+ // Commands: loadAd / showAd (JS UIManager.dispatchViewManagerCommand)
18
+ RCT_EXTERN_METHOD(loadAd:(nonnull NSNumber *)reactTag)
19
+ RCT_EXTERN_METHOD(showAd:(nonnull NSNumber *)reactTag)
20
+
21
+ @end
22
+
@@ -0,0 +1,239 @@
1
+ import Foundation
2
+ import UIKit
3
+ import React
4
+ import AdWhaleSDK
5
+
6
+ @objc(RNAdWhaleMediationCustomNativeAdViewManager)
7
+ final class RNAdWhaleMediationCustomNativeAdViewManager: RCTViewManager {
8
+ override static func requiresMainQueueSetup() -> Bool { true }
9
+
10
+ override func view() -> UIView! {
11
+ log("view() create WhaleMediationNativeCustomContainer")
12
+ return WhaleMediationNativeCustomContainer()
13
+ }
14
+
15
+ // iOS에서 UIManager.dispatchViewManagerCommand 사용을 위해 .m extern에서 호출됨
16
+ @objc func loadAd(_ reactTag: NSNumber) {
17
+ log("loadAd command received reactTag=\(reactTag)")
18
+ bridge.uiManager.addUIBlock { _, viewRegistry in
19
+ let view = self.resolveContainerView(reactTag: reactTag, viewRegistry: viewRegistry)
20
+ self.log("loadAd UIBlock resolved view=\(view != nil)")
21
+ if let view {
22
+ Task { @MainActor in view.loadAd() }
23
+ } else {
24
+ self.retryCommand(reactTag: reactTag, commandName: "loadAd", attempt: 1)
25
+ }
26
+ }
27
+ }
28
+
29
+ @objc func showAd(_ reactTag: NSNumber) {
30
+ log("showAd command received reactTag=\(reactTag)")
31
+ bridge.uiManager.addUIBlock { _, viewRegistry in
32
+ let view = self.resolveContainerView(reactTag: reactTag, viewRegistry: viewRegistry)
33
+ self.log("showAd UIBlock resolved view=\(view != nil)")
34
+ if let view {
35
+ Task { @MainActor in view.showAd() }
36
+ } else {
37
+ self.retryCommand(reactTag: reactTag, commandName: "showAd", attempt: 1)
38
+ }
39
+ }
40
+ }
41
+
42
+ private func log(_ message: String) {
43
+ print("[adwhale_sdk_react_native] RNAdWhaleMediationCustomNativeAdViewManager \(message)")
44
+ }
45
+
46
+ private func resolveContainerView(
47
+ reactTag: NSNumber,
48
+ viewRegistry: [NSNumber: UIView]?
49
+ ) -> WhaleMediationNativeCustomContainer? {
50
+ if let direct = viewRegistry?[reactTag] as? WhaleMediationNativeCustomContainer {
51
+ return direct
52
+ }
53
+ let normalizedTag = NSNumber(value: reactTag.intValue)
54
+ if let normalized = viewRegistry?[normalizedTag] as? WhaleMediationNativeCustomContainer {
55
+ return normalized
56
+ }
57
+ if let uiManagerView = bridge.uiManager.view(forReactTag: reactTag) as? WhaleMediationNativeCustomContainer {
58
+ return uiManagerView
59
+ }
60
+ if let uiManagerView = bridge.uiManager.view(forReactTag: normalizedTag) as? WhaleMediationNativeCustomContainer {
61
+ return uiManagerView
62
+ }
63
+ return nil
64
+ }
65
+
66
+ private func retryCommand(reactTag: NSNumber, commandName: String, attempt: Int) {
67
+ guard attempt <= 5 else {
68
+ log("\(commandName) retry exhausted reactTag=\(reactTag)")
69
+ return
70
+ }
71
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
72
+ guard
73
+ let view = self.bridge.uiManager.view(forReactTag: reactTag) as? WhaleMediationNativeCustomContainer
74
+ else {
75
+ self.log("\(commandName) retry \(attempt) failed reactTag=\(reactTag)")
76
+ self.retryCommand(reactTag: reactTag, commandName: commandName, attempt: attempt + 1)
77
+ return
78
+ }
79
+ self.log("\(commandName) retry \(attempt) resolved view=true")
80
+ Task { @MainActor in
81
+ if commandName == "loadAd" {
82
+ view.loadAd()
83
+ } else {
84
+ view.showAd()
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ @objcMembers
92
+ final class WhaleMediationNativeCustomContainer: UIView, AdWhaleNativeAdLoaderDelegate, AdWhaleNativeAdDelegate {
93
+ // Props
94
+ var placementUid: String = ""
95
+ var factoryId: String = ""
96
+ var placementName: String = ""
97
+ var region: String = ""
98
+ var gcoder: NSDictionary?
99
+
100
+ // Events
101
+ var onNativeAdLoaded: RCTDirectEventBlock?
102
+ var onNativeAdFailedToLoad: RCTDirectEventBlock?
103
+ var onNativeAdShowFailed: RCTDirectEventBlock?
104
+ var onNativeAdClicked: RCTDirectEventBlock?
105
+ var onNativeAdClosed: RCTDirectEventBlock?
106
+
107
+ private var nativeAdView: AdWhaleNativeAdView?
108
+ private var currentNativeAd: AdWhaleNativeAd?
109
+ private var loader: AdWhaleNativeAdLoader?
110
+
111
+ deinit {
112
+ loader?.destroy()
113
+ }
114
+
115
+ override func layoutSubviews() {
116
+ super.layoutSubviews()
117
+ nativeAdView?.frame = bounds
118
+ }
119
+
120
+ @MainActor func loadAd() {
121
+ log("loadAd() called placementUid=\(placementUid) factoryId=\(factoryId) bounds=\(bounds)")
122
+ guard !placementUid.isEmpty else {
123
+ log("loadAd() failed: placementUid is empty")
124
+ onNativeAdFailedToLoad?([
125
+ "statusCode": -1,
126
+ "message": "Placement UID is not set.",
127
+ ])
128
+ return
129
+ }
130
+ guard !factoryId.isEmpty else {
131
+ log("loadAd() failed: factoryId is empty")
132
+ onNativeAdFailedToLoad?([
133
+ "statusCode": RNAdWhaleNativeErrorConst.nativeBinderConfigurationErrorCode,
134
+ "message": "Native custom binder: factoryId is required.",
135
+ ])
136
+ return
137
+ }
138
+ guard let root = RCTPresentedViewController() else {
139
+ log("loadAd() failed: rootViewController is nil")
140
+ onNativeAdFailedToLoad?([
141
+ "statusCode": -1,
142
+ "message": "No root view controller.",
143
+ ])
144
+ return
145
+ }
146
+ guard let view = AdWhaleNativeCustomViewFactoryRegistry.make(factoryId: factoryId, frame: bounds) else {
147
+ log("loadAd() failed: factory not found for factoryId=\(factoryId)")
148
+ onNativeAdFailedToLoad?([
149
+ "statusCode": RNAdWhaleNativeErrorConst.nativeBinderConfigurationErrorCode,
150
+ "message": "NativeAdView factory not found for factoryId: \(factoryId)",
151
+ ])
152
+ return
153
+ }
154
+
155
+ // Cleanup previous
156
+ loader?.destroy()
157
+ loader = nil
158
+ currentNativeAd = nil
159
+
160
+ nativeAdView?.removeFromSuperview()
161
+ nativeAdView = view
162
+ view.frame = bounds
163
+ addSubview(view)
164
+
165
+ let l = AdWhaleNativeAdLoader(adUnitId: placementUid, rootViewController: root)
166
+ l.delegate = self
167
+ loader = l
168
+ log("loadAd() start loader.loadAd() adUnitId=\(placementUid)")
169
+ l.loadAd()
170
+ }
171
+
172
+ /// iOS는 Custom binding이므로 load 성공 후 bind(view)로 렌더링을 완료합니다.
173
+ @MainActor func showAd() {
174
+ log("showAd() called hasLoader=\(loader != nil) hasNativeAdView=\(nativeAdView != nil) hasCurrentNativeAd=\(currentNativeAd != nil)")
175
+ guard let loader, let nativeAdView, currentNativeAd != nil else {
176
+ log("showAd() failed: native ad is not loaded")
177
+ onNativeAdShowFailed?([
178
+ "statusCode": -1,
179
+ "message": "Native ad is not loaded.",
180
+ ])
181
+ return
182
+ }
183
+ log("showAd() bind native ad view")
184
+ loader.bind(nativeAdView)
185
+ }
186
+
187
+ // MARK: - AdWhaleNativeAdLoaderDelegate
188
+ nonisolated func nativeAdLoaderDidReceiveAd(_ nativeAd: AdWhaleNativeAd) {
189
+ Task { @MainActor in
190
+ self.log("nativeAdLoaderDidReceiveAd")
191
+ self.currentNativeAd = nativeAd
192
+ nativeAd.delegate = self
193
+ self.onNativeAdLoaded?([:])
194
+ }
195
+ }
196
+
197
+ nonisolated func nativeAdLoaderDidFailToReceiveAd(_ nativeAd: AdWhaleNativeAdLoader, error: any Error) {
198
+ _ = nativeAd
199
+ Task { @MainActor in
200
+ let ns = error as NSError
201
+ self.log("nativeAdLoaderDidFailToReceiveAd code=\(ns.code) message=\(ns.localizedDescription)")
202
+ self.onNativeAdFailedToLoad?([
203
+ "statusCode": ns.code,
204
+ "message": ns.localizedDescription,
205
+ ])
206
+ }
207
+ }
208
+
209
+ // MARK: - AdWhaleNativeAdDelegate
210
+ nonisolated func nativeAdDidClickAd(_ nativeAd: AdWhaleNativeAd) {
211
+ _ = nativeAd
212
+ Task { @MainActor in
213
+ self.log("nativeAdDidClickAd")
214
+ self.onNativeAdClicked?([:])
215
+ }
216
+ }
217
+
218
+ nonisolated func nativeAdDidDismissScreen(_ nativeAd: AdWhaleNativeAd) {
219
+ _ = nativeAd
220
+ Task { @MainActor in
221
+ self.log("nativeAdDidDismissScreen")
222
+ self.onNativeAdClosed?([:])
223
+ }
224
+ }
225
+
226
+ nonisolated func nativeAdDidImpression(_ nativeAd: AdWhaleNativeAd) { _ = nativeAd }
227
+ nonisolated func nativeAdWillPresentScreen(_ nativeAd: AdWhaleNativeAd) { _ = nativeAd }
228
+ nonisolated func nativeAdWillDismissScreen(_ nativeAd: AdWhaleNativeAd) { _ = nativeAd }
229
+ nonisolated func nativeAdWillLeaveApplication(_ nativeAd: AdWhaleNativeAd) { _ = nativeAd }
230
+
231
+ private func log(_ message: String) {
232
+ print("[adwhale_sdk_react_native] WhaleMediationNativeCustomContainer \(message)")
233
+ }
234
+ }
235
+
236
+ enum RNAdWhaleNativeErrorConst {
237
+ static let nativeBinderConfigurationErrorCode: Int = -2000
238
+ }
239
+
@@ -0,0 +1,18 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(RNAdWhaleMediationInterstitialAd, NSObject)
4
+
5
+ RCT_EXTERN_METHOD(setPlacementName:(NSString *)placementName)
6
+ RCT_EXTERN_METHOD(setRegion:(NSString *)region)
7
+ RCT_EXTERN_METHOD(setGcoder:(NSDictionary *)gcoder)
8
+
9
+ // Legacy API
10
+ RCT_EXTERN_METHOD(loadAd:(NSString *)placementUid)
11
+ RCT_EXTERN_METHOD(showAd)
12
+
13
+ // Flutter-sync API
14
+ RCT_EXTERN_METHOD(loadInterstitialAd:(NSDictionary *)args)
15
+ RCT_EXTERN_METHOD(showAdWithoutView:(nonnull NSNumber *)adId)
16
+ RCT_EXTERN_METHOD(destroyAd:(nonnull NSNumber *)adId)
17
+
18
+ @end