cloudx-react-native 3.11.0 → 3.11.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 (77) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/java/com/cloudxreactnative/CloudXAdEvents.kt +9 -0
  3. package/android/src/main/java/com/cloudxreactnative/CloudXAdInfo.kt +76 -0
  4. package/android/src/main/java/com/cloudxreactnative/CloudXModuleImpl.kt +110 -0
  5. package/android/src/main/java/com/cloudxreactnative/CloudXNativeAdHolder.kt +184 -0
  6. package/android/src/main/java/com/cloudxreactnative/CloudXNativeAdView.kt +209 -70
  7. package/android/src/main/java/com/cloudxreactnative/CloudXNativeAdViewManagerImpl.kt +6 -0
  8. package/android/src/newarch/com/cloudxreactnative/CloudXModule.kt +9 -0
  9. package/android/src/newarch/com/cloudxreactnative/CloudXNativeAdViewManager.kt +2 -0
  10. package/android/src/oldarch/com/cloudxreactnative/CloudXModule.kt +14 -0
  11. package/android/src/oldarch/com/cloudxreactnative/CloudXNativeAdViewManager.kt +3 -0
  12. package/android/src/test/java/com/cloudxreactnative/CloudXNativeAdPreloadTest.kt +351 -0
  13. package/android/src/test/java/com/cloudxreactnative/FakeReadable.kt +2 -2
  14. package/ios/CloudX.h +12 -0
  15. package/ios/CloudX.mm +94 -1
  16. package/ios/CloudXNativeAdHolder.h +66 -0
  17. package/ios/CloudXNativeAdHolder.mm +247 -0
  18. package/ios/CloudXNativeAdView.h +9 -0
  19. package/ios/CloudXNativeAdView.mm +308 -64
  20. package/ios/CloudXNativeAdViewManager.mm +7 -0
  21. package/lib/commonjs/CloudXNativeAd.js +178 -0
  22. package/lib/commonjs/CloudXNativeAd.js.map +1 -0
  23. package/lib/commonjs/CloudXNativeAdView.js +58 -11
  24. package/lib/commonjs/CloudXNativeAdView.js.map +1 -1
  25. package/lib/commonjs/hooks.js +203 -0
  26. package/lib/commonjs/hooks.js.map +1 -1
  27. package/lib/commonjs/index.js +13 -0
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/commonjs/specs/CloudXNativeAdViewNativeComponent.js +2 -0
  30. package/lib/commonjs/specs/CloudXNativeAdViewNativeComponent.js.map +1 -1
  31. package/lib/commonjs/specs/NativeCloudX.js.map +1 -1
  32. package/lib/commonjs/types/NativeAd.js +21 -1
  33. package/lib/commonjs/types/NativeAd.js.map +1 -1
  34. package/lib/commonjs/types/index.js.map +1 -1
  35. package/lib/commonjs/version.js +1 -1
  36. package/lib/module/CloudXNativeAd.js +170 -0
  37. package/lib/module/CloudXNativeAd.js.map +1 -0
  38. package/lib/module/CloudXNativeAdView.js +58 -11
  39. package/lib/module/CloudXNativeAdView.js.map +1 -1
  40. package/lib/module/hooks.js +202 -0
  41. package/lib/module/hooks.js.map +1 -1
  42. package/lib/module/index.js +2 -1
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/specs/CloudXNativeAdViewNativeComponent.js +2 -0
  45. package/lib/module/specs/CloudXNativeAdViewNativeComponent.js.map +1 -1
  46. package/lib/module/specs/NativeCloudX.js.map +1 -1
  47. package/lib/module/types/NativeAd.js +26 -1
  48. package/lib/module/types/NativeAd.js.map +1 -1
  49. package/lib/module/types/index.js.map +1 -1
  50. package/lib/module/version.js +1 -1
  51. package/lib/typescript/src/CloudXNativeAd.d.ts +58 -0
  52. package/lib/typescript/src/CloudXNativeAd.d.ts.map +1 -0
  53. package/lib/typescript/src/CloudXNativeAdView.d.ts +6 -13
  54. package/lib/typescript/src/CloudXNativeAdView.d.ts.map +1 -1
  55. package/lib/typescript/src/hooks.d.ts +30 -0
  56. package/lib/typescript/src/hooks.d.ts.map +1 -1
  57. package/lib/typescript/src/index.d.ts +4 -3
  58. package/lib/typescript/src/index.d.ts.map +1 -1
  59. package/lib/typescript/src/specs/CloudXNativeAdViewNativeComponent.d.ts +23 -2
  60. package/lib/typescript/src/specs/CloudXNativeAdViewNativeComponent.d.ts.map +1 -1
  61. package/lib/typescript/src/specs/NativeCloudX.d.ts +13 -0
  62. package/lib/typescript/src/specs/NativeCloudX.d.ts.map +1 -1
  63. package/lib/typescript/src/types/NativeAd.d.ts +76 -13
  64. package/lib/typescript/src/types/NativeAd.d.ts.map +1 -1
  65. package/lib/typescript/src/types/index.d.ts +1 -1
  66. package/lib/typescript/src/types/index.d.ts.map +1 -1
  67. package/lib/typescript/src/version.d.ts +1 -1
  68. package/package.json +1 -1
  69. package/src/CloudXNativeAd.ts +177 -0
  70. package/src/CloudXNativeAdView.tsx +82 -15
  71. package/src/hooks.ts +236 -0
  72. package/src/index.ts +14 -1
  73. package/src/specs/CloudXNativeAdViewNativeComponent.ts +27 -2
  74. package/src/specs/NativeCloudX.ts +20 -0
  75. package/src/types/NativeAd.ts +85 -18
  76. package/src/types/index.ts +4 -0
  77. package/src/version.ts +1 -1
