feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
This commit is contained in:
parent
5cd1de6859
commit
708e20f747
406 changed files with 42464 additions and 6199 deletions
229
apps/desktop/src/main/services/ads/AdMediationEngine.ts
Normal file
229
apps/desktop/src/main/services/ads/AdMediationEngine.ts
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
// 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<string, IAdNetworkAdapter> = 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>): 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<AdMediationAuctionResult> {
|
||||
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<never>((_, 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<AdImpressionEvent, 'timestamp'>): 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<AdRewardResult> {
|
||||
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()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue