novvy-ads-react-native 1.0.0-beta.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 (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/android/build.gradle +84 -0
  4. package/android/gradle.properties +4 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/ai/novvy/reactnative/NovvyAdContainerView.kt +63 -0
  7. package/android/src/main/java/ai/novvy/reactnative/NovvyAdsModule.kt +390 -0
  8. package/android/src/main/java/ai/novvy/reactnative/NovvyAdsPackage.kt +46 -0
  9. package/android/src/main/java/ai/novvy/reactnative/NovvyPauseAdViewManager.kt +68 -0
  10. package/android/src/main/java/ai/novvy/reactnative/NovvyScrollAdViewManager.kt +72 -0
  11. package/android/src/main/java/ai/novvy/reactnative/RnPlayerAdapter.kt +18 -0
  12. package/android/src/main/java/ai/novvy/reactnative/ViewReadyEvent.kt +26 -0
  13. package/app.plugin.js +4 -0
  14. package/ios/NovvyAds.mm +168 -0
  15. package/ios/NovvyAdsImpl.swift +409 -0
  16. package/ios/NovvyPauseAdView.mm +79 -0
  17. package/ios/NovvyScrollAdView.mm +80 -0
  18. package/ios/RnPlayerAdapter.swift +14 -0
  19. package/lib/commonjs/NovvyAds.js +87 -0
  20. package/lib/commonjs/NovvyAds.js.map +1 -0
  21. package/lib/commonjs/ads/NovvyAd.js +121 -0
  22. package/lib/commonjs/ads/NovvyAd.js.map +1 -0
  23. package/lib/commonjs/ads/NovvyEndAd.js +19 -0
  24. package/lib/commonjs/ads/NovvyEndAd.js.map +1 -0
  25. package/lib/commonjs/ads/NovvyFullScreenAd.js +49 -0
  26. package/lib/commonjs/ads/NovvyFullScreenAd.js.map +1 -0
  27. package/lib/commonjs/ads/NovvyInterstitialAd.js +19 -0
  28. package/lib/commonjs/ads/NovvyInterstitialAd.js.map +1 -0
  29. package/lib/commonjs/ads/NovvyMidAd.js +19 -0
  30. package/lib/commonjs/ads/NovvyMidAd.js.map +1 -0
  31. package/lib/commonjs/ads/NovvyPauseAd.js +116 -0
  32. package/lib/commonjs/ads/NovvyPauseAd.js.map +1 -0
  33. package/lib/commonjs/ads/NovvyRewardedAd.js +53 -0
  34. package/lib/commonjs/ads/NovvyRewardedAd.js.map +1 -0
  35. package/lib/commonjs/ads/NovvyScrollAd.js +136 -0
  36. package/lib/commonjs/ads/NovvyScrollAd.js.map +1 -0
  37. package/lib/commonjs/eventBus.js +60 -0
  38. package/lib/commonjs/eventBus.js.map +1 -0
  39. package/lib/commonjs/index.js +90 -0
  40. package/lib/commonjs/index.js.map +1 -0
  41. package/lib/commonjs/package.json +1 -0
  42. package/lib/commonjs/spec/NativeNovvyAds.js +27 -0
  43. package/lib/commonjs/spec/NativeNovvyAds.js.map +1 -0
  44. package/lib/commonjs/spec/NovvyPauseAdViewNativeComponent.js +16 -0
  45. package/lib/commonjs/spec/NovvyPauseAdViewNativeComponent.js.map +1 -0
  46. package/lib/commonjs/spec/NovvyScrollAdViewNativeComponent.js +17 -0
  47. package/lib/commonjs/spec/NovvyScrollAdViewNativeComponent.js.map +1 -0
  48. package/lib/commonjs/spec/codegen-event-emitter.d.js +2 -0
  49. package/lib/commonjs/spec/codegen-event-emitter.d.js.map +1 -0
  50. package/lib/commonjs/types.js +54 -0
  51. package/lib/commonjs/types.js.map +1 -0
  52. package/lib/commonjs/views/NovvyPauseAdView.js +76 -0
  53. package/lib/commonjs/views/NovvyPauseAdView.js.map +1 -0
  54. package/lib/commonjs/views/NovvyScrollAdView.js +63 -0
  55. package/lib/commonjs/views/NovvyScrollAdView.js.map +1 -0
  56. package/lib/module/NovvyAds.js +83 -0
  57. package/lib/module/NovvyAds.js.map +1 -0
  58. package/lib/module/ads/NovvyAd.js +117 -0
  59. package/lib/module/ads/NovvyAd.js.map +1 -0
  60. package/lib/module/ads/NovvyEndAd.js +15 -0
  61. package/lib/module/ads/NovvyEndAd.js.map +1 -0
  62. package/lib/module/ads/NovvyFullScreenAd.js +45 -0
  63. package/lib/module/ads/NovvyFullScreenAd.js.map +1 -0
  64. package/lib/module/ads/NovvyInterstitialAd.js +15 -0
  65. package/lib/module/ads/NovvyInterstitialAd.js.map +1 -0
  66. package/lib/module/ads/NovvyMidAd.js +15 -0
  67. package/lib/module/ads/NovvyMidAd.js.map +1 -0
  68. package/lib/module/ads/NovvyPauseAd.js +112 -0
  69. package/lib/module/ads/NovvyPauseAd.js.map +1 -0
  70. package/lib/module/ads/NovvyRewardedAd.js +49 -0
  71. package/lib/module/ads/NovvyRewardedAd.js.map +1 -0
  72. package/lib/module/ads/NovvyScrollAd.js +132 -0
  73. package/lib/module/ads/NovvyScrollAd.js.map +1 -0
  74. package/lib/module/eventBus.js +55 -0
  75. package/lib/module/eventBus.js.map +1 -0
  76. package/lib/module/index.js +22 -0
  77. package/lib/module/index.js.map +1 -0
  78. package/lib/module/package.json +1 -0
  79. package/lib/module/spec/NativeNovvyAds.js +28 -0
  80. package/lib/module/spec/NativeNovvyAds.js.map +1 -0
  81. package/lib/module/spec/NovvyPauseAdViewNativeComponent.js +13 -0
  82. package/lib/module/spec/NovvyPauseAdViewNativeComponent.js.map +1 -0
  83. package/lib/module/spec/NovvyScrollAdViewNativeComponent.js +14 -0
  84. package/lib/module/spec/NovvyScrollAdViewNativeComponent.js.map +1 -0
  85. package/lib/module/spec/codegen-event-emitter.d.js +2 -0
  86. package/lib/module/spec/codegen-event-emitter.d.js.map +1 -0
  87. package/lib/module/types.js +56 -0
  88. package/lib/module/types.js.map +1 -0
  89. package/lib/module/views/NovvyPauseAdView.js +70 -0
  90. package/lib/module/views/NovvyPauseAdView.js.map +1 -0
  91. package/lib/module/views/NovvyScrollAdView.js +57 -0
  92. package/lib/module/views/NovvyScrollAdView.js.map +1 -0
  93. package/lib/typescript/NovvyAds.d.ts +69 -0
  94. package/lib/typescript/NovvyAds.d.ts.map +1 -0
  95. package/lib/typescript/ads/NovvyAd.d.ts +57 -0
  96. package/lib/typescript/ads/NovvyAd.d.ts.map +1 -0
  97. package/lib/typescript/ads/NovvyEndAd.d.ts +7 -0
  98. package/lib/typescript/ads/NovvyEndAd.d.ts.map +1 -0
  99. package/lib/typescript/ads/NovvyFullScreenAd.d.ts +19 -0
  100. package/lib/typescript/ads/NovvyFullScreenAd.d.ts.map +1 -0
  101. package/lib/typescript/ads/NovvyInterstitialAd.d.ts +7 -0
  102. package/lib/typescript/ads/NovvyInterstitialAd.d.ts.map +1 -0
  103. package/lib/typescript/ads/NovvyMidAd.d.ts +7 -0
  104. package/lib/typescript/ads/NovvyMidAd.d.ts.map +1 -0
  105. package/lib/typescript/ads/NovvyPauseAd.d.ts +45 -0
  106. package/lib/typescript/ads/NovvyPauseAd.d.ts.map +1 -0
  107. package/lib/typescript/ads/NovvyRewardedAd.d.ts +19 -0
  108. package/lib/typescript/ads/NovvyRewardedAd.d.ts.map +1 -0
  109. package/lib/typescript/ads/NovvyScrollAd.d.ts +57 -0
  110. package/lib/typescript/ads/NovvyScrollAd.d.ts.map +1 -0
  111. package/lib/typescript/eventBus.d.ts +15 -0
  112. package/lib/typescript/eventBus.d.ts.map +1 -0
  113. package/lib/typescript/index.d.ts +15 -0
  114. package/lib/typescript/index.d.ts.map +1 -0
  115. package/lib/typescript/spec/NativeNovvyAds.d.ts +73 -0
  116. package/lib/typescript/spec/NativeNovvyAds.d.ts.map +1 -0
  117. package/lib/typescript/spec/NovvyPauseAdViewNativeComponent.d.ts +18 -0
  118. package/lib/typescript/spec/NovvyPauseAdViewNativeComponent.d.ts.map +1 -0
  119. package/lib/typescript/spec/NovvyScrollAdViewNativeComponent.d.ts +19 -0
  120. package/lib/typescript/spec/NovvyScrollAdViewNativeComponent.d.ts.map +1 -0
  121. package/lib/typescript/types.d.ts +77 -0
  122. package/lib/typescript/types.d.ts.map +1 -0
  123. package/lib/typescript/views/NovvyPauseAdView.d.ts +21 -0
  124. package/lib/typescript/views/NovvyPauseAdView.d.ts.map +1 -0
  125. package/lib/typescript/views/NovvyScrollAdView.d.ts +22 -0
  126. package/lib/typescript/views/NovvyScrollAdView.d.ts.map +1 -0
  127. package/novvy-ads-react-native.podspec +71 -0
  128. package/package.json +84 -0
  129. package/plugin/build/index.d.ts +11 -0
  130. package/plugin/build/index.js +53 -0
  131. package/react-native.config.js +7 -0
  132. package/src/NovvyAds.ts +98 -0
  133. package/src/ads/NovvyAd.ts +125 -0
  134. package/src/ads/NovvyEndAd.ts +13 -0
  135. package/src/ads/NovvyFullScreenAd.ts +46 -0
  136. package/src/ads/NovvyInterstitialAd.ts +13 -0
  137. package/src/ads/NovvyMidAd.ts +13 -0
  138. package/src/ads/NovvyPauseAd.ts +125 -0
  139. package/src/ads/NovvyRewardedAd.ts +49 -0
  140. package/src/ads/NovvyScrollAd.ts +138 -0
  141. package/src/eventBus.ts +59 -0
  142. package/src/index.tsx +29 -0
  143. package/src/spec/NativeNovvyAds.ts +134 -0
  144. package/src/spec/NovvyPauseAdViewNativeComponent.ts +20 -0
  145. package/src/spec/NovvyScrollAdViewNativeComponent.ts +21 -0
  146. package/src/spec/codegen-event-emitter.d.ts +10 -0
  147. package/src/types.ts +82 -0
  148. package/src/views/NovvyPauseAdView.tsx +80 -0
  149. package/src/views/NovvyScrollAdView.tsx +71 -0
@@ -0,0 +1,98 @@
1
+ import { sha256 } from 'js-sha256';
2
+
3
+ import { Native } from './eventBus';
4
+ import type { NovvyUserContext, NovvyContentContext } from './types';
5
+
6
+ /**
7
+ * SDK entry point for Novvy Ads.
8
+ *
9
+ * A **thin** surface — it only wraps the native SDK's top-level entry (init +
10
+ * user/content context + email hashing). Ads themselves are created and driven
11
+ * directly through their classes ({@link NovvyInterstitialAd},
12
+ * {@link NovvyRewardedAd}, {@link NovvyMidAd}, {@link NovvyEndAd},
13
+ * {@link NovvyScrollAd}, {@link NovvyPauseAd}); the host owns each instance and
14
+ * wires its per-ad callbacks. Mirrors the Flutter plugin's `NovvyAds`.
15
+ *
16
+ * ```ts
17
+ * // 1. Initialize once at app startup (optionally with a user profile).
18
+ * const ok = await NovvyAds.initialize({
19
+ * appId: '...', endpoint: '...', apiKey: '...',
20
+ * userContext: { userId: '...', isPaidUser: true },
21
+ * });
22
+ *
23
+ * // 2. Set / refresh content context (and user profile if not set above).
24
+ * NovvyAds.setContentContext({ seriesName: '...', episodeNumber: 1 });
25
+ *
26
+ * // 3. Create an ad, wire callbacks, load & show.
27
+ * const ad = new NovvyRewardedAd('...');
28
+ * let earned = false;
29
+ * ad.onAdLoaded = () => ad.show();
30
+ * ad.onUserEarnedReward = () => { earned = true; };
31
+ * ad.onAdDismissed = () => { if (earned) grantReward(); };
32
+ * ad.onAdFailedToLoad = () => {};
33
+ * ad.load();
34
+ * ```
35
+ */
36
+ export const NovvyAds = {
37
+ /**
38
+ * Initialize the Novvy SDK. Must be called before creating any ads.
39
+ *
40
+ * Resolves with the native init-complete signal — `true` on success, `false`
41
+ * if initialization failed. May be called again (e.g. after login/logout);
42
+ * the native SDK handles re-initialization idempotently.
43
+ *
44
+ * @param userContext optional user-targeting context applied at init time
45
+ * (equivalent to calling {@link setUserContext} right after).
46
+ */
47
+ async initialize(options: {
48
+ appId: string;
49
+ endpoint: string;
50
+ apiKey: string;
51
+ userContext?: NovvyUserContext | null;
52
+ }): Promise<boolean> {
53
+ try {
54
+ return await Native.initialize(
55
+ options.appId,
56
+ options.endpoint,
57
+ options.apiKey,
58
+ options.userContext ?? null
59
+ );
60
+ } catch {
61
+ return false;
62
+ }
63
+ },
64
+
65
+ /**
66
+ * Hashes an email address using SHA-256 for privacy compliance. Use before
67
+ * passing an email to {@link NovvyUserContext.hashedEmail}. Pure JS — mirrors
68
+ * the native SDK's `helperHashEmail()`.
69
+ */
70
+ helperHashEmail(email: string): string {
71
+ return sha256(email.trim().toLowerCase());
72
+ },
73
+
74
+ /** Replaces the entire user context (null fields clear existing values). */
75
+ setUserContext(context: NovvyUserContext): void {
76
+ Native.setUserContext(context);
77
+ },
78
+
79
+ /** Incrementally merges user context fields (null fields preserve existing values). */
80
+ updateUserContext(partial: NovvyUserContext): void {
81
+ Native.updateUserContext(partial);
82
+ },
83
+
84
+ /**
85
+ * Replaces the entire content context (null fields clear existing values).
86
+ *
87
+ * Call before each `load()` / `attach()` in drama scenarios — the backend
88
+ * needs the current series/episode or it returns no fill.
89
+ */
90
+ setContentContext(context: NovvyContentContext): void {
91
+ Native.setContentContext(context);
92
+ },
93
+
94
+ /** Incrementally merges content context fields (null fields preserve existing values). */
95
+ updateContentContext(partial: NovvyContentContext): void {
96
+ Native.updateContentContext(partial);
97
+ },
98
+ };
@@ -0,0 +1,125 @@
1
+ import { Native, subscribe } from '../eventBus';
2
+ import type { NativeAdEvent } from '../spec/NativeNovvyAds';
3
+ import { NovvyAdEvent } from '../types';
4
+
5
+ /**
6
+ * Base class for all Novvy ad types.
7
+ *
8
+ * Mirrors the Flutter plugin's `NovvyAd` (lib/novvy_ads_plugin.dart).
9
+ * Instances are owned by the host: keep a reference alive until the ad
10
+ * completes (`onAdDismissed` / `onAdHidden`) and then call {@link destroy}.
11
+ */
12
+ export abstract class NovvyAd {
13
+ readonly adUnitId: string;
14
+ private readonly _adObjectId: string;
15
+
16
+ protected _isReady = false;
17
+ protected _disposed = false;
18
+
19
+ private _unsubscribe: (() => void) | null = null;
20
+
21
+ /** Called when the ad has loaded successfully. */
22
+ onAdLoaded?: () => void;
23
+ /** Called when the ad fails to load / request. */
24
+ onAdFailedToLoad?: (error: string) => void;
25
+
26
+ constructor(adUnitId: string) {
27
+ this.adUnitId = adUnitId;
28
+ this._adObjectId = NovvyAd._generateUniqueId();
29
+ }
30
+
31
+ /** The unique object ID for this ad instance. */
32
+ get adObjectId(): string {
33
+ return this._adObjectId;
34
+ }
35
+
36
+ /** Whether the ad is loaded and ready to show. */
37
+ get isReady(): boolean {
38
+ return this._isReady;
39
+ }
40
+
41
+ /**
42
+ * Subscribes to this ad's native event stream (filtered by object ID).
43
+ * Idempotent.
44
+ */
45
+ protected _subscribe(): void {
46
+ if (this._unsubscribe) {
47
+ return;
48
+ }
49
+ this._unsubscribe = subscribe(this._adObjectId, (event) =>
50
+ this._handleInternalEvent(event)
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Loads the ad. Not used by {@link NovvyPauseAd} (realtime, no load step).
56
+ *
57
+ * Callback-driven: the outcome is delivered via {@link onAdLoaded} /
58
+ * {@link onAdFailedToLoad}.
59
+ */
60
+ load(): void {
61
+ if (this._isReady || this._disposed) {
62
+ return;
63
+ }
64
+ this._subscribe();
65
+ try {
66
+ this._loadNative();
67
+ } catch (e) {
68
+ this.onAdFailedToLoad?.(String(e));
69
+ this.destroy();
70
+ }
71
+ }
72
+
73
+ /** Releases the ad resources. Idempotent. */
74
+ destroy(): void {
75
+ if (this._disposed) {
76
+ return;
77
+ }
78
+ this._disposed = true;
79
+ this._unsubscribe?.();
80
+ this._unsubscribe = null;
81
+ Native.disposeAd(this._adObjectId);
82
+ }
83
+
84
+ // Abstract members
85
+ protected abstract _loadNative(): void;
86
+ protected abstract _handleInternalEvent(event: NativeAdEvent): void;
87
+
88
+ /**
89
+ * Shared handling of the common load / fail transitions.
90
+ * Returns true if the event was consumed here.
91
+ */
92
+ protected _handleCommonLoad(event: NativeAdEvent): boolean {
93
+ switch (event.event) {
94
+ case NovvyAdEvent.Loaded:
95
+ this._isReady = true;
96
+ this.onAdLoaded?.();
97
+ return true;
98
+ case NovvyAdEvent.FailedToLoad:
99
+ this._isReady = false;
100
+ this.onAdFailedToLoad?.(event.error ?? 'Unknown error');
101
+ this.destroy();
102
+ return true;
103
+ default:
104
+ return false;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Monotonically increasing per-process counter guaranteeing `adObjectId`
110
+ * uniqueness even when many ads are constructed within the same millisecond
111
+ * (e.g. batch-preloading feed items in a synchronous loop). `Date.now()` has
112
+ * only millisecond resolution, so timestamp + random alone left a real
113
+ * (~1/10000 per pair) collision window; a colliding id would cross-wire two
114
+ * ads' event streams. The counter closes that window; the timestamp/random
115
+ * still disambiguate ids across separate app launches.
116
+ */
117
+ private static _counter = 0;
118
+
119
+ private static _generateUniqueId(): string {
120
+ const timestamp = Date.now();
121
+ const random = Math.floor(Math.random() * 10000);
122
+ const seq = NovvyAd._counter++;
123
+ return `ad_${timestamp}_${random}_${seq}`;
124
+ }
125
+ }
@@ -0,0 +1,13 @@
1
+ import { NovvyFullScreenAd } from './NovvyFullScreenAd';
2
+
3
+ /** An end-roll (full-screen) ad, shown after an episode finishes. */
4
+ export class NovvyEndAd extends NovvyFullScreenAd {
5
+ protected _loadNative(): void {
6
+ this.native.loadEndAd(this.adUnitId, this.adObjectId);
7
+ }
8
+
9
+ show(): void {
10
+ this._assertReady();
11
+ this.native.showEndAd(this.adObjectId);
12
+ }
13
+ }
@@ -0,0 +1,46 @@
1
+ import { Native } from '../eventBus';
2
+ import type { NativeAdEvent } from '../spec/NativeNovvyAds';
3
+ import { NovvyAdEvent } from '../types';
4
+ import { NovvyAd } from './NovvyAd';
5
+
6
+ /**
7
+ * Base for full-screen ads that follow load → show → dismissed.
8
+ * Mirrors the Flutter plugin's `_NovvyFullScreenAd`.
9
+ */
10
+ export abstract class NovvyFullScreenAd extends NovvyAd {
11
+ /** Called when the ad is displayed. */
12
+ onAdShowed?: () => void;
13
+ /** Called when the ad is dismissed (closed) by the user. */
14
+ onAdDismissed?: () => void;
15
+
16
+ /** Shows the ad. Must be called after it has loaded. */
17
+ abstract show(): void;
18
+
19
+ protected _handleInternalEvent(event: NativeAdEvent): void {
20
+ if (this._handleCommonLoad(event)) {
21
+ return;
22
+ }
23
+ switch (event.event) {
24
+ case NovvyAdEvent.Showed:
25
+ this.onAdShowed?.();
26
+ break;
27
+ case NovvyAdEvent.Dismissed:
28
+ this.onAdDismissed?.();
29
+ this.destroy();
30
+ break;
31
+ default:
32
+ break;
33
+ }
34
+ }
35
+
36
+ protected _assertReady(): void {
37
+ if (!this._isReady) {
38
+ throw new Error('Ad must be loaded before showing.');
39
+ }
40
+ }
41
+
42
+ /** Uses the module accessor in subclasses. */
43
+ protected get native() {
44
+ return Native;
45
+ }
46
+ }
@@ -0,0 +1,13 @@
1
+ import { NovvyFullScreenAd } from './NovvyFullScreenAd';
2
+
3
+ /** An interstitial (full-screen) ad. */
4
+ export class NovvyInterstitialAd extends NovvyFullScreenAd {
5
+ protected _loadNative(): void {
6
+ this.native.loadInterstitialAd(this.adUnitId, this.adObjectId);
7
+ }
8
+
9
+ show(): void {
10
+ this._assertReady();
11
+ this.native.showInterstitialAd(this.adObjectId);
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ import { NovvyFullScreenAd } from './NovvyFullScreenAd';
2
+
3
+ /** A mid-roll (full-screen) ad, shown at an in-episode break. */
4
+ export class NovvyMidAd extends NovvyFullScreenAd {
5
+ protected _loadNative(): void {
6
+ this.native.loadMidAd(this.adUnitId, this.adObjectId);
7
+ }
8
+
9
+ show(): void {
10
+ this._assertReady();
11
+ this.native.showMidAd(this.adObjectId);
12
+ }
13
+ }
@@ -0,0 +1,125 @@
1
+ import { Native } from '../eventBus';
2
+ import type { NativeAdEvent } from '../spec/NativeNovvyAds';
3
+ import { NovvyAdEvent, type NovvyPlayerAdapter } from '../types';
4
+ import { NovvyAd } from './NovvyAd';
5
+
6
+ /** Interval at which player state is pushed to native (mirrors Flutter's 500ms Timer). */
7
+ const PLAYER_PUSH_INTERVAL_MS = 500;
8
+
9
+ /**
10
+ * A realtime pause card (fixed 260×90) shown while the player is paused.
11
+ *
12
+ * Unlike other ads there is **no** {@link load} step: embedding
13
+ * `<NovvyPauseAdView>` issues the request immediately via {@link attach}.
14
+ * Show it the moment playback is paused and {@link destroy} on resume
15
+ * ({@link onAdHidden}). Mirrors the Flutter plugin's `NovvyPauseAd`.
16
+ */
17
+ export class NovvyPauseAd extends NovvyAd {
18
+ /** The Fabric component name used by `<NovvyPauseAdView>`. */
19
+ static readonly viewType = 'NovvyPauseAdView';
20
+ /** Pause card width in logical pixels (matches native SDK). */
21
+ static readonly cardWidth = 260;
22
+ /** Pause card height in logical pixels (matches native SDK). */
23
+ static readonly cardHeight = 90;
24
+
25
+ /** Called when the pause card is shown. */
26
+ onAdShowed?: () => void;
27
+ /** Called when the card is hidden (playback resumed). */
28
+ onAdHidden?: () => void;
29
+ /**
30
+ * Called when the native card's shown state changes, so a view can toggle
31
+ * touch passthrough / opacity (mirrors Flutter's `shownNotifier`).
32
+ */
33
+ onShownChanged?: (shown: boolean) => void;
34
+
35
+ private _player: NovvyPlayerAdapter | null = null;
36
+ private _timer: ReturnType<typeof setInterval> | null = null;
37
+
38
+ protected _loadNative(): never {
39
+ throw new Error('NovvyPauseAd has no load step; use attach().');
40
+ }
41
+
42
+ /**
43
+ * Attach the pause ad and issue the request in realtime.
44
+ *
45
+ * The player's current state is read once and sent with the request; a
46
+ * 500ms timer then keeps pushing state so the SDK can detect resumption.
47
+ */
48
+ attach(player: NovvyPlayerAdapter): void {
49
+ if (this._disposed) {
50
+ return;
51
+ }
52
+ this._player = player;
53
+ this._subscribe();
54
+ Native.attachPauseAd(
55
+ this.adUnitId,
56
+ this.adObjectId,
57
+ player.currentPositionMs,
58
+ player.isPlaying
59
+ );
60
+ this._startPlayerTimer();
61
+ }
62
+
63
+ /** Detach the pause ad and stop pushing player state. */
64
+ detach(): void {
65
+ this._stopPlayerTimer();
66
+ this._player = null;
67
+ Native.detachPauseAd(this.adObjectId);
68
+ }
69
+
70
+ destroy(): void {
71
+ if (this._disposed) {
72
+ return;
73
+ }
74
+ this._stopPlayerTimer();
75
+ this._player = null;
76
+ super.destroy();
77
+ }
78
+
79
+ private _startPlayerTimer(): void {
80
+ this._stopPlayerTimer();
81
+ this._timer = setInterval(() => {
82
+ const player = this._player;
83
+ if (!player) {
84
+ return;
85
+ }
86
+ Native.updatePauseAdPlayerState(
87
+ this.adObjectId,
88
+ player.currentPositionMs,
89
+ player.isPlaying
90
+ );
91
+ }, PLAYER_PUSH_INTERVAL_MS);
92
+ }
93
+
94
+ private _stopPlayerTimer(): void {
95
+ if (this._timer) {
96
+ clearInterval(this._timer);
97
+ this._timer = null;
98
+ }
99
+ }
100
+
101
+ protected _handleInternalEvent(event: NativeAdEvent): void {
102
+ switch (event.event) {
103
+ case NovvyAdEvent.FailedToLoad:
104
+ this.onAdFailedToLoad?.(event.error ?? 'Unknown error');
105
+ // Stops the player timer + unsubscribes before releasing native —
106
+ // avoids a leaked 500ms push loop on failure.
107
+ this.destroy();
108
+ break;
109
+ case NovvyAdEvent.Showed:
110
+ if (!this._disposed) {
111
+ this.onShownChanged?.(true);
112
+ }
113
+ this.onAdShowed?.();
114
+ break;
115
+ case NovvyAdEvent.Hidden:
116
+ if (!this._disposed) {
117
+ this.onShownChanged?.(false);
118
+ }
119
+ this.onAdHidden?.();
120
+ break;
121
+ default:
122
+ break;
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,49 @@
1
+ import { Native } from '../eventBus';
2
+ import type { NativeAdEvent } from '../spec/NativeNovvyAds';
3
+ import { NovvyAdEvent } from '../types';
4
+ import { NovvyAd } from './NovvyAd';
5
+
6
+ /** A rewarded video ad. */
7
+ export class NovvyRewardedAd extends NovvyAd {
8
+ /** Called when the ad is displayed. */
9
+ onAdShowed?: () => void;
10
+ /** Called when the ad is dismissed by the user. */
11
+ onAdDismissed?: () => void;
12
+ /**
13
+ * Called once when the user earns the reward. No reward payload — the native
14
+ * SDKs deliver a parameterless signal (grant the host's own reward).
15
+ */
16
+ onUserEarnedReward?: () => void;
17
+
18
+ protected _loadNative(): void {
19
+ Native.loadRewardedAd(this.adUnitId, this.adObjectId);
20
+ }
21
+
22
+ /** Shows the rewarded video ad. Must be called after it has loaded. */
23
+ show(): void {
24
+ if (!this._isReady) {
25
+ throw new Error('Ad must be loaded before showing.');
26
+ }
27
+ Native.showRewardedAd(this.adObjectId);
28
+ }
29
+
30
+ protected _handleInternalEvent(event: NativeAdEvent): void {
31
+ if (this._handleCommonLoad(event)) {
32
+ return;
33
+ }
34
+ switch (event.event) {
35
+ case NovvyAdEvent.Showed:
36
+ this.onAdShowed?.();
37
+ break;
38
+ case NovvyAdEvent.EarnedReward:
39
+ this.onUserEarnedReward?.();
40
+ break;
41
+ case NovvyAdEvent.Dismissed:
42
+ this.onAdDismissed?.();
43
+ this.destroy();
44
+ break;
45
+ default:
46
+ break;
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,138 @@
1
+ import { Native } from '../eventBus';
2
+ import type { NativeAdEvent } from '../spec/NativeNovvyAds';
3
+ import { NovvyAdEvent } from '../types';
4
+ import { NovvyAd } from './NovvyAd';
5
+
6
+ /**
7
+ * A scroll ad that renders as a native view inserted into a scrollable list.
8
+ *
9
+ * Lifecycle: {@link load} → embed `<NovvyScrollAdView>` → {@link setPlaying} as
10
+ * it enters / leaves the viewport → {@link destroy}. Attach/detach are driven
11
+ * by the `<NovvyScrollAdView>` component's mount lifecycle.
12
+ */
13
+ export class NovvyScrollAd extends NovvyAd {
14
+ /** The Fabric component name used by `<NovvyScrollAdView>`. */
15
+ static readonly viewType = 'NovvyScrollAdView';
16
+
17
+ /**
18
+ * Optional override for the episode number used in the bid request. When
19
+ * non-null, takes precedence over the value set via
20
+ * {@link NovvyAds.setContentContext}.
21
+ */
22
+ readonly episodeNumber?: number | null;
23
+
24
+ /** Called when the ad becomes visible in the viewport. */
25
+ onAdShowed?: () => void;
26
+ /** Called when the ad is hidden / removed from the viewport. */
27
+ onAdHidden?: () => void;
28
+ /**
29
+ * Called every second during playback with the remaining seconds
30
+ * (0 = user can swipe past the ad).
31
+ */
32
+ onAdPlaybackTick?: (remainingSeconds: number) => void;
33
+
34
+ constructor(adUnitId: string, episodeNumber?: number | null) {
35
+ super(adUnitId);
36
+ this.episodeNumber = episodeNumber;
37
+ }
38
+
39
+ // ── Attach coordination ────────────────────────────────────────────────
40
+ //
41
+ // The native SCROLL contract is strictly two-stage: `load()` must resolve the
42
+ // creative BEFORE `attach()` — the native engine drops an early attach with a
43
+ // "not ready" log and never retries (see NovvyPlacementAd.attach). But the two
44
+ // triggers here are independent: `load()` is host-driven and network-bound,
45
+ // while the native container becomes available on the view's `onViewReady`
46
+ // (local, fast). View-ready therefore almost always beats `loaded`, so a naive
47
+ // "attach on view-ready" fires too early, silently no-ops, and the slot stays
48
+ // on its black placeholder for every creative type.
49
+ //
50
+ // We instead attach only once BOTH signals have arrived (whichever is last),
51
+ // and buffer the host's play intent so it survives being set before attach and
52
+ // is re-applied across detach/re-attach (scroll off- and back on-screen).
53
+ private _viewReady = false;
54
+ private _attached = false;
55
+ /** Last play intent from the host; (re)applied on each attach. */
56
+ private _playing = false;
57
+ private _durationSeconds: number | null = null;
58
+ private _useDefaultSwipeOverlay = true;
59
+
60
+ protected _loadNative(): void {
61
+ Native.loadScrollAd(this.adUnitId, this.adObjectId, this.episodeNumber ?? null);
62
+ }
63
+
64
+ /**
65
+ * Signals that the native container is ready (fired automatically by
66
+ * `<NovvyScrollAdView>` on `onViewReady`). Records the attach params and
67
+ * mounts the ad once it has also finished loading.
68
+ */
69
+ attach(durationSeconds?: number | null, useDefaultSwipeOverlay = true): void {
70
+ this._durationSeconds = durationSeconds ?? null;
71
+ this._useDefaultSwipeOverlay = useDefaultSwipeOverlay;
72
+ this._viewReady = true;
73
+ this._maybeAttach();
74
+ }
75
+
76
+ /** Detach the scroll ad from its container. */
77
+ detach(): void {
78
+ this._viewReady = false;
79
+ this._attached = false;
80
+ Native.detachScrollAd(this.adObjectId);
81
+ }
82
+
83
+ /**
84
+ * Set the playing state. Call `setPlaying(true)` when the item scrolls into
85
+ * the viewport and `setPlaying(false)` when it scrolls out. Safe to call
86
+ * before the ad has attached — the intent is buffered and applied on attach.
87
+ */
88
+ setPlaying(playing: boolean): void {
89
+ this._playing = playing;
90
+ if (this._attached) {
91
+ Native.setScrollAdPlaying(this.adObjectId, playing);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Attach once the ad is loaded AND the native view is ready — the native
97
+ * engine requires load-before-attach and does not retry, so we gate on both.
98
+ */
99
+ private _maybeAttach(): void {
100
+ if (this._attached || this._disposed || !this._isReady || !this._viewReady) {
101
+ return;
102
+ }
103
+ Native.attachScrollAd(
104
+ this.adObjectId,
105
+ this._durationSeconds,
106
+ this._useDefaultSwipeOverlay
107
+ );
108
+ this._attached = true;
109
+ // Re-apply the buffered play intent now that a native host exists.
110
+ if (this._playing) {
111
+ Native.setScrollAdPlaying(this.adObjectId, true);
112
+ }
113
+ }
114
+
115
+ protected _handleInternalEvent(event: NativeAdEvent): void {
116
+ if (this._handleCommonLoad(event)) {
117
+ // `loaded` may arrive after the view is already ready; attach now that
118
+ // the native engine has the creative (after any onAdLoaded → setPlaying).
119
+ if (event.event === NovvyAdEvent.Loaded) {
120
+ this._maybeAttach();
121
+ }
122
+ return;
123
+ }
124
+ switch (event.event) {
125
+ case NovvyAdEvent.Showed:
126
+ this.onAdShowed?.();
127
+ break;
128
+ case NovvyAdEvent.Hidden:
129
+ this.onAdHidden?.();
130
+ break;
131
+ case NovvyAdEvent.PlaybackTick:
132
+ this.onAdPlaybackTick?.(event.remainingSeconds ?? 0);
133
+ break;
134
+ default:
135
+ break;
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,59 @@
1
+ import NovvyAdsNative from './spec/NativeNovvyAds';
2
+ import type { NativeAdEvent } from './spec/NativeNovvyAds';
3
+
4
+ /**
5
+ * Central hub over the single native→JS `onAdEvent` stream.
6
+ *
7
+ * Mirrors the Flutter plugin's `_NovvyAdsImpl` broadcast stream: one native
8
+ * event source is fanned out to per-ad handlers filtered by `adObjectId`.
9
+ */
10
+
11
+ export type AdEventHandler = (event: NativeAdEvent) => void;
12
+
13
+ const handlers = new Map<string, Set<AdEventHandler>>();
14
+ let subscription: { remove(): void } | null = null;
15
+
16
+ function ensureSubscribed(): void {
17
+ if (subscription) {
18
+ return;
19
+ }
20
+ subscription = NovvyAdsNative.onAdEvent((event: NativeAdEvent) => {
21
+ const set = handlers.get(event.adObjectId);
22
+ if (!set) {
23
+ return;
24
+ }
25
+ // Copy so a handler may unsubscribe/destroy during dispatch.
26
+ for (const handler of Array.from(set)) {
27
+ handler(event);
28
+ }
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Subscribe to events for a single ad instance. Returns an unsubscribe fn.
34
+ */
35
+ export function subscribe(
36
+ adObjectId: string,
37
+ handler: AdEventHandler
38
+ ): () => void {
39
+ ensureSubscribed();
40
+ let set = handlers.get(adObjectId);
41
+ if (!set) {
42
+ set = new Set();
43
+ handlers.set(adObjectId, set);
44
+ }
45
+ set.add(handler);
46
+ return () => {
47
+ const current = handlers.get(adObjectId);
48
+ if (!current) {
49
+ return;
50
+ }
51
+ current.delete(handler);
52
+ if (current.size === 0) {
53
+ handlers.delete(adObjectId);
54
+ }
55
+ };
56
+ }
57
+
58
+ /** The underlying TurboModule instance. */
59
+ export const Native = NovvyAdsNative;