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
@@ -1,5 +1,6 @@
1
1
  package com.adwhalesdkreactnative;
2
2
 
3
+ import android.app.Activity;
3
4
  import android.util.Log;
4
5
 
5
6
  import androidx.annotation.NonNull;
@@ -10,6 +11,8 @@ import com.facebook.react.bridge.ReactApplicationContext;
10
11
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
11
12
  import com.facebook.react.bridge.ReactMethod;
12
13
  import com.facebook.react.bridge.ReadableMap;
14
+ import com.facebook.react.bridge.ReadableArray;
15
+ import com.facebook.react.bridge.ReadableType;
13
16
  import com.facebook.react.bridge.UiThreadUtil;
14
17
  import com.facebook.react.bridge.WritableMap;
15
18
  import com.facebook.react.modules.core.DeviceEventManagerModule;
@@ -30,12 +33,22 @@ import java.util.Map;
30
33
  public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule implements AdWhaleMediationRewardedAdLoadCallback, AdWhaleMediationFullScreenContentCallback, AdWhaleMediationUserEarnedRewardListener {
31
34
 
32
35
  public static final String REACT_CLASS_NAME = RNAdWhaleMediationRewardAd.class.getSimpleName();
36
+
37
+ // Flutter와 싱크: adId 기반 멀티 인스턴스 지원
38
+ private final Map<Integer, AdWhaleMediationRewardAd> adsById = new HashMap<>();
39
+ /** 멀티 로드 시 콜백마다 올바른 adId로 onAdEvent 를 보내기 위한 리스너 (공유 this + lastActiveAdId 는 경합) */
40
+ private final Map<Integer, PerAdRewardListener> rewardListenersById = new HashMap<>();
41
+ private int lastActiveAdId = 0;
42
+
33
43
  private AdWhaleMediationRewardAd adWhaleMediationRewardAd;
34
44
  private String placementUid;
35
45
  private String placementName;
36
46
  private String region;
37
47
  private double lt;
38
48
  private double lng;
49
+ // 보상형 SSV(서버 사이드 검증) 전용 옵션
50
+ private String userId;
51
+ private Map<String, String> customData;
39
52
  private final ReactApplicationContext reactContext;
40
53
 
41
54
  public RNAdWhaleMediationRewardAd(ReactApplicationContext context) {
@@ -75,21 +88,169 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
75
88
  }
76
89
  }
77
90
 
91
+ /**
92
+ * 보상형 SSV(서버 사이드 검증)용 앱 사용자 id 설정.
93
+ * AdMob / AdManager SSV postback 의 user_id 로 전달됩니다. (그 외 네트워크에서는 무시)
94
+ */
95
+ @ReactMethod
96
+ public void setUserId(String userId) {
97
+ Log.e(REACT_CLASS_NAME, "userId: " + userId);
98
+ this.userId = userId;
99
+ }
100
+
101
+ /**
102
+ * 보상형 SSV(서버 사이드 검증)용 커스텀 데이터 설정.
103
+ * AdMob / AdManager SSV postback 의 custom_data 로 전달되며, SDK 내부에서 JSON 직렬화됩니다.
104
+ * (그 외 네트워크에서는 무시)
105
+ */
106
+ @ReactMethod
107
+ public void setCustomData(ReadableMap customDataMap) {
108
+ Log.e(REACT_CLASS_NAME, "customData: " + customDataMap);
109
+ this.customData = toStringMap(customDataMap);
110
+ }
111
+
112
+ /** ReadableMap 을 SDK 가 요구하는 Map<String, String> 으로 변환 (값은 문자열로 강제) */
113
+ @Nullable
114
+ private static Map<String, String> toStringMap(@Nullable ReadableMap map) {
115
+ if (map == null) {
116
+ return null;
117
+ }
118
+ Map<String, String> result = new HashMap<>();
119
+ com.facebook.react.bridge.ReadableMapKeySetIterator it = map.keySetIterator();
120
+ while (it.hasNextKey()) {
121
+ String key = it.nextKey();
122
+ switch (map.getType(key)) {
123
+ case String:
124
+ result.put(key, map.getString(key));
125
+ break;
126
+ case Number:
127
+ result.put(key, String.valueOf(map.getDouble(key)));
128
+ break;
129
+ case Boolean:
130
+ result.put(key, String.valueOf(map.getBoolean(key)));
131
+ break;
132
+ default:
133
+ // null / Map / Array 등은 무시
134
+ break;
135
+ }
136
+ }
137
+ return result;
138
+ }
139
+
140
+ /** 보상형 SSV 옵션(userId / customData)을 SDK 인스턴스에 적용 */
141
+ private static void applySsvOptions(
142
+ @NonNull AdWhaleMediationRewardAd ad,
143
+ @Nullable String userId,
144
+ @Nullable Map<String, String> customData) {
145
+ try {
146
+ if (userId != null && !userId.isEmpty()) {
147
+ ad.setUserId(userId);
148
+ }
149
+ } catch (Throwable t) {
150
+ Log.w(REACT_CLASS_NAME, "setUserId failed: " + t.getMessage());
151
+ }
152
+ try {
153
+ if (customData != null && !customData.isEmpty()) {
154
+ ad.setCustomData(customData);
155
+ }
156
+ } catch (Throwable t) {
157
+ Log.w(REACT_CLASS_NAME, "setCustomData failed: " + t.getMessage());
158
+ }
159
+ }
160
+
78
161
  @ReactMethod
