react-native-buzzvil-ad 0.1.0 → 0.2.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Conventional Commits](https://www.conventionalcommits.org/) and
5
+ [Keep a Changelog](https://keepachangelog.com/); this project adheres to
6
+ [Semantic Versioning](https://semver.org/).
7
+
8
+ > **Unofficial package.** `react-native-buzzvil-ad` is community-maintained and
9
+ > is **not** affiliated with, endorsed by, or supported by Buzzvil.
10
+
11
+ ## 0.2.0 (2026-06-13)
12
+
13
+ ### Features
14
+
15
+ - **interstitial:** add Buzzvil BuzzBenefit v6 interstitial ads (full-screen
16
+ `dialog` / `bottomSheet`) on Android & iOS — `loadInterstitial(unitId, type?)`
17
+ (resolves on load, rejects on failure), `showInterstitial(unitId)`, and the
18
+ `onInterstitialClosed` event via `addInterstitialClosedListener(unitId, cb)`.
19
+ Built on the existing `Buzzvil` TurboModule using a New-Architecture typed
20
+ `EventEmitter`; native instances are tracked per `unitId`. (#5)
21
+ - **example:** add interstitial smoke-test controls (type toggle, load/show,
22
+ close-event log) to the example app. (#5)
23
+
24
+ ### Bug Fixes
25
+
26
+ - **interstitial:** reject a new `loadInterstitial` while an instance for the
27
+ same `unitId` is still loaded or showing, so the previous ad's close can no
28
+ longer remove a newer instance (and, on iOS, can no longer deallocate a
29
+ currently-visible ad). (#5)
30
+
31
+ ### Build / Dependencies
32
+
33
+ - **deps:** resolve OSV-Scanner advisories (all dev-only, not shipped) — bump
34
+ `joi` to 17.13.4 (#2), pin `esbuild` `^0.28.1` via `resolutions`, and document
35
+ the `fast-xml-parser` suppression in `osv-scanner.toml`. (#4)
36
+
37
+ ## 0.1.0 (2026-06-13)
38
+
39
+ Initial public release as the unofficial `react-native-buzzvil-ad` package
40
+ (renamed from the scoped `@dongminyu/react-native-buzzvil`).
41
+
42
+ ### Features
43
+
44
+ - **session:** BuzzBenefit v6 session API via the `Buzzvil` TurboModule —
45
+ `initialize`, `login`, `logout`, `isLoggedIn`, and `showBenefitHub` (the
46
+ BenefitHub offerwall), with a primitives-only spec + sentinel contract.
47
+ - **native-ad:** `BuzzvilNativeAdView` Fabric component for in-feed native ads
48
+ on Android & iOS — inventory-size layout variants (banner / card), self-sizing
49
+ wrapper, and `onAdLoaded` / `onAdFailed` / `onAdClicked` / `onImpressed` /
50
+ `onRewarded` events. iOS calls the Swift SDK directly from Objective-C++
51
+ (no shim).
52
+ - **login:** dev-mode warnings for non-PII / malformed `userId` values (Buzzvil
53
+ rejects emails and other identifiable ids).
54
+
55
+ ### Documentation
56
+
57
+ - Add the SDK API-mapping spec, the ad-format expansion design, and project
58
+ guidance (`CLAUDE.md`) covering the two native surfaces.
@@ -1,6 +1,7 @@
1
1
  package com.buzzvil
2
2
 
3
3
  import android.app.Application
4
+ import com.facebook.react.bridge.Arguments
4
5
  import com.facebook.react.bridge.Promise
5
6
  import com.facebook.react.bridge.ReactApplicationContext
6
7
  import com.facebook.react.bridge.UiThreadUtil
@@ -12,6 +13,9 @@ import com.buzzvil.buzzbenefit.BuzzBenefitConfig
12
13
  import com.buzzvil.buzzbenefit.benefithub.BuzzBenefitHub
13
14
  import com.buzzvil.buzzbenefit.benefithub.BuzzBenefitHubConfig
14
15
  import com.buzzvil.buzzbenefit.benefithub.BuzzBenefitHubPage
16
+ import com.buzzvil.buzzbenefit.BuzzAdError
17
+ import com.buzzvil.buzzbenefit.interstitial.BuzzInterstitial
18
+ import com.buzzvil.buzzbenefit.interstitial.BuzzInterstitialListener
15
19
  import com.buzzvil.sdk.BuzzvilSdk
16
20
  import com.buzzvil.sdk.BuzzvilSdkLoginListener
17
21
  import com.buzzvil.sdk.BuzzvilSdkUser
@@ -84,6 +88,85 @@ class BuzzvilModule(
84
88
  }
85
89
  }
86
90
 
91
+ // --- Interstitial ---
92
+
93
+ // Design Decision 1: one BuzzInterstitial instance per unitId, so a later
94
+ // showInterstitial(unitId) presents the instance loaded by loadInterstitial.
95
+ private val interstitials = mutableMapOf<String, BuzzInterstitial>()
96
+
97
+ override fun loadInterstitial(
98
+ unitId: String,
99
+ type: String,
100
+ promise: Promise,
101
+ ) {
102
+ // Reject a new load while an instance for this unitId already exists — in
103
+ // flight, loaded-and-waiting-to-show, or currently showing (parity with iOS).
104
+ // Overwriting interstitials[unitId] would let the OLD instance's onAdClosed
105
+ // remove the NEW one by unitId, no-op'ing the next show(). The entry is
106
+ // cleared on close/failure, after which a fresh load is allowed.
107
+ if (interstitials.containsKey(unitId)) {
108
+ promise.reject(
109
+ "buzzvil_interstitial_load_failed",
110
+ "An interstitial for this unitId is already loaded or loading; wait for onInterstitialClosed before loading again.",
111
+ )
112
+ return
113
+ }
114
+ // type sentinel: "bottomSheet" → bottom sheet; "dialog"/""/unknown → dialog.
115
+ val builder = BuzzInterstitial.Builder(unitId)
116
+ val interstitial =
117
+ if (type == "bottomSheet") builder.buildBottomSheet() else builder.buildDialog()
118
+ if (interstitial == null) {
119
+ promise.reject(
120
+ "buzzvil_interstitial_load_failed",
121
+ "Failed to build a BuzzInterstitial for unitId=$unitId.",
122
+ )
123
+ return
124
+ }
125
+ // Store from load-start; the guard above keys off its presence, and it's
126
+ // removed on close/failure.
127
+ interstitials[unitId] = interstitial
128
+
129
+ // The SDK listener can fire repeatedly; settle the promise exactly once.
130
+ var settled = false
131
+ interstitial.load(
132
+ object : BuzzInterstitialListener() {
133
+ override fun onAdLoaded() {
134
+ if (settled) return
135
+ settled = true
136
+ // Keep the instance in `interstitials` for the later show().
137
+ promise.resolve(null)
138
+ }
139
+
140
+ override fun onAdLoadFailed(error: BuzzAdError?) {
141
+ if (settled) return
142
+ settled = true
143
+ interstitials.remove(unitId)
144
+ promise.reject(
145
+ "buzzvil_interstitial_load_failed",
146
+ error?.message ?: error?.type?.toString() ?: "Interstitial load failed.",
147
+ )
148
+ }
149
+
150
+ override fun onAdClosed() {
151
+ // New-Arch typed EventEmitter: codegen generates this concrete emit
152
+ // method on the spec base; payload is a flat primitive map.
153
+ emitOnInterstitialClosed(Arguments.createMap().apply { putString("unitId", unitId) })
154
+ // Lifecycle: drop the dismissed instance so the map doesn't retain it
155
+ // (and so a fresh loadInterstitial for this unitId is allowed again).
156
+ interstitials.remove(unitId)
157
+ }
158
+ },
159
+ )
160
+ }
161
+
162
+ override fun showInterstitial(unitId: String) {
163
+ // show() presents UI — must run on the main thread (parity with BenefitHub).
164
+ UiThreadUtil.runOnUiThread {
165
+ val activity = currentActivity ?: return@runOnUiThread
166
+ interstitials[unitId]?.show(activity)
167
+ }
168
+ }
169
+
87
170
  companion object {
88
171
  const val NAME = NativeBuzzvilSpec.NAME
89
172
  }
package/ios/Buzzvil.h CHANGED
@@ -1,5 +1,8 @@
1
1
  #import <BuzzvilSpec/BuzzvilSpec.h>
2
2
 
3
- @interface Buzzvil : NSObject <NativeBuzzvilSpec>
3
+ // Subclass the codegen base (not bare NSObject): NativeBuzzvilSpecBase provides
4
+ // setEventEmitterCallback: and the generated emitOnInterstitialClosed:, which
5
+ // the interstitial "closed" event is dispatched through.
6
+ @interface Buzzvil : NativeBuzzvilSpecBase <NativeBuzzvilSpec>
4
7
 
5
8
  @end
package/ios/Buzzvil.mm CHANGED
@@ -8,7 +8,52 @@
8
8
  #import <BuzzvilSDK/BuzzvilSDK-Swift.h>
9
9
  #import <BuzzAdBenefitSDK/BuzzAdBenefitSDK-Swift.h>
10
10
 
11
- @implementation Buzzvil
11
+ @class BuzzInterstitialLoader;
12
+
13
+ /**
14
+ * Per-load helper that conforms to `BuzzInterstitialDelegate` and captures the
15
+ * promise blocks + `unitId` for one `loadInterstitial` call.
16
+ *
17
+ * Why a helper at all: `BuzzInterstitial.delegate` is a **weak** property and
18
+ * its delegate callbacks pass back the `BuzzInterstitial` (not the unitId). A
19
+ * dedicated loader (a) owns the `BuzzInterstitial` strongly so a later
20
+ * `showInterstitial` can present it, (b) carries the `unitId` directly so no
21
+ * instance→unitId reverse lookup is needed, and (c) is retained by the module's
22
+ * `_loaders` map so ARC doesn't dealloc it the moment `loadInterstitial`
23
+ * returns (which would silently drop every callback and hang the promise).
24
+ *
25
+ * The back-ref to the module is **weak** to avoid a retain cycle (the module is
26
+ * a long-lived TurboModule singleton). The module owns loader removal so a
27
+ * loader's final act is `[module removeLoaderForUnitId:]` — after which `self`
28
+ * may be deallocated, so callbacks touch nothing on `self` afterwards.
29
+ */
30
+ @interface BuzzInterstitialLoader : NSObject <BuzzInterstitialDelegate>
31
+ @property (nonatomic, strong) BuzzInterstitial *interstitial;
32
+ @property (nonatomic, copy) NSString *unitId;
33
+ @property (nonatomic, copy) RCTPromiseResolveBlock resolve;
34
+ @property (nonatomic, copy) RCTPromiseRejectBlock reject;
35
+ @property (nonatomic, assign) BOOL settled; // settle the promise exactly once
36
+ @property (nonatomic, weak) Buzzvil *module;
37
+ @end
38
+
39
+ @implementation Buzzvil {
40
+ // unitId → loader. Retains each loader (and its BuzzInterstitial) from load
41
+ // until did-fail / did-dismiss removes it (parity with Android's map cleanup).
42
+ NSMutableDictionary<NSString *, BuzzInterstitialLoader *> *_loaders;
43
+ }
44
+
45
+ - (NSMutableDictionary<NSString *, BuzzInterstitialLoader *> *)loaders
46
+ {
47
+ if (_loaders == nil) {
48
+ _loaders = [NSMutableDictionary new];
49
+ }
50
+ return _loaders;
51
+ }
52
+
53
+ - (void)removeLoaderForUnitId:(NSString *)unitId
54
+ {
55
+ [self.loaders removeObjectForKey:unitId];
56
+ }
12
57
 
13
58
  - (void)initialize:(NSString *)appId
14
59
  {
@@ -86,6 +131,61 @@
86
131
  });
87
132
  }
88
133
 
134
+ #pragma mark - Interstitial
135
+
136
+ - (void)loadInterstitial:(NSString *)unitId
137
+ type:(NSString *)type
138
+ resolve:(RCTPromiseResolveBlock)resolve
139
+ reject:(RCTPromiseRejectBlock)reject
140
+ {
141
+ // type sentinel: "bottomSheet" → bottom sheet; "dialog"/""/unknown → dialog
142
+ // (parity with Android's buildBottomSheet()/buildDialog()).
143
+ BuzzInterstitialType interstitialType =
144
+ [type isEqualToString:@"bottomSheet"] ? BuzzInterstitialTypeBottomSheet : BuzzInterstitialTypeDialog;
145
+
146
+ // Reject a new load while a loader for this unitId already exists — in flight,
147
+ // loaded-and-waiting-to-show, or currently showing (parity with Android).
148
+ // Overwriting self.loaders[unitId] would (a) let the OLD loader's didDismiss
149
+ // remove the NEW loader by unitId, and (b) dealloc the existing loader — and,
150
+ // as the sole strong owner of its BuzzInterstitial, dealloc a currently-visible
151
+ // interstitial. The entry is cleared on dismiss/failure, then a fresh load is OK.
152
+ BuzzInterstitialLoader *existing = self.loaders[unitId];
153
+ if (existing != nil) {
154
+ reject(@"buzzvil_interstitial_load_failed",
155
+ @"An interstitial for this unitId is already loaded or loading; wait for onInterstitialClosed before loading again.", nil);
156
+ return;
157
+ }
158
+
159
+ BuzzInterstitialLoader *loader = [BuzzInterstitialLoader new];
160
+ loader.unitId = unitId;
161
+ loader.resolve = resolve;
162
+ loader.reject = reject;
163
+ loader.module = self;
164
+ loader.interstitial = [[BuzzInterstitial alloc] initWithUnitId:unitId type:interstitialType];
165
+ loader.interstitial.delegate = loader; // delegate is weak — loader must outlive this call
166
+
167
+ // Retain the loader (and its BuzzInterstitial) until did-fail / did-dismiss.
168
+ self.loaders[unitId] = loader;
169
+
170
+ [loader.interstitial load];
171
+ }
172
+
173
+ - (void)showInterstitial:(NSString *)unitId
174
+ {
175
+ // present presents UI — must run on the main thread (parity with BenefitHub).
176
+ dispatch_async(dispatch_get_main_queue(), ^{
177
+ UIViewController *presenter = RCTPresentedViewController();
178
+ if (presenter == nil) {
179
+ return;
180
+ }
181
+ BuzzInterstitialLoader *loader = self.loaders[unitId];
182
+ if (loader == nil) {
183
+ return; // nothing loaded for this unitId — no-op
184
+ }
185
+ [loader.interstitial presentOnViewController:presenter];
186
+ });
187
+ }
188
+
89
189
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
90
190
  (const facebook::react::ObjCTurboModule::InitParams &)params
91
191
  {
@@ -98,3 +198,44 @@
98
198
  }
99
199
 
100
200
  @end
201
+
202
+ #pragma mark - BuzzInterstitialLoader
203
+
204
+ @implementation BuzzInterstitialLoader
205
+
206
+ - (void)buzzInterstitialDidLoadAd:(BuzzInterstitial *)interstitial
207
+ {
208
+ if (self.settled) {
209
+ return;
210
+ }
211
+ self.settled = YES;
212
+ self.resolve(nil);
213
+ // Keep the loader in the map — showInterstitial needs it until dismiss.
214
+ }
215
+
216
+ - (void)buzzInterstitialDidFailToLoadAd:(BuzzInterstitial *)interstitial withError:(NSError *)error
217
+ {
218
+ if (self.settled) {
219
+ return;
220
+ }
221
+ self.settled = YES;
222
+ // Capture before removal: removing from the map may dealloc self.
223
+ NSString *unitId = self.unitId;
224
+ RCTPromiseRejectBlock reject = self.reject;
225
+ Buzzvil *module = self.module;
226
+ reject(@"buzzvil_interstitial_load_failed", error.localizedDescription ?: @"Interstitial load failed.", error);
227
+ [module removeLoaderForUnitId:unitId]; // final act — touch nothing on self after this
228
+ }
229
+
230
+ - (void)buzzInterstitialDidDismiss:(UIViewController *)viewController
231
+ {
232
+ // Capture before removal: removing from the map may dealloc self.
233
+ NSString *unitId = self.unitId;
234
+ Buzzvil *module = self.module;
235
+ // New-Arch typed EventEmitter: codegen generates emitOnInterstitialClosed on
236
+ // the spec base; payload is a flat NSDictionary { unitId } (parity with Android).
237
+ [module emitOnInterstitialClosed:@{@"unitId" : unitId}];
238
+ [module removeLoaderForUnitId:unitId]; // final act — touch nothing on self after this
239
+ }
240
+
241
+ @end
@@ -2,6 +2,12 @@
2
2
 
3
3
  import { TurboModuleRegistry } from 'react-native';
4
4
 
5
+ /**
6
+ * Payload for the interstitial "closed" event. Flat primitive (codegen events
7
+ * cannot carry nested objects); `unitId` identifies which placement closed so
8
+ * the JS wrapper can route the callback to the right listener.
9
+ */
10
+
5
11
  /**
6
12
  * TurboModule spec for the native `Buzzvil` module — a thin bridge over
7
13
  * Buzzvil's BuzzBenefit v6 SDKs (Android / iOS).
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeBuzzvil.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;;AAEpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAiDA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,SAAS,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeBuzzvil.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;;AAGpE;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA0FA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,SAAS,CAAC","ignoreList":[]}
@@ -2,4 +2,5 @@
2
2
 
3
3
  export { initialize, login, logout, isLoggedIn, showBenefitHub } from './buzzvil';
4
4
  export { BuzzvilNativeAdView } from './BuzzvilNativeAdView';
5
+ export { loadInterstitial, showInterstitial, addInterstitialClosedListener } from './interstitial';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["initialize","login","logout","isLoggedIn","showBenefitHub","BuzzvilNativeAdView"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SACEA,UAAU,EACVC,KAAK,EACLC,MAAM,EACNC,UAAU,EACVC,cAAc,QACT,WAAW;AAElB,SAASC,mBAAmB,QAAQ,uBAAuB","ignoreList":[]}
1
+ {"version":3,"names":["initialize","login","logout","isLoggedIn","showBenefitHub","BuzzvilNativeAdView","loadInterstitial","showInterstitial","addInterstitialClosedListener"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SACEA,UAAU,EACVC,KAAK,EACLC,MAAM,EACNC,UAAU,EACVC,cAAc,QACT,WAAW;AAElB,SAASC,mBAAmB,QAAQ,uBAAuB;AAE3D,SACEC,gBAAgB,EAChBC,gBAAgB,EAChBC,6BAA6B,QACxB,gBAAgB","ignoreList":[]}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Web fallback. Buzzvil's interstitial is mobile-only, so the imperative
5
+ * methods fail **at call time** (never at import time). `.native.tsx` is used
6
+ * on iOS/Android.
7
+ */
8
+
9
+ const UNSUPPORTED = 'react-native-buzzvil is not supported on web.';
10
+ export function loadInterstitial(_unitId, _type = 'dialog') {
11
+ return Promise.reject(new Error(UNSUPPORTED));
12
+ }
13
+ export function showInterstitial(_unitId) {
14
+ throw new Error(UNSUPPORTED);
15
+ }
16
+
17
+ /** No-op on web: the close event never fires, so `remove()` does nothing. */
18
+ export function addInterstitialClosedListener(_unitId, _cb) {
19
+ return {
20
+ remove() {}
21
+ };
22
+ }
23
+ //# sourceMappingURL=interstitial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["UNSUPPORTED","loadInterstitial","_unitId","_type","Promise","reject","Error","showInterstitial","addInterstitialClosedListener","_cb","remove"],"sourceRoot":"../../src","sources":["interstitial.tsx"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;;AAEA,MAAMA,WAAW,GAAG,+CAA+C;AAEnE,OAAO,SAASC,gBAAgBA,CAC9BC,OAAe,EACfC,KAAuB,GAAG,QAAQ,EACnB;EACf,OAAOC,OAAO,CAACC,MAAM,CAAC,IAAIC,KAAK,CAACN,WAAW,CAAC,CAAC;AAC/C;AAEA,OAAO,SAASO,gBAAgBA,CAACL,OAAe,EAAQ;EACtD,MAAM,IAAII,KAAK,CAACN,WAAW,CAAC;AAC9B;;AAEA;AACA,OAAO,SAASQ,6BAA6BA,CAC3CN,OAAe,EACfO,GAAe,EACK;EACpB,OAAO;IACLC,MAAMA,CAAA,EAAG,CAAC;EACZ,CAAC;AACH","ignoreList":[]}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ import Buzzvil from "./NativeBuzzvil.js";
4
+ /**
5
+ * Native (iOS/Android) implementation of the public Interstitial API. Wraps the
6
+ * primitive-only native spec (`./NativeBuzzvil`) in friendly functions; raw
7
+ * `NativeBuzzvil` calls stay out of the public surface.
8
+ *
9
+ * The native side holds one interstitial instance per `unitId` (design
10
+ * Decision 1), so `loadInterstitial` then `showInterstitial` operate on the
11
+ * same placement.
12
+ */
13
+
14
+ /**
15
+ * Load an interstitial for `unitId`. Resolves once loaded, rejects on
16
+ * load failure.
17
+ *
18
+ * @param type `'dialog'` (default) or `'bottomSheet'`.
19
+ */
20
+ export function loadInterstitial(unitId, type = 'dialog') {
21
+ return Buzzvil.loadInterstitial(unitId, type);
22
+ }
23
+
24
+ /** Present the interstitial previously loaded for `unitId`. */
25
+ export function showInterstitial(unitId) {
26
+ Buzzvil.showInterstitial(unitId);
27
+ }
28
+
29
+ /**
30
+ * Subscribe to the "interstitial closed" event for a specific `unitId`. The
31
+ * native emitter is module-wide (one event for all placements), so the
32
+ * callback is gated on `event.unitId === unitId` — listeners for other units
33
+ * never fire. Returns a handle whose `remove()` unsubscribes.
34
+ */
35
+ export function addInterstitialClosedListener(unitId, cb) {
36
+ const subscription = Buzzvil.onInterstitialClosed(event => {
37
+ if (event.unitId === unitId) {
38
+ cb();
39
+ }
40
+ });
41
+ return {
42
+ remove() {
43
+ subscription.remove();
44
+ }
45
+ };
46
+ }
47
+ //# sourceMappingURL=interstitial.native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["Buzzvil","loadInterstitial","unitId","type","showInterstitial","addInterstitialClosedListener","cb","subscription","onInterstitialClosed","event","remove"],"sourceRoot":"../../src","sources":["interstitial.native.tsx"],"mappings":";;AAAA,OAAOA,OAAO,MAAM,oBAAiB;AAGrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAC9BC,MAAc,EACdC,IAAsB,GAAG,QAAQ,EAClB;EACf,OAAOH,OAAO,CAACC,gBAAgB,CAACC,MAAM,EAAEC,IAAI,CAAC;AAC/C;;AAEA;AACA,OAAO,SAASC,gBAAgBA,CAACF,MAAc,EAAQ;EACrDF,OAAO,CAACI,gBAAgB,CAACF,MAAM,CAAC;AAClC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,6BAA6BA,CAC3CH,MAAc,EACdI,EAAc,EACM;EACpB,MAAMC,YAAY,GAAGP,OAAO,CAACQ,oBAAoB,CAAEC,KAAK,IAAK;IAC3D,IAAIA,KAAK,CAACP,MAAM,KAAKA,MAAM,EAAE;MAC3BI,EAAE,CAAC,CAAC;IACN;EACF,CAAC,CAAC;EACF,OAAO;IACLI,MAAMA,CAAA,EAAG;MACPH,YAAY,CAACG,MAAM,CAAC,CAAC;IACvB;EACF,CAAC;AACH","ignoreList":[]}
@@ -1,4 +1,13 @@
1
1
  import { type TurboModule } from 'react-native';
2
+ import type { CodegenTypes } from 'react-native';
3
+ /**
4
+ * Payload for the interstitial "closed" event. Flat primitive (codegen events
5
+ * cannot carry nested objects); `unitId` identifies which placement closed so
6
+ * the JS wrapper can route the callback to the right listener.
7
+ */
8
+ type InterstitialClosedEvent = Readonly<{
9
+ unitId: string;
10
+ }>;
2
11
  /**
3
12
  * TurboModule spec for the native `Buzzvil` module — a thin bridge over
4
13
  * Buzzvil's BuzzBenefit v6 SDKs (Android / iOS).
@@ -66,6 +75,44 @@ export interface Spec extends TurboModule {
66
75
  * See the sentinel contract above for `routePath` / `showHistory`.
67
76
  */
68
77
  showBenefitHub(routePath: string, showHistory: boolean): void;
78
+ /**
79
+ * Load an interstitial ad for `unitId`. Resolves when the ad has loaded
80
+ * (Android `onAdLoaded` / iOS `BuzzInterstitialDidLoadAd`), rejects on
81
+ * load failure (`onAdLoadFailed` / `DidFail(toLoadAd:)`).
82
+ *
83
+ * The native side holds one `BuzzInterstitial` instance per `unitId` (see
84
+ * design Decision 1: a `unitId`-keyed map), so a later `showInterstitial`
85
+ * presents this loaded instance.
86
+ *
87
+ * @param unitId Interstitial ad unit id from the Buzzvil admin.
88
+ * @param type `'dialog'` or `'bottomSheet'` — selects the native build
89
+ * variant (`buildDialog()` / `buildBottomSheet()`). Carried as a plain
90
+ * string (no codegen enums); the friendly `InterstitialType` union and the
91
+ * `'dialog'` default live in the JS wrapper (`./interstitial.native.tsx`).
92
+ */
93
+ loadInterstitial(unitId: string, type: string): Promise<void>;
94
+ /**
95
+ * Present the interstitial instance previously loaded for `unitId`. No-op if
96
+ * nothing is loaded. UI work — runs on the main thread (parity with
97
+ * `showBenefitHub`).
98
+ */
99
+ showInterstitial(unitId: string): void;
100
+ /**
101
+ * Fires when an interstitial is dismissed (Android `onAdClosed` / iOS
102
+ * `BuzzInterstitialDidDismiss`). The payload's `unitId` identifies which
103
+ * placement closed; the JS wrapper filters by it so each
104
+ * `addInterstitialClosedListener(unitId, …)` only fires for its own unit.
105
+ *
106
+ * Uses a codegen typed `EventEmitter` (RN New-Architecture event member):
107
+ * codegen generates a concrete `emitOnInterstitialClosed` on the native spec
108
+ * base class, so the native impl emits via that — no manual
109
+ * `addListener`/`removeListeners` plumbing. Imported as
110
+ * `CodegenTypes.EventEmitter` (the runtime `EventEmitter` exported from
111
+ * `react-native` is a class, not this type); codegen matches by the bare
112
+ * type name `EventEmitter`, so the `CodegenTypes.`-qualified form is
113
+ * recognized identically.
114
+ */
115
+ readonly onInterstitialClosed: CodegenTypes.EventEmitter<InterstitialClosedEvent>;
69
116
  }
70
117
  declare const _default: Spec;
71
118
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"NativeBuzzvil.d.ts","sourceRoot":"","sources":["../../../src/NativeBuzzvil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExE,yDAAyD;IACzD,MAAM,IAAI,IAAI,CAAC;IAEf,wFAAwF;IACxF,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/B;;;;;;;OAOG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/D;;AAED,wBAAiE"}
1
+ {"version":3,"file":"NativeBuzzvil.d.ts","sourceRoot":"","sources":["../../../src/NativeBuzzvil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;GAIG;AACH,KAAK,uBAAuB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExE,yDAAyD;IACzD,MAAM,IAAI,IAAI,CAAC;IAEf,wFAAwF;IACxF,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/B;;;;;;;OAOG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;IAE9D;;;;;;;;;;;;;;OAcG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D;;;;OAIG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,oBAAoB,EAAE,YAAY,CAAC,YAAY,CAAC,uBAAuB,CAAC,CAAC;CACnF;;AAED,wBAAiE"}
@@ -2,4 +2,6 @@ export { initialize, login, logout, isLoggedIn, showBenefitHub, } from './buzzvi
2
2
  export type { BuzzvilUser, BuzzvilGender, BenefitHubOptions } from './types.js';
3
3
  export { BuzzvilNativeAdView } from './BuzzvilNativeAdView';
4
4
  export type { BuzzvilNativeAdLayout, BuzzvilNativeAdViewProps } from './types.js';
5
+ export { loadInterstitial, showInterstitial, addInterstitialClosedListener, } from './interstitial';
6
+ export type { InterstitialType } from './types.js';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,EACL,MAAM,EACN,UAAU,EACV,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAS,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,YAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,EACL,MAAM,EACN,UAAU,EACV,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAS,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,YAAS,CAAC;AAC/E,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,6BAA6B,GAC9B,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAS,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { InterstitialType } from './types.js';
2
+ export declare function loadInterstitial(_unitId: string, _type?: InterstitialType): Promise<void>;
3
+ export declare function showInterstitial(_unitId: string): void;
4
+ /** No-op on web: the close event never fires, so `remove()` does nothing. */
5
+ export declare function addInterstitialClosedListener(_unitId: string, _cb: () => void): {
6
+ remove(): void;
7
+ };
8
+ //# sourceMappingURL=interstitial.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interstitial.d.ts","sourceRoot":"","sources":["../../../src/interstitial.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAS,CAAC;AAUhD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,gBAA2B,GACjC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED,6EAA6E;AAC7E,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,IAAI,GACd;IAAE,MAAM,IAAI,IAAI,CAAA;CAAE,CAIpB"}
@@ -0,0 +1,29 @@
1
+ import type { InterstitialType } from './types.js';
2
+ /**
3
+ * Native (iOS/Android) implementation of the public Interstitial API. Wraps the
4
+ * primitive-only native spec (`./NativeBuzzvil`) in friendly functions; raw
5
+ * `NativeBuzzvil` calls stay out of the public surface.
6
+ *
7
+ * The native side holds one interstitial instance per `unitId` (design
8
+ * Decision 1), so `loadInterstitial` then `showInterstitial` operate on the
9
+ * same placement.
10
+ */
11
+ /**
12
+ * Load an interstitial for `unitId`. Resolves once loaded, rejects on
13
+ * load failure.
14
+ *
15
+ * @param type `'dialog'` (default) or `'bottomSheet'`.
16
+ */
17
+ export declare function loadInterstitial(unitId: string, type?: InterstitialType): Promise<void>;
18
+ /** Present the interstitial previously loaded for `unitId`. */
19
+ export declare function showInterstitial(unitId: string): void;
20
+ /**
21
+ * Subscribe to the "interstitial closed" event for a specific `unitId`. The
22
+ * native emitter is module-wide (one event for all placements), so the
23
+ * callback is gated on `event.unitId === unitId` — listeners for other units
24
+ * never fire. Returns a handle whose `remove()` unsubscribes.
25
+ */
26
+ export declare function addInterstitialClosedListener(unitId: string, cb: () => void): {
27
+ remove(): void;
28
+ };
29
+ //# sourceMappingURL=interstitial.native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interstitial.native.d.ts","sourceRoot":"","sources":["../../../src/interstitial.native.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAS,CAAC;AAEhD;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,gBAA2B,GAChC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,IAAI,GACb;IAAE,MAAM,IAAI,IAAI,CAAA;CAAE,CAWpB"}
@@ -13,6 +13,11 @@ export interface BuzzvilUser {
13
13
  /** 4-digit birth year, e.g. `1990`. */
14
14
  birthYear?: number;
15
15
  }
16
+ /**
17
+ * Interstitial presentation style. `'dialog'` → `BuzzInterstitial.buildDialog()`;
18
+ * `'bottomSheet'` → `.buildBottomSheet()`. Carried to native as a plain string.
19
+ */
20
+ export type InterstitialType = 'dialog' | 'bottomSheet';
16
21
  /** Options for presenting the BenefitHub (offerwall). */
17
22
  export interface BenefitHubOptions {
18
23
  /** BenefitHub page number from the Buzzvil admin (advanced routing). */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,kEAAkE;AAClE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE9C,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAC7B,QAAQ,GACR,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,CAAC;AAEd,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,cAAc,EAAE,SAAS,CAAC,OAAO,cAAc,EAAE,SAAS,CAAC,CAAC;IAC3E,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5D,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5D,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAChD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,kEAAkE;AAClE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE9C,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,aAAa,CAAC;AAExD,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,+DAA+D;AAC/D,MAAM,MAAM,qBAAqB,GAC7B,QAAQ,GACR,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,CAAC;AAEd,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,cAAc,EAAE,SAAS,CAAC,OAAO,cAAc,EAAE,SAAS,CAAC,CAAC;IAC3E,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5D,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5D,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAChD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-buzzvil-ad",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Unofficial, community-maintained React Native wrapper for the Buzzvil BuzzBenefit SDK (Android & iOS). Not affiliated with, endorsed by, or supported by Buzzvil.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -20,6 +20,7 @@
20
20
  "cpp",
21
21
  "*.podspec",
22
22
  "react-native.config.js",
23
+ "CHANGELOG.md",
23
24
  "!ios/build",
24
25
  "!android/build",
25
26
  "!android/gradle",
@@ -88,6 +89,7 @@
88
89
  "react-native": "*"
89
90
  },
90
91
  "resolutions": {
92
+ "esbuild": "^0.28.1",
91
93
  "jest": "30.4.1",
92
94
  "jest-cli": "30.4.1",
93
95
  "@jest/core": "30.4.1",
@@ -1,4 +1,12 @@
1
1
  import { TurboModuleRegistry, type TurboModule } from 'react-native';
2
+ import type { CodegenTypes } from 'react-native';
3
+
4
+ /**
5
+ * Payload for the interstitial "closed" event. Flat primitive (codegen events
6
+ * cannot carry nested objects); `unitId` identifies which placement closed so
7
+ * the JS wrapper can route the callback to the right listener.
8
+ */
9
+ type InterstitialClosedEvent = Readonly<{ unitId: string }>;
2
10
 
3
11
  /**
4
12
  * TurboModule spec for the native `Buzzvil` module — a thin bridge over
@@ -71,6 +79,47 @@ export interface Spec extends TurboModule {
71
79
  * See the sentinel contract above for `routePath` / `showHistory`.
72
80
  */
73
81
  showBenefitHub(routePath: string, showHistory: boolean): void;
82
+
83
+ /**
84
+ * Load an interstitial ad for `unitId`. Resolves when the ad has loaded
85
+ * (Android `onAdLoaded` / iOS `BuzzInterstitialDidLoadAd`), rejects on
86
+ * load failure (`onAdLoadFailed` / `DidFail(toLoadAd:)`).
87
+ *
88
+ * The native side holds one `BuzzInterstitial` instance per `unitId` (see
89
+ * design Decision 1: a `unitId`-keyed map), so a later `showInterstitial`
90
+ * presents this loaded instance.
91
+ *
92
+ * @param unitId Interstitial ad unit id from the Buzzvil admin.
93
+ * @param type `'dialog'` or `'bottomSheet'` — selects the native build
94
+ * variant (`buildDialog()` / `buildBottomSheet()`). Carried as a plain
95
+ * string (no codegen enums); the friendly `InterstitialType` union and the
96
+ * `'dialog'` default live in the JS wrapper (`./interstitial.native.tsx`).
97
+ */
98
+ loadInterstitial(unitId: string, type: string): Promise<void>;
99
+
100
+ /**
101
+ * Present the interstitial instance previously loaded for `unitId`. No-op if
102
+ * nothing is loaded. UI work — runs on the main thread (parity with
103
+ * `showBenefitHub`).
104
+ */
105
+ showInterstitial(unitId: string): void;
106
+
107
+ /**
108
+ * Fires when an interstitial is dismissed (Android `onAdClosed` / iOS
109
+ * `BuzzInterstitialDidDismiss`). The payload's `unitId` identifies which
110
+ * placement closed; the JS wrapper filters by it so each
111
+ * `addInterstitialClosedListener(unitId, …)` only fires for its own unit.
112
+ *
113
+ * Uses a codegen typed `EventEmitter` (RN New-Architecture event member):
114
+ * codegen generates a concrete `emitOnInterstitialClosed` on the native spec
115
+ * base class, so the native impl emits via that — no manual
116
+ * `addListener`/`removeListeners` plumbing. Imported as
117
+ * `CodegenTypes.EventEmitter` (the runtime `EventEmitter` exported from
118
+ * `react-native` is a class, not this type); codegen matches by the bare
119
+ * type name `EventEmitter`, so the `CodegenTypes.`-qualified form is
120
+ * recognized identically.
121
+ */
122
+ readonly onInterstitialClosed: CodegenTypes.EventEmitter<InterstitialClosedEvent>;
74
123
  }
75
124
 
76
125
  export default TurboModuleRegistry.getEnforcing<Spec>('Buzzvil');
package/src/index.tsx CHANGED
@@ -8,3 +8,9 @@ export {
8
8
  export type { BuzzvilUser, BuzzvilGender, BenefitHubOptions } from './types';
9
9
  export { BuzzvilNativeAdView } from './BuzzvilNativeAdView';
10
10
  export type { BuzzvilNativeAdLayout, BuzzvilNativeAdViewProps } from './types';
11
+ export {
12
+ loadInterstitial,
13
+ showInterstitial,
14
+ addInterstitialClosedListener,
15
+ } from './interstitial';
16
+ export type { InterstitialType } from './types';
@@ -0,0 +1,52 @@
1
+ import Buzzvil from './NativeBuzzvil';
2
+ import type { InterstitialType } from './types';
3
+
4
+ /**
5
+ * Native (iOS/Android) implementation of the public Interstitial API. Wraps the
6
+ * primitive-only native spec (`./NativeBuzzvil`) in friendly functions; raw
7
+ * `NativeBuzzvil` calls stay out of the public surface.
8
+ *
9
+ * The native side holds one interstitial instance per `unitId` (design
10
+ * Decision 1), so `loadInterstitial` then `showInterstitial` operate on the
11
+ * same placement.
12
+ */
13
+
14
+ /**
15
+ * Load an interstitial for `unitId`. Resolves once loaded, rejects on
16
+ * load failure.
17
+ *
18
+ * @param type `'dialog'` (default) or `'bottomSheet'`.
19
+ */
20
+ export function loadInterstitial(
21
+ unitId: string,
22
+ type: InterstitialType = 'dialog'
23
+ ): Promise<void> {
24
+ return Buzzvil.loadInterstitial(unitId, type);
25
+ }
26
+
27
+ /** Present the interstitial previously loaded for `unitId`. */
28
+ export function showInterstitial(unitId: string): void {
29
+ Buzzvil.showInterstitial(unitId);
30
+ }
31
+
32
+ /**
33
+ * Subscribe to the "interstitial closed" event for a specific `unitId`. The
34
+ * native emitter is module-wide (one event for all placements), so the
35
+ * callback is gated on `event.unitId === unitId` — listeners for other units
36
+ * never fire. Returns a handle whose `remove()` unsubscribes.
37
+ */
38
+ export function addInterstitialClosedListener(
39
+ unitId: string,
40
+ cb: () => void
41
+ ): { remove(): void } {
42
+ const subscription = Buzzvil.onInterstitialClosed((event) => {
43
+ if (event.unitId === unitId) {
44
+ cb();
45
+ }
46
+ });
47
+ return {
48
+ remove() {
49
+ subscription.remove();
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,30 @@
1
+ import type { InterstitialType } from './types';
2
+
3
+ /**
4
+ * Web fallback. Buzzvil's interstitial is mobile-only, so the imperative
5
+ * methods fail **at call time** (never at import time). `.native.tsx` is used
6
+ * on iOS/Android.
7
+ */
8
+
9
+ const UNSUPPORTED = 'react-native-buzzvil is not supported on web.';
10
+
11
+ export function loadInterstitial(
12
+ _unitId: string,
13
+ _type: InterstitialType = 'dialog'
14
+ ): Promise<void> {
15
+ return Promise.reject(new Error(UNSUPPORTED));
16
+ }
17
+
18
+ export function showInterstitial(_unitId: string): void {
19
+ throw new Error(UNSUPPORTED);
20
+ }
21
+
22
+ /** No-op on web: the close event never fires, so `remove()` does nothing. */
23
+ export function addInterstitialClosedListener(
24
+ _unitId: string,
25
+ _cb: () => void
26
+ ): { remove(): void } {
27
+ return {
28
+ remove() {},
29
+ };
30
+ }
package/src/types.ts CHANGED
@@ -16,6 +16,12 @@ export interface BuzzvilUser {
16
16
  birthYear?: number;
17
17
  }
18
18
 
19
+ /**
20
+ * Interstitial presentation style. `'dialog'` → `BuzzInterstitial.buildDialog()`;
21
+ * `'bottomSheet'` → `.buildBottomSheet()`. Carried to native as a plain string.
22
+ */
23
+ export type InterstitialType = 'dialog' | 'bottomSheet';
24
+
19
25
  /** Options for presenting the BenefitHub (offerwall). */
20
26
  export interface BenefitHubOptions {
21
27
  /** BenefitHub page number from the Buzzvil admin (advanced routing). */