@vynix-org/react-native-ads-sdk 0.1.1

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 (128) hide show
  1. package/AdsSdk.podspec +52 -0
  2. package/LICENSE +20 -0
  3. package/README.md +95 -0
  4. package/android/build.gradle +108 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +5 -0
  7. package/android/src/main/java/com/adssdk/AdsSdkModule.kt +23 -0
  8. package/android/src/main/java/com/adssdk/AdsSdkPackage.kt +33 -0
  9. package/ios/AdsSdk.h +5 -0
  10. package/ios/AdsSdk.mm +18 -0
  11. package/lib/module/ads-manager/AppOpenAdsManager.js +131 -0
  12. package/lib/module/ads-manager/AppOpenAdsManager.js.map +1 -0
  13. package/lib/module/ads-manager/GoogleMobileAdsManager.js +62 -0
  14. package/lib/module/ads-manager/GoogleMobileAdsManager.js.map +1 -0
  15. package/lib/module/ads-manager/InterstitialAdsManager.js +291 -0
  16. package/lib/module/ads-manager/InterstitialAdsManager.js.map +1 -0
  17. package/lib/module/ads-manager/NativeAdsManager.js +405 -0
  18. package/lib/module/ads-manager/NativeAdsManager.js.map +1 -0
  19. package/lib/module/ads-manager/RewardedAdsManager.js +328 -0
  20. package/lib/module/ads-manager/RewardedAdsManager.js.map +1 -0
  21. package/lib/module/ads-manager/RewardedInterstitialAdsManager.js +328 -0
  22. package/lib/module/ads-manager/RewardedInterstitialAdsManager.js.map +1 -0
  23. package/lib/module/components/BannerAdComponent.js +159 -0
  24. package/lib/module/components/BannerAdComponent.js.map +1 -0
  25. package/lib/module/components/FullscreenStyles.js +162 -0
  26. package/lib/module/components/FullscreenStyles.js.map +1 -0
  27. package/lib/module/components/NativeAdComponent.js +172 -0
  28. package/lib/module/components/NativeAdComponent.js.map +1 -0
  29. package/lib/module/components/NativeAdFullscreenComponent.js +205 -0
  30. package/lib/module/components/NativeAdFullscreenComponent.js.map +1 -0
  31. package/lib/module/components/NativeStyles.js +143 -0
  32. package/lib/module/components/NativeStyles.js.map +1 -0
  33. package/lib/module/event/custom-event.js +29 -0
  34. package/lib/module/event/custom-event.js.map +1 -0
  35. package/lib/module/hook/useAppOpenAd.js +138 -0
  36. package/lib/module/hook/useAppOpenAd.js.map +1 -0
  37. package/lib/module/hook/useInterstitialAd.js +157 -0
  38. package/lib/module/hook/useInterstitialAd.js.map +1 -0
  39. package/lib/module/hook/useNativeAd.js +147 -0
  40. package/lib/module/hook/useNativeAd.js.map +1 -0
  41. package/lib/module/hook/useRewardedAd.js +166 -0
  42. package/lib/module/hook/useRewardedAd.js.map +1 -0
  43. package/lib/module/hook/useRewardedInterstitialAd.js +166 -0
  44. package/lib/module/hook/useRewardedInterstitialAd.js.map +1 -0
  45. package/lib/module/index.js +14 -0
  46. package/lib/module/index.js.map +1 -0
  47. package/lib/module/package.json +1 -0
  48. package/lib/module/stores/interstitial-ads-store.js +205 -0
  49. package/lib/module/stores/interstitial-ads-store.js.map +1 -0
  50. package/lib/module/stores/native-ads-store.js +204 -0
  51. package/lib/module/stores/native-ads-store.js.map +1 -0
  52. package/lib/module/stores/rewarded-ads-store.js +216 -0
  53. package/lib/module/stores/rewarded-ads-store.js.map +1 -0
  54. package/lib/module/stores/rewarded-interstitial-ads-store.js +216 -0
  55. package/lib/module/stores/rewarded-interstitial-ads-store.js.map +1 -0
  56. package/lib/module/types/ads-types.js +25 -0
  57. package/lib/module/types/ads-types.js.map +1 -0
  58. package/lib/typescript/package.json +1 -0
  59. package/lib/typescript/src/ads-manager/AppOpenAdsManager.d.ts +38 -0
  60. package/lib/typescript/src/ads-manager/AppOpenAdsManager.d.ts.map +1 -0
  61. package/lib/typescript/src/ads-manager/GoogleMobileAdsManager.d.ts +22 -0
  62. package/lib/typescript/src/ads-manager/GoogleMobileAdsManager.d.ts.map +1 -0
  63. package/lib/typescript/src/ads-manager/InterstitialAdsManager.d.ts +22 -0
  64. package/lib/typescript/src/ads-manager/InterstitialAdsManager.d.ts.map +1 -0
  65. package/lib/typescript/src/ads-manager/NativeAdsManager.d.ts +59 -0
  66. package/lib/typescript/src/ads-manager/NativeAdsManager.d.ts.map +1 -0
  67. package/lib/typescript/src/ads-manager/RewardedAdsManager.d.ts +25 -0
  68. package/lib/typescript/src/ads-manager/RewardedAdsManager.d.ts.map +1 -0
  69. package/lib/typescript/src/ads-manager/RewardedInterstitialAdsManager.d.ts +25 -0
  70. package/lib/typescript/src/ads-manager/RewardedInterstitialAdsManager.d.ts.map +1 -0
  71. package/lib/typescript/src/components/BannerAdComponent.d.ts +22 -0
  72. package/lib/typescript/src/components/BannerAdComponent.d.ts.map +1 -0
  73. package/lib/typescript/src/components/FullscreenStyles.d.ts +155 -0
  74. package/lib/typescript/src/components/FullscreenStyles.d.ts.map +1 -0
  75. package/lib/typescript/src/components/NativeAdComponent.d.ts +24 -0
  76. package/lib/typescript/src/components/NativeAdComponent.d.ts.map +1 -0
  77. package/lib/typescript/src/components/NativeAdFullscreenComponent.d.ts +26 -0
  78. package/lib/typescript/src/components/NativeAdFullscreenComponent.d.ts.map +1 -0
  79. package/lib/typescript/src/components/NativeStyles.d.ts +139 -0
  80. package/lib/typescript/src/components/NativeStyles.d.ts.map +1 -0
  81. package/lib/typescript/src/event/custom-event.d.ts +8 -0
  82. package/lib/typescript/src/event/custom-event.d.ts.map +1 -0
  83. package/lib/typescript/src/hook/useAppOpenAd.d.ts +19 -0
  84. package/lib/typescript/src/hook/useAppOpenAd.d.ts.map +1 -0
  85. package/lib/typescript/src/hook/useInterstitialAd.d.ts +20 -0
  86. package/lib/typescript/src/hook/useInterstitialAd.d.ts.map +1 -0
  87. package/lib/typescript/src/hook/useNativeAd.d.ts +20 -0
  88. package/lib/typescript/src/hook/useNativeAd.d.ts.map +1 -0
  89. package/lib/typescript/src/hook/useRewardedAd.d.ts +27 -0
  90. package/lib/typescript/src/hook/useRewardedAd.d.ts.map +1 -0
  91. package/lib/typescript/src/hook/useRewardedInterstitialAd.d.ts +27 -0
  92. package/lib/typescript/src/hook/useRewardedInterstitialAd.d.ts.map +1 -0
  93. package/lib/typescript/src/index.d.ts +10 -0
  94. package/lib/typescript/src/index.d.ts.map +1 -0
  95. package/lib/typescript/src/stores/interstitial-ads-store.d.ts +36 -0
  96. package/lib/typescript/src/stores/interstitial-ads-store.d.ts.map +1 -0
  97. package/lib/typescript/src/stores/native-ads-store.d.ts +36 -0
  98. package/lib/typescript/src/stores/native-ads-store.d.ts.map +1 -0
  99. package/lib/typescript/src/stores/rewarded-ads-store.d.ts +39 -0
  100. package/lib/typescript/src/stores/rewarded-ads-store.d.ts.map +1 -0
  101. package/lib/typescript/src/stores/rewarded-interstitial-ads-store.d.ts +39 -0
  102. package/lib/typescript/src/stores/rewarded-interstitial-ads-store.d.ts.map +1 -0
  103. package/lib/typescript/src/types/ads-types.d.ts +164 -0
  104. package/lib/typescript/src/types/ads-types.d.ts.map +1 -0
  105. package/package.json +177 -0
  106. package/src/ads-manager/AppOpenAdsManager.ts +155 -0
  107. package/src/ads-manager/GoogleMobileAdsManager.ts +68 -0
  108. package/src/ads-manager/InterstitialAdsManager.ts +371 -0
  109. package/src/ads-manager/NativeAdsManager.ts +445 -0
  110. package/src/ads-manager/RewardedAdsManager.ts +383 -0
  111. package/src/ads-manager/RewardedInterstitialAdsManager.ts +410 -0
  112. package/src/components/BannerAdComponent.tsx +205 -0
  113. package/src/components/FullscreenStyles.ts +158 -0
  114. package/src/components/NativeAdComponent.tsx +208 -0
  115. package/src/components/NativeAdFullscreenComponent.tsx +261 -0
  116. package/src/components/NativeStyles.ts +141 -0
  117. package/src/event/custom-event.ts +30 -0
  118. package/src/hook/useAppOpenAd.ts +161 -0
  119. package/src/hook/useInterstitialAd.ts +195 -0
  120. package/src/hook/useNativeAd.ts +173 -0
  121. package/src/hook/useRewardedAd.ts +205 -0
  122. package/src/hook/useRewardedInterstitialAd.ts +212 -0
  123. package/src/index.tsx +33 -0
  124. package/src/stores/interstitial-ads-store.ts +287 -0
  125. package/src/stores/native-ads-store.ts +272 -0
  126. package/src/stores/rewarded-ads-store.ts +290 -0
  127. package/src/stores/rewarded-interstitial-ads-store.ts +318 -0
  128. package/src/types/ads-types.ts +239 -0