79
162
  public void loadAd(String placementUid) {
80
163
  Log.e(REACT_CLASS_NAME, "loadAd()");
164
+ lastActiveAdId = 0;
81
165
  this.placementUid = placementUid;
82
166
  adWhaleMediationRewardAd = new AdWhaleMediationRewardAd(placementUid);
83
167
  adWhaleMediationRewardAd.setAdWhaleMediationFullScreenContentCallback(this);
84
168
  adWhaleMediationRewardAd.setPlacementName(placementName);
85
169
  adWhaleMediationRewardAd.setRegion(region);
86
170
  adWhaleMediationRewardAd.setGcoder(lt, lng);
171
+ // 보상형 SSV(서버 사이드 검증) 옵션 적용
172
+ applySsvOptions(adWhaleMediationRewardAd, userId, customData);
87
173
 
88
174
  UiThreadUtil.runOnUiThread(() -> {
89
175
  adWhaleMediationRewardAd.loadAd(this);
90
176
  });
91
177
  }
92
178
 
179
+ /**
180
+ * Flutter MethodChannel과 동일 네이밍:
181
+ * loadRewardAd({adId, placementUid, region, gcoder:[lt,lng], placementName})
182
+ */
183
+ @ReactMethod
184
+ public void loadRewardAd(ReadableMap args) {
185
+ if (args == null) {
186
+ Log.e(REACT_CLASS_NAME, "loadRewardAd args is null");
187
+ return;
188
+ }
189
+ if (!args.hasKey("adId") || args.getType("adId") != ReadableType.Number) {
190
+ Log.e(REACT_CLASS_NAME, "loadRewardAd missing adId (number)");
191
+ return;
192
+ }
193
+ if (!args.hasKey("placementUid") || args.getType("placementUid") != ReadableType.String) {
194
+ Log.e(REACT_CLASS_NAME, "loadRewardAd missing placementUid (string)");
195
+ return;
196
+ }
197
+ final int adId = args.getInt("adId");
198
+ final String placementUid = args.getString("placementUid");
199
+
200
+ final AdWhaleMediationRewardAd rewardAd = new AdWhaleMediationRewardAd(placementUid);
201
+ final PerAdRewardListener perAdListener = new PerAdRewardListener(adId);
202
+ rewardListenersById.put(adId, perAdListener);
203
+ rewardAd.setAdWhaleMediationFullScreenContentCallback(perAdListener);
204
+
205
+ try {
206
+ if (args.hasKey("region") && args.getType("region") == ReadableType.String) {
207
+ String r = args.getString("region");
208
+ if (r != null && !r.isEmpty()) rewardAd.setRegion(r);
209
+ }
210
+ } catch (Throwable t) {
211
+ Log.w(REACT_CLASS_NAME, "setRegion failed: " + t.getMessage());
212
+ }
213
+
214
+ try {
215
+ if (args.hasKey("placementName") && args.getType("placementName") == ReadableType.String) {
216
+ String pn = args.getString("placementName");
217
+ if (pn != null && !pn.isEmpty()) rewardAd.setPlacementName(pn);
218
+ }
219
+ } catch (Throwable t) {
220
+ Log.w(REACT_CLASS_NAME, "setPlacementName failed: " + t.getMessage());
221
+ }
222
+
223
+ try {
224
+ if (args.hasKey("gcoder") && args.getType("gcoder") == ReadableType.Array) {
225
+ ReadableArray arr = args.getArray("gcoder");
226
+ if (arr != null && arr.size() >= 2
227
+ && arr.getType(0) == ReadableType.Number
228
+ && arr.getType(1) == ReadableType.Number) {
229
+ double lat = arr.getDouble(0);
230
+ double lng = arr.getDouble(1);
231
+ rewardAd.setGcoder(lat, lng);
232
+ }
233
+ }
234
+ } catch (Throwable t) {
235
+ Log.w(REACT_CLASS_NAME, "setGcoder failed: " + t.getMessage());
236
+ }
237
+
238
+ // 보상형 SSV(서버 사이드 검증) 옵션: userId / customData
239
+ String ssvUserId = null;
240
+ if (args.hasKey("userId") && args.getType("userId") == ReadableType.String) {
241
+ ssvUserId = args.getString("userId");
242
+ }
243
+ Map<String, String> ssvCustomData = null;
244
+ if (args.hasKey("customData") && args.getType("customData") == ReadableType.Map) {
245
+ ssvCustomData = toStringMap(args.getMap("customData"));
246
+ }
247
+ applySsvOptions(rewardAd, ssvUserId, ssvCustomData);
248
+
249
+ adsById.put(adId, rewardAd);
250
+
251
+ UiThreadUtil.runOnUiThread(() -> rewardAd.loadAd(perAdListener));
252
+ }
253
+
93
254
  @ReactMethod
