diff --git a/com.onesignal.unity.android/Runtime/AndroidDebugManager.cs b/com.onesignal.unity.android/Runtime/AndroidDebugManager.cs index 4ae7cb3d..1f5f88d4 100644 --- a/com.onesignal.unity.android/Runtime/AndroidDebugManager.cs +++ b/com.onesignal.unity.android/Runtime/AndroidDebugManager.cs @@ -48,6 +48,8 @@ public LogLevel LogLevel get => _logLevel; set { + if (InputGuard.IsInvalidLogLevel(value, "setLogLevel")) + return; _logLevel = value; _debug.Call("setLogLevel", ToLogLevel(value)); } @@ -58,6 +60,8 @@ public LogLevel AlertLevel get => _alertLevel; set { + if (InputGuard.IsInvalidLogLevel(value, "setAlertLevel")) + return; _alertLevel = value; _debug.Call("setAlertLevel", ToLogLevel(value)); } diff --git a/com.onesignal.unity.android/Runtime/AndroidInAppMessagesManager.cs b/com.onesignal.unity.android/Runtime/AndroidInAppMessagesManager.cs index 741f760a..5de71ecb 100644 --- a/com.onesignal.unity.android/Runtime/AndroidInAppMessagesManager.cs +++ b/com.onesignal.unity.android/Runtime/AndroidInAppMessagesManager.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using OneSignalSDK; using OneSignalSDK.Android.Utilities; using OneSignalSDK.InAppMessages; using OneSignalSDK.InAppMessages.Internal; @@ -56,16 +57,36 @@ public bool Paused set => _inAppMessages.Call("setPaused", value); } - public void AddTrigger(string key, string value) => + public void AddTrigger(string key, string value) + { + if ( + InputGuard.IsMissing(key, "addTrigger: key") + || InputGuard.IsNull(value, "addTrigger: value") + ) + return; _inAppMessages.Call("addTrigger", key, value); + } - public void AddTriggers(Dictionary triggers) => + public void AddTriggers(Dictionary triggers) + { + if (InputGuard.HasMissingEntries(triggers, "addTriggers", true)) + return; _inAppMessages.Call("addTriggers", triggers.ToMap()); + } - public void RemoveTrigger(string key) => _inAppMessages.Call("removeTrigger", key); + public void RemoveTrigger(string key) + { + if (InputGuard.IsMissing(key, "removeTrigger: key")) + return; + _inAppMessages.Call("removeTrigger", key); + } - public void RemoveTriggers(params string[] keys) => + public void RemoveTriggers(params string[] keys) + { + if (InputGuard.IsMissingAny(keys, "removeTriggers: key")) + return; _inAppMessages.Call("removeTriggers", keys.ToArrayList()); + } public void ClearTriggers() => _inAppMessages.Call("clearTriggers"); diff --git a/com.onesignal.unity.android/Runtime/AndroidSessionManager.cs b/com.onesignal.unity.android/Runtime/AndroidSessionManager.cs index cb729bd5..af081a2f 100644 --- a/com.onesignal.unity.android/Runtime/AndroidSessionManager.cs +++ b/com.onesignal.unity.android/Runtime/AndroidSessionManager.cs @@ -39,11 +39,28 @@ public AndroidSessionManager(AndroidJavaClass sdkClass) _session = sdkClass.CallStatic("getSession"); } - public void AddOutcome(string name) => _session.Call("addOutcome", name); + public void AddOutcome(string name) + { + if (InputGuard.IsMissing(name, "addOutcome: name")) + return; + _session.Call("addOutcome", name); + } - public void AddUniqueOutcome(string name) => _session.Call("addUniqueOutcome", name); + public void AddUniqueOutcome(string name) + { + if (InputGuard.IsMissing(name, "addUniqueOutcome: name")) + return; + _session.Call("addUniqueOutcome", name); + } - public void AddOutcomeWithValue(string name, float value) => + public void AddOutcomeWithValue(string name, float value) + { + if ( + InputGuard.IsMissing(name, "addOutcomeWithValue: name") + || InputGuard.IsNotFinite(value, "addOutcomeWithValue: value") + ) + return; _session.Call("addOutcomeWithValue", name, value); + } } } diff --git a/com.onesignal.unity.android/Runtime/AndroidUserManager.cs b/com.onesignal.unity.android/Runtime/AndroidUserManager.cs index 1b7242a6..869965be 100644 --- a/com.onesignal.unity.android/Runtime/AndroidUserManager.cs +++ b/com.onesignal.unity.android/Runtime/AndroidUserManager.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using OneSignalSDK; using OneSignalSDK.Android.User.Models; using OneSignalSDK.Android.Utilities; using OneSignalSDK.User; @@ -73,7 +74,13 @@ public IPushSubscription PushSubscription public string Language { - set => _user.Call("setLanguage", value); + set + { + // Empty string is the reset to the device language. Null is not. + if (InputGuard.IsNull(value, "setLanguage: language")) + return; + _user.Call("setLanguage", value); + } } public Dictionary GetTags() @@ -82,35 +89,102 @@ public Dictionary GetTags() return obj.MapToDictionary(); } - public void AddTag(string key, string value) => _user.Call("addTag", key, value); + public void AddTag(string key, string value) + { + if ( + InputGuard.IsMissing(key, "addTag: key") + || InputGuard.IsNull(value, "addTag: value") + ) + return; + _user.Call("addTag", key, value); + } - public void AddTags(Dictionary tags) => _user.Call("addTags", tags.ToMap()); + public void AddTags(Dictionary tags) + { + if (InputGuard.HasMissingEntries(tags, "addTags", true)) + return; + _user.Call("addTags", tags.ToMap()); + } - public void RemoveTag(string key) => _user.Call("removeTag", key); + public void RemoveTag(string key) + { + if (InputGuard.IsMissing(key, "removeTag: key")) + return; + _user.Call("removeTag", key); + } - public void RemoveTags(params string[] keys) => + public void RemoveTags(params string[] keys) + { + if (InputGuard.IsMissingAny(keys, "removeTags: key")) + return; _user.Call("removeTags", keys.ToArrayList()); + } - public void AddAlias(string label, string id) => _user.Call("addAlias", label, id); + public void AddAlias(string label, string id) + { + if ( + InputGuard.IsMissing(label, "addAlias: label") + || InputGuard.IsMissing(id, "addAlias: id") + ) + return; + _user.Call("addAlias", label, id); + } - public void AddAliases(Dictionary aliases) => + public void AddAliases(Dictionary aliases) + { + if (InputGuard.HasMissingEntries(aliases, "addAliases", false)) + return; _user.Call("addAliases", aliases.ToMap()); + } - public void RemoveAlias(string label) => _user.Call("removeAlias", label); + public void RemoveAlias(string label) + { + if (InputGuard.IsMissing(label, "removeAlias: label")) + return; + _user.Call("removeAlias", label); + } - public void RemoveAliases(params string[] labels) => + public void RemoveAliases(params string[] labels) + { + if (InputGuard.IsMissingAny(labels, "removeAliases: label")) + return; _user.Call("removeAliases", labels.ToArrayList()); + } - public void AddEmail(string email) => _user.Call("addEmail", email); + public void AddEmail(string email) + { + if (InputGuard.IsMissing(email, "addEmail: email")) + return; + _user.Call("addEmail", email); + } - public void RemoveEmail(string email) => _user.Call("removeEmail", email); + public void RemoveEmail(string email) + { + if (InputGuard.IsMissing(email, "removeEmail: email")) + return; + _user.Call("removeEmail", email); + } - public void AddSms(string sms) => _user.Call("addSms", sms); + public void AddSms(string sms) + { + if (InputGuard.IsMissing(sms, "addSms: sms")) + return; + _user.Call("addSms", sms); + } - public void RemoveSms(string sms) => _user.Call("removeSms", sms); + public void RemoveSms(string sms) + { + if (InputGuard.IsMissing(sms, "removeSms: sms")) + return; + _user.Call("removeSms", sms); + } - public void TrackEvent(string name, Dictionary properties = null) => - _user.Call("trackEvent", name, properties?.ToMap()); + public void TrackEvent(string name, Dictionary properties = null) + { + if (InputGuard.IsMissing(name, "trackEvent: name")) + return; + _user.Call("trackEvent", name, InputGuard.ReplaceNonFiniteNumbers(properties)?.ToMap()); + } public void Initialize() { diff --git a/com.onesignal.unity.android/Runtime/OneSignalAndroid.cs b/com.onesignal.unity.android/Runtime/OneSignalAndroid.cs index 25665873..c8b7bc8f 100644 --- a/com.onesignal.unity.android/Runtime/OneSignalAndroid.cs +++ b/com.onesignal.unity.android/Runtime/OneSignalAndroid.cs @@ -130,6 +130,8 @@ public override bool ConsentRequired public override void Initialize(string appId) { + if (InputGuard.IsMissing(appId, "initialize: appId")) + return; var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); var activity = unityPlayer.GetStatic("currentActivity"); @@ -199,6 +201,8 @@ private static void _enableHardwareAcceleration(AndroidJavaObject activity) public override void Login(string externalId, string jwtBearerToken = null) { + if (InputGuard.IsMissing(externalId, "login: externalId")) + return; _sdkClass.CallStatic("login", externalId, jwtBearerToken); } diff --git a/com.onesignal.unity.core/Runtime/InputGuard.cs b/com.onesignal.unity.core/Runtime/InputGuard.cs new file mode 100644 index 00000000..0215cf91 --- /dev/null +++ b/com.onesignal.unity.core/Runtime/InputGuard.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using OneSignalSDK.Debug.Models; +using OneSignalSDK.Debug.Utilities; + +namespace OneSignalSDK +{ + internal static class InputGuard + { + public static bool IsMissing(string value, string api) + { + if (!string.IsNullOrEmpty(value)) + return false; + SDKDebug.Error(api + " is required"); + return true; + } + + public static bool IsMissingAny(string[] values, string api) + { + if (values == null) + return IsMissing(null, api); + foreach (var value in values) + { + if (IsMissing(value, api)) + return true; + } + return false; + } + + public static bool IsNull(object value, string api) + { + if (value != null) + return false; + SDKDebug.Error(api + " is required"); + return true; + } + + public static bool IsNotFinite(float value, string api) + { + if (!float.IsNaN(value) && !float.IsInfinity(value)) + return false; + SDKDebug.Error(api + " must be a finite number"); + return true; + } + + public static bool IsInvalidLogLevel(LogLevel value, string api) + { + if (Enum.IsDefined(typeof(LogLevel), value)) + return false; + SDKDebug.Error(api + " is not a valid log level"); + return true; + } + + // Native JSON serializers reject NaN and Infinity (Android throws, iOS drops the payload). + public static Dictionary ReplaceNonFiniteNumbers( + IDictionary values + ) + { + if (values == null) + return null; + var result = new Dictionary(values.Count); + foreach (var pair in values) + result[pair.Key] = ReplaceNonFiniteNumber(pair.Value); + return result; + } + + private static object ReplaceNonFiniteNumber(object value) + { + switch (value) + { + case double d when double.IsNaN(d) || double.IsInfinity(d): + case float f when float.IsNaN(f) || float.IsInfinity(f): + return null; + case IDictionary dict: + return ReplaceNonFiniteNumbers(dict); + case IList list: + var items = new List(list.Count); + foreach (var item in list) + items.Add(ReplaceNonFiniteNumber(item)); + return items; + default: + return value; + } + } + + public static bool HasMissingEntries( + IDictionary values, + string api, + bool allowEmptyValue + ) + { + if (values == null) + return IsNull(values, api); + foreach (var pair in values) + { + if (IsMissing(pair.Key, api + ": key")) + return true; + if ( + allowEmptyValue + ? IsNull(pair.Value, api + ": value") + : IsMissing(pair.Value, api + ": value") + ) + return true; + } + return false; + } + } +} diff --git a/com.onesignal.unity.ios/Runtime/OneSignaliOS.cs b/com.onesignal.unity.ios/Runtime/OneSignaliOS.cs index 6b42dfed..e7d6eca8 100644 --- a/com.onesignal.unity.ios/Runtime/OneSignaliOS.cs +++ b/com.onesignal.unity.ios/Runtime/OneSignaliOS.cs @@ -143,6 +143,8 @@ public override bool ConsentRequired public override void Initialize(string appId) { + if (InputGuard.IsMissing(appId, "initialize: appId")) + return; _oneSignalInitialize(appId); if (_inAppMessages == null) @@ -183,7 +185,9 @@ public override void Initialize(string appId) public override void Login(string externalId, string jwtBearerToken = null) { - if (jwtBearerToken == null) + if (InputGuard.IsMissing(externalId, "login: externalId")) + return; + if (string.IsNullOrWhiteSpace(jwtBearerToken)) { _oneSignalLogin(externalId); } diff --git a/com.onesignal.unity.ios/Runtime/iOSDebugManager.cs b/com.onesignal.unity.ios/Runtime/iOSDebugManager.cs index ac88982e..2948ca0c 100644 --- a/com.onesignal.unity.ios/Runtime/iOSDebugManager.cs +++ b/com.onesignal.unity.ios/Runtime/iOSDebugManager.cs @@ -48,6 +48,8 @@ public LogLevel LogLevel get => _logLevel; set { + if (InputGuard.IsInvalidLogLevel(value, "setLogLevel")) + return; _logLevel = value; _oneSignalDebugSetLogLevel((int)value); } @@ -58,6 +60,8 @@ public LogLevel AlertLevel get => _alertLevel; set { + if (InputGuard.IsInvalidLogLevel(value, "setAlertLevel")) + return; _alertLevel = value; _oneSignalDebugSetAlertLevel((int)value); } diff --git a/com.onesignal.unity.ios/Runtime/iOSInAppMessagesManager.cs b/com.onesignal.unity.ios/Runtime/iOSInAppMessagesManager.cs index 43f0b446..d898c64c 100644 --- a/com.onesignal.unity.ios/Runtime/iOSInAppMessagesManager.cs +++ b/com.onesignal.unity.ios/Runtime/iOSInAppMessagesManager.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using OneSignalSDK; using OneSignalSDK.InAppMessages; using OneSignalSDK.InAppMessages.Internal; using OneSignalSDK.InAppMessages.Models; @@ -105,16 +106,36 @@ public bool Paused set => _oneSignalInAppMessagesSetPaused(value); } - public void AddTrigger(string key, string value) => - _oneSignalInAppMessagesAddTrigger(key, value.ToString()); + public void AddTrigger(string key, string value) + { + if ( + InputGuard.IsMissing(key, "addTrigger: key") + || InputGuard.IsNull(value, "addTrigger: value") + ) + return; + _oneSignalInAppMessagesAddTrigger(key, value); + } - public void AddTriggers(Dictionary triggers) => + public void AddTriggers(Dictionary triggers) + { + if (InputGuard.HasMissingEntries(triggers, "addTriggers", true)) + return; _oneSignalInAppMessagesAddTriggers(Json.Serialize(triggers)); + } - public void RemoveTrigger(string key) => _oneSignalInAppMessagesRemoveTrigger(key); + public void RemoveTrigger(string key) + { + if (InputGuard.IsMissing(key, "removeTrigger: key")) + return; + _oneSignalInAppMessagesRemoveTrigger(key); + } - public void RemoveTriggers(params string[] keys) => + public void RemoveTriggers(params string[] keys) + { + if (InputGuard.IsMissingAny(keys, "removeTriggers: key")) + return; _oneSignalInAppMessagesRemoveTriggers(Json.Serialize(keys)); + } public void ClearTriggers() => _oneSignalInAppMessagesClearTriggers(); diff --git a/com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs b/com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs index 7562942f..5ef30f03 100644 --- a/com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs +++ b/com.onesignal.unity.ios/Runtime/iOSLiveActivitiesManager.cs @@ -70,6 +70,11 @@ BooleanResponseDelegate callback public async Task EnterAsync(string activityId, string token) { + if ( + InputGuard.IsMissing(activityId, "enter: activityId") + || InputGuard.IsMissing(token, "enter: token") + ) + return false; var (proxy, hashCode) = WaitingProxy._setupProxy(); _oneSignalEnterLiveActivity(activityId, token, hashCode, BooleanCallbackProxy); return await proxy; @@ -78,6 +83,8 @@ public async Task EnterAsync(string activityId, string token) [System.Obsolete("Currently unsupported, avoid using this method.")] public async Task ExitAsync(string activityId) { + if (InputGuard.IsMissing(activityId, "exit: activityId")) + return false; var (proxy, hashCode) = WaitingProxy._setupProxy(); _oneSignalExitLiveActivity(activityId, hashCode, BooleanCallbackProxy); return await proxy; @@ -85,11 +92,18 @@ public async Task ExitAsync(string activityId) public void RemovePushToStartToken(string activityType) { + if (InputGuard.IsMissing(activityType, "removePushToStartToken: activityType")) + return; _oneSignalRemovePushToStartToken(activityType); } public void SetPushToStartToken(string activityType, string token) { + if ( + InputGuard.IsMissing(activityType, "setPushToStartToken: activityType") + || InputGuard.IsMissing(token, "setPushToStartToken: token") + ) + return; _oneSignalSetPushToStartToken(activityType, token); } @@ -116,10 +130,16 @@ public void StartDefault( IDictionary content ) { + if ( + InputGuard.IsMissing(activityId, "startDefault: activityId") + || InputGuard.IsNull(attributes, "startDefault: attributes") + || InputGuard.IsNull(content, "startDefault: content") + ) + return; _oneSignalStartDefaultLiveActivity( activityId, - Json.Serialize(attributes), - Json.Serialize(content) + Json.Serialize(InputGuard.ReplaceNonFiniteNumbers(attributes)), + Json.Serialize(InputGuard.ReplaceNonFiniteNumbers(content)) ); } diff --git a/com.onesignal.unity.ios/Runtime/iOSSessionManager.cs b/com.onesignal.unity.ios/Runtime/iOSSessionManager.cs index e8a0d034..be5adf31 100644 --- a/com.onesignal.unity.ios/Runtime/iOSSessionManager.cs +++ b/com.onesignal.unity.ios/Runtime/iOSSessionManager.cs @@ -42,11 +42,28 @@ internal sealed class iOSSessionManager : ISessionManager [DllImport("__Internal")] private static extern void _oneSignalSessionAddOutcomeWithValue(string name, float value); - public void AddOutcome(string name) => _oneSignalSessionAddOutcome(name); + public void AddOutcome(string name) + { + if (InputGuard.IsMissing(name, "addOutcome: name")) + return; + _oneSignalSessionAddOutcome(name); + } - public void AddUniqueOutcome(string name) => _oneSignalSessionAddUniqueOutcome(name); + public void AddUniqueOutcome(string name) + { + if (InputGuard.IsMissing(name, "addUniqueOutcome: name")) + return; + _oneSignalSessionAddUniqueOutcome(name); + } - public void AddOutcomeWithValue(string name, float value) => + public void AddOutcomeWithValue(string name, float value) + { + if ( + InputGuard.IsMissing(name, "addOutcomeWithValue: name") + || InputGuard.IsNotFinite(value, "addOutcomeWithValue: value") + ) + return; _oneSignalSessionAddOutcomeWithValue(name, value); + } } } diff --git a/com.onesignal.unity.ios/Runtime/iOSUserManager.cs b/com.onesignal.unity.ios/Runtime/iOSUserManager.cs index 66ddd344..f78fd50a 100644 --- a/com.onesignal.unity.ios/Runtime/iOSUserManager.cs +++ b/com.onesignal.unity.ios/Runtime/iOSUserManager.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using OneSignalSDK; using OneSignalSDK.iOS.User.Models; using OneSignalSDK.User; using OneSignalSDK.User.Internal; @@ -125,7 +126,13 @@ public IPushSubscription PushSubscription public string Language { - set => _oneSignalUserSetLanguage(value); + set + { + // Empty string is the reset to the device language. Null is not. + if (InputGuard.IsNull(value, "setLanguage: language")) + return; + _oneSignalUserSetLanguage(value); + } } public Dictionary GetTags() @@ -138,38 +145,105 @@ public Dictionary GetTags() return dict; } - public void AddTag(string key, string value) => _oneSignalUserAddTag(key, value); + public void AddTag(string key, string value) + { + if ( + InputGuard.IsMissing(key, "addTag: key") + || InputGuard.IsNull(value, "addTag: value") + ) + return; + _oneSignalUserAddTag(key, value); + } - public void AddTags(Dictionary tags) => + public void AddTags(Dictionary tags) + { + if (InputGuard.HasMissingEntries(tags, "addTags", true)) + return; _oneSignalUserAddTags(Json.Serialize(tags)); + } - public void RemoveTag(string key) => _oneSignalUserRemoveTag(key); + public void RemoveTag(string key) + { + if (InputGuard.IsMissing(key, "removeTag: key")) + return; + _oneSignalUserRemoveTag(key); + } - public void RemoveTags(params string[] keys) => + public void RemoveTags(params string[] keys) + { + if (InputGuard.IsMissingAny(keys, "removeTags: key")) + return; _oneSignalUserRemoveTags(Json.Serialize(keys)); + } - public void AddAlias(string label, string id) => _oneSignalUserAddAlias(label, id); + public void AddAlias(string label, string id) + { + if ( + InputGuard.IsMissing(label, "addAlias: label") + || InputGuard.IsMissing(id, "addAlias: id") + ) + return; + _oneSignalUserAddAlias(label, id); + } - public void AddAliases(Dictionary aliases) => + public void AddAliases(Dictionary aliases) + { + if (InputGuard.HasMissingEntries(aliases, "addAliases", false)) + return; _oneSignalUserAddAliases(Json.Serialize(aliases)); + } - public void RemoveAlias(string label) => _oneSignalUserRemoveAlias(label); + public void RemoveAlias(string label) + { + if (InputGuard.IsMissing(label, "removeAlias: label")) + return; + _oneSignalUserRemoveAlias(label); + } - public void RemoveAliases(params string[] labels) => + public void RemoveAliases(params string[] labels) + { + if (InputGuard.IsMissingAny(labels, "removeAliases: label")) + return; _oneSignalUserRemoveAliases(Json.Serialize(labels)); + } - public void AddEmail(string email) => _oneSignalUserAddEmail(email); + public void AddEmail(string email) + { + if (InputGuard.IsMissing(email, "addEmail: email")) + return; + _oneSignalUserAddEmail(email); + } - public void RemoveEmail(string email) => _oneSignalUserRemoveEmail(email); + public void RemoveEmail(string email) + { + if (InputGuard.IsMissing(email, "removeEmail: email")) + return; + _oneSignalUserRemoveEmail(email); + } - public void AddSms(string sms) => _oneSignalUserAddSms(sms); + public void AddSms(string sms) + { + if (InputGuard.IsMissing(sms, "addSms: sms")) + return; + _oneSignalUserAddSms(sms); + } - public void RemoveSms(string sms) => _oneSignalUserRemoveSms(sms); + public void RemoveSms(string sms) + { + if (InputGuard.IsMissing(sms, "removeSms: sms")) + return; + _oneSignalUserRemoveSms(sms); + } public void TrackEvent(string name, Dictionary properties = null) { + if (InputGuard.IsMissing(name, "trackEvent: name")) + return; if (properties != null) - _oneSignalUserTrackEvent(name, Json.Serialize(properties)); + _oneSignalUserTrackEvent( + name, + Json.Serialize(InputGuard.ReplaceNonFiniteNumbers(properties)) + ); else _oneSignalUserTrackEvent(name, null); }