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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/android/build.gradle +84 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/ai/novvy/reactnative/NovvyAdContainerView.kt +63 -0
- package/android/src/main/java/ai/novvy/reactnative/NovvyAdsModule.kt +390 -0
- package/android/src/main/java/ai/novvy/reactnative/NovvyAdsPackage.kt +46 -0
- package/android/src/main/java/ai/novvy/reactnative/NovvyPauseAdViewManager.kt +68 -0
- package/android/src/main/java/ai/novvy/reactnative/NovvyScrollAdViewManager.kt +72 -0
- package/android/src/main/java/ai/novvy/reactnative/RnPlayerAdapter.kt +18 -0
- package/android/src/main/java/ai/novvy/reactnative/ViewReadyEvent.kt +26 -0
- package/app.plugin.js +4 -0
- package/ios/NovvyAds.mm +168 -0
- package/ios/NovvyAdsImpl.swift +409 -0
- package/ios/NovvyPauseAdView.mm +79 -0
- package/ios/NovvyScrollAdView.mm +80 -0
- package/ios/RnPlayerAdapter.swift +14 -0
- package/lib/commonjs/NovvyAds.js +87 -0
- package/lib/commonjs/NovvyAds.js.map +1 -0
- package/lib/commonjs/ads/NovvyAd.js +121 -0
- package/lib/commonjs/ads/NovvyAd.js.map +1 -0
- package/lib/commonjs/ads/NovvyEndAd.js +19 -0
- package/lib/commonjs/ads/NovvyEndAd.js.map +1 -0
- package/lib/commonjs/ads/NovvyFullScreenAd.js +49 -0
- package/lib/commonjs/ads/NovvyFullScreenAd.js.map +1 -0
- package/lib/commonjs/ads/NovvyInterstitialAd.js +19 -0
- package/lib/commonjs/ads/NovvyInterstitialAd.js.map +1 -0
- package/lib/commonjs/ads/NovvyMidAd.js +19 -0
- package/lib/commonjs/ads/NovvyMidAd.js.map +1 -0
- package/lib/commonjs/ads/NovvyPauseAd.js +116 -0
- package/lib/commonjs/ads/NovvyPauseAd.js.map +1 -0
- package/lib/commonjs/ads/NovvyRewardedAd.js +53 -0
- package/lib/commonjs/ads/NovvyRewardedAd.js.map +1 -0
- package/lib/commonjs/ads/NovvyScrollAd.js +136 -0
- package/lib/commonjs/ads/NovvyScrollAd.js.map +1 -0
- package/lib/commonjs/eventBus.js +60 -0
- package/lib/commonjs/eventBus.js.map +1 -0
- package/lib/commonjs/index.js +90 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/spec/NativeNovvyAds.js +27 -0
- package/lib/commonjs/spec/NativeNovvyAds.js.map +1 -0
- package/lib/commonjs/spec/NovvyPauseAdViewNativeComponent.js +16 -0
- package/lib/commonjs/spec/NovvyPauseAdViewNativeComponent.js.map +1 -0
- package/lib/commonjs/spec/NovvyScrollAdViewNativeComponent.js +17 -0
- package/lib/commonjs/spec/NovvyScrollAdViewNativeComponent.js.map +1 -0
- package/lib/commonjs/spec/codegen-event-emitter.d.js +2 -0
- package/lib/commonjs/spec/codegen-event-emitter.d.js.map +1 -0
- package/lib/commonjs/types.js +54 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/views/NovvyPauseAdView.js +76 -0
- package/lib/commonjs/views/NovvyPauseAdView.js.map +1 -0
- package/lib/commonjs/views/NovvyScrollAdView.js +63 -0
- package/lib/commonjs/views/NovvyScrollAdView.js.map +1 -0
- package/lib/module/NovvyAds.js +83 -0
- package/lib/module/NovvyAds.js.map +1 -0
- package/lib/module/ads/NovvyAd.js +117 -0
- package/lib/module/ads/NovvyAd.js.map +1 -0
- package/lib/module/ads/NovvyEndAd.js +15 -0
- package/lib/module/ads/NovvyEndAd.js.map +1 -0
- package/lib/module/ads/NovvyFullScreenAd.js +45 -0
- package/lib/module/ads/NovvyFullScreenAd.js.map +1 -0
- package/lib/module/ads/NovvyInterstitialAd.js +15 -0
- package/lib/module/ads/NovvyInterstitialAd.js.map +1 -0
- package/lib/module/ads/NovvyMidAd.js +15 -0
- package/lib/module/ads/NovvyMidAd.js.map +1 -0
- package/lib/module/ads/NovvyPauseAd.js +112 -0
- package/lib/module/ads/NovvyPauseAd.js.map +1 -0
- package/lib/module/ads/NovvyRewardedAd.js +49 -0
- package/lib/module/ads/NovvyRewardedAd.js.map +1 -0
- package/lib/module/ads/NovvyScrollAd.js +132 -0
- package/lib/module/ads/NovvyScrollAd.js.map +1 -0
- package/lib/module/eventBus.js +55 -0
- package/lib/module/eventBus.js.map +1 -0
- package/lib/module/index.js +22 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/spec/NativeNovvyAds.js +28 -0
- package/lib/module/spec/NativeNovvyAds.js.map +1 -0
- package/lib/module/spec/NovvyPauseAdViewNativeComponent.js +13 -0
- package/lib/module/spec/NovvyPauseAdViewNativeComponent.js.map +1 -0
- package/lib/module/spec/NovvyScrollAdViewNativeComponent.js +14 -0
- package/lib/module/spec/NovvyScrollAdViewNativeComponent.js.map +1 -0
- package/lib/module/spec/codegen-event-emitter.d.js +2 -0
- package/lib/module/spec/codegen-event-emitter.d.js.map +1 -0
- package/lib/module/types.js +56 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/views/NovvyPauseAdView.js +70 -0
- package/lib/module/views/NovvyPauseAdView.js.map +1 -0
- package/lib/module/views/NovvyScrollAdView.js +57 -0
- package/lib/module/views/NovvyScrollAdView.js.map +1 -0
- package/lib/typescript/NovvyAds.d.ts +69 -0
- package/lib/typescript/NovvyAds.d.ts.map +1 -0
- package/lib/typescript/ads/NovvyAd.d.ts +57 -0
- package/lib/typescript/ads/NovvyAd.d.ts.map +1 -0
- package/lib/typescript/ads/NovvyEndAd.d.ts +7 -0
- package/lib/typescript/ads/NovvyEndAd.d.ts.map +1 -0
- package/lib/typescript/ads/NovvyFullScreenAd.d.ts +19 -0
- package/lib/typescript/ads/NovvyFullScreenAd.d.ts.map +1 -0
- package/lib/typescript/ads/NovvyInterstitialAd.d.ts +7 -0
- package/lib/typescript/ads/NovvyInterstitialAd.d.ts.map +1 -0
- package/lib/typescript/ads/NovvyMidAd.d.ts +7 -0
- package/lib/typescript/ads/NovvyMidAd.d.ts.map +1 -0
- package/lib/typescript/ads/NovvyPauseAd.d.ts +45 -0
- package/lib/typescript/ads/NovvyPauseAd.d.ts.map +1 -0
- package/lib/typescript/ads/NovvyRewardedAd.d.ts +19 -0
- package/lib/typescript/ads/NovvyRewardedAd.d.ts.map +1 -0
- package/lib/typescript/ads/NovvyScrollAd.d.ts +57 -0
- package/lib/typescript/ads/NovvyScrollAd.d.ts.map +1 -0
- package/lib/typescript/eventBus.d.ts +15 -0
- package/lib/typescript/eventBus.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +15 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/spec/NativeNovvyAds.d.ts +73 -0
- package/lib/typescript/spec/NativeNovvyAds.d.ts.map +1 -0
- package/lib/typescript/spec/NovvyPauseAdViewNativeComponent.d.ts +18 -0
- package/lib/typescript/spec/NovvyPauseAdViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/spec/NovvyScrollAdViewNativeComponent.d.ts +19 -0
- package/lib/typescript/spec/NovvyScrollAdViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +77 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/views/NovvyPauseAdView.d.ts +21 -0
- package/lib/typescript/views/NovvyPauseAdView.d.ts.map +1 -0
- package/lib/typescript/views/NovvyScrollAdView.d.ts +22 -0
- package/lib/typescript/views/NovvyScrollAdView.d.ts.map +1 -0
- package/novvy-ads-react-native.podspec +71 -0
- package/package.json +84 -0
- package/plugin/build/index.d.ts +11 -0
- package/plugin/build/index.js +53 -0
- package/react-native.config.js +7 -0
- package/src/NovvyAds.ts +98 -0
- package/src/ads/NovvyAd.ts +125 -0
- package/src/ads/NovvyEndAd.ts +13 -0
- package/src/ads/NovvyFullScreenAd.ts +46 -0
- package/src/ads/NovvyInterstitialAd.ts +13 -0
- package/src/ads/NovvyMidAd.ts +13 -0
- package/src/ads/NovvyPauseAd.ts +125 -0
- package/src/ads/NovvyRewardedAd.ts +49 -0
- package/src/ads/NovvyScrollAd.ts +138 -0
- package/src/eventBus.ts +59 -0
- package/src/index.tsx +29 -0
- package/src/spec/NativeNovvyAds.ts +134 -0
- package/src/spec/NovvyPauseAdViewNativeComponent.ts +20 -0
- package/src/spec/NovvyScrollAdViewNativeComponent.ts +21 -0
- package/src/spec/codegen-event-emitter.d.ts +10 -0
- package/src/types.ts +82 -0
- package/src/views/NovvyPauseAdView.tsx +80 -0
- package/src/views/NovvyScrollAdView.tsx +71 -0
package/src/NovvyAds.ts
ADDED
|
@@ -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
|
+
}
|
package/src/eventBus.ts
ADDED
|
@@ -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;
|