94
255
  public void showAd() {
95
256
  Log.e(REACT_CLASS_NAME, "showAd()");
@@ -98,9 +259,63 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
98
259
  return;
99
260
  }
100
261
 
101
- UiThreadUtil.runOnUiThread(() -> {
102
- adWhaleMediationRewardAd.showAd(this);
103
- });
262
+ lastActiveAdId = 0;
263
+ UiThreadUtil.runOnUiThread(() -> showRewardAdOnUi(adWhaleMediationRewardAd, this));
264
+ }
265
+
266
+ /** Flutter 네이밍: showAdWithoutView(adId) */
267
+ @ReactMethod
268
+ public void showAdWithoutView(int adId) {
269
+ AdWhaleMediationRewardAd ad = adsById.get(adId);
270
+ if (ad == null) {
271
+ Log.e(REACT_CLASS_NAME, "showAdWithoutView: ad not found. adId=" + adId);
272
+ return;
273
+ }
274
+ PerAdRewardListener listener = rewardListenersById.get(adId);
275
+ if (listener == null) {
276
+ Log.e(REACT_CLASS_NAME, "showAdWithoutView: listener not found. adId=" + adId);
277
+ return;
278
+ }
279
+ UiThreadUtil.runOnUiThread(() -> showRewardAdOnUi(ad, listener));
280
+ }
281
+
282
+ /**
283
+ * SDK는 Activity 생명주기와 연동해 onAdDismissed / 보상 콜백을 지연·발행합니다.
284
+ * {@code showAd(Listener)}만 쓰면 현재 Activity를 못 잡아 콜백이 누락될 수 있어
285
+ * {@code showAd(Activity, Listener)}를 우선 사용합니다.
286
+ */
287
+ /**
288
+ * @param listener 전면/로드/보상 콜백을 동일 adId로 보낼 리스턴스 (싱글턴은 {@code this})
289
+ */
290
+ private void showRewardAdOnUi(
291
+ @NonNull AdWhaleMediationRewardAd ad,
292
+ @NonNull AdWhaleMediationUserEarnedRewardListener listener) {
293
+ Activity activity = getCurrentActivity();
294
+ if (activity != null && !activity.isFinishing()) {
295
+ try {
296
+ ad.showAd(activity, listener);
297
+ return;
298
+ } catch (Throwable t) {
299
+ Log.w(REACT_CLASS_NAME, "showAd(activity, listener) failed, fallback: " + t.getMessage());
300
+ }
301
+ } else {
302
+ Log.w(REACT_CLASS_NAME, "showRewardAdOnUi: no valid Activity, using showAd(listener)");
303
+ }
304
+ ad.showAd(listener);
305
+ }
306
+
307
+ /** Flutter 네이밍: destroyAd(adId) */
308
+ @ReactMethod
309
+ public void destroyAd(int adId) {
310
+ rewardListenersById.remove(adId);
311
+ AdWhaleMediationRewardAd ad = adsById.remove(adId);
312
+ if (ad != null) {
313
+ try {
314
+ ad.destroy();
315
+ } catch (Throwable t) {
316
+ Log.w(REACT_CLASS_NAME, "destroyAd failed: " + t.getMessage());
317
+ }
318
+ }
104
319
  }