package/src/hooks.ts CHANGED
@@ -9,6 +9,10 @@ import { useEffect, useState, useCallback, useRef } from 'react';
9
9
  import type { EventSubscription } from 'react-native';
10
10
  import NativeCloudX from './specs/NativeCloudX';
11
11
  import { emitter } from './EventEmitter';
12
+ import { CloudXNativeAd } from './CloudXNativeAd';
13
+ import { useCloudXSdkInitializationGate } from './useCloudXSdkInitialization';
14
+ import type { CloudXAdLoadFailedInfo } from './types/AdInfo';
15
+ import type { CloudXNativeAdLoadOptions } from './types/NativeAd';
12
16
 
13
17
  const {
14
18
  ON_INTERSTITIAL_LOADED_EVENT,
@@ -429,3 +433,235 @@ export function useCloudXBanner(adUnitId: string): CloudXBannerHookResult {
429
433
 
430
434
  return { isLoaded, error };
431
435
  }
436
+
437
+ // ---------------------------------------------------------------------------
438
+ // useCloudXNativeAd
439
+ // ---------------------------------------------------------------------------
440
+
441
+ export type CloudXNativeAdHookStatus = 'idle' | 'loading' | 'loaded' | 'expired' | 'error';
442
+
443
+ export type CloudXNativeAdHookResult = {
444
+ status: CloudXNativeAdHookStatus;
445
+ nativeAd: CloudXNativeAd | null;
446
+ error: CloudXAdLoadFailedInfo | null;
447
+ load: () => void;
448
+ destroy: () => void;
449
+ };
450
+
451
+ const stableSerialize = (value: unknown): unknown => {
452
+ if (value === null || typeof value !== 'object') {
453
+ return value;
454
+ }
455
+ if (Array.isArray(value)) {
456
+ return value.map(stableSerialize);
457
+ }
458
+ const record = value as Record<string, unknown>;
459
+ const sortedKeys = Object.keys(record).sort();
460
+ const result: Record<string, unknown> = {};
461
+ for (const key of sortedKeys) {
462
+ const val = record[key];
463
+ if (val !== undefined) {
464
+ result[key] = stableSerialize(val);
465
+ }
466
+ }
467
+ return result;
468
+ };
469
+
470
+ const getOptionsSignature = (options?: CloudXNativeAdLoadOptions): string => {
471
+ if (!options) return '{}';
472
+ return JSON.stringify(stableSerialize(options));
473
+ };
474
+
475
+ /**
476
+ * Hook for managing a preloaded native ad lifecycle.
477
+ *
478
+ * Preloads a native ad ahead of render and holds it until unmount, manual
479
+ * destruction, or identity change. Unlike view-managed ads, a preloaded ad's
480
+ * lifecycle belongs to its handle and this hook: unmounting a view bound to it
481
+ * does not destroy it.
482
+ *
483
+ * With autoLoad (the default) the first load waits until the SDK has
484
+ * initialized, as `CloudXNativeAdView` does. A `load()` call made before
485
+ * initialization fails with `NOT_INITIALIZED`.
486
+ *
487
+ * @param adUnitId - The ad unit identifier, or null to keep idle.
488
+ * @param options - Load options configuring the auction and loader.
489
+ * @param config - Optional configuration (autoLoad defaults to true).
490
+ */
491
+ export function useCloudXNativeAd(
492
+ adUnitId: string | null,
493
+ options?: CloudXNativeAdLoadOptions,
494
+ config?: { autoLoad?: boolean }
495
+ ): CloudXNativeAdHookResult {
496
+ const autoLoad = config?.autoLoad ?? true;
497
+ const optionsSignature = getOptionsSignature(options);
498
+ const isSdkInitialized = useCloudXSdkInitializationGate('useCloudXNativeAd');
499
+
500
+ const [status, setStatus] = useState<CloudXNativeAdHookStatus>(() =>
501
+ autoLoad && adUnitId ? 'loading' : 'idle'
502
+ );
503
+ const [nativeAd, setNativeAd] = useState<CloudXNativeAd | null>(null);
504
+ const [error, setError] = useState<CloudXAdLoadFailedInfo | null>(null);
505
+
506
+ const mountedRef = useRef(true);
507
+ const generationRef = useRef(0);
508
+ const heldAdRef = useRef<CloudXNativeAd | null>(null);
509
+ const optionsRef = useRef(options);
510
+ optionsRef.current = options;
511
+
512
+ const inflightRef = useRef<{
513
+ signature: string;
514
+ generation: number;
515
+ promise: Promise<CloudXNativeAd>;
516
+ } | null>(null);
517
+
518
+ const lastSignatureRef = useRef<string | null>(null);
519
+
520
+ const executeLoad = useCallback(() => {
521
+ if (!adUnitId) {
522
+ setStatus('idle');
523
+ setNativeAd(null);
524
+ setError(null);
525
+ return;
526
+ }
527
+
528
+ const currentSignature = `${adUnitId}::${optionsSignature}`;
529
+
530
+ // Coalesce concurrent loads with the same signature (React StrictMode
531
+ // double effects must not start two loads).
532
+ if (inflightRef.current && inflightRef.current.signature === currentSignature) {
533
+ return;
534
+ }
535
+
536
+ const generation = ++generationRef.current;
537
+ setStatus('loading');
538
+ setError(null);
539
+
540
+ if (heldAdRef.current) {
541
+ heldAdRef.current.destroy();
542
+ heldAdRef.current = null;
543
+ setNativeAd(null);
544
+ }
545
+
546
+ const currentOptions = optionsRef.current;
547
+ const promise = CloudXNativeAd.load(adUnitId, currentOptions);
548
+ inflightRef.current = { signature: currentSignature, generation, promise };
549
+
550
+ promise
551
+ .then((ad) => {
552
+ if (inflightRef.current?.promise === promise) {
553
+ inflightRef.current = null;
554
+ }
555
+
556
+ // A load that resolves after unmount, after destroy(), or after the signature
557
+ // changed is destroyed immediately and never published (generation counter).
558
+ if (!mountedRef.current || generationRef.current !== generation) {
559
+ ad.destroy();
560
+ return;
561
+ }
562
+
563
+ if (heldAdRef.current && heldAdRef.current !== ad) {
564
+ heldAdRef.current.destroy();
565
+ }
566
+ heldAdRef.current = ad;
567
+
568
+ // On the handle's 'expired' event: status 'expired', nativeAd null,
569
+ // and the expired handle destroyed.
570
+ ad.addListener('expired', () => {
571
+ if (generationRef.current === generation && heldAdRef.current === ad) {
572
+ heldAdRef.current = null;
573
+ setStatus('expired');
574
+ setNativeAd(null);
575
+ ad.destroy();
576
+ }
577
+ });
578
+
579
+ setStatus('loaded');
580
+ setNativeAd(ad);
581
+ setError(null);
582
+ })
583
+ .catch((err: CloudXAdLoadFailedInfo) => {
584
+ if (inflightRef.current?.promise === promise) {
585
+ inflightRef.current = null;
586
+ }
587
+
588
+ // Ignores stale rejection after identity change, destroy, or unmount.
589
+ if (!mountedRef.current || generationRef.current !== generation) {
590
+ return;
591
+ }
592
+
593
+ setStatus('error');
594
+ setNativeAd(null);
595
+ setError(err);
596
+ });
597
+ }, [adUnitId, optionsSignature]);
598
+
599
+ // Unmount owns the release. Kept apart from the load effect so a re-run
600
+ // of that effect (autoLoad flipping, say) never destroys an ad this hook
601
+ // has already published. Under StrictMode the simulated unmount only
602
+ // clears mountedRef; the remount sets it again, so a load in flight from
603
+ // the first mount is still published rather than duplicated.
604
+ useEffect(() => {
605
+ mountedRef.current = true;
606
+ return () => {
607
+ mountedRef.current = false;
608
+ if (heldAdRef.current) {
609
+ heldAdRef.current.destroy();
610
+ heldAdRef.current = null;
611
+ }
612
+ };
613
+ }, []);
614
+
615
+ useEffect(() => {
616
+ const currentSignature = adUnitId ? `${adUnitId}::${optionsSignature}` : '';
617
+ const isFirstRun = lastSignatureRef.current === null;
618
+ const signatureChanged = !isFirstRun && lastSignatureRef.current !== currentSignature;
619
+ lastSignatureRef.current = currentSignature;
620
+
621
+ if (signatureChanged) {
622
+ if (heldAdRef.current) {
623
+ heldAdRef.current.destroy();
624
+ heldAdRef.current = null;
625
+ }
626
+ // Never leave the previous identity's (now destroyed) handle
627
+ // published while the replacement loads, nor its status or error:
628
+ // with autoLoad off nothing replaces them, and a `loaded` status
629
+ // with no ad, or a `loading` one that never settles, would stick.
630
+ setNativeAd(null);
631
+ setStatus('idle');
632
+ setError(null);
633
+ generationRef.current++;
634
+ inflightRef.current = null;
635
+
636
+ if (!adUnitId) {
637
+ return;
638
+ }
639
+ }
640
+
641
+ // A held ad survives autoLoad flipping back on; only an identity
642
+ // change (handled above) replaces it.
643
+ if (autoLoad && adUnitId && isSdkInitialized && !heldAdRef.current) {
644
+ executeLoad();
645
+ }
646
+ }, [adUnitId, optionsSignature, autoLoad, isSdkInitialized, executeLoad]);
647
+
648
+ const load = useCallback(() => {
649
+ executeLoad();
650
+ }, [executeLoad]);
651
+
652
+ const destroy = useCallback(() => {
653
+ generationRef.current++;
654
+ inflightRef.current = null;
655
+
656
+ if (heldAdRef.current) {
657
+ heldAdRef.current.destroy();
658
+ heldAdRef.current = null;
659
+ }
660
+
661
+ setStatus('idle');
662
+ setNativeAd(null);
663
+ setError(null);
664
+ }, []);
665
+
666
+ return { status, nativeAd, error, load, destroy };
667
+ }
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export { CloudXRewardedAd } from './CloudXRewardedAd';
15
15
  export { CloudXBannerAd } from './CloudXBannerAd';
16
16
  export { CloudXMRECAd } from './CloudXMRECAd';
17
17
  export { CloudXAdPosition } from './CloudXAdPosition';
18
+ export { CloudXNativeAd } from './CloudXNativeAd';
18
19
 
19
20
  // Ad View Components (in-tree; layer correctly under app overlays)
20
21
  export { CloudXBannerView } from './CloudXBannerView';
@@ -47,12 +48,20 @@ export { CloudXErrorCode } from './CloudXErrorCode';
47
48
  export { requestTrackingAuthorization, getTrackingAuthorizationStatus, CloudXATTStatus } from './AppTrackingTransparency';
48
49
 
49
50
  // Hooks
50
- export { useCloudXInterstitial, useCloudXAppOpen, useCloudXRewarded, useCloudXBanner } from './hooks';
51
+ export {
52
+ useCloudXInterstitial,
53
+ useCloudXAppOpen,
54
+ useCloudXRewarded,
55
+ useCloudXBanner,
56
+ useCloudXNativeAd,
57
+ } from './hooks';
51
58
  export type {
52
59
  CloudXInterstitialHookResult,
53
60
  CloudXAppOpenHookResult,
54
61
  CloudXRewardedHookResult,
55
62
  CloudXBannerHookResult,
63
+ CloudXNativeAdHookResult,
64
+ CloudXNativeAdHookStatus,
56
65
  } from './hooks';
57
66
 
58
67
  // Types
@@ -92,6 +101,10 @@ export type {
92
101
  CloudXNativeAdMetadata,
93
102
  CloudXNativeAdViewHandle,
94
103
  CloudXNativeAdViewProps,
104
+ CloudXNativeAdViewComponentProps,
105
+ CloudXNativeAdViewPreloadedProps,
106
+ CloudXNativeAdLoadOptions,
107
+ CloudXNativeAdEventType,
95
108
  CloudXLocalExtraParameters,
96
109
  } from './types';
97
110
  export { CloudXNativeTemplate } from './types';
@@ -40,6 +40,11 @@ export type CloudXNativeAdLoadFailedEvent = Readonly<{
40
40
  message?: string | null;
41
41
  }>;
42
42
 
43
+ /** Internal: names the preloaded ad that just left the view. */
44
+ export type CloudXNativeAdBindingClearedEvent = Readonly<{
45
+ nativeAdId: string;
46
+ }>;
47
+
43
48
  export type NativeExtraParameterEntry = Readonly<{
44
49
  key: string;
45
50
  value: UnsafeMixed;
@@ -53,8 +58,15 @@ export type NativeExtraParameterEntry = Readonly<{
53
58
  * normal RN z-ordering.
54
59
  */
55
60
  export interface NativeProps extends ViewProps {
56
- /** Ad unit ID used to load the ad. */
57
- adUnitId: string;
61
+ /** Ad unit ID used to load the ad. Ignored when `nativeAdId` is set. */
62
+ adUnitId?: string | null;
63
+
64
+ /**
65
+ * Id of an ad already loaded by `loadNativeAd`. When set, the view never
66
+ * loads: it renders the held ad on attach and leaves its lifetime to the
67
+ * holder, so unmounting does not destroy it.
68
+ */
69
+ nativeAdId?: string | null;
58
70
 
59
71
  /**
60
72
  * Publisher-selected template name (e.g. "small_template",
@@ -103,6 +115,12 @@ export interface NativeProps extends ViewProps {
103
115
  /** Called when the ad fails to render/display (e.g. manual render before load). */
104
116
  onAdDisplayFailedEvent: DirectEventHandler<CloudXNativeAdLoadFailedEvent>;
105
117
 
118
+ /**
119
+ * Called each time an ad is rendered into this view and registered for
120
+ * click and impression tracking. Fires again after a re-bind.
121
+ */
122
+ onAdDisplayedEvent: DirectEventHandler<CloudXNativeAdInfoEvent>;
123
+
106
124
  /** Called when the ad is clicked. */
107
125
  onAdClickedEvent: DirectEventHandler<CloudXNativeAdInfoEvent>;
108
126
 
@@ -114,6 +132,13 @@ export interface NativeProps extends ViewProps {
114
132
 
115
133
  /** Called when ad revenue is paid. */
116
134
  onAdRevenuePaidEvent: DirectEventHandler<CloudXNativeAdInfoEvent>;
135
+
136
+ /**
137
+ * Internal, not a publisher callback: native released the preloaded ad
138
+ * this view showed (destroy, expiry, or a newer view took it), so the JS
139
+ * view clears its asset components.
140
+ */
141
+ onAdBindingClearedEvent?: DirectEventHandler<CloudXNativeAdBindingClearedEvent>;
117
142
  }
118
143
 
119
144
  export interface NativeCommands {
@@ -61,6 +61,12 @@ export interface Spec extends TurboModule {
61
61
  ON_APPOPEN_DISPLAY_FAILED_EVENT: string;
62
62
  ON_APPOPEN_HIDDEN_EVENT: string;
63
63
  ON_APPOPEN_AD_REVENUE_PAID: string;
64
+
65
+ // Preloaded native ad events. Every payload carries `nativeAdId`.
66
+ ON_NATIVE_AD_CLICKED_EVENT: string;
67
+ ON_NATIVE_AD_EXPIRED_EVENT: string;
68
+ ON_NATIVE_AD_CLOSED_EVENT: string;
69
+ ON_NATIVE_AD_REVENUE_PAID: string;
64
70
  };
65
71
 
66
72
  // =========================================================================
@@ -171,6 +177,20 @@ export interface Spec extends TurboModule {
171
177
  showAppOpen(adUnitId: string, placement?: string | null, customData?: string | null): void;
172
178
  destroyAppOpen(adUnitId: string): void;
173
179
 
180
+ // =========================================================================
181
+ // Native (load first, render later)
182
+ // =========================================================================
183
+
184
+ /**
185
+ * Loads a native ad with no view attached. Resolves with
186
+ * `{ nativeAdId, adInfo }` once an ad is held natively; rejects with the
187
+ * CloudX error code as the promise code. The ad stays held until
188
+ * `destroyNativeAd`, expiry, or module invalidation.
189
+ */
190
+ loadNativeAd(adUnitId: string, options: UnsafeObject): Promise<UnsafeObject>;
191
+ /** Releases a held native ad. Idempotent; unknown ids are ignored. */
192
+ destroyNativeAd(nativeAdId: string): void;
193
+
174
194
  // =========================================================================
175
195
  // Event Emitter (required by NativeEventEmitter)
176
196
  // =========================================================================
@@ -1,7 +1,8 @@
1
1
  import type { ViewProps } from 'react-native';
2
2
  import type { CloudXAdDisplayFailedInfo, CloudXAdInfo } from './AdInfo';
3
3
  import type { CloudXAdProps } from './AdProps';
4
- import type { CloudXExtraParameterValue } from './ExtraParameters';
4
+ import type { CloudXExtraParameters, CloudXExtraParameterValue } from './ExtraParameters';
5
+ import type { CloudXNativeAd } from '../CloudXNativeAd';
5
6
 
6
7
  /**
7
8
  * Built-in native ad template variants rendered by the SDK's view component.
@@ -85,41 +86,107 @@ export type CloudXLocalExtraParameters = {
85
86
  };
86
87
 
87
88
  /**
88
- * Props for the native-ad view component.
89
+ * Load-time options for {@link CloudXNativeAd.load}. These configure the
90
+ * auction and the loader, so they are fixed once the ad is loaded; the view
91
+ * that later renders the ad only chooses the layout.
92
+ */
93
+ export type CloudXNativeAdLoadOptions = {
94
+ placement?: string | null;
95
+ customData?: string | null;
96
+ extraParameters?: CloudXExtraParameters | null;
97
+ localExtraParameters?: CloudXLocalExtraParameters | null;
98
+ disableVideoFullScreen?: boolean;
99
+ startVideoUnmuted?: boolean;
100
+ hideVideoMediaControls?: boolean;
101
+ };
102
+
103
+ /** Events a preloaded {@link CloudXNativeAd} reports on its own handle. */
104
+ export type CloudXNativeAdEventType = 'clicked' | 'revenuePaid' | 'expired' | 'closed';
105
+
106
+ /** Props every native-ad view accepts, whichever way the ad was loaded. */
107
+ type CloudXNativeAdViewRenderProps = ViewProps & {
108
+ template?: CloudXNativeTemplate;
109
+ /** Selects between the SDK's bundled template layouts and manual asset binding. */
110
+ renderMode?: 'template' | 'manual';
111
+ /**
112
+ * Called each time the ad is rendered into this view and registered for
113
+ * click and impression tracking. Fires again after a re-bind.
114
+ */
115
+ onAdDisplayed?: (adInfo: CloudXAdInfo) => void;
116
+ /**
117
+ * Called when an ad fails to display or track in this view.
118
+ *
119
+ * Emitted on both platforms. Android fires it for post-load
120
+ * adapter/render errors and when click/impression registration fails in
121
+ * manual mode. iOS fires it on the manual-mode failure paths: render
122
+ * requested before load, expired ad, and registration refused. With
123
+ * `nativeAd`, it also fires when the handle is destroyed or expired.
124
+ */
125
+ onAdDisplayFailed?: (errorInfo: CloudXAdDisplayFailedInfo) => void;
126
+ };
127
+
128
+ /**
129
+ * Props for a native-ad view that loads its own ad when it attaches and
130
+ * destroys it on unmount. This is the shape `CloudXNativeAdView` has always
131
+ * taken, so wrappers that extend, `Omit` or `Pick` it keep compiling.
89
132
  *
90
133
  * Callbacks inherited from {@link CloudXAdProps} (`onAdLoaded`, `onAdLoadFailed`,
91
134
  * `onAdClicked`, `onAdRevenuePaid`) receive unwrapped payloads, not synthetic events.
92
135
  */
93
- export type CloudXNativeAdViewProps = CloudXAdProps &
94
- ViewProps & {
95
- template?: CloudXNativeTemplate;
136
+ export type CloudXNativeAdViewProps = CloudXNativeAdViewRenderProps &
137
+ CloudXAdProps & {
96
138
  localExtraParameters?: CloudXLocalExtraParameters;
97
139
  disableVideoFullScreen?: boolean;
98
140
  startVideoUnmuted?: boolean;
99
141
  hideVideoMediaControls?: boolean;
100
- /** Selects between the SDK's bundled template layouts and manual asset binding. */
101
- renderMode?: 'template' | 'manual';
102
- /**
103
- * Called when a loaded ad subsequently fails to display or track.
104
- *
105
- * Emitted on both platforms. Android fires it for post-load
106
- * adapter/render errors and when click/impression registration fails in
107
- * manual mode. iOS fires it on the three manual-mode failure paths:
108
- * render requested before load, expired ad, and registration refused.
109
- */
110
- onAdDisplayFailed?: (errorInfo: CloudXAdDisplayFailedInfo) => void;
111
142
  onAdExpired?: (adInfo: CloudXAdInfo) => void;
112
143
  onAdClosed?: (adInfo: CloudXAdInfo) => void;
144
+ nativeAd?: never;
113
145
  };
114
146
 
147
+ /**
148
+ * The view renders an ad loaded earlier with {@link CloudXNativeAd.load}. It
149
+ * never loads and never destroys the ad. Clicks, revenue, expiry and close
150
+ * are reported once, on the handle (`nativeAd.addListener`), never on the
151
+ * view, so revenue cannot be counted twice.
152
+ */
153
+ export type CloudXNativeAdViewPreloadedProps = CloudXNativeAdViewRenderProps & {
154
+ nativeAd: CloudXNativeAd;
155
+ adUnitId?: never;
156
+ placement?: never;
157
+ customData?: never;
158
+ extraParameters?: never;
159
+ localExtraParameters?: never;
160
+ disableVideoFullScreen?: never;
161
+ startVideoUnmuted?: never;
162
+ hideVideoMediaControls?: never;
163
+ onAdLoaded?: never;
164
+ onAdLoadFailed?: never;
165
+ onAdClicked?: never;
166
+ onAdRevenuePaid?: never;
167
+ onAdExpired?: never;
168
+ onAdClosed?: never;
169
+ };
170
+
171
+ /**
172
+ * Everything `CloudXNativeAdView` accepts: either it loads its own ad
173
+ * ({@link CloudXNativeAdViewProps}) or it renders one loaded earlier
174
+ * ({@link CloudXNativeAdViewPreloadedProps}). Mixing the two is a type error.
175
+ */
176
+ export type CloudXNativeAdViewComponentProps = CloudXNativeAdViewProps | CloudXNativeAdViewPreloadedProps;
177
+
115
178
  export type CloudXNativeAdViewHandle = {
116
179
  /**
117
- * Requests a load. This does NOT force a new auction: both native SDKs
180
+ * Requests a load. No-op when the view renders a preloaded `nativeAd`.
181
+ * This does NOT force a new auction: both native SDKs
118
182
  * re-serve the ad they already hold while it is neither expired nor
119
183
  * destroyed, including after its impression. Changed targeting is
120
184
  * therefore not picked up. Call `destroyAd()` first for a fresh ad.
121
185
  */
122
186
  loadAd: () => void;
123
- /** Releases the native ad and its resources. */
187
+ /**
188
+ * Releases the native ad and its resources. No-op when the view renders a
189
+ * preloaded `nativeAd`: the handle owns that ad, so call `nativeAd.destroy()`.
190
+ */
124
191
  destroyAd: () => void;
125
192
  };
@@ -29,6 +29,10 @@ export type {
29
29
  CloudXNativeAdMetadata,
30
30
  CloudXNativeAdViewHandle,
31
31
  CloudXNativeAdViewProps,
32
+ CloudXNativeAdViewComponentProps,
33
+ CloudXNativeAdViewPreloadedProps,
34
+ CloudXNativeAdLoadOptions,
35
+ CloudXNativeAdEventType,
32
36
  CloudXLocalExtraParameters,
33
37
  } from './NativeAd';
34
38
  export { CloudXNativeTemplate, CloudXNativeAssetType } from './NativeAd';
package/src/version.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  // This value is reported to the native SDK in CloudX.initialize() and is used
4
4
  // for wrapper-version telemetry/attribution, so a stale value mis-attributes
5
5
  // installs to the wrong release.
6
- export const PLUGIN_VERSION = '3.11.0';
6
+ export const PLUGIN_VERSION = '3.11.1';