/** * BigCrunchInterstitial - API for interstitial ads */ import { NativeBigCrunchAds, BigCrunchAdsEventEmitter, NativeEventNames } from './NativeBigCrunchAds'; import type { InterstitialAd, AdRequestOptions, AdEvent, AdEventListener, EventSubscription, AdError, AdRevenue, } from './types'; /** * Static API for BigCrunch interstitial ads * * @example * ```typescript * // Load an interstitial * await BigCrunchInterstitial.load({ placementId: 'interstitial-level-complete' }); * * // Show when ready * if (await BigCrunchInterstitial.isLoaded('interstitial-level-complete')) { * await BigCrunchInterstitial.show('interstitial-level-complete'); * } * ``` */ export class BigCrunchInterstitial { private static instances = new Map(); /** * Load an interstitial ad * * @param options - Ad request options including placementId */ static async load(options: AdRequestOptions): Promise { const { placementId } = options; // Create or get existing instance let instance = this.instances.get(placementId); if (!instance) { instance = new InterstitialAdInstance(placementId); this.instances.set(placementId, instance); } return instance.load(options); } /** * Show a loaded interstitial ad * * @param placementId - The placement ID of the ad to show */ static async show(placementId: string): Promise { const instance = this.instances.get(placementId); if (!instance) { throw new Error(`No interstitial loaded for placement: ${placementId}`); } return instance.show(); } /** * Check if an interstitial is loaded and ready to show * * @param placementId - The placement ID to check */ static async isLoaded(placementId: string): Promise { const instance = this.instances.get(placementId); if (!instance) { return false; } return instance.isLoaded(); } /** * Destroy an interstitial instance * * @param placementId - The placement ID of the ad to destroy */ static destroy(placementId: string): void { const instance = this.instances.get(placementId); if (instance) { instance.destroy(); this.instances.delete(placementId); } } /** * Destroy all interstitial instances */ static destroyAll(): void { this.instances.forEach(instance => instance.destroy()); this.instances.clear(); } /** * Create an interstitial ad instance * Alternative to static methods for more control * * @param placementId - The placement ID for this ad */ static createAd(placementId: string): InterstitialAd { let instance = this.instances.get(placementId); if (!instance) { instance = new InterstitialAdInstance(placementId); this.instances.set(placementId, instance); } return instance; } /** * Add event listener for a specific placement * * @param placementId - The placement ID to listen to * @param eventType - The event type to listen for * @param listener - The callback function */ static addEventListener( placementId: string, eventType: T['type'], listener: AdEventListener ): EventSubscription { const instance = this.instances.get(placementId); if (!instance) { const newInstance = new InterstitialAdInstance(placementId); this.instances.set(placementId, newInstance); return newInstance.addEventListener(eventType, listener); } return instance.addEventListener(eventType, listener); } } /** * Internal interstitial ad instance */ class InterstitialAdInstance implements InterstitialAd { private placementId: string; private subscriptions: EventSubscription[] = []; private listeners = new Map>>(); constructor(placementId: string) { this.placementId = placementId; this.setupEventListeners(); } async load(options: AdRequestOptions): Promise { return NativeBigCrunchAds.loadInterstitial(options); } async show(): Promise { return NativeBigCrunchAds.showInterstitial(this.placementId); } async isLoaded(): Promise { return NativeBigCrunchAds.isInterstitialLoaded(this.placementId); } destroy(): void { this.removeAllListeners(); NativeBigCrunchAds.destroyInterstitial(this.placementId).catch(console.error); } addEventListener( eventType: T['type'], listener: AdEventListener ): EventSubscription { // Add to local listeners if (!this.listeners.has(eventType)) { this.listeners.set(eventType, new Set()); } // Cast listener to base type for storage - runtime behavior is the same const typedListener = listener as AdEventListener; this.listeners.get(eventType)!.add(typedListener); // Return subscription return { remove: () => { const listeners = this.listeners.get(eventType); if (listeners) { listeners.delete(typedListener); } }, }; } removeAllListeners(): void { this.subscriptions.forEach(sub => sub.remove()); this.subscriptions = []; this.listeners.clear(); } private setupEventListeners(): void { // Each event type may be fed by multiple native events // (show failures arrive as a separate FailedToShow event) const eventMap: Record = { adLoaded: [NativeEventNames.INTERSTITIAL_AD_LOADED], adFailedToLoad: [ NativeEventNames.INTERSTITIAL_AD_FAILED_TO_LOAD, NativeEventNames.INTERSTITIAL_AD_FAILED_TO_SHOW, ], adImpression: [NativeEventNames.INTERSTITIAL_AD_IMPRESSION], adClicked: [NativeEventNames.INTERSTITIAL_AD_CLICKED], adOpened: [NativeEventNames.INTERSTITIAL_AD_OPENED], adClosed: [NativeEventNames.INTERSTITIAL_AD_CLOSED], adRevenue: [NativeEventNames.INTERSTITIAL_AD_REVENUE], }; Object.entries(eventMap).forEach(([eventType, nativeEvents]) => { nativeEvents.forEach((nativeEvent) => { const subscription = BigCrunchAdsEventEmitter.addListener(nativeEvent, (event: any) => { // Check if event is for this placement if (event.placementId === this.placementId) { // Transform event data if needed let transformedEvent: AdEvent; switch (eventType) { case 'adFailedToLoad': transformedEvent = { type: eventType as any, placementId: this.placementId, format: 'interstitial', timestamp: Date.now(), error: { code: event.errorCode || 'UNKNOWN', message: event.errorMessage || 'Ad failed to load', underlyingError: event.underlyingError, } as AdError, }; break; case 'adRevenue': transformedEvent = { type: eventType as any, placementId: this.placementId, format: 'interstitial', timestamp: Date.now(), revenue: { valueMicros: event.valueMicros, currencyCode: event.currencyCode, adUnitId: event.adUnitId, precision: event.precision, } as AdRevenue, }; break; default: transformedEvent = { type: eventType as any, placementId: this.placementId, format: 'interstitial', timestamp: Date.now(), ...event, }; } // Notify local listeners const listeners = this.listeners.get(eventType); if (listeners) { listeners.forEach(listener => listener(transformedEvent)); } } }); this.subscriptions.push(subscription); }); }); } } // Export as default export default BigCrunchInterstitial;