105
320
 
106
321
 
@@ -109,12 +324,17 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
109
324
  public void onAdClicked() {
110
325
  Log.i(REACT_CLASS_NAME, "onAdClicked()");
111
326
  sendEvnet("onRewardAdClicked", null);
327
+ sendAdEvent(lastActiveAdId, "onRewardAdClicked", null);
112
328
  }
113
329
 
114
330
  @Override
115
331
  public void onAdDismissed() {
116
332
  Log.i(REACT_CLASS_NAME, "onAdDismissed()");
117
333
  sendEvnet("onRewardAdDismissed", null);
334
+ sendAdEvent(lastActiveAdId, "onRewardAdDismissed", null);
335
+ if (adsById.containsKey(lastActiveAdId)) {
336
+ destroyAd(lastActiveAdId);
337
+ }
118
338
  }
119
339
 
120
340
  @Override
@@ -123,6 +343,8 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
123
343
  WritableMap params = Arguments.createMap();
124
344
  params.putInt("statusCode", statusCode);
125
345
  params.putString("message", message);
346
+ // NOTE: emit() 이후에는 Map 이 consume 될 수 있으므로, payload 복사(sendAdEvent)를 먼저 수행
347
+ sendAdEvent(lastActiveAdId, "onRewardAdFailedToShow", params);
126
348
  sendEvnet("onRewardAdFailedToShow", params);
127
349
  }
128
350
 
@@ -130,6 +352,7 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
130
352
  public void onAdShowed() {
131
353
  Log.i(REACT_CLASS_NAME, "onAdShowed()");
132
354
  sendEvnet("onRewardAdShowed", null);
355
+ sendAdEvent(lastActiveAdId, "onRewardAdShowed", null);
133
356
  }
134
357
 
135
358
  /// AdWhaleMediationRewardedAdLoadCallback
@@ -137,6 +360,7 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
137
360
  public void onAdLoaded(AdWhaleMediationRewardAd adWhaleMediationRewardAd, String message) {
138
361
  Log.i(REACT_CLASS_NAME, "onAdLoaded(+ " + message + ")");
139
362
  sendEvnet("onRewardAdLoaded", null);
363
+ sendAdEvent(lastActiveAdId, "onRewardAdLoaded", null);
140
364
  }
141
365
 
142
366
  @Override
@@ -145,6 +369,8 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
145
369
  WritableMap params = Arguments.createMap();
146
370
  params.putInt("statusCode", statusCode);
