Files
Commercialization.tapadn/DirichletMediation/Runtime/DirichletMediationSdk.cs

2102 lines
76 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using UnityEngine;
namespace Dirichlet.Mediation
{
/// <summary>
/// Entry point of the Dirichlet Mediation Unity wrapper.
/// Provides initialization, configuration, and shared helpers that are platform agnostic.
/// </summary>
public static class DirichletSdk
{
private static readonly IDirichletPlatformBridge Bridge = DirichletPlatformBridgeFactory.Create();
private static SynchronizationContext unityContext;
private static int unityThreadId;
private static readonly Queue<Action> pendingActions = new Queue<Action>();
private static readonly object pendingActionsLock = new object();
private static UnityThreadPump pump;
/// <summary>
/// Indicates whether the mediation SDK was initialized successfully.
/// </summary>
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();
}
/// <summary>
/// Initializes the mediation SDK using the aggregator-style configuration.
/// </summary>
public static void Init(
DirichletAdConfig config,
Action<DirichletInitResult> onSuccess = null,
Action<DirichletError> onFailure = null)
{
InitializeInternal(config, onSuccess, onFailure);
}
private static void InitializeInternal(
DirichletAdConfig config,
Action<DirichletInitResult> onSuccess,
Action<DirichletError> 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);
}
/// <summary>
/// Requests runtime permissions if the underlying SDK requires them.
/// </summary>
public static void RequestPermissionIfNecessary()
{
Bridge?.RequestPermissionIfNeeded();
}
[Obsolete("Use RequestPermissionIfNecessary() instead.")]
public static void RequestPermissionIfNeeded()
{
RequestPermissionIfNecessary();
}
/// <summary>
/// Returns the native SDK version if available.
/// </summary>
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<UnityThreadPump>();
}
private sealed class UnityThreadPump : MonoBehaviour
{
private readonly List<Action> executionBuffer = new List<Action>(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]
/// <summary>
/// 配置 Dirichlet Mediation SDK 的初始化参数
/// </summary>
public sealed class DirichletAdConfig
{
public long MediaId { get; }
public string MediaName { get; }
public string MediaKey { get; }
public string GameChannel { get; }
/// <summary>
/// 子渠道标识
/// </summary>
/// <remarks>
/// <para><b>Android:</b> 用于区分不同的子渠道来源</para>
/// <para><b>iOS:</b> 此属性不使用,传递的值会被忽略</para>
/// </remarks>
public string SubChannel { get; }
public bool DebugEnabled { get; }
public string TapClientId { get; }
public bool ShakeEnabled { get; }
public string CustomConfigJson { get; }
public string DataJson { get; }
/// <summary>
/// 控制是否允许访问广告标识符
/// </summary>
/// <remarks>
/// <para><b>iOS:</b> 控制 IDFA 访问。设置为 true 时iOS 14+ 会检查 ATT 授权状态后读取 IDFA。</para>
/// <para><b>Android:</b> 此属性不使用Android 使用 OAID/AAID 机制。</para>
/// <para><b>默认值:</b> true</para>
/// </remarks>
public bool AllowIDFAAccess { get; }
/// <summary>
/// 外部配置的 aTagsJSON 格式)
/// </summary>
/// <remarks>
/// <para>可选配置,用于传递额外的标签信息到广告 SDK。</para>
/// <para><b>格式:</b> JSON 字符串,例如 {"key":"value"}</para>
/// <para><b>平台支持:</b> iOS/Android 通用</para>
/// </remarks>
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) ? "<null>" : 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;
}
/// <summary>
/// 设置子渠道标识Android onlyiOS 会忽略此值)
/// </summary>
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;
}
/// <summary>
/// 设置是否允许访问广告标识符iOS: IDFAAndroid: 忽略)
/// </summary>
/// <param name="enabled">true 表示允许访问默认false 表示禁止访问</param>
public Builder AllowIDFAAccess(bool enabled)
{
allowIDFAAccess = enabled;
return this;
}
/// <summary>
/// 设置外部 aTagsJSON 格式,两平台通用)
/// </summary>
/// <param name="value">JSON 字符串,例如 {"key":"value"}</param>
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");
}
/// <summary>
/// 平台桥接层使用的初始化选项(内部类)
/// </summary>
internal sealed class DirichletPlatformInitOptions
{
public string AppId { get; set; }
public long MediaId { get; set; }
public string Channel { get; set; }
/// <summary>
/// 子渠道标识Android only
/// </summary>
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; }
/// <summary>
/// 控制 IDFA 访问iOS only
/// </summary>
public bool AllowIDFAAccess { get; set; }
/// <summary>
/// 外部 aTags JSON通用
/// </summary>
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) ? "<null>" : "***")}, TapClientId={(string.IsNullOrEmpty(TapClientId) ? "<null>" : TapClientId)}, ShakeEnabled={ShakeEnabled}, AllowIDFAAccess={AllowIDFAAccess}, ATags={(string.IsNullOrEmpty(ATags) ? "<null>" : 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<DirichletInitResult> onSuccess, Action<DirichletError> onFailure);
void LoadRewardVideoAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure);
bool ShowRewardVideoAd(DirichletPlatformAdHandle handle);
void LoadInterstitialAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure);
bool ShowInterstitialAd(DirichletPlatformAdHandle handle);
void LoadBannerAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure);
bool ShowBannerAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options);
void LoadSplashAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure);
bool ShowSplashAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options);
void DestroyAd(DirichletPlatformAdHandle handle);
bool IsAdValid(DirichletPlatformAdHandle handle);
void RequestPermissionIfNeeded();
string GetSdkVersion();
/// <summary>
/// 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.
/// </summary>
void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener);
/// <summary>
/// Shows an interstitial ad with automatic load-and-show logic. Android bridge maps to native auto cache.
/// </summary>
void ShowInterstitialAutoAd(DirichletAdRequest request, IDirichletInterstitialAutoAdListener listener);
/// <summary>
/// Shows a banner ad with automatic load + rotation logic. Android only.
/// </summary>
void ShowBannerAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletBannerAutoAdListener listener);
/// <summary>
/// Shows a splash ad with automatic load logic. Android bridge maps to native auto cache.
/// </summary>
void ShowSplashAutoAd(DirichletAdRequest request, DirichletAdShowOptions options, IDirichletSplashAutoAdListener listener);
/// <summary>
/// Preloads an ad for later auto-show. Android only.
/// </summary>
/// <param name="type">Ad type code; native SDK currently only handles type=3 (reward video).</param>
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<string, AndroidLoadCallback> loadCallbacks = new Dictionary<string, AndroidLoadCallback>();
private readonly object loadCallbacksLock = new object();
public void Initialize(DirichletPlatformInitOptions options, Action<DirichletInitResult> onSuccess, Action<DirichletError> 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<DirichletError> failure;
public AndroidInitCallback(Action success, Action<DirichletError> 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<string>("getSdkVersion") ?? "android-unknown";
}
catch (Exception ex)
{
Debug.LogWarning($"[Dirichlet][Android] getSdkVersion failed: {ex.Message}");
return "android-error";
}
}
public void LoadRewardVideoAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure)
{
LoadAdInternal(DirichletAdType.RewardVideo, request, onSuccess, onFailure);
}
public bool ShowRewardVideoAd(DirichletPlatformAdHandle handle)
{
return ShowAdInternal(handle, null);
}
public void LoadInterstitialAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure)
{
LoadAdInternal(DirichletAdType.Interstitial, request, onSuccess, onFailure);
}
public bool ShowInterstitialAd(DirichletPlatformAdHandle handle)
{
return ShowAdInternal(handle, null);
}
public void LoadBannerAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure)
{
LoadAdInternal(DirichletAdType.Banner, request, onSuccess, onFailure);
}
public bool ShowBannerAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options)
{
return ShowAdInternal(handle, options);
}
public void LoadSplashAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> 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<bool>("isAdValid", handle.DebugId);
}
catch (Exception ex)
{
Debug.LogWarning($"[DirichletMediation][Android] IsAdValid failed: {ex.Message}");
return false;
}
}
private void LoadAdInternal(DirichletAdType adType, DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> 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<string>("loadRewardVideoAd", extras, callback);
break;
case DirichletAdType.Interstitial:
handleId = BridgeClass.CallStatic<string>("loadInterstitialAd", extras, callback);
break;
case DirichletAdType.Banner:
handleId = BridgeClass.CallStatic<string>("loadBannerAd", extras, callback);
break;
case DirichletAdType.Splash:
handleId = BridgeClass.CallStatic<string>("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<bool>("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<string, object> 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<AndroidJavaObject>("put", kv.Key, boolValue);
break;
case int intValue:
json.Call<AndroidJavaObject>("put", kv.Key, intValue);
break;
case long longValue:
json.Call<AndroidJavaObject>("put", kv.Key, longValue);
break;
case float floatValue:
json.Call<AndroidJavaObject>("put", kv.Key, (double)floatValue);
break;
case double doubleValue:
json.Call<AndroidJavaObject>("put", kv.Key, doubleValue);
break;
case Enum enumValue:
json.Call<AndroidJavaObject>("put", kv.Key, Convert.ToInt32(enumValue, CultureInfo.InvariantCulture));
break;
default:
json.Call<AndroidJavaObject>("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<DirichletError> failure;
public AndroidLoadCallback(AndroidDirichletBridge owner, string handleId, Action success, Action<DirichletError> 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<string, IOSLoadCallback> loadCallbacks = new Dictionary<string, IOSLoadCallback>();
private readonly object loadCallbacksLock = new object();
private static bool loadCallbackReceiverInitialized;
private static bool initCallbackReceiverInitialized;
private static readonly object initCallbackLock = new object();
private static Action<DirichletInitResult> pendingInitSuccess;
private static Action<DirichletError> pendingInitFailure;
public void Initialize(DirichletPlatformInitOptions options, Action<DirichletInitResult> onSuccess, Action<DirichletError> 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<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure)
{
LoadAdInternal(DirichletAdType.RewardVideo, request, onSuccess, onFailure);
}
public bool ShowRewardVideoAd(DirichletPlatformAdHandle handle)
{
return ShowAdInternal(handle, null);
}
public void LoadInterstitialAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure)
{
LoadAdInternal(DirichletAdType.Interstitial, request, onSuccess, onFailure);
}
public bool ShowInterstitialAd(DirichletPlatformAdHandle handle)
{
return ShowAdInternal(handle, null);
}
public void LoadBannerAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> onFailure)
{
LoadAdInternal(DirichletAdType.Banner, request, onSuccess, onFailure);
}
public bool ShowBannerAd(DirichletPlatformAdHandle handle, DirichletAdShowOptions options)
{
return ShowAdInternal(handle, options);
}
public void LoadSplashAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> 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<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> 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<string, object> 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<IOSLoadCallbackReceiver>();
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<IOSInitCallbackReceiver>();
receiver.bridge = this;
initCallbackReceiverInitialized = true;
}
internal void HandleLoadCallback(string payload)
{
if (string.IsNullOrEmpty(payload))
{
return;
}
try
{
var message = JsonUtility.FromJson<LoadCallbackPayload>(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<InitCallbackPayload>(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<DirichletInitResult> successCallback;
Action<DirichletError> 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<DirichletError> failure;
public IOSLoadCallback(IOSDirichletBridge owner, string handleId, Action success, Action<DirichletError> 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<DirichletInitResult> onSuccess, Action<DirichletError> onFailure)
{
Debug.LogWarning("[Dirichlet] No platform bridge available (editor/unsupported platform).");
DirichletSdk.DispatchToUnityThread(() => onSuccess?.Invoke(DirichletInitResult.Ok("noop")));
}
public void InitializeWithoutTap(DirichletPlatformInitOptions options, Action<DirichletInitResult> onSuccess, Action<DirichletError> 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<DirichletPlatformAdHandle> onSuccess)
{
Debug.Log($"[Dirichlet] LoadAd noop for {handle.DebugId}");
DirichletSdk.DispatchToUnityThread(() => onSuccess?.Invoke(handle));
}
public void LoadRewardVideoAd(DirichletAdRequest request, Action<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> 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<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> 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<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> 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<DirichletPlatformAdHandle> onSuccess, Action<DirichletError> 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
}