using System; using System.Collections.Generic; using System.Globalization; using System.Threading; using UnityEngine; namespace Dirichlet.Mediation { /// /// Entry point of the Dirichlet Mediation Unity wrapper. /// Provides initialization, configuration, and shared helpers that are platform agnostic. /// public static class DirichletSdk { private static readonly IDirichletPlatformBridge Bridge = DirichletPlatformBridgeFactory.Create(); private static SynchronizationContext unityContext; private static int unityThreadId; private static readonly Queue pendingActions = new Queue(); private static readonly object pendingActionsLock = new object(); private static UnityThreadPump pump; /// /// Indicates whether the mediation SDK was initialized successfully. /// public static bool IsInitialized { get; private set; } public static bool IsUnityThread => unityThreadId != 0 && Thread.CurrentThread.ManagedThreadId == unityThreadId; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void CaptureUnitySynchronizationContext() { unityThreadId = Thread.CurrentThread.ManagedThreadId; unityContext = SynchronizationContext.Current; EnsureUnityThreadPump(); } // Internal thread dispatcher. Public SDK callbacks are already marshalled to Unity thread when needed. internal static void DispatchToUnityThread(Action action) { if (action == null) { return; } if (IsUnityThread) { try { action(); } catch (Exception ex) { Debug.LogException(ex); } return; } if (unityContext != null) { unityContext.Post(_ => { try { action(); } catch (Exception ex) { Debug.LogException(ex); } }, null); return; } lock (pendingActionsLock) { pendingActions.Enqueue(action); } EnsureUnityThreadPump(); } /// /// Initializes the mediation SDK using the aggregator-style configuration. /// public static void Init( DirichletAdConfig config, Action onSuccess = null, Action onFailure = null) { InitializeInternal(config, onSuccess, onFailure); } private static void InitializeInternal( DirichletAdConfig config, Action onSuccess, Action onFailure) { if (IsInitialized) { Debug.Log("[Dirichlet] Initialize called, but SDK is already initialized."); DispatchToUnityThread(() => onSuccess?.Invoke(DirichletInitResult.AlreadyInitialized())); return; } if (config == null) { DirichletAdManager.Clear(); var error = new DirichletError("invalid_config", "DirichletAdConfig cannot be null"); Debug.LogError(error); DispatchToUnityThread(() => onFailure?.Invoke(error)); return; } var options = config.ToPlatformOptions(); if (options == null) { DirichletAdManager.Clear(); var error = new DirichletError("invalid_config", "Failed to map DirichletAdConfig to native options"); Debug.LogError(error); DispatchToUnityThread(() => onFailure?.Invoke(error)); return; } DirichletAdManager.ApplyConfig(config); void SuccessHandler(DirichletInitResult result) { DispatchToUnityThread(() => { IsInitialized = result?.Success ?? false; onSuccess?.Invoke(result ?? DirichletInitResult.Ok("bridge_returned_null")); }); } void FailureHandler(DirichletError error) { DispatchToUnityThread(() => { IsInitialized = false; onFailure?.Invoke(error ?? new DirichletError("bridge_error", "Initialization failed")); }); } Bridge.Initialize(options, SuccessHandler, FailureHandler); } /// /// Requests runtime permissions if the underlying SDK requires them. /// public static void RequestPermissionIfNecessary() { Bridge?.RequestPermissionIfNeeded(); } [Obsolete("Use RequestPermissionIfNecessary() instead.")] public static void RequestPermissionIfNeeded() { RequestPermissionIfNecessary(); } /// /// Returns the native SDK version if available. /// public static string GetVersion() => Bridge?.GetSdkVersion() ?? "unknown"; [Obsolete("Use GetVersion() instead.")] public static string GetSdkVersion() => GetVersion(); internal static IDirichletPlatformBridge GetBridge() => Bridge; private static void EnsureUnityThreadPump() { if (pump != null || !Application.isPlaying) { return; } if (!IsUnityThread) { unityContext?.Post(_ => EnsureUnityThreadPump(), null); return; } var host = new GameObject("DirichletUnityThreadPump") { hideFlags = HideFlags.HideAndDontSave }; UnityEngine.Object.DontDestroyOnLoad(host); pump = host.AddComponent(); } private sealed class UnityThreadPump : MonoBehaviour { private readonly List executionBuffer = new List(8); private void Awake() { UnityEngine.Object.DontDestroyOnLoad(gameObject); } private void Update() { executionBuffer.Clear(); lock (pendingActionsLock) { while (pendingActions.Count > 0) { executionBuffer.Add(pendingActions.Dequeue()); } } for (int i = 0; i < executionBuffer.Count; i++) { var action = executionBuffer[i]; try { action?.Invoke(); } catch (Exception ex) { Debug.LogException(ex); } } } private void OnDestroy() { if (ReferenceEquals(pump, this)) { pump = null; } } } } #region Initialization data public enum DirichletAdNetworkType { Unknown = 0, Csj = 1, Gdt = 2, Tap = 3 } [Serializable] /// /// 配置 Dirichlet Mediation SDK 的初始化参数 /// public sealed class DirichletAdConfig { public long MediaId { get; } public string MediaName { get; } public string MediaKey { get; } public string GameChannel { get; } /// /// 子渠道标识 /// /// /// Android: 用于区分不同的子渠道来源 /// iOS: 此属性不使用,传递的值会被忽略 /// public string SubChannel { get; } public bool DebugEnabled { get; } public string TapClientId { get; } public bool ShakeEnabled { get; } public string CustomConfigJson { get; } public string DataJson { get; } /// /// 控制是否允许访问广告标识符 /// /// /// iOS: 控制 IDFA 访问。设置为 true 时,iOS 14+ 会检查 ATT 授权状态后读取 IDFA。 /// Android: 此属性不使用,Android 使用 OAID/AAID 机制。 /// 默认值: true /// public bool AllowIDFAAccess { get; } /// /// 外部配置的 aTags(JSON 格式) /// /// /// 可选配置,用于传递额外的标签信息到广告 SDK。 /// 格式: JSON 字符串,例如 {"key":"value"} /// 平台支持: iOS/Android 通用 /// public string ATags { get; } internal string LegacyAppId { get; } private DirichletAdConfig(Builder builder) { MediaId = builder.mediaId; LegacyAppId = builder.legacyAppId; MediaName = builder.mediaName; MediaKey = builder.mediaKey; GameChannel = string.IsNullOrEmpty(builder.gameChannel) ? "default" : builder.gameChannel; SubChannel = builder.subChannel; DebugEnabled = builder.enableDebug; TapClientId = builder.tapClientId; ShakeEnabled = builder.shakeEnabled; CustomConfigJson = builder.customConfigJson; DataJson = builder.dataJson; AllowIDFAAccess = builder.allowIDFAAccess; ATags = builder.aTags; } public Builder ToBuilder() { return new Builder() .WithMediaId(MediaId) .WithMediaName(MediaName) .WithMediaKey(MediaKey) .WithGameChannel(GameChannel) .WithSubChannel(SubChannel) .EnableDebug(DebugEnabled) .WithTapClientId(TapClientId) .ShakeEnabled(ShakeEnabled) .WithCustomConfigJson(CustomConfigJson) .WithDataJson(DataJson) .WithAppId(LegacyAppId) .AllowIDFAAccess(AllowIDFAAccess) .WithATags(ATags); } internal DirichletPlatformInitOptions ToPlatformOptions() { return new DirichletPlatformInitOptions { MediaId = MediaId, AppId = LegacyAppId, Channel = GameChannel, SubChannel = SubChannel, EnableLog = DebugEnabled, MediaName = MediaName, MediaKey = MediaKey, TapClientId = TapClientId, ShakeEnabled = ShakeEnabled, CustomConfigJson = CustomConfigJson, DataJson = DataJson, AllowIDFAAccess = AllowIDFAAccess, ATags = ATags }; } public override string ToString() { return $"DirichletAdConfig(MediaId={MediaId}, MediaName={MediaName}, GameChannel={GameChannel}, SubChannel={SubChannel}, DebugEnabled={DebugEnabled}, TapClientId={(string.IsNullOrEmpty(TapClientId) ? "" : TapClientId)}, ShakeEnabled={ShakeEnabled})"; } public sealed class Builder { internal long mediaId; internal string mediaName = "Unity Dirichlet Demo"; internal string mediaKey; internal string gameChannel = "default"; internal string subChannel; internal bool enableDebug = true; internal string tapClientId; internal bool shakeEnabled = true; internal string customConfigJson; internal string dataJson; internal string legacyAppId; internal bool allowIDFAAccess = true; internal string aTags; public Builder WithMediaId(long value) { mediaId = value; return this; } public Builder WithAppId(string appId) { legacyAppId = appId; if (!string.IsNullOrEmpty(appId) && long.TryParse(appId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed) && parsed > 0) { mediaId = parsed; } return this; } public Builder WithMediaName(string value) { mediaName = value; return this; } public Builder WithMediaKey(string value) { mediaKey = value; return this; } public Builder WithGameChannel(string value) { gameChannel = value; return this; } /// /// 设置子渠道标识(Android only,iOS 会忽略此值) /// public Builder WithSubChannel(string value) { subChannel = value; return this; } public Builder EnableDebug(bool enabled) { enableDebug = enabled; return this; } public Builder WithTapClientId(string value) { tapClientId = value; return this; } public Builder ShakeEnabled(bool enabled) { shakeEnabled = enabled; return this; } public Builder WithCustomConfigJson(string json) { customConfigJson = json; return this; } public Builder WithDataJson(string json) { dataJson = json; return this; } /// /// 设置是否允许访问广告标识符(iOS: IDFA,Android: 忽略) /// /// true 表示允许访问(默认),false 表示禁止访问 public Builder AllowIDFAAccess(bool enabled) { allowIDFAAccess = enabled; return this; } /// /// 设置外部 aTags(JSON 格式,两平台通用) /// /// JSON 字符串,例如 {"key":"value"} public Builder WithATags(string value) { aTags = value; return this; } public DirichletAdConfig Build() { return new DirichletAdConfig(this); } } } public sealed class DirichletInitResult { public bool Success { get; } public string Message { get; } private DirichletInitResult(bool success, string message) { Success = success; Message = message; } public static DirichletInitResult Ok(string message = null) => new DirichletInitResult(true, message); public static DirichletInitResult Failed(string message) => new DirichletInitResult(false, message); public static DirichletInitResult AlreadyInitialized() => new DirichletInitResult(true, "already_initialized"); } /// /// 平台桥接层使用的初始化选项(内部类) /// internal sealed class DirichletPlatformInitOptions { public string AppId { get; set; } public long MediaId { get; set; } public string Channel { get; set; } /// /// 子渠道标识(Android only) /// public string SubChannel { get; set; } public bool EnableLog { get; set; } public string MediaName { get; set; } public string MediaKey { get; set; } public string TapClientId { get; set; } public bool ShakeEnabled { get; set; } public string CustomConfigJson { get; set; } public string DataJson { get; set; } /// /// 控制 IDFA 访问(iOS only) /// public bool AllowIDFAAccess { get; set; } /// /// 外部 aTags JSON(通用) /// public string ATags { get; set; } internal string GetAppIdString() { if (MediaId > 0) { return MediaId.ToString(CultureInfo.InvariantCulture); } return AppId ?? string.Empty; } public override string ToString() { return $"DirichletPlatformInitOptions(MediaId={MediaId}, AppId={AppId}, Channel={Channel}, SubChannel={SubChannel}, EnableLog={EnableLog}, MediaName={MediaName}, MediaKey={(string.IsNullOrEmpty(MediaKey) ? "" : "***")}, TapClientId={(string.IsNullOrEmpty(TapClientId) ? "" : TapClientId)}, ShakeEnabled={ShakeEnabled}, AllowIDFAAccess={AllowIDFAAccess}, ATags={(string.IsNullOrEmpty(ATags) ? "" : ATags)})"; } } public sealed class DirichletError { public string Code { get; } public string Message { get; } public string Adapter { get; } public string Network { get; } public DirichletError(string code, string message, string adapter = null, string network = null) { Code = string.IsNullOrEmpty(code) ? "unknown" : code; Message = message ?? string.Empty; Adapter = adapter; Network = network; } public override string ToString() { return $"DirichletError(Code={Code}, Message={Message}, Adapter={Adapter}, Network={Network})"; } } #endregion #region Platform bridge plumbing internal interface IDirichletPlatformBridge { void Initialize(DirichletPlatformInitOptions options, Action onSuccess, Action onFailure); void LoadRewardVideoAd(DirichletAdRequest request, Action onSuccess, Action onFailure); bool ShowRewardVideoAd(DirichletPlatformAdHandle handle); void LoadInterstitialAd(DirichletAdRequest request, Action onSuccess, Action onFailure); bool ShowInterstitialAd(DirichletPlatformAdHandle handle); void LoadBannerAd(DirichletAdRequest request, Action onSuccess, Action onFailure); bool ShowBannerAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options); void LoadSplashAd(DirichletAdRequest request, Action onSuccess, Action onFailure); bool ShowSplashAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options); void DestroyAd(DirichletPlatformAdHandle handle); bool IsAdValid(DirichletPlatformAdHandle handle); void RequestPermissionIfNeeded(); string GetSdkVersion(); /// /// Shows a reward video ad with automatic load-and-show logic. /// Android bridge maps to native auto cache; higher layers may emulate load-then-show elsewhere. /// void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener); /// /// Shows an interstitial ad with automatic load-and-show logic. Android bridge maps to native auto cache. /// void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener); /// /// Shows a banner ad with automatic load + rotation logic. Android only. /// void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener); /// /// Shows a splash ad with automatic load logic. Android bridge maps to native auto cache. /// void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener); /// /// Preloads an ad for later auto-show. Android only. /// /// Ad type code; native SDK currently only handles type=3 (reward video). void PreLoad(DirichletAdRequest request, int type); } internal static class DirichletPlatformBridgeFactory { private static IDirichletPlatformBridge instance; internal static IDirichletPlatformBridge Create() { if (instance != null) { return instance; } #if UNITY_ANDROID && !UNITY_EDITOR instance = new AndroidDirichletBridge(); #elif UNITY_IOS && !UNITY_EDITOR instance = new IOSDirichletBridge(); #else instance = new NoopDirichletBridge(); #endif return instance; } internal static void OverrideForTesting(IDirichletPlatformBridge customBridge) { instance = customBridge; } } #if UNITY_ANDROID && !UNITY_EDITOR internal sealed class AndroidDirichletBridge : IDirichletPlatformBridge { private const string BridgeClassName = "com.dirichlet.unity.DirichletUnityBridge"; private static AndroidJavaClass cachedBridgeClass; private static AndroidJavaClass BridgeClass { get { if (cachedBridgeClass == null) { cachedBridgeClass = new AndroidJavaClass(BridgeClassName); } return cachedBridgeClass; } } private readonly Dictionary loadCallbacks = new Dictionary(); private readonly object loadCallbacksLock = new object(); public void Initialize(DirichletPlatformInitOptions options, Action onSuccess, Action onFailure) { if (options == null) { DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("android_invalid_options", "Initialization options cannot be null"))); return; } try { var dataPayload = !string.IsNullOrEmpty(options.DataJson) ? options.DataJson : options.CustomConfigJson; // Do not block Unity thread on Android init. The Java bridge enforces a timeout and reports via callback. var callback = new AndroidInitCallback( () => onSuccess?.Invoke(DirichletInitResult.Ok("android_bridge")), onFailure); BridgeClass.CallStatic( "initializeAsync", options.GetAppIdString(), options.Channel ?? string.Empty, options.SubChannel ?? string.Empty, options.EnableLog, options.MediaName ?? string.Empty, options.MediaKey ?? string.Empty, options.TapClientId ?? string.Empty, dataPayload ?? string.Empty, options.ShakeEnabled, callback); } catch (Exception ex) { Debug.LogException(ex); DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("android_exception", ex.Message))); } } private sealed class AndroidInitCallback : AndroidJavaProxy { // Reuse the existing Java callback interface to avoid adding more bridge-only types. private const string ListenerInterface = "com.dirichlet.unity.DirichletUnityBridge$LoadListener"; private readonly Action success; private readonly Action failure; public AndroidInitCallback(Action success, Action failure) : base(ListenerInterface) { this.success = success; this.failure = failure; } // Called from Java (case-sensitive method name). public void onSuccess() { DirichletSdk.DispatchToUnityThread(() => success?.Invoke()); } // Called from Java (case-sensitive method name). public void onError(string code, string message) { var errorCode = string.IsNullOrEmpty(code) ? "android_init_failed" : code; DirichletSdk.DispatchToUnityThread(() => failure?.Invoke(new DirichletError(errorCode, message ?? string.Empty))); } } public void RequestPermissionIfNeeded() { try { BridgeClass.CallStatic("requestPermissionIfNeeded"); } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] requestPermissionIfNeeded failed: {ex.Message}"); } } public string GetSdkVersion() { try { return BridgeClass.CallStatic("getSdkVersion") ?? "android-unknown"; } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] getSdkVersion failed: {ex.Message}"); return "android-error"; } } public void LoadRewardVideoAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { LoadAdInternal(DirichletAdType.RewardVideo, request, onSuccess, onFailure); } public bool ShowRewardVideoAd(DirichletPlatformAdHandle handle) { return ShowAdInternal(handle, null); } public void LoadInterstitialAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { LoadAdInternal(DirichletAdType.Interstitial, request, onSuccess, onFailure); } public bool ShowInterstitialAd(DirichletPlatformAdHandle handle) { return ShowAdInternal(handle, null); } public void LoadBannerAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { LoadAdInternal(DirichletAdType.Banner, request, onSuccess, onFailure); } public bool ShowBannerAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options) { return ShowAdInternal(handle, options); } public void LoadSplashAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { LoadAdInternal(DirichletAdType.Splash, request, onSuccess, onFailure); } public bool ShowSplashAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options) { return ShowAdInternal(handle, options); } public void DestroyAd(DirichletPlatformAdHandle handle) { DestroyAdInternal(handle); } public bool IsAdValid(DirichletPlatformAdHandle handle) { if (handle == null || string.IsNullOrEmpty(handle.DebugId)) { return false; } try { return BridgeClass.CallStatic("isAdValid", handle.DebugId); } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][Android] IsAdValid failed: {ex.Message}"); return false; } } private void LoadAdInternal(DirichletAdType adType, DirichletAdRequest request, Action onSuccess, Action onFailure) { if (request == null) { DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("invalid_request", "Request cannot be null"))); return; } try { var payload = request.ToBridgePayload(); var callback = new AndroidLoadCallback(this, null, () => { // Handle will be set in callback's onSuccess }, onFailure); string handleId; using (var extras = BuildJsonObject(payload)) { // Use the new direct load methods that match native SDK pattern // extras already contains space_id from request.ToBridgePayload() switch (adType) { case DirichletAdType.RewardVideo: handleId = BridgeClass.CallStatic("loadRewardVideoAd", extras, callback); break; case DirichletAdType.Interstitial: handleId = BridgeClass.CallStatic("loadInterstitialAd", extras, callback); break; case DirichletAdType.Banner: handleId = BridgeClass.CallStatic("loadBannerAd", extras, callback); break; case DirichletAdType.Splash: handleId = BridgeClass.CallStatic("loadSplashAd", extras, callback); break; default: DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("unsupported_type", $"Unsupported ad type: {adType}"))); return; } } if (string.IsNullOrEmpty(handleId)) { DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("invalid_handle", "Bridge returned null handle"))); return; } // Create handle - simple wrapper around handle string var handle = DirichletPlatformAdHandle.FromNative(handleId); callback.SetHandle(handle); callback.SetSuccessCallback(() => onSuccess?.Invoke(handle)); lock (loadCallbacksLock) { loadCallbacks[handleId] = callback; } } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] LoadAd failed: {ex.Message}"); DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("android_exception", ex.Message))); } } private bool ShowAdInternal(DirichletPlatformAdHandle handle, DirichletAdShowOptions options) { try { var payload = options?.ToBridgePayload(); using (var extras = BuildJsonObject(payload)) { return BridgeClass.CallStatic("showAd", handle.DebugId, extras); } } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] ShowAd failed: {ex.Message}"); return false; } } private void DestroyAdInternal(DirichletPlatformAdHandle handle) { try { BridgeClass.CallStatic("destroyAd", handle.DebugId); } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] DestroyAd failed: {ex.Message}"); } finally { RemoveLoadCallback(handle?.DebugId); } } private AndroidJavaObject BuildJsonObject(Dictionary dictionary) { if (dictionary == null || dictionary.Count == 0) { return null; } AndroidJavaObject json = null; try { json = new AndroidJavaObject("org.json.JSONObject"); foreach (var kv in dictionary) { if (string.IsNullOrEmpty(kv.Key)) { continue; } var value = kv.Value; if (value == null) { continue; } try { switch (value) { case bool boolValue: json.Call("put", kv.Key, boolValue); break; case int intValue: json.Call("put", kv.Key, intValue); break; case long longValue: json.Call("put", kv.Key, longValue); break; case float floatValue: json.Call("put", kv.Key, (double)floatValue); break; case double doubleValue: json.Call("put", kv.Key, doubleValue); break; case Enum enumValue: json.Call("put", kv.Key, Convert.ToInt32(enumValue, CultureInfo.InvariantCulture)); break; default: json.Call("put", kv.Key, value.ToString()); break; } } catch (Exception putEx) { Debug.LogWarning($"[Dirichlet][Android] Failed to add extra {kv.Key}: {putEx.Message}"); } } } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] Failed to build json object: {ex.Message}"); } return json; } private void RemoveLoadCallback(string handleId) { if (string.IsNullOrEmpty(handleId)) { return; } lock (loadCallbacksLock) { loadCallbacks.Remove(handleId); } } private sealed class AndroidLoadCallback : AndroidJavaProxy { private readonly AndroidDirichletBridge owner; private string handleId; private Action success; private readonly Action failure; public AndroidLoadCallback(AndroidDirichletBridge owner, string handleId, Action success, Action failure) : base("com.dirichlet.unity.DirichletUnityBridge$LoadListener") { this.owner = owner; this.handleId = handleId; this.success = success; this.failure = failure; } public void SetHandle(DirichletPlatformAdHandle handle) { if (handle != null) { handleId = handle.DebugId; } } public void SetSuccessCallback(Action callback) { success = callback; } public void onSuccess() { owner.RemoveLoadCallback(handleId); DirichletSdk.DispatchToUnityThread(() => success?.Invoke()); } public void onError(string code, string message) { owner.RemoveLoadCallback(handleId); var errorCode = string.IsNullOrEmpty(code) ? "android_error" : code; DirichletSdk.DispatchToUnityThread(() => failure?.Invoke(new DirichletError(errorCode, message ?? string.Empty))); } } public void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener) { if (request == null) { DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("invalid_request", "Request cannot be null"))); return; } try { var payload = request.ToBridgePayload(); var callback = new AndroidRewardVideoAutoAdCallback(listener); using (var extras = BuildJsonObject(payload)) { BridgeClass.CallStatic("showRewardVideoAutoAd", extras, callback); } } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] ShowRewardVideoAutoAd failed: {ex.Message}"); DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("android_exception", ex.Message))); } } public void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener) { if (request == null) { DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("invalid_request", "Request cannot be null"))); return; } try { var payload = request.ToBridgePayload(); var callback = new AndroidInterstitialAutoAdCallback(listener); using (var extras = BuildJsonObject(payload)) { BridgeClass.CallStatic("showInterstitialAutoAd", extras, callback); } } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] ShowInterstitialAutoAd failed: {ex.Message}"); DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("android_exception", ex.Message))); } } public void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener) { if (request == null) { DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("invalid_request", "Request cannot be null"))); return; } try { var payload = request.ToBridgePayload(); var optionsPayload = (options ?? new DirichletAdShowOptions()).ToBridgePayload(); var callback = new AndroidBannerAutoAdCallback(listener); using (var extras = BuildJsonObject(payload)) using (var optsJson = BuildJsonObject(optionsPayload)) { BridgeClass.CallStatic("showBannerAutoAd", extras, optsJson, callback); } } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] ShowBannerAutoAd failed: {ex.Message}"); DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("android_exception", ex.Message))); } } public void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener) { if (request == null) { DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("invalid_request", "Request cannot be null"))); return; } try { var payload = request.ToBridgePayload(); var optionsPayload = (options ?? new DirichletAdShowOptions()).ToBridgePayload(); var callback = new AndroidSplashAutoAdCallback(listener); using (var extras = BuildJsonObject(payload)) using (var optsJson = BuildJsonObject(optionsPayload)) { BridgeClass.CallStatic("showSplashAutoAd", extras, optsJson, callback); } } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] ShowSplashAutoAd failed: {ex.Message}"); DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("android_exception", ex.Message))); } } public void PreLoad(DirichletAdRequest request, int type) { if (request == null) { Debug.LogWarning("[Dirichlet][Android] PreLoad called with null request"); return; } try { var payload = request.ToBridgePayload(); using (var extras = BuildJsonObject(payload)) { BridgeClass.CallStatic("preLoad", extras, type); } } catch (Exception ex) { Debug.LogWarning($"[Dirichlet][Android] PreLoad failed: {ex.Message}"); } } private sealed class AndroidInterstitialAutoAdCallback : AndroidJavaProxy { private readonly IDirichletInterstitialAutoAdListener listener; public AndroidInterstitialAutoAdCallback(IDirichletInterstitialAutoAdListener listener) : base("com.dirichlet.unity.DirichletUnityBridge$InterstitialAutoAdListener") { this.listener = listener; } public void onError(string code, string message) { var errorCode = string.IsNullOrEmpty(code) ? "android_error" : code; DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError(errorCode, message ?? string.Empty))); } public void onAdShow() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdShow()); } public void onAdClose() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdClose()); } public void onAdClick() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdClick()); } } private sealed class AndroidBannerAutoAdCallback : AndroidJavaProxy { private readonly IDirichletBannerAutoAdListener listener; public AndroidBannerAutoAdCallback(IDirichletBannerAutoAdListener listener) : base("com.dirichlet.unity.DirichletUnityBridge$BannerAutoAdListener") { this.listener = listener; } public void onError(string code, string message) { var errorCode = string.IsNullOrEmpty(code) ? "android_error" : code; DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError(errorCode, message ?? string.Empty))); } public void onAdShow() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdShow()); } public void onAdClose() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdClose()); } public void onAdClick() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdClick()); } } private sealed class AndroidSplashAutoAdCallback : AndroidJavaProxy { private readonly IDirichletSplashAutoAdListener listener; public AndroidSplashAutoAdCallback(IDirichletSplashAutoAdListener listener) : base("com.dirichlet.unity.DirichletUnityBridge$SplashAutoAdListener") { this.listener = listener; } public void onError(string code, string message) { var errorCode = string.IsNullOrEmpty(code) ? "android_error" : code; DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError(errorCode, message ?? string.Empty))); } public void onAdShow() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdShow()); } public void onAdClose() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdClose()); } public void onAdClick() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdClick()); } } private sealed class AndroidRewardVideoAutoAdCallback : AndroidJavaProxy { private readonly IDirichletRewardVideoAutoAdListener listener; public AndroidRewardVideoAutoAdCallback(IDirichletRewardVideoAutoAdListener listener) : base("com.dirichlet.unity.DirichletUnityBridge$RewardVideoAutoAdListener") { this.listener = listener; } public void onError(string code, string message) { var errorCode = string.IsNullOrEmpty(code) ? "android_error" : code; DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError(errorCode, message ?? string.Empty))); } public void onAdShow() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdShow()); } public void onAdClose() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdClose()); } public void onRewardVerify(bool rewardVerify, int rewardAmount, string rewardName, int code, string msg) { var args = new DirichletRewardVerificationEventArgs(rewardVerify, rewardAmount, rewardName ?? string.Empty, code, msg ?? string.Empty); DirichletSdk.DispatchToUnityThread(() => listener?.OnRewardVerify(args)); } public void onAdClick() { DirichletSdk.DispatchToUnityThread(() => listener?.OnAdClick()); } } } #elif UNITY_IOS && !UNITY_EDITOR internal sealed class IOSDirichletBridge : IDirichletPlatformBridge { [System.Runtime.InteropServices.DllImport("__Internal")] private static extern bool DirichletMediationUnityBridge_Initialize( string mediaId, string mediaKey, bool enableLog, string mediaName, string gameChannel, bool shakeEnabled, bool allowIDFAAccess, string aTags); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern void DirichletMediationUnityBridge_RequestPermissionIfNeeded(); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern string DirichletMediationUnityBridge_GetSdkVersion(); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern string DirichletMediationUnityBridge_LoadRewardVideoAd(long spaceId, string extras); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern string DirichletMediationUnityBridge_LoadInterstitialAd(long spaceId, string extras); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern string DirichletMediationUnityBridge_LoadBannerAd(long spaceId, string extras); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern string DirichletMediationUnityBridge_LoadSplashAd(long spaceId, string extras); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern bool DirichletMediationUnityBridge_ShowAd(string handleId, string extras); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern void DirichletMediationUnityBridge_DestroyAd(string handleId); [System.Runtime.InteropServices.DllImport("__Internal")] private static extern bool DirichletMediationUnityBridge_IsAdValid(string handleId); private readonly Dictionary loadCallbacks = new Dictionary(); private readonly object loadCallbacksLock = new object(); private static bool loadCallbackReceiverInitialized; private static bool initCallbackReceiverInitialized; private static readonly object initCallbackLock = new object(); private static Action pendingInitSuccess; private static Action pendingInitFailure; public void Initialize(DirichletPlatformInitOptions options, Action onSuccess, Action onFailure) { if (options == null) { DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("ios_invalid_options", "Initialization options cannot be null"))); return; } // Register callbacks for async result lock (initCallbackLock) { pendingInitSuccess = onSuccess; pendingInitFailure = onFailure; } EnsureInitCallbackReceiver(); try { // iOS Mediation SDK uses async callback (aligned with Ad Unity implementation) var started = DirichletMediationUnityBridge_Initialize( options.GetAppIdString(), options.MediaKey ?? string.Empty, options.EnableLog, options.MediaName ?? string.Empty, options.Channel ?? string.Empty, options.ShakeEnabled, options.AllowIDFAAccess, options.ATags ?? string.Empty); if (!started) { lock (initCallbackLock) { pendingInitSuccess = null; pendingInitFailure = null; } DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("ios_init_rejected", "Initialization could not be started"))); } } catch (Exception ex) { Debug.LogException(ex); lock (initCallbackLock) { pendingInitSuccess = null; pendingInitFailure = null; } DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("ios_exception", ex.Message))); } } public void RequestPermissionIfNeeded() { try { DirichletMediationUnityBridge_RequestPermissionIfNeeded(); } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] RequestPermissionIfNeeded failed: {ex.Message}"); } } public string GetSdkVersion() { try { return DirichletMediationUnityBridge_GetSdkVersion() ?? "ios-unknown"; } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] GetSdkVersion failed: {ex.Message}"); return "ios-error"; } } public void LoadRewardVideoAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { LoadAdInternal(DirichletAdType.RewardVideo, request, onSuccess, onFailure); } public bool ShowRewardVideoAd(DirichletPlatformAdHandle handle) { return ShowAdInternal(handle, null); } public void LoadInterstitialAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { LoadAdInternal(DirichletAdType.Interstitial, request, onSuccess, onFailure); } public bool ShowInterstitialAd(DirichletPlatformAdHandle handle) { return ShowAdInternal(handle, null); } public void LoadBannerAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { LoadAdInternal(DirichletAdType.Banner, request, onSuccess, onFailure); } public bool ShowBannerAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options) { return ShowAdInternal(handle, options); } public void LoadSplashAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { LoadAdInternal(DirichletAdType.Splash, request, onSuccess, onFailure); } public bool ShowSplashAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options) { return ShowAdInternal(handle, options); } public void DestroyAd(DirichletPlatformAdHandle handle) { DestroyAdInternal(handle); } public bool IsAdValid(DirichletPlatformAdHandle handle) { if (handle == null || string.IsNullOrEmpty(handle.DebugId)) { return false; } try { return DirichletMediationUnityBridge_IsAdValid(handle.DebugId); } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] IsAdValid failed: {ex.Message}"); return false; } } private void LoadAdInternal(DirichletAdType adType, DirichletAdRequest request, Action onSuccess, Action onFailure) { if (request == null) { DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("invalid_request", "Request cannot be null"))); return; } try { var payload = request.ToBridgePayload(); var callback = new IOSLoadCallback(this, null, () => { }, onFailure); string handleId; var extrasJson = BuildJsonString(payload); switch (adType) { case DirichletAdType.RewardVideo: handleId = DirichletMediationUnityBridge_LoadRewardVideoAd(request.SpaceId, extrasJson); break; case DirichletAdType.Interstitial: handleId = DirichletMediationUnityBridge_LoadInterstitialAd(request.SpaceId, extrasJson); break; case DirichletAdType.Banner: handleId = DirichletMediationUnityBridge_LoadBannerAd(request.SpaceId, extrasJson); break; case DirichletAdType.Splash: handleId = DirichletMediationUnityBridge_LoadSplashAd(request.SpaceId, extrasJson); break; default: DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("unsupported_type", $"Unsupported ad type: {adType}"))); return; } if (string.IsNullOrEmpty(handleId)) { DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("invalid_handle", "Bridge returned null handle"))); return; } // Create handle and setup callback var handle = DirichletPlatformAdHandle.FromNative(handleId); callback.SetHandle(handle); callback.SetSuccessCallback(() => onSuccess?.Invoke(handle)); lock (loadCallbacksLock) { loadCallbacks[handleId] = callback; } // Ensure load callback receiver is initialized EnsureLoadCallbackReceiver(); } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] LoadAd failed: {ex.Message}"); DirichletSdk.DispatchToUnityThread(() => onFailure?.Invoke(new DirichletError("ios_exception", ex.Message))); } } private bool ShowAdInternal(DirichletPlatformAdHandle handle, DirichletAdShowOptions options) { try { var payload = options?.ToBridgePayload(); var extrasJson = BuildJsonString(payload); return DirichletMediationUnityBridge_ShowAd(handle.DebugId, extrasJson); } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] ShowAd failed: {ex.Message}"); return false; } } private void DestroyAdInternal(DirichletPlatformAdHandle handle) { try { DirichletMediationUnityBridge_DestroyAd(handle.DebugId); } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] DestroyAd failed: {ex.Message}"); } finally { RemoveLoadCallback(handle?.DebugId); } } private string BuildJsonString(Dictionary dictionary) { if (dictionary == null || dictionary.Count == 0) { return string.Empty; } try { var jsonBuilder = new System.Text.StringBuilder(); jsonBuilder.Append("{"); var first = true; foreach (var kv in dictionary) { if (string.IsNullOrEmpty(kv.Key) || kv.Value == null) { continue; } if (!first) { jsonBuilder.Append(","); } first = false; AppendJsonString(jsonBuilder, kv.Key); jsonBuilder.Append(":"); AppendJsonValue(jsonBuilder, kv.Value); } jsonBuilder.Append("}"); return jsonBuilder.ToString(); } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] Failed to build json string: {ex.Message}"); return string.Empty; } } private static void AppendJsonValue(System.Text.StringBuilder builder, object value) { if (value == null) { builder.Append("null"); return; } if (value is string stringValue) { AppendJsonString(builder, stringValue); return; } if (value is bool boolValue) { builder.Append(boolValue ? "true" : "false"); return; } if (value is IFormattable formattable) { builder.Append(formattable.ToString(null, CultureInfo.InvariantCulture)); return; } AppendJsonString(builder, value.ToString()); } private static void AppendJsonString(System.Text.StringBuilder builder, string value) { builder.Append('"'); if (!string.IsNullOrEmpty(value)) { foreach (var c in value) { switch (c) { case '\\': builder.Append("\\\\"); break; case '"': builder.Append("\\\""); break; case '\b': builder.Append("\\b"); break; case '\f': builder.Append("\\f"); break; case '\n': builder.Append("\\n"); break; case '\r': builder.Append("\\r"); break; case '\t': builder.Append("\\t"); break; default: if (char.IsControl(c)) { builder.Append("\\u"); builder.Append(((int)c).ToString("x4", CultureInfo.InvariantCulture)); } else { builder.Append(c); } break; } } } builder.Append('"'); } private void RemoveLoadCallback(string handleId) { if (string.IsNullOrEmpty(handleId)) { return; } lock (loadCallbacksLock) { loadCallbacks.Remove(handleId); } } private void EnsureLoadCallbackReceiver() { if (!DirichletSdk.IsUnityThread) { DirichletSdk.DispatchToUnityThread(EnsureLoadCallbackReceiver); return; } const string receiverName = "DirichletMediationIOSLoadCallbackReceiver"; if (loadCallbackReceiverInitialized) { var existing = GameObject.Find(receiverName); if (existing != null) { return; } Debug.LogWarning("[DirichletMediation][iOS] LoadCallbackReceiver was destroyed, recreating..."); loadCallbackReceiverInitialized = false; } var host = new GameObject(receiverName) { hideFlags = HideFlags.HideAndDontSave }; UnityEngine.Object.DontDestroyOnLoad(host); var receiver = host.AddComponent(); receiver.bridge = this; loadCallbackReceiverInitialized = true; } private void EnsureInitCallbackReceiver() { if (!DirichletSdk.IsUnityThread) { DirichletSdk.DispatchToUnityThread(EnsureInitCallbackReceiver); return; } const string receiverName = "DirichletMediationIOSInitCallbackReceiver"; if (initCallbackReceiverInitialized) { var existing = GameObject.Find(receiverName); if (existing != null) { return; } Debug.LogWarning("[DirichletMediation][iOS] InitCallbackReceiver was destroyed, recreating..."); initCallbackReceiverInitialized = false; } var host = new GameObject(receiverName) { hideFlags = HideFlags.HideAndDontSave }; UnityEngine.Object.DontDestroyOnLoad(host); var receiver = host.AddComponent(); receiver.bridge = this; initCallbackReceiverInitialized = true; } internal void HandleLoadCallback(string payload) { if (string.IsNullOrEmpty(payload)) { return; } try { var message = JsonUtility.FromJson(payload); if (message == null || string.IsNullOrEmpty(message.handle)) { return; } IOSLoadCallback callback; lock (loadCallbacksLock) { if (!loadCallbacks.TryGetValue(message.handle, out callback)) { Debug.LogWarning($"[DirichletMediation][iOS] No callback found for handle: {message.handle}"); return; } } if (message.eventName == "load_success") { callback.OnSuccess(); } else if (message.eventName == "load_error") { var code = message.data?.code.ToString() ?? "unknown"; var msg = message.data?.message ?? "Unknown error"; callback.OnError(code, msg); } } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] Failed to handle load callback: {ex.Message}\n{payload}"); } } internal void HandleInitCallback(string payload) { if (string.IsNullOrEmpty(payload)) { return; } InitCallbackPayload message = null; try { message = JsonUtility.FromJson(payload); } catch (Exception ex) { Debug.LogWarning($"[DirichletMediation][iOS] Failed to parse init callback: {ex.Message}\n{payload}"); } var success = message?.success ?? false; var data = message?.data; var code = data?.code ?? -1; var domain = data?.domain; var description = data?.message; Action successCallback; Action failureCallback; lock (initCallbackLock) { successCallback = pendingInitSuccess; failureCallback = pendingInitFailure; pendingInitSuccess = null; pendingInitFailure = null; } if (success) { var messageText = string.IsNullOrEmpty(description) ? "ios_mediation_bridge" : description; var result = DirichletInitResult.Ok(messageText); DirichletSdk.DispatchToUnityThread(() => successCallback?.Invoke(result)); return; } var errorCode = code > 0 ? $"ios_init_{code}" : "ios_init_failed"; var errorMessage = string.IsNullOrEmpty(description) ? "Initialization failed" : description; if (!string.IsNullOrEmpty(domain)) { errorMessage = $"{errorMessage} ({domain})"; } var error = new DirichletError(errorCode, errorMessage); DirichletSdk.DispatchToUnityThread(() => { if (failureCallback != null) { failureCallback(error); } else { Debug.LogWarning($"[DirichletMediation][iOS] Init failure received but no callback registered: {error}"); } }); } [Serializable] private class LoadCallbackPayload { public string handle; public string eventName; public string adType; public LoadCallbackPayloadData data; } [Serializable] private class LoadCallbackPayloadData { public int code; public string message; } [Serializable] private class InitCallbackPayload { public bool success; public InitCallbackPayloadData data; } [Serializable] private class InitCallbackPayloadData { public int code; public string message; public string domain; } private class IOSLoadCallbackReceiver : MonoBehaviour { public IOSDirichletBridge bridge; public void OnLoadCallback(string payload) { bridge?.HandleLoadCallback(payload); } } private class IOSInitCallbackReceiver : MonoBehaviour { public IOSDirichletBridge bridge; public void OnInitCallback(string payload) { bridge?.HandleInitCallback(payload); } } private sealed class IOSLoadCallback { private readonly IOSDirichletBridge owner; private string handleId; private Action success; private readonly Action failure; public IOSLoadCallback(IOSDirichletBridge owner, string handleId, Action success, Action failure) { this.owner = owner; this.handleId = handleId; this.success = success; this.failure = failure; } public void SetHandle(DirichletPlatformAdHandle handle) { if (handle != null) { handleId = handle.DebugId; } } public void SetSuccessCallback(Action callback) { success = callback; } public void OnSuccess() { owner.RemoveLoadCallback(handleId); DirichletSdk.DispatchToUnityThread(() => success?.Invoke()); } public void OnError(string code, string message) { owner.RemoveLoadCallback(handleId); DirichletSdk.DispatchToUnityThread(() => failure?.Invoke(new DirichletError(code, message))); } } public void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener) { // iOS hasn't added this API yet DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("not_supported", "showRewardVideoAutoAd is not supported on iOS yet"))); } public void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener) { DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("not_supported", "showInterstitialAutoAd is not supported on iOS yet"))); } public void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener) { DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("not_supported", "showBannerAutoAd is not supported on iOS yet"))); } public void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener) { DirichletSdk.DispatchToUnityThread(() => listener?.OnError(new DirichletError("not_supported", "showSplashAutoAd is not supported on iOS yet"))); } public void PreLoad(DirichletAdRequest request, int type) { Debug.LogWarning("[DirichletMediation][iOS] PreLoad is not supported on iOS yet"); } } #else internal sealed class NoopDirichletBridge : IDirichletPlatformBridge { public void Initialize(DirichletPlatformInitOptions options, Action onSuccess, Action onFailure) { Debug.LogWarning("[Dirichlet] No platform bridge available (editor/unsupported platform)."); DirichletSdk.DispatchToUnityThread(() => onSuccess?.Invoke(DirichletInitResult.Ok("noop"))); } public void InitializeWithoutTap(DirichletPlatformInitOptions options, Action onSuccess, Action onFailure) { Debug.LogWarning("[Dirichlet] InitializeWithoutTap ignored on noop bridge."); DirichletSdk.DispatchToUnityThread(() => onSuccess?.Invoke(DirichletInitResult.Ok("noop"))); } public void UpdateConfig(DirichletPlatformInitOptions options) { Debug.Log("[Dirichlet] UpdateConfig ignored on noop bridge."); } public void RequestPermissionIfNeeded() { Debug.Log("[Dirichlet] RequestPermissionIfNeeded ignored on noop bridge."); } public string GetSdkVersion() => "noop"; private static DirichletPlatformAdHandle CreateStubHandle() { return DirichletPlatformAdHandle.CreateStub(); } private static void LoadStubAd(DirichletPlatformAdHandle handle, Action onSuccess) { Debug.Log($"[Dirichlet] LoadAd noop for {handle.DebugId}"); DirichletSdk.DispatchToUnityThread(() => onSuccess?.Invoke(handle)); } public void LoadRewardVideoAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { var handle = CreateStubHandle(); LoadStubAd(handle, onSuccess); } public bool ShowRewardVideoAd(DirichletPlatformAdHandle handle) { Debug.Log($"[Dirichlet] ShowRewardAd noop for {handle.DebugId}"); return true; } public void LoadInterstitialAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { var handle = CreateStubHandle(); LoadStubAd(handle, onSuccess); } public bool ShowInterstitialAd(DirichletPlatformAdHandle handle) { Debug.Log($"[Dirichlet] ShowInterstitialAd noop for {handle.DebugId}"); return true; } public void LoadBannerAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { var handle = CreateStubHandle(); LoadStubAd(handle, onSuccess); } public bool ShowBannerAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options) { Debug.Log($"[Dirichlet] ShowBannerAd noop for {handle.DebugId}"); return true; } public void LoadSplashAd(DirichletAdRequest request, Action onSuccess, Action onFailure) { var handle = CreateStubHandle(); LoadStubAd(handle, onSuccess); } public bool ShowSplashAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options) { Debug.Log($"[Dirichlet] ShowSplashAd noop for {handle.DebugId}"); return true; } public void DestroyAd(DirichletPlatformAdHandle handle) { Debug.Log($"[Dirichlet] DestroyAd noop for {handle.DebugId}"); } public bool IsAdValid(DirichletPlatformAdHandle handle) { Debug.Log($"[Dirichlet] IsAdValid noop for {handle?.DebugId}"); return true; } public void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener) { Debug.Log("[Dirichlet] ShowRewardVideoAutoAd noop"); DirichletSdk.DispatchToUnityThread(() => { listener?.OnAdShow(); listener?.OnRewardVerify(new DirichletRewardVerificationEventArgs(true, 10, "noop_reward", 0, "noop")); listener?.OnAdClose(); }); } public void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener) { Debug.Log("[Dirichlet] ShowInterstitialAutoAd noop"); DirichletSdk.DispatchToUnityThread(() => { listener?.OnAdShow(); listener?.OnAdClose(); }); } public void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener) { Debug.Log("[Dirichlet] ShowBannerAutoAd noop"); DirichletSdk.DispatchToUnityThread(() => listener?.OnAdShow()); } public void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener) { Debug.Log("[Dirichlet] ShowSplashAutoAd noop"); DirichletSdk.DispatchToUnityThread(() => { listener?.OnAdShow(); listener?.OnAdClose(); }); } public void PreLoad(DirichletAdRequest request, int type) { Debug.Log($"[Dirichlet] PreLoad noop (type={type})"); } } #endif #endregion }