147
371
  params.putString("message", message);
372
+ // NOTE: emit() 이후에는 Map 이 consume 될 수 있으므로, payload 복사(sendAdEvent)를 먼저 수행
373
+ sendAdEvent(lastActiveAdId, "onRewardAdFailedToLoad", params);
148
374
  sendEvnet("onRewardAdFailedToLoad", params);
149
375
  }
150
376
 
@@ -154,8 +380,14 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
154
380
  public void onUserRewarded(AdWhaleMediationRewardItem adWhaleMediationRewardItem) {
155
381
  Log.i(REACT_CLASS_NAME, "onUserRewarded(" + adWhaleMediationRewardItem.toString() + ")");
156
382
  WritableMap params = Arguments.createMap();
383
+ // Flutter와 동일 키: RewardType / RewardAmount (대소문자 포함)
384
+ params.putString("RewardType", adWhaleMediationRewardItem.getRewardType());
385
+ params.putInt("RewardAmount", adWhaleMediationRewardItem.getRewardAmount());
386
+ // 레거시 호환 키도 유지
157
387
  params.putString("type", adWhaleMediationRewardItem.getRewardType());
158
388
  params.putInt("amount", adWhaleMediationRewardItem.getRewardAmount());
389
+ // NOTE: emit() 이후에는 Map 이 consume 될 수 있으므로, payload 복사(sendAdEvent)를 먼저 수행
390
+ sendAdEvent(lastActiveAdId, "onUserRewarded", params);
159
391
  sendEvnet("onUserRewarded", params);
160
392
  }
161
393
 
@@ -167,9 +399,36 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
167
399
  }
168
400
 
169
401
  private void sendEvnet(String eventName, @Nullable WritableMap params) {
170
- getReactApplicationContext()
171
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
402
+ final ReactApplicationContext ctx = getReactApplicationContext();
403
+ if (ctx == null) {
404
+ return;
405
+ }
406
+ // SDK 콜백이 메인이 아닌 스레드에서 올 수 있어 RN 브리지는 UI 큐에서 emit
407
+ ctx.runOnUiQueueThread(() -> {
408
+ try {
409
+ ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
172
410
  .emit(eventName, params);
411
+ } catch (Throwable t) {
412
+ Log.w(REACT_CLASS_NAME, "emit failed: " + eventName + " — " + t.getMessage());
413
+ }
414
+ });
415
+ }
416
+
417
+ /**
418
+ * Flutter `onAdEvent` 스타일: payload에 eventName + adId 포함.
419
+ * payload: { adId, eventName, statusCode?, message?, RewardType?, RewardAmount? }
420
+ */
421
+ private void sendAdEvent(int adId, @NonNull String eventName, @Nullable WritableMap original) {
422
+ WritableMap payload = Arguments.createMap();
423
+ payload.putInt("adId", adId);
424
+ payload.putString("eventName", eventName);
425
+ if (original != null) {
426
+ if (original.hasKey("statusCode")) payload.putInt("statusCode", original.getInt("statusCode"));
427
+ if (original.hasKey("message")) payload.putString("message", original.getString("message"));
428
+ if (original.hasKey("RewardType")) payload.putString("RewardType", original.getString("RewardType"));
429
+ if (original.hasKey("RewardAmount")) payload.putInt("RewardAmount", original.getInt("RewardAmount"));
430
+ }
431
+ sendEvnet("onAdEvent", payload);
173
432
  }
174
433
 
175
434
  @ReactMethod
@@ -178,7 +437,83 @@ public class RNAdWhaleMediationRewardAd extends ReactContextBaseJavaModule imple
178
437
  }
179
438
 
180
439
  @ReactMethod
181
- public void removeListeners(Integer count) {
440
+ public void removeListeners(Integer count) {
182
441
  //TODO NOTHING
183
442
  }
443
+
444
+ /**
445
+ * 멀티 보상형 인스턴스당 1개: 로드/전면/보상 콜백 모두 boundAdId 로 onAdEvent 전달.
446
+ */
447
+ private final class PerAdRewardListener implements AdWhaleMediationRewardedAdLoadCallback,
448
+ AdWhaleMediationFullScreenContentCallback,
449
+ AdWhaleMediationUserEarnedRewardListener {
450
+ private final int boundAdId;
451
+
452
+ PerAdRewardListener(int boundAdId) {
453
+ this.boundAdId = boundAdId;
454
+ }
455
+
456
+ @Override
457
+ public void onAdClicked() {
458
+ Log.i(REACT_CLASS_NAME, "onAdClicked() adId=" + boundAdId);
459
+ sendEvnet("onRewardAdClicked", null);
460
+ sendAdEvent(boundAdId, "onRewardAdClicked", null);
461
+ }
462
+
463
+ @Override
464
+ public void onAdDismissed() {
465
+ Log.i(REACT_CLASS_NAME, "onAdDismissed() adId=" + boundAdId);
466
+ sendEvnet("onRewardAdDismissed", null);
467
+ sendAdEvent(boundAdId, "onRewardAdDismissed", null);
468
+ if (adsById.containsKey(boundAdId)) {
469
+ destroyAd(boundAdId);
470
+ }
471
+ }
472
+
473
+ @Override
474
+ public void onFailedToShow(int statusCode, String message) {
475
+ Log.i(REACT_CLASS_NAME, "onFailedToShow(" + statusCode + ", " + message + ") adId=" + boundAdId);
476
+ WritableMap params = Arguments.createMap();
477
+ params.putInt("statusCode", statusCode);
478
+ params.putString("message", message);
479
+ sendAdEvent(boundAdId, "onRewardAdFailedToShow", params);
480
+ sendEvnet("onRewardAdFailedToShow", params);
481
+ }
482
+
483
+ @Override
484
+ public void onAdShowed() {
485
+ Log.i(REACT_CLASS_NAME, "onAdShowed() adId=" + boundAdId);
486
+ sendEvnet("onRewardAdShowed", null);
487
+ sendAdEvent(boundAdId, "onRewardAdShowed", null);
488
+ }
489
+
490
+ @Override
491
+ public void onAdLoaded(AdWhaleMediationRewardAd adWhaleMediationRewardAd, String message) {
492
+ Log.i(REACT_CLASS_NAME, "onAdLoaded(+ " + message + ") adId=" + boundAdId);
493
+ sendEvnet("onRewardAdLoaded", null);
494
+ sendAdEvent(boundAdId, "onRewardAdLoaded", null);
495
+ }
496
+
497
+ @Override
498
+ public void onAdFailedToLoad(int statusCode, String message) {
499
+ Log.i(REACT_CLASS_NAME, "onAdFailedToLoad(" + statusCode + ", " + message + ") adId=" + boundAdId);
500
+ WritableMap params = Arguments.createMap();
501
+ params.putInt("statusCode", statusCode);
502
+ params.putString("message", message);
503
+ sendAdEvent(boundAdId, "onRewardAdFailedToLoad", params);
504
+ sendEvnet("onRewardAdFailedToLoad", params);
505
+ }
506
+
507
+ @Override
508
+ public void onUserRewarded(AdWhaleMediationRewardItem adWhaleMediationRewardItem) {
509
+ Log.i(REACT_CLASS_NAME, "onUserRewarded(" + adWhaleMediationRewardItem + ") adId=" + boundAdId);
510
+ WritableMap params = Arguments.createMap();
511
+ params.putString("RewardType", adWhaleMediationRewardItem.getRewardType());
512
+ params.putInt("RewardAmount", adWhaleMediationRewardItem.getRewardAmount());
513
+ params.putString("type", adWhaleMediationRewardItem.getRewardType());
514
+ params.putInt("amount", adWhaleMediationRewardItem.getRewardAmount());
515
+ sendAdEvent(boundAdId, "onUserRewarded", params);
516
+ sendEvnet("onUserRewarded", params);
517
+ }
518
+ }
184
519
  }