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

2102 lines
76 KiB
C#
Raw Normal View History

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.
2026-06-12 16:05:13 +08:00
/// Android bridge maps to native auto cache; higher layers may emulate load-then-show elsewhere.
/// </summary>
void ShowRewardVideoAutoAd(DirichletAdRequest request, IDirichletRewardVideoAutoAdListener listener);
/// <summary>
2026-06-12 16:05:13 +08:00
/// 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>
2026-06-12 16:05:13 +08:00
/// 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;
2026-06-12 16:05:13 +08:00
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;
}
}
2026-06-12 16:05:13 +08:00
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
}