@@ -0,0 +1,445 @@
1
+ import {
2
+ AdStatus,
3
+ type AdConfig,
4
+ type AdData,
5
+ type AdInstance,
6
+ type AdManagerState,
7
+ } from '../types/ads-types';
8
+ import { googleMobileAdsManager } from './GoogleMobileAdsManager';
9
+ import { CustomEventEmitter } from '../event/custom-event';
10
+
11
+ export class NativeAdsManager extends CustomEventEmitter {
12
+ private state: AdManagerState = {
13
+ ads: new Map(),
14
+ loadingKeys: new Set(),
15
+ displayedKeys: new Set(),
16
+ };
17
+
18
+ private configs: Map<string, AdConfig> = new Map();
19
+ private loadPromises: Map<string, Promise<void>> = new Map();
20
+
21
+ constructor() {
22
+ super();
23
+ // Tự động khởi tạo Google Mobile Ads SDK
24
+ this.initializeGoogleMobileAds();
25
+ }
26
+
27
+ /**
28
+ * Khởi tạo Google Mobile Ads SDK
29
+ */
30
+ private async initializeGoogleMobileAds(): Promise<void> {
31
+ try {
32
+ await googleMobileAdsManager.initialize();
33
+ } catch (error) {
34
+ console.warn('Failed to initialize Google Mobile Ads SDK:', error);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Preload một native ad với config
40
+ */
41
+ async preloadAd(config: AdConfig): Promise<void> {
42
+ const fullConfig: AdConfig = {
43
+ ...config,
44
+ timeout: config.timeout ?? 10000,
45
+ maxRetries: config.maxRetries ?? 3,
46
+ };
47
+
48
+ const { adKey, adId } = fullConfig;
49
+ this.configs.set(adKey, fullConfig);
50
+
51
+ // Tạo ad instance nếu chưa tồn tại
52
+ if (!this.state.ads.has(adKey)) {
53
+ const adInstance: AdInstance = {
54
+ adKey,
55
+ adId,
56
+ status: AdStatus.IDLE,
57
+ data: null,
58
+ error: null,
59
+ loadTime: null,
60
+ displayTime: null,
61
+ retryCount: 0,
62
+ isUsingHighPriority: false,
63
+ hasTriedHighPriority: false,
64
+ };
65
+ this.state.ads.set(adKey, adInstance);
66
+ }
67
+
68
+ // Nếu đang load rồi thì return promise hiện tại
69
+ if (this.loadPromises.has(adKey)) {
70
+ return this.loadPromises.get(adKey)!;
71
+ }
72
+
73
+ // Bắt đầu load
74
+ const loadPromise = this.loadAdInternal(adKey);
75
+ this.loadPromises.set(adKey, loadPromise);
76
+
77
+ try {
78
+ await loadPromise;
79
+ } finally {
80
+ this.loadPromises.delete(adKey);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Load ad ngay lập tức (alias của preloadAd)
86
+ */
87
+ async loadAd(config: AdConfig): Promise<void> {
88
+ return this.preloadAd(config);
89
+ }
90
+
91
+ /**
92
+ * Hiển thị ad đã được load
93
+ */
94
+ async displayAd(adKey: string): Promise<AdInstance | null> {
95
+ const ad = this.state.ads.get(adKey);
96
+
97
+ if (!ad) {
98
+ console.warn(`Ad with key "${adKey}" not found`);
99
+ return null;
100
+ }
101
+
102
+ if (ad.status === AdStatus.LOADED) {
103
+ ad.status = AdStatus.DISPLAYED;
104
+ ad.displayTime = Date.now();
105
+ this.state.displayedKeys.add(adKey);
106
+
107
+ this.emit('adDisplayed', ad);
108
+
109
+ // const config = this.configs.get(adKey);
110
+ // if (config) {
111
+ // // Preload ad mới trong background (không await để không block)
112
+ // this.preloadAd(adKey, config.adId, config).catch((error) => {
113
+ // console.warn(`Failed to preload new ad for key "${adKey}":`, error);
114
+ // });
115
+ // }
116
+
117
+ return ad;
118
+ }
119
+
120
+ if (ad.status === AdStatus.LOADING) {
121
+ // Đợi load xong
122
+ await this.waitForAdLoad(adKey);
123
+ return this.displayAd(adKey);
124
+ }
125
+
126
+ if (ad.status === AdStatus.ERROR) {
127
+ // Thử load lại
128
+ await this.refreshAd(adKey);
129
+ return this.displayAd(adKey);
130
+ }
131
+
132
+ console.warn(
133
+ `Cannot display ad with key "${adKey}" in status: ${ad.status}`
134
+ );
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * Lấy ad instance
140
+ */
141
+ getAd(adKey: string): AdInstance | null {
142
+ return this.state.ads.get(adKey) || null;
143
+ }
144
+
145
+ /**
146
+ * Xóa ad
147
+ */
148
+ removeAd(adKey: string): void {
149
+ const ad = this.state.ads.get(adKey);
150
+ if (ad?.data?.unsubscribeListeners) {
151
+ ad.data.unsubscribeListeners();
152
+ }
153
+
154
+ this.state.ads.delete(adKey);
155
+ this.state.loadingKeys.delete(adKey);
156
+ this.state.displayedKeys.delete(adKey);
157
+ this.configs.delete(adKey);
158
+ this.loadPromises.delete(adKey);
159
+ this.emit('adRemoved', adKey);
160
+ }
161
+
162
+ /**
163
+ * Refresh ad (load lại)
164
+ */
165
+ async refreshAd(adKey: string): Promise<void> {
166
+ const config = this.configs.get(adKey);
167
+ if (!config) {
168
+ throw new Error(`Config for key "${adKey}" not found`);
169
+ }
170
+
171
+ // Reset status
172
+ const ad = this.state.ads.get(adKey);
173
+ if (ad) {
174
+ ad.status = AdStatus.IDLE;
175
+ ad.error = null;
176
+ ad.retryCount = 0;
177
+ }
178
+
179
+ return this.preloadAd(config);
180
+ }
181
+
182
+ /**
183
+ * Clear tất cả ads
184
+ */
185
+ clearAllAds(): void {
186
+ const keys = Array.from(this.state.ads.keys());
187
+ keys.forEach((adKey) => this.removeAd(adKey));
188
+ this.emit('allAdsCleared');
189
+ }
190
+
191
+ /**
192
+ * Lấy state hiện tại
193
+ */
194
+ getState(): AdManagerState {
195
+ return {
196
+ ads: new Map(this.state.ads),
197
+ loadingKeys: new Set(this.state.loadingKeys),
198
+ displayedKeys: new Set(this.state.displayedKeys),
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Internal method để load ad
204
+ */
205
+ private async loadAdInternal(adKey: string): Promise<void> {
206
+ const ad = this.state.ads.get(adKey);
207
+ const config = this.configs.get(adKey);
208
+
209
+ if (!ad || !config) {
210
+ throw new Error(`Ad or config for key "${adKey}" not found`);
211
+ }
212
+
213
+ // Xác định adId sẽ được sử dụng
214
+ let adIdToLoad = config.adId;
215
+
216
+ // Nếu có adHighpriorityId và chưa thử load high priority
217
+ if (config.adHighpriorityId && !ad.hasTriedHighPriority) {
218
+ adIdToLoad = config.adHighpriorityId;
219
+ ad.isUsingHighPriority = true;
220
+ } else {
221
+ ad.isUsingHighPriority = false;
222
+ }
223
+
224
+ // Kiểm tra retry limit
225
+ if (ad.retryCount >= config.maxRetries!) {
226
+ // Nếu đang dùng high priority và đã hết retry, fallback về adId bình thường
227
+ if (ad.isUsingHighPriority) {
228
+ console.log(
229
+ `High priority ad failed after ${config.maxRetries} retries, falling back to normal ad`
230
+ );
231
+ ad.hasTriedHighPriority = true;
232
+ ad.retryCount = 0; // Reset retry count cho adId bình thường
233
+ ad.isUsingHighPriority = false;
234
+ // Không return, tiếp tục load với adId bình thường ở lần recursive call
235
+ return this.loadAdInternal(adKey);
236
+ } else {
237
+ // Nếu đã dùng adId bình thường mà vẫn lỗi
238
+ ad.status = AdStatus.ERROR;
239
+ ad.error = `Max retries (${config.maxRetries}) exceeded`;
240
+ this.emit('adError', ad);
241
+ return;
242
+ }
243
+ }
244
+
245
+ ad.status = AdStatus.LOADING;
246
+ ad.retryCount++;
247
+ this.state.loadingKeys.add(adKey);
248
+
249
+ this.emit('adLoading', ad);
250
+
251
+ try {
252
+ // Gọi native method để load ad
253
+ const adData = await this.loadNativeAd(adIdToLoad, config.timeout!);
254
+
255
+ ad.status = AdStatus.LOADED;
256
+ ad.data = adData;
257
+ ad.error = null;
258
+ ad.loadTime = Date.now();
259
+ ad.retryCount = 0;
260
+
261
+ // Nếu load high priority thành công, đánh dấu đã thử
262
+ if (ad.isUsingHighPriority) {
263
+ ad.hasTriedHighPriority = true;
264
+ }
265
+
266
+ this.state.loadingKeys.delete(adKey);
267
+ this.emit('adLoaded', ad);
268
+ } catch (error) {
269
+ ad.status = AdStatus.ERROR;
270
+ ad.error = error instanceof Error ? error.message : String(error);
271
+ this.state.loadingKeys.delete(adKey);
272
+ this.emit('adError', ad);
273
+ throw error;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Đợi ad load xong
279
+ */
280
+ private async waitForAdLoad(adKey: string): Promise<void> {
281
+ return new Promise((resolve, reject) => {
282
+ const timeout = setTimeout(() => {
283
+ reject(new Error(`Timeout waiting for ad "${adKey}" to load`));
284
+ }, 10000);
285
+
286
+ const checkAd = () => {
287
+ const ad = this.state.ads.get(adKey);
288
+ if (!ad) {
289
+ clearTimeout(timeout);
290
+ reject(new Error(`Ad "${adKey}" not found`));
291
+ return;
292
+ }
293
+
294
+ if (ad.status === AdStatus.LOADED) {
295
+ clearTimeout(timeout);
296
+ resolve();
297
+ } else if (ad.status === AdStatus.ERROR) {
298
+ clearTimeout(timeout);
299
+ reject(new Error(ad.error || 'Unknown error'));
300
+ } else {
301
+ setTimeout(checkAd, 100);
302
+ }
303
+ };
304
+
305
+ checkAd();
306
+ });
307
+ }
308
+
309
+ private googleMobileAdsModule: any = null;
310
+
311
+ /**
312
+ * Load native ad sử dụng react-native-google-mobile-ads
313
+ */
314
+ private async loadNativeAd(adId: string, timeout: number): Promise<AdData> {
315
+ try {
316
+ // Đảm bảo Google Mobile Ads SDK đã được khởi tạo
317
+ await googleMobileAdsManager.initialize();
318
+
319
+ // Cache import để tránh import nhiều lần
320
+ if (!this.googleMobileAdsModule) {
321
+ this.googleMobileAdsModule = await import(
322
+ 'react-native-google-mobile-ads'
323
+ );
324
+ }
325
+
326
+ const {
327
+ NativeAd,
328
+ TestIds,
329
+ NativeAdChoicesPlacement,
330
+ NativeAdEventType,
331
+ NativeMediaAspectRatio,
332
+ } = this.googleMobileAdsModule;
333
+
334
+ // Sử dụng test ID nếu adId là test
335
+ const finalAdId = adId === 'test' ? TestIds.NATIVE : adId;
336
+
337
+ // Tạo promise với timeout
338
+ const loadPromise = new Promise<AdData>((resolve, reject) => {
339
+ const timeoutId = setTimeout(() => {
340
+ reject(new Error(`Timeout loading ad after ${timeout}ms`));
341
+ }, timeout);
342
+
343
+ // Tạo native ad request
344
+ NativeAd.createForAdRequest(finalAdId, {
345
+ aspectRatio: NativeMediaAspectRatio.LANDSCAPE,
346
+ adChoicesPlacement: NativeAdChoicesPlacement.TOP_RIGHT,
347
+ startVideoMuted: false,
348
+ })
349
+ .then((nativeAd: InstanceType<typeof NativeAd>) => {
350
+ clearTimeout(timeoutId);
351
+
352
+ console.log('nativeAd', 'loadNativeAd');
353
+
354
+ // Thêm event listeners cho native ad
355
+ const unsubscribeClicked = nativeAd.addAdEventListener(
356
+ NativeAdEventType.CLICKED,
357
+ () => {
358
+ console.log('Native ad clicked');
359
+ this.emit('adClicked', { adId, nativeAd });
360
+ }
361
+ );
362
+
363
+ const unsubscribeImpression = nativeAd.addAdEventListener(
364
+ NativeAdEventType.IMPRESSION,
365
+ () => {
366
+ console.log('Native ad impression recorded');
367
+ this.emit('adImpression', { adId, nativeAd });
368
+ }
369
+ );
370
+ // const unsubscribeOpened = nativeAd.addAdEventListener(
371
+ // AdEventType.OPENED,
372
+ // () => {
373
+ // console.log('Native ad opened');
374
+ // this.emit('adOpened', { adId, nativeAd });
375
+ // }
376
+ // );
377
+
378
+ // const unsubscribeClosed = nativeAd.addAdEventListener(
379
+ // AdEventType.CLOSED,
380
+ // () => {
381
+ // console.log('Native ad closed');
382
+ // this.emit('adClosed', { adId, nativeAd });
383
+ // }
384
+ // );
385
+
386
+ // Chuyển đổi native ad data thành AdData format
387
+ const adData: AdData = {
388
+ id: adId,
389
+ title: nativeAd.headline || '',
390
+ description: nativeAd.body || '',
391
+ imageUrl: nativeAd.images?.[0]?.url || nativeAd.icon?.url || '',
392
+ callToAction: nativeAd.callToAction || 'Learn More',
393
+ advertiser: nativeAd.advertiser || '',
394
+ rating: nativeAd.starRating || undefined,
395
+ price: nativeAd.price || undefined,
396
+ store: nativeAd.store || undefined,
397
+ // Thêm các field khác từ native ad
398
+ nativeAd: nativeAd, // Lưu reference đến native ad object
399
+ iconUrl: nativeAd.icon?.url,
400
+ images:
401
+ nativeAd.images?.map((img: { url: any }) => img.url) || [],
402
+ // Lưu unsubscribe functions để cleanup sau này
403
+ unsubscribeListeners: () => {
404
+ unsubscribeClicked();
405
+ unsubscribeImpression();
406
+ },
407
+ };
408
+
409
+ resolve(adData);
410
+ })
411
+ .catch((error: { message: any }) => {
412
+ clearTimeout(timeoutId);
413
+ reject(new Error(`Failed to load native ad: ${error.message}`));
414
+ });
415
+ });
416
+
417
+ return await loadPromise;
418
+ } catch (error) {
419
+ // Fallback về mock data nếu có lỗi import hoặc load
420
+ console.warn('Failed to load native ad, using mock data:', error);
421
+
422
+ return new Promise((resolve) => {
423
+ setTimeout(
424
+ () => {
425
+ resolve({
426
+ id: adId,
427
+ title: 'Sample Ad Title',
428
+ description: 'This is a sample ad description',
429
+ imageUrl: 'https://via.placeholder.com/300x200',
430
+ callToAction: 'Learn More',
431
+ advertiser: 'Sample Advertiser',
432
+ rating: 4.5,
433
+ price: '$9.99',
434
+ store: 'App Store',
435
+ });
436
+ },
437
+ 1000 + Math.random() * 2000
438
+ );
439
+ });
440
+ }
441
+ }
442
+ }
443
+
444
+ // Singleton instance
445
+ export const nativeAdsManager = new NativeAdsManager();