adwhale-sdk-react-native 2.7.204 → 2.7.400

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 (109) hide show
  1. package/AdwhaleSdkReactNative.podspec +22 -0
  2. package/README.md +95 -91
  3. package/android/build.gradle +35 -1
  4. package/android/proguard-rules.pro +58 -0
  5. package/android/src/main/java/com/adwhalesdkreactnative/AdWhaleCustomNativeBinderFactory.java +18 -0
  6. package/android/src/main/java/com/adwhalesdkreactnative/AdwhaleSdkReactNativePackage.java +51 -6
  7. package/android/src/main/java/com/adwhalesdkreactnative/BinderFactory.java +9 -0
  8. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdSettingModule.java +148 -49
  9. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAdView.java +46 -27
  10. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationAppOpenAd.java +148 -0
  11. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationCustomNativeAdView.java +37 -46
  12. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationExitPopupAd.java +240 -0
  13. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationInterstitialAd.java +205 -0
  14. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationLoggerModule.java +2 -1
  15. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationNativeAdViewListenerBridge.java +83 -0
  16. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationRewardAd.java +341 -6
  17. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTemplateNativeAdView.java +191 -70
  18. package/android/src/main/java/com/adwhalesdkreactnative/RNAdWhaleMediationTransitionPopupAd.java +218 -0
  19. package/android/src/main/java/com/adwhalesdkreactnative/RNWrapperView.java +137 -0
  20. package/android/src/main/java/com/adwhalesdkreactnative/SimpleBinderFactory.java +1 -1
  21. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodArgConst.java +112 -0
  22. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodCallConst.java +113 -0
  23. package/android/src/main/java/com/adwhalesdkreactnative/common/RNMethodResultConst.java +28 -0
  24. package/build/generated/ios/ReactAppDependencyProvider.podspec +34 -0
  25. package/ios/AdWhaleNativeCustomViewFactoryRegistry.swift +56 -0
  26. package/ios/AdwhaleSdkReactNative.mm +96 -5
  27. package/ios/AdwhaleSdkReactNativeModule.swift +148 -0
  28. package/ios/RNAdWhaleMediationAdViewManager.m +38 -0
  29. package/ios/RNAdWhaleMediationAdViewManager.swift +14 -0
  30. package/ios/RNAdWhaleMediationAppOpenAd.m +18 -0
  31. package/ios/RNAdWhaleMediationAppOpenAd.swift +175 -0
  32. package/ios/RNAdWhaleMediationCustomNativeAdViewManager.m +22 -0
  33. package/ios/RNAdWhaleMediationCustomNativeAdViewManager.swift +239 -0
  34. package/ios/RNAdWhaleMediationInterstitialAd.m +18 -0
  35. package/ios/RNAdWhaleMediationInterstitialAd.swift +161 -0
  36. package/ios/RNAdWhaleMediationRewardAd.m +22 -0
  37. package/ios/RNAdWhaleMediationRewardAd.swift +206 -0
  38. package/ios/WhaleMediationBannerContainer.swift +182 -0
  39. package/lib/module/AdWhaleAdView.js +58 -3
  40. package/lib/module/AdWhaleAdView.js.map +1 -1
  41. package/lib/module/AdWhaleAppOpenAd.js +223 -25
  42. package/lib/module/AdWhaleAppOpenAd.js.map +1 -1
  43. package/lib/module/AdWhaleExitPopupAd.js +93 -0
  44. package/lib/module/AdWhaleExitPopupAd.js.map +1 -0
  45. package/lib/module/AdWhaleInterstitialAd.js +146 -22
  46. package/lib/module/AdWhaleInterstitialAd.js.map +1 -1
  47. package/lib/module/AdWhaleMediationAds.js +75 -4
  48. package/lib/module/AdWhaleMediationAds.js.map +1 -1
  49. package/lib/module/AdWhaleNativeCustomView.js +59 -8
  50. package/lib/module/AdWhaleNativeCustomView.js.map +1 -1
  51. package/lib/module/AdWhaleNativeTemplateView.js +37 -19
  52. package/lib/module/AdWhaleNativeTemplateView.js.map +1 -1
  53. package/lib/module/AdWhaleRewardAd.js +221 -24
  54. package/lib/module/AdWhaleRewardAd.js.map +1 -1
  55. package/lib/module/AdWhaleTransitionPopupAd.js +87 -0
  56. package/lib/module/AdWhaleTransitionPopupAd.js.map +1 -0
  57. package/lib/module/NativeAdwhaleSdkReactNative.js +16 -1
  58. package/lib/module/NativeAdwhaleSdkReactNative.js.map +1 -1
  59. package/lib/module/constants/AdWhaleMethodArgConst.js +53 -0
  60. package/lib/module/constants/AdWhaleMethodArgConst.js.map +1 -0
  61. package/lib/module/constants/AdWhaleMethodCallConst.js +56 -0
  62. package/lib/module/constants/AdWhaleMethodCallConst.js.map +1 -0
  63. package/lib/module/constants/AdWhaleMethodResultConst.js +16 -0
  64. package/lib/module/constants/AdWhaleMethodResultConst.js.map +1 -0
  65. package/lib/module/index.js +10 -1
  66. package/lib/module/index.js.map +1 -1
  67. package/lib/typescript/src/AdWhaleAdView.d.ts +20 -2
  68. package/lib/typescript/src/AdWhaleAdView.d.ts.map +1 -1
  69. package/lib/typescript/src/AdWhaleAppOpenAd.d.ts +10 -0
  70. package/lib/typescript/src/AdWhaleAppOpenAd.d.ts.map +1 -1
  71. package/lib/typescript/src/AdWhaleExitPopupAd.d.ts +36 -0
  72. package/lib/typescript/src/AdWhaleExitPopupAd.d.ts.map +1 -0
  73. package/lib/typescript/src/AdWhaleInterstitialAd.d.ts +16 -0
  74. package/lib/typescript/src/AdWhaleInterstitialAd.d.ts.map +1 -1
  75. package/lib/typescript/src/AdWhaleMediationAds.d.ts +13 -5
  76. package/lib/typescript/src/AdWhaleMediationAds.d.ts.map +1 -1
  77. package/lib/typescript/src/AdWhaleNativeCustomView.d.ts +7 -4
  78. package/lib/typescript/src/AdWhaleNativeCustomView.d.ts.map +1 -1
  79. package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts +20 -7
  80. package/lib/typescript/src/AdWhaleNativeTemplateView.d.ts.map +1 -1
  81. package/lib/typescript/src/AdWhaleRewardAd.d.ts +63 -6
  82. package/lib/typescript/src/AdWhaleRewardAd.d.ts.map +1 -1
  83. package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts +33 -0
  84. package/lib/typescript/src/AdWhaleTransitionPopupAd.d.ts.map +1 -0
  85. package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts +28 -6
  86. package/lib/typescript/src/NativeAdwhaleSdkReactNative.d.ts.map +1 -1
  87. package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts +51 -0
  88. package/lib/typescript/src/constants/AdWhaleMethodArgConst.d.ts.map +1 -0
  89. package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts +53 -0
  90. package/lib/typescript/src/constants/AdWhaleMethodCallConst.d.ts.map +1 -0
  91. package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts +14 -0
  92. package/lib/typescript/src/constants/AdWhaleMethodResultConst.d.ts.map +1 -0
  93. package/lib/typescript/src/index.d.ts +10 -2
  94. package/lib/typescript/src/index.d.ts.map +1 -1
  95. package/package.json +1 -1
  96. package/src/AdWhaleAdView.tsx +92 -4
  97. package/src/AdWhaleAppOpenAd.ts +293 -38
  98. package/src/AdWhaleExitPopupAd.ts +183 -0
  99. package/src/AdWhaleInterstitialAd.ts +206 -36
  100. package/src/AdWhaleMediationAds.ts +108 -4
  101. package/src/AdWhaleNativeCustomView.tsx +85 -23
  102. package/src/AdWhaleNativeTemplateView.tsx +79 -42
  103. package/src/AdWhaleRewardAd.ts +317 -51
  104. package/src/AdWhaleTransitionPopupAd.ts +171 -0
  105. package/src/NativeAdwhaleSdkReactNative.ts +30 -6
  106. package/src/constants/AdWhaleMethodArgConst.ts +50 -0
  107. package/src/constants/AdWhaleMethodCallConst.ts +60 -0
  108. package/src/constants/AdWhaleMethodResultConst.ts +13 -0
  109. package/src/index.ts +35 -0
@@ -0,0 +1,161 @@
1
+ import Foundation
2
+ import React
3
+ import AdWhaleSDK
4
+
5
+ @objc(RNAdWhaleMediationInterstitialAd)
6
+ final class RNAdWhaleMediationInterstitialAd: RCTEventEmitter {
7
+
8
+ private final class Holder: NSObject, AdWhaleInterstitialDelegate {
9
+ let adId: Int
10
+ let module: RNAdWhaleMediationInterstitialAd
11
+ let ad: AdWhaleInterstitialAd
12
+
13
+ init(adId: Int, module: RNAdWhaleMediationInterstitialAd, ad: AdWhaleInterstitialAd) {
14
+ self.adId = adId
15
+ self.module = module
16
+ self.ad = ad
17
+ super.init()
18
+ self.ad.interstitialDelegate = self
19
+ }
20
+
21
+ func adDidReceiveInterstitialAd(_ ad: AdWhaleInterstitialAd) {
22
+ _ = ad
23
+ module.emit(adId: adId, eventName: "onInterstitialAdLoaded", payload: nil)
24
+ }
25
+
26
+ func adDidFailToReceiveInterstitialAdWithError(_ error: any Error) {
27
+ module.emitError(adId: adId, eventName: "onInterstitialAdLoadFailed", error: error)
28
+ }
29
+
30
+ func ad(_ ad: AdWhaleInterstitialAd, didFailToPresentInterstitialAdWithError error: any Error) {
31
+ _ = ad
32
+ module.emitError(adId: adId, eventName: "onInterstitialAdShowFailed", error: error)
33
+ }
34
+
35
+ func adWillPresentInterstitialAd(_ ad: AdWhaleInterstitialAd) {
36
+ _ = ad
37
+ module.emit(adId: adId, eventName: "onInterstitialAdShowed", payload: nil)
38
+ }
39
+
40
+ func adDidDismissInterstitialAd(_ ad: AdWhaleInterstitialAd) {
41
+ _ = ad
42
+ module.emit(adId: adId, eventName: "onInterstitialAdClosed", payload: nil)
43
+ module.destroyAd(adId)
44
+ }
45
+ }
46
+
47
+ private var holdersById: [Int: Holder] = [:]
48
+
49
+ // 레거시 옵션(기존 JS API)
50
+ private var legacyPlacementName: String?
51
+ private var legacyRegion: String?
52
+ private var legacyGcoder: (Double, Double)?
53
+ private var legacyLastAdId: Int = 0
54
+
55
+ override static func requiresMainQueueSetup() -> Bool { true }
56
+
57
+ override func supportedEvents() -> [String]! {
58
+ [
59
+ "onInterstitialAdLoaded",
60
+ "onInterstitialAdLoadFailed",
61
+ "onInterstitialAdShowed",
62
+ "onInterstitialAdShowFailed",
63
+ "onInterstitialAdClosed",
64
+ "onInterstitialAdClicked", // iOS delegate에 직접 콜백은 없지만 문자열은 유지
65
+ "onAdEvent",
66
+ ]
67
+ }
68
+
69
+ // MARK: - Flutter-sync API (권장)
70
+
71
+ /// loadInterstitialAd({adId, placementUid, placementName?, region?, gcoder?})
72
+ @objc func loadInterstitialAd(_ args: NSDictionary) {
73
+ let adId = (args["adId"] as? NSNumber)?.intValue ?? 0
74
+ let placementUid = (args["placementUid"] as? String) ?? ""
75
+ if placementUid.isEmpty { return }
76
+
77
+ let ad = AdWhaleInterstitialAd()
78
+ // iOS SDK 공개 API에는 setRegion/setGcoder/setPlacementName 등이 없음 (Android 전용). 무시.
79
+ let holder = Holder(adId: adId, module: self, ad: ad)
80
+ holdersById[adId] = holder
81
+ DispatchQueue.main.async {
82
+ ad.load(placementUid)
83
+ }
84
+ }
85
+
86
+ /// showAdWithoutView(adId)
87
+ @objc func showAdWithoutView(_ adId: NSNumber) {
88
+ let id = adId.intValue
89
+ guard let holder = holdersById[id] else { return }
90
+ DispatchQueue.main.async {
91
+ guard let vc = RCTPresentedViewController() else {
92
+ self.emit(
93
+ adId: id,
94
+ eventName: "onInterstitialAdShowFailed",
95
+ payload: ["statusCode": -1, "message": "No root view controller"]
96
+ )
97
+ return
98
+ }
99
+ holder.ad.show(vc)
100
+ }
101
+ }
102
+
103
+ /// destroyAd(adId)
104
+ @objc func destroyAd(_ adId: NSNumber) {
105
+ destroyAd(adId.intValue)
106
+ }
107
+
108
+ private func destroyAd(_ adId: Int) {
109
+ if let holder = holdersById.removeValue(forKey: adId) {
110
+ holder.ad.interstitialDelegate = nil
111
+ }
112
+ }
113
+
114
+ // MARK: - Legacy API (기존 JS와 호환)
115
+
116
+ @objc func setPlacementName(_ placementName: String) { legacyPlacementName = placementName }
117
+ @objc func setRegion(_ region: String) { legacyRegion = region }
118
+ @objc func setGcoder(_ gcoder: NSDictionary) {
119
+ let lt = (gcoder["lt"] as? NSNumber)?.doubleValue ?? 0
120
+ let lng = (gcoder["lng"] as? NSNumber)?.doubleValue ?? 0
121
+ legacyGcoder = (lt, lng)
122
+ }
123
+
124
+ @objc func loadAd(_ placementUid: String) {
125
+ legacyLastAdId += 1
126
+ loadInterstitialAd([
127
+ "adId": legacyLastAdId,
128
+ "placementUid": placementUid,
129
+ "placementName": legacyPlacementName ?? "",
130
+ "region": legacyRegion ?? "",
131
+ "gcoder": legacyGcoder != nil ? [legacyGcoder!.0, legacyGcoder!.1] : [],
132
+ ])
133
+ }
134
+
135
+ @objc func showAd() {
136
+ showAdWithoutView(NSNumber(value: legacyLastAdId))
137
+ }
138
+
139
+ // MARK: - Event helpers
140
+
141
+ private func emit(adId: Int, eventName: String, payload: [String: Any]?) {
142
+ // 레거시 이벤트
143
+ sendEvent(withName: eventName, body: payload)
144
+
145
+ // Flutter-style 단일 이벤트
146
+ var body: [String: Any] = ["adId": adId, "eventName": eventName]
147
+ if let payload {
148
+ for (k, v) in payload { body[k] = v }
149
+ }
150
+ sendEvent(withName: "onAdEvent", body: body)
151
+ }
152
+
153
+ private func emitError(adId: Int, eventName: String, error: Error) {
154
+ let ns = error as NSError
155
+ emit(adId: adId, eventName: eventName, payload: [
156
+ "statusCode": ns.code,
157
+ "message": ns.localizedDescription,
158
+ ])
159
+ }
160
+ }
161
+
@@ -0,0 +1,22 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(RNAdWhaleMediationRewardAd, 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
+ // 보상형 SSV(서버 사이드 검증) API
10
+ RCT_EXTERN_METHOD(setUserId:(NSString *)userId)
11
+ RCT_EXTERN_METHOD(setCustomData:(NSDictionary *)customData)
12
+
13
+ // Legacy API
14
+ RCT_EXTERN_METHOD(loadAd:(NSString *)placementUid)
15
+ RCT_EXTERN_METHOD(showAd)
16
+
17
+ // Flutter-sync API
18
+ RCT_EXTERN_METHOD(loadRewardAd:(NSDictionary *)args)
19
+ RCT_EXTERN_METHOD(showAdWithoutView:(nonnull NSNumber *)adId)
20
+ RCT_EXTERN_METHOD(destroyAd:(nonnull NSNumber *)adId)
21
+
22
+ @end
@@ -0,0 +1,206 @@
1
+ import Foundation
2
+ import React
3
+ import AdWhaleSDK
4
+
5
+ @objc(RNAdWhaleMediationRewardAd)
6
+ final class RNAdWhaleMediationRewardAd: RCTEventEmitter {
7
+
8
+ private final class Holder: NSObject, AdWhaleRewardDelegate {
9
+ let adId: Int
10
+ let module: RNAdWhaleMediationRewardAd
11
+ let ad: AdWhaleRewardAd
12
+
13
+ init(adId: Int, module: RNAdWhaleMediationRewardAd, ad: AdWhaleRewardAd) {
14
+ self.adId = adId
15
+ self.module = module
16
+ self.ad = ad
17
+ super.init()
18
+ self.ad.rewardDelegate = self
19
+ }
20
+
21
+ func adDidReceiveRewardAd(_ ad: AdWhaleRewardAd) {
22
+ _ = ad
23
+ module.emit(adId: adId, eventName: "onRewardAdLoaded", payload: nil)
24
+ }
25
+
26
+ func adDidEarnReward(_ reward: AdWhaleReward) {
27
+ let type = reward.type
28
+ let amountInt = Int(truncating: reward.amount)
29
+ module.emit(adId: adId, eventName: "onUserRewarded", payload: [
30
+ "RewardType": type,
31
+ "RewardAmount": amountInt,
32
+ // 레거시 키도 유지
33
+ "type": type,
34
+ "amount": amountInt,
35
+ ])
36
+ }
37
+
38
+ func adDidFailToReceiveRewardAdWithError(_ error: any Error) {
39
+ module.emitError(adId: adId, eventName: "onRewardAdFailedToLoad", error: error)
40
+ }
41
+
42
+ func ad(_ ad: AdWhaleRewardAd, didFailToPresentRewardAdWithError error: any Error) {
43
+ _ = ad
44
+ module.emitError(adId: adId, eventName: "onRewardAdFailedToShow", error: error)
45
+ }
46
+
47
+ func adWillPresentRewardAd(_ ad: AdWhaleRewardAd) {
48
+ _ = ad
49
+ module.emit(adId: adId, eventName: "onRewardAdShowed", payload: nil)
50
+ }
51
+
52
+ func adDidDismissRewardAd(_ ad: AdWhaleRewardAd) {
53
+ _ = ad
54
+ module.emit(adId: adId, eventName: "onRewardAdDismissed", payload: nil)
55
+ module.destroyAd(adId)
56
+ }
57
+ }
58
+
59
+ private var holdersById: [Int: Holder] = [:]
60
+
61
+ // 레거시 옵션(기존 JS API)
62
+ private var legacyPlacementName: String?
63
+ private var legacyRegion: String?
64
+ private var legacyGcoder: (Double, Double)?
65
+ // 보상형 SSV(서버 사이드 검증) 전용 옵션
66
+ private var legacyUserId: String?
67
+ private var legacyCustomData: [String: String]?
68
+ private var legacyLastAdId: Int = 0
69
+
70
+ override static func requiresMainQueueSetup() -> Bool { true }
71
+
72
+ override func supportedEvents() -> [String]! {
73
+ [
74
+ "onRewardAdLoaded",
75
+ "onRewardAdFailedToLoad",
76
+ "onRewardAdShowed",
77
+ "onRewardAdFailedToShow",
78
+ "onRewardAdDismissed",
79
+ "onRewardAdClicked", // iOS delegate에 직접 콜백은 없지만 문자열 유지
80
+ "onUserRewarded",
81
+ "onAdEvent",
82
+ ]
83
+ }
84
+
85
+ // MARK: - Flutter-sync API (권장)
86
+
87
+ /// loadRewardAd({adId, placementUid, placementName?, region?, gcoder?, userId?, customData?})
88
+ @objc func loadRewardAd(_ args: NSDictionary) {
89
+ let adId = (args["adId"] as? NSNumber)?.intValue ?? 0
90
+ let placementUid = (args["placementUid"] as? String) ?? ""
91
+ if placementUid.isEmpty { return }
92
+
93
+ let ad = AdWhaleRewardAd()
94
+ let holder = Holder(adId: adId, module: self, ad: ad)
95
+ holdersById[adId] = holder
96
+
97
+ // 보상형 SSV(서버 사이드 검증) 옵션 적용
98
+ let userId = args["userId"] as? String
99
+ let customData = (args["customData"] as? [String: String])
100
+ ?? (args["customData"] as? NSDictionary as? [String: String])
101
+ Self.applySsvOptions(ad: ad, userId: userId, customData: customData)
102
+
103
+ DispatchQueue.main.async {
104
+ ad.load(placementUid)
105
+ }
106
+ }
107
+
108
+ /// 보상형 SSV 옵션(userId / customData)을 SDK 인스턴스에 적용.
109
+ /// AdMob / AdManager 만 동작하며, 그 외 네트워크에서는 무시(no-op)됩니다.
110
+ private static func applySsvOptions(
111
+ ad: AdWhaleRewardAd,
112
+ userId: String?,
113
+ customData: [String: String]?
114
+ ) {
115
+ if let userId, !userId.isEmpty {
116
+ ad.setUserId(userId)
117
+ }
118
+ if let customData, !customData.isEmpty {
119
+ ad.setCustomData(customData)
120
+ }
121
+ }
122
+
123
+ /// showAdWithoutView(adId)
124
+ @objc func showAdWithoutView(_ adId: NSNumber) {
125
+ let id = adId.intValue
126
+ guard let holder = holdersById[id] else { return }
127
+ DispatchQueue.main.async {
128
+ guard let vc = RCTPresentedViewController() else {
129
+ self.emit(
130
+ adId: id,
131
+ eventName: "onRewardAdFailedToShow",
132
+ payload: ["statusCode": -1, "message": "No root view controller"]
133
+ )
134
+ return
135
+ }
136
+ holder.ad.show(vc)
137
+ }
138
+ }
139
+
140
+ /// destroyAd(adId)
141
+ @objc func destroyAd(_ adId: NSNumber) {
142
+ destroyAd(adId.intValue)
143
+ }
144
+
145
+ private func destroyAd(_ adId: Int) {
146
+ if let holder = holdersById.removeValue(forKey: adId) {
147
+ holder.ad.rewardDelegate = nil
148
+ }
149
+ }
150
+
151
+ // MARK: - Legacy API (기존 JS와 호환)
152
+
153
+ @objc func setPlacementName(_ placementName: String) { legacyPlacementName = placementName }
154
+ @objc func setRegion(_ region: String) { legacyRegion = region }
155
+ @objc func setGcoder(_ gcoder: NSDictionary) {
156
+ let lt = (gcoder["lt"] as? NSNumber)?.doubleValue ?? 0
157
+ let lng = (gcoder["lng"] as? NSNumber)?.doubleValue ?? 0
158
+ legacyGcoder = (lt, lng)
159
+ }
160
+
161
+ /// 보상형 SSV(서버 사이드 검증)용 앱 사용자 id 설정 (AdMob / AdManager 전용)
162
+ @objc func setUserId(_ userId: String) { legacyUserId = userId }
163
+ /// 보상형 SSV(서버 사이드 검증)용 커스텀 데이터 설정 (AdMob / AdManager 전용)
164
+ @objc func setCustomData(_ customData: NSDictionary) {
165
+ legacyCustomData = customData as? [String: String]
166
+ }
167
+
168
+ @objc func loadAd(_ placementUid: String) {
169
+ legacyLastAdId += 1
170
+ var args: [String: Any] = [
171
+ "adId": legacyLastAdId,
172
+ "placementUid": placementUid,
173
+ "placementName": legacyPlacementName ?? "",
174
+ "region": legacyRegion ?? "",
175
+ "gcoder": legacyGcoder != nil ? [legacyGcoder!.0, legacyGcoder!.1] : [],
176
+ ]
177
+ if let legacyUserId { args["userId"] = legacyUserId }
178
+ if let legacyCustomData { args["customData"] = legacyCustomData }
179
+ loadRewardAd(args as NSDictionary)
180
+ }
181
+
182
+ @objc func showAd() {
183
+ showAdWithoutView(NSNumber(value: legacyLastAdId))
184
+ }
185
+
186
+ // MARK: - Event helpers
187
+
188
+ private func emit(adId: Int, eventName: String, payload: [String: Any]?) {
189
+ sendEvent(withName: eventName, body: payload)
190
+
191
+ var body: [String: Any] = ["adId": adId, "eventName": eventName]
192
+ if let payload {
193
+ for (k, v) in payload { body[k] = v }
194
+ }
195
+ sendEvent(withName: "onAdEvent", body: body)
196
+ }
197
+
198
+ private func emitError(adId: Int, eventName: String, error: Error) {
199
+ let ns = error as NSError
200
+ emit(adId: adId, eventName: eventName, payload: [
201
+ "statusCode": ns.code,
202
+ "message": ns.localizedDescription,
203
+ ])
204
+ }
205
+ }
206
+
@@ -0,0 +1,182 @@
1
+ import UIKit
2
+ import React
3
+ import AdWhaleSDK
4
+
5
+ /// RN `RNAdWhaleMediationAdView` — Android `RNAdWhaleMediationAdView` 와 동일 역할.
6
+ @objcMembers
7
+ @MainActor
8
+ public final class WhaleMediationBannerContainer: UIView, AdWhaleBannerDelegate {
9
+
10
+ @objc public var onAdLoaded: RCTDirectEventBlock?
11
+ @objc public var onAdLoadFailed: RCTDirectEventBlock?
12
+ @objc public var onAdClicked: RCTDirectEventBlock?
13
+
14
+ /// Flutter와 동일한 개념의 배너 인스턴스 식별자(선택).
15
+ @objc public var adId: Int = 0
16
+
17
+ @objc public var placementUid: String = "" {
18
+ didSet {
19
+ banner?.setAdUnitID(placementUid)
20
+ scheduleLoadIfNeeded()
21
+ }
22
+ }
23
+
24
+ /// RN prop `adSize` → `RCT_REMAP_VIEW_PROPERTY(adSize, adSizeString, NSString)`
25
+ @objc public var adSizeString: String = "" {
26
+ didSet {
27
+ if !Self.isBannerAdSizeSupportedOnIOS(adSizeString) {
28
+ if let b = banner {
29
+ b.destroy()
30
+ b.removeFromSuperview()
31
+ banner = nil
32
+ }
33
+ scheduleLoadIfNeeded()
34
+ return
35
+ }
36
+ let sz = Self.mapRNAdSizeString(adSizeString)
37
+ banner?.setAdSize(sz)
38
+ scheduleLoadIfNeeded()
39
+ }
40
+ }
41
+
42
+ /// RN prop `loadAd`
43
+ @objc public var shouldLoadAd: Bool = false {
44
+ didSet {
45
+ if oldValue == shouldLoadAd { return }
46
+ scheduleLoadIfNeeded()
47
+ }
48
+ }
49
+
50
+ @objc public var adaptiveAnchorWidth: Int = 0
51
+
52
+ @objc public var placementName: String = ""
53
+ @objc public var region: String = ""
54
+ @objc public var gcoder: NSDictionary?
55
+
56
+ private var banner: AdWhaleBannerAd?
57
+
58
+ deinit {
59
+ banner?.destroy()
60
+ }
61
+
62
+ public override func layoutSubviews() {
63
+ super.layoutSubviews()
64
+ banner?.frame = bounds
65
+ }
66
+
67
+ private static func isBannerAdSizeSupportedOnIOS(_ name: String?) -> Bool {
68
+ guard let name, !name.isEmpty else { return true }
69
+ if name == "BANNER250x250" || name == "ADAPTIVE_ANCHOR" { return false }
70
+ return true
71
+ }
72
+
73
+ private static func unsupportedMessage(_ name: String?) -> String {
74
+ if name == "BANNER250x250" {
75
+ return "AdWhale iOS SDK does not support BANNER250x250."
76
+ }
77
+ if name == "ADAPTIVE_ANCHOR" {
78
+ return "AdWhale iOS SDK does not support ADAPTIVE_ANCHOR."
79
+ }
80
+ return "AdWhale iOS SDK does not support this banner size."
81
+ }
82
+
83
+ private static func mapRNAdSizeString(_ name: String?) -> AdWhaleAdSize {
84
+ guard let name, !name.isEmpty else { return .banner }
85
+ switch name {
86
+ case "BANNER320x50": return .banner
87
+ case "BANNER320x100": return .largeBanner
88
+ case "BANNER300x250": return .mediumRectangle
89
+ default: return .banner
90
+ }
91
+ }
92
+
93
+ private func scheduleLoadIfNeeded() {
94
+ guard shouldLoadAd, !placementUid.isEmpty else { return }
95
+ weak var weakSelf = self
96
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
97
+ weakSelf?.performBannerLoad()
98
+ }
99
+ }
100
+
101
+ private func ensureBanner() {
102
+ guard banner == nil else { return }
103
+ let sz = Self.mapRNAdSizeString(adSizeString)
104
+ let b = AdWhaleBannerAd(sz)
105
+ b.translatesAutoresizingMaskIntoConstraints = true
106
+ b.setDelegate(self)
107
+ addSubview(b)
108
+ banner = b
109
+ if !placementUid.isEmpty {
110
+ b.setAdUnitID(placementUid)
111
+ }
112
+ b.setAdSize(sz)
113
+ b.frame = bounds
114
+ }
115
+
116
+ private func performBannerLoad() {
117
+ guard shouldLoadAd, !placementUid.isEmpty else { return }
118
+ if !Self.isBannerAdSizeSupportedOnIOS(adSizeString) {
119
+ onAdLoadFailed?([
120
+ "adId": adId,
121
+ "statusCode": -100,
122
+ "message": Self.unsupportedMessage(adSizeString),
123
+ ])
124
+ return
125
+ }
126
+ ensureBanner()
127
+ guard let root = RCTPresentedViewController() else {
128
+ onAdLoadFailed?([
129
+ "adId": adId,
130
+ "statusCode": -1,
131
+ "message": "Root view controller is not available for banner load.",
132
+ ])
133
+ return
134
+ }
135
+ banner?.setRootViewController(root)
136
+ banner?.setAdUnitID(placementUid)
137
+ banner?.setAdSize(Self.mapRNAdSizeString(adSizeString))
138
+ banner?.load()
139
+ }
140
+
141
+ // MARK: - AdWhaleBannerDelegate
142
+
143
+ public func bannerViewDidReceiveAd(_ bannerView: AdWhaleBannerAd) {
144
+ _ = bannerView
145
+ onAdLoaded?([
146
+ "adId": adId,
147
+ ])
148
+ }
149
+
150
+ public func bannerView(_ bannerView: AdWhaleBannerAd, didFailToReceiveAdWithError error: Error) {
151
+ _ = bannerView
152
+ let ns = error as NSError
153
+ onAdLoadFailed?([
154
+ "adId": adId,
155
+ "statusCode": ns.code,
156
+ "message": error.localizedDescription,
157
+ ])
158
+ }
159
+
160
+ public func bannerViewDidRecordImpression(_ bannerView: AdWhaleBannerAd) {
161
+ _ = bannerView
162
+ }
163
+
164
+ public func bannerViewWillPresentScreen(_ bannerView: AdWhaleBannerAd) {
165
+ _ = bannerView
166
+ }
167
+
168
+ public func bannerViewWillDismissScreen(_ bannerView: AdWhaleBannerAd) {
169
+ _ = bannerView
170
+ }
171
+
172
+ public func bannerViewDidDismissScreen(_ bannerView: AdWhaleBannerAd) {
173
+ _ = bannerView
174
+ }
175
+
176
+ public func bannerViewDidRecordClick(_ bannerView: AdWhaleBannerAd) {
177
+ _ = bannerView
178
+ onAdClicked?([
179
+ "adId": adId,
180
+ ])
181
+ }
182
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/AdWhaleAdView.tsx
4
4
  import React from 'react';
5
- import { requireNativeComponent } from 'react-native';
5
+ import { Platform, requireNativeComponent } from 'react-native';
6
6
  import { jsx as _jsx } from "react/jsx-runtime";
7
7
  /**
8
8
  * AdWhale 배너 광고 사이즈 enum
@@ -15,20 +15,73 @@ export let AdWhaleAdSize = /*#__PURE__*/function (AdWhaleAdSize) {
15
15
  AdWhaleAdSize["BANNER_320x100"] = "BANNER320x100";
16
16
  /** 중간 직사각형 (300x250) */
17
17
  AdWhaleAdSize["BANNER_300x250"] = "BANNER300x250";
18
- /** 정사각형 (250x250) */
18
+ /**
19
+ * 정사각형 (250x250)
20
+ * iOS: AdWhale iOS SDK 미지원 — 네이티브에서 로드하지 않으며 `onAdLoadFailed`(statusCode -100) 가 호출됩니다.
21
+ */
19
22
  AdWhaleAdSize["BANNER_250x250"] = "BANNER250x250";
20
- /** 적응형 앵커 배너 - adaptiveAnchorWidth와 함께 사용 */
23
+ /**
24
+ * 적응형 앵커 배너 - adaptiveAnchorWidth와 함께 사용
25
+ * iOS: AdWhale iOS SDK 미지원 — 네이티브에서 로드하지 않으며 `onAdLoadFailed`(statusCode -100) 가 호출됩니다.
26
+ */
21
27
  AdWhaleAdSize["ADAPTIVE_ANCHOR"] = "ADAPTIVE_ANCHOR";
22
28
  return AdWhaleAdSize;
23
29
  }({});
30
+
31
+ /**
32
+ * AdWhale iOS SDK 가 해당 배너 사이즈를 지원하는지 (UI 비활성화 등에 사용).
33
+ * Android / 기타 플랫폼에서는 항상 true.
34
+ */
35
+ export function isAdWhaleBannerAdSizeSupportedOnIos(adSize) {
36
+ if (Platform.OS !== 'ios') {
37
+ return true;
38
+ }
39
+ return adSize !== AdWhaleAdSize.BANNER_250x250 && adSize !== AdWhaleAdSize.ADAPTIVE_ANCHOR;
40
+ }
41
+ /** iOS: BANNER250x250 / ADAPTIVE_ANCHOR 요청 시 네이티브 `onAdLoadFailed` 의 statusCode */
42
+ export const ADWHALE_BANNER_IOS_UNSUPPORTED_STATUS_CODE = -100;
24
43
  const RNAdWhaleMediationAdView = requireNativeComponent('RNAdWhaleMediationAdView');
44
+ const AD_SIZE_ENUM_NAME_BY_VALUE = {
45
+ [AdWhaleAdSize.BANNER_320x50]: 'BANNER_320x50',
46
+ [AdWhaleAdSize.BANNER_320x100]: 'BANNER_320x100',
47
+ [AdWhaleAdSize.BANNER_300x250]: 'BANNER_300x250',
48
+ [AdWhaleAdSize.BANNER_250x250]: 'BANNER_250x250',
49
+ [AdWhaleAdSize.ADAPTIVE_ANCHOR]: 'ADAPTIVE_ANCHOR'
50
+ };
25
51
  export const AdWhaleAdView = ({
52
+ onAdLoaded,
26
53
  onAdLoadFailed,
27
54
  adSize,
28
55
  adaptiveAnchorWidth,
56
+ loadAd,
29
57
  ...restProps
30
58
  }) => {
59
+ const lastCreateBannerLogKeyRef = React.useRef(null);
60
+ React.useEffect(() => {
61
+ if (!loadAd) {
62
+ return;
63
+ }
64
+ const enumName = AD_SIZE_ENUM_NAME_BY_VALUE[adSize] ?? adSize;
65
+ const adaptiveWidthForLog = adSize === AdWhaleAdSize.ADAPTIVE_ANCHOR ? String(adaptiveAnchorWidth ?? 0) : 'null';
66
+ const logKey = `${enumName}:${adaptiveWidthForLog}`;
67
+ if (lastCreateBannerLogKeyRef.current === logKey) {
68
+ return;
69
+ }
70
+ lastCreateBannerLogKeyRef.current = logKey;
71
+ console.log(`AdWhaleAdView.tsx _createBanner size=AdWhaleAdSize.${enumName}(${adSize}), adaptiveWidth=${adaptiveWidthForLog}`);
72
+ }, [loadAd, adSize, adaptiveAnchorWidth]);
73
+ const handleAdLoaded = () => {
74
+ const enumName = AD_SIZE_ENUM_NAME_BY_VALUE[adSize] ?? adSize;
75
+ console.log(`AdWhaleAdView.tsx BannerAd onLoaded for size AdWhaleAdSize.${enumName}(${adSize})`);
76
+ onAdLoaded?.();
77
+ };
31
78
  const handleAdLoadFailed = e => {
79
+ if (Platform.OS === 'ios' && e.nativeEvent.statusCode === ADWHALE_BANNER_IOS_UNSUPPORTED_STATUS_CODE) {
80
+ const enumName = AD_SIZE_ENUM_NAME_BY_VALUE[adSize] ?? adSize;
81
+ const errorMessage = adSize === AdWhaleAdSize.BANNER_250x250 ? '250x250 is not supported on iOS' : 'adaptive banner is not supported on iOS';
82
+ console.log(`AdWhaleAdView.tsx BannerAd onLoadFailed for AdWhaleAdSize.${enumName}: errorCode: UNSUPPORTED_BANNER_SIZE, errorMessage: ${errorMessage}`);
83
+ return;
84
+ }
32
85
  onAdLoadFailed?.(e.nativeEvent);
33
86
  };
34
87
 
@@ -38,6 +91,8 @@ export const AdWhaleAdView = ({
38
91
  ...restProps,
39
92
  adSize: adSize,
40
93
  adaptiveAnchorWidth: nativeAdaptiveAnchorWidth,
94
+ loadAd: loadAd,
95
+ onAdLoaded: handleAdLoaded,
41
96
  onAdLoadFailed: onAdLoadFailed ? handleAdLoadFailed : undefined
42
97
  });
43
98
  };
@@ -1 +1 @@
1
- {"version":3,"names":["React","requireNativeComponent","jsx","_jsx","AdWhaleAdSize","RNAdWhaleMediationAdView","AdWhaleAdView","onAdLoadFailed","adSize","adaptiveAnchorWidth","restProps","handleAdLoadFailed","e","nativeEvent","nativeAdaptiveAnchorWidth","ADAPTIVE_ANCHOR","undefined"],"sourceRoot":"../../src","sources":["AdWhaleAdView.tsx"],"mappings":";;AAAA;AACA,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,sBAAsB,QAAQ,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAGtD;AACA;AACA;AACA;AACA,WAAYC,aAAa,0BAAbA,aAAa;EACvB;EADUA,aAAa;EAGvB;EAHUA,aAAa;EAKvB;EALUA,aAAa;EAOvB;EAPUA,aAAa;EASvB;EATUA,aAAa;EAAA,OAAbA,aAAa;AAAA;AAuDzB,MAAMC,wBAAwB,GAC5BJ,sBAAsB,CAAoB,0BAA0B,CAAC;AAEvE,OAAO,MAAMK,aAA+C,GAAGA,CAAC;EAC9DC,cAAc;EACdC,MAAM;EACNC,mBAAmB;EACnB,GAAGC;AACL,CAAC,KAAK;EACJ,MAAMC,kBAAuD,GAAGC,CAAC,IAAI;IACnEL,cAAc,GAAGK,CAAC,CAACC,WAAW,CAAC;EACjC,CAAC;;EAED;EACA,MAAMC,yBAAyB,GAC7BN,MAAM,KAAKJ,aAAa,CAACW,eAAe,GAAGN,mBAAmB,GAAGO,SAAS;EAE5E,oBACEb,IAAA,CAACE,wBAAwB;IAAA,GACnBK,SAAS;IACbF,MAAM,EAAEA,MAAO;IACfC,mBAAmB,EAAEK,yBAA0B;IAC/CP,cAAc,EAAEA,cAAc,GAAGI,kBAAkB,GAAGK;EAAU,CACjE,CAAC;AAEN,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["React","Platform","requireNativeComponent","jsx","_jsx","AdWhaleAdSize","isAdWhaleBannerAdSizeSupportedOnIos","adSize","OS","BANNER_250x250","ADAPTIVE_ANCHOR","ADWHALE_BANNER_IOS_UNSUPPORTED_STATUS_CODE","RNAdWhaleMediationAdView","AD_SIZE_ENUM_NAME_BY_VALUE","BANNER_320x50","BANNER_320x100","BANNER_300x250","AdWhaleAdView","onAdLoaded","onAdLoadFailed","adaptiveAnchorWidth","loadAd","restProps","lastCreateBannerLogKeyRef","useRef","useEffect","enumName","adaptiveWidthForLog","String","logKey","current","console","log","handleAdLoaded","handleAdLoadFailed","e","nativeEvent","statusCode","errorMessage","nativeAdaptiveAnchorWidth","undefined"],"sourceRoot":"../../src","sources":["AdWhaleAdView.tsx"],"mappings":"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","ignoreList":[]}