// apps/desktop/src/main/services/ads/AdMediationEngine.ts // Production-Ready Unified Multi-Ad Mediation & Header Bidding Engine import type { AdMediationConfig, AdMediationAuctionRequest, AdMediationAuctionResult, AdCreativePayload, AdImpressionEvent, AdRewardResult, AdRevenueStats, AdNetworkId, } from '@d3ro/core/types' import type { IAdNetworkAdapter } from './BaseAdAdapter' import { EthicalAdsAdapter } from './EthicalAdsAdapter' import { CarbonAdsAdapter } from './CarbonAdsAdapter' import { PlaywireAdapter } from './PlaywireAdapter' import { UnityAdsAdapter } from './UnityAdsAdapter' import { AppLovinAdapter } from './AppLovinAdapter' import { GoogleAdManagerAdapter } from './GoogleAdManagerAdapter' import { InMobiAdapter } from './InMobiAdapter' import { PubMaticAdapter } from './PubMaticAdapter' import { MintegralAdapter } from './MintegralAdapter' import { DirectHouseSponsorAdapter } from './DirectHouseSponsorAdapter' import { getAdSettlementService } from './AdSettlementService' export class AdMediationEngine { private static instance: AdMediationEngine | null = null private adapters: Map = new Map() private config: AdMediationConfig private impressionHistory: AdImpressionEvent[] = [] private lastRewardTimestamp = 0 private constructor() { // Register all 10+ Production Ad Adapters const adapterList: IAdNetworkAdapter[] = [ new DirectHouseSponsorAdapter(), new PlaywireAdapter(), new EthicalAdsAdapter(), new CarbonAdsAdapter(), new UnityAdsAdapter(), new AppLovinAdapter(), new GoogleAdManagerAdapter(), new InMobiAdapter(), new PubMaticAdapter(), new MintegralAdapter(), ] for (const adapter of adapterList) { this.adapters.set(adapter.networkId, adapter) } this.config = { networks: adapterList.map((a, idx) => ({ id: a.networkId, name: a.networkName, enabled: true, priority: idx + 1, floorEcpm: a.defaultFloorEcpm, adapterType: 'rest_json', })), rewardTokensAmount: 50, rewardCooldownSeconds: 60, houseAdFallback: true, headerBiddingTimeoutMs: 800, defaultFloorEcpm: 2.0, } } public static getInstance(): AdMediationEngine { if (!AdMediationEngine.instance) { AdMediationEngine.instance = new AdMediationEngine() } return AdMediationEngine.instance } public getConfig(): AdMediationConfig { return { ...this.config } } public setConfig(newConfig: Partial): AdMediationConfig { this.config = { ...this.config, ...newConfig } return this.getConfig() } /** * Execute real-time Header Bidding Auction across all enabled ad networks */ public async runAuction(request: AdMediationAuctionRequest): Promise { const auctionStart = Date.now() const timeoutMs = request.auctionTimeoutMs || this.config.headerBiddingTimeoutMs const floorEcpm = request.floorEcpm || this.config.defaultFloorEcpm const enabledAdapters = Array.from(this.adapters.values()).filter((adapter) => { const netConfig = this.config.networks.find((n) => n.id === adapter.networkId) return (netConfig ? netConfig.enabled : true) && adapter.supportedFormats.includes(request.format) }) // Query all participating demand sources in parallel with timeout const bidPromises = enabledAdapters.map(async (adapter) => { const adapterStart = Date.now() try { const bidResult = await Promise.race([ adapter.requestBid(request), new Promise((_, reject) => setTimeout(() => reject(new Error('Bid Timeout')), timeoutMs) ), ]) return { networkId: adapter.networkId, networkName: adapter.networkName, bidEcpm: bidResult.hasBid ? bidResult.bidEcpm : 0, creative: bidResult.creative, latencyMs: Date.now() - adapterStart, status: (bidResult.hasBid ? 'bid' : 'no_bid') as 'bid' | 'no_bid', } } catch (err) { return { networkId: adapter.networkId, networkName: adapter.networkName, bidEcpm: 0, creative: undefined, latencyMs: Date.now() - adapterStart, status: (err instanceof Error && err.message === 'Bid Timeout' ? 'timeout' : 'error') as 'timeout' | 'error', } } }) const bidResults = await Promise.all(bidPromises) // Sort valid bids by eCPM descending (First-Price Auction) const validBids = bidResults .filter((b) => b.status === 'bid' && b.creative && b.bidEcpm >= floorEcpm) .sort((a, b) => b.bidEcpm - a.bidEcpm) let winningCreative: AdCreativePayload if (validBids.length > 0 && validBids[0].creative) { winningCreative = validBids[0].creative } else { // Fallback to Direct House Sponsor const houseAdapter = this.adapters.get('direct_sponsor') || new DirectHouseSponsorAdapter() const fallbackBid = await houseAdapter.requestBid(request) winningCreative = fallbackBid.creative! } const totalLatency = Date.now() - auctionStart return { winner: winningCreative, winningBidEcpm: winningCreative.bidEcpm, participatingBids: bidResults.map((b) => ({ networkId: b.networkId, networkName: b.networkName, bidEcpm: b.bidEcpm, latencyMs: b.latencyMs, status: b.status, })), totalAuctionLatencyMs: totalLatency, auctionTimestamp: Date.now(), } } public recordImpression(event: Omit): void { const fullEvent: AdImpressionEvent = { ...event, timestamp: Date.now(), } this.impressionHistory.push(fullEvent) const adapter = this.adapters.get(event.network) if (adapter) { adapter.reportImpression(event.adId).catch(() => {}) } // Register into Settlement Ledger getAdSettlementService().recordImpression(event.network, event.earnedEcpm || 3.5) } public recordClick(adId: string, networkId: string): void { const adapter = this.adapters.get(networkId) if (adapter) { adapter.reportClick(adId).catch(() => {}) } getAdSettlementService().recordClick(networkId) } public async claimReward(adId: string, networkId: string): Promise { const now = Date.now() const cooldownMs = this.config.rewardCooldownSeconds * 1000 if (now - this.lastRewardTimestamp < cooldownMs) { const waitSeconds = Math.ceil((cooldownMs - (now - this.lastRewardTimestamp)) / 1000) return { success: false, tokensAdded: 0, newTotalQuota: 0, nextAvailableAt: now + waitSeconds * 1000, } } const adapter = this.adapters.get(networkId) let tokenAmount = this.config.rewardTokensAmount if (adapter && adapter.reportRewardCompletion) { const res = await adapter.reportRewardCompletion(adId) if (res.success && res.tokenReward) tokenAmount = res.tokenReward } this.lastRewardTimestamp = now getAdSettlementService().recordCompletion(networkId) return { success: true, tokensAdded: tokenAmount, newTotalQuota: 100 + tokenAmount, // Demo / actual license service quota boost rewardId: `rew_${Date.now()}`, } } public getRevenueStats(period = '2026-08'): AdRevenueStats { return getAdSettlementService().getRevenueStats(period) } } export function getAdMediationEngine(): AdMediationEngine { return AdMediationEngine.getInstance() }