diff --git a/Bugsnag.Maui/Bugsnag.Maui.csproj b/Bugsnag.Maui/Bugsnag.Maui.csproj index a568ec9..6e9aa2c 100644 --- a/Bugsnag.Maui/Bugsnag.Maui.csproj +++ b/Bugsnag.Maui/Bugsnag.Maui.csproj @@ -3,7 +3,7 @@ net8.0;net8.0-android - net8.0;net8.0-ios + net8.0;net8.0-ios;net8.0-android true true enable @@ -43,12 +43,10 @@ - - ..\Bugsnag.Android\bin\$(Configuration)\$(TargetFramework)34\Bugsnag.Android.dll - + - + ..\Bugsnag.iOS\bin\$(Configuration)\$(TargetFramework)\Bugsnag.iOS.dll diff --git a/Bugsnag.Maui/BugsnagBuilder.android.cs b/Bugsnag.Maui/BugsnagBuilder.android.cs new file mode 100644 index 0000000..19ba3fa --- /dev/null +++ b/Bugsnag.Maui/BugsnagBuilder.android.cs @@ -0,0 +1,17 @@ +using AndroidConfiguration = Com.Bugsnag.Android.Configuration; + +namespace Bugsnag.Maui; + +public partial class BugsnagBuilder +{ + internal partial BugsnagMaui Build() + { + ArgumentException.ThrowIfNullOrEmpty(this.apiKey); + + var config = new AndroidConfiguration(this.apiKey); + config.AutoDetectErrors = this.autoDetectErrors; + config.LaunchDurationMillis = this.launchDurationMillis ?? config.LaunchDurationMillis; + config.ReleaseStage = this.releaseStage.ToString().ToLowerInvariant(); + return new BugsnagMaui(config); + } +} \ No newline at end of file diff --git a/Bugsnag.Maui/BugsnagBuilder.cs b/Bugsnag.Maui/BugsnagBuilder.cs new file mode 100644 index 0000000..bb8ab9f --- /dev/null +++ b/Bugsnag.Maui/BugsnagBuilder.cs @@ -0,0 +1,37 @@ +namespace Bugsnag.Maui; + +public partial class BugsnagBuilder +{ + private string? apiKey; + private ReleaseStage releaseStage; + private bool autoDetectErrors = true; + private int? launchDurationMillis; + + public BugsnagBuilder WithApiKey(string apiKey) + { + ArgumentException.ThrowIfNullOrEmpty(apiKey); + this.apiKey = this.apiKey; + return this; + } + + public BugsnagBuilder WithReleaseStage(ReleaseStage releaseStage) + { + this.releaseStage = releaseStage; + return this; + } + + + public BugsnagBuilder WithAutoDetectErrors(bool autoDetectErrors) + { + this.autoDetectErrors = autoDetectErrors; + return this; + } + + public BugsnagBuilder WithLaunchDurationMillis(int launchDurationMillis) + { + this.launchDurationMillis = launchDurationMillis; + return this; + } + + internal partial BugsnagMaui Build(); +} \ No newline at end of file diff --git a/Bugsnag.Maui/BugsnagBuilder.ios.cs b/Bugsnag.Maui/BugsnagBuilder.ios.cs new file mode 100644 index 0000000..8448a35 --- /dev/null +++ b/Bugsnag.Maui/BugsnagBuilder.ios.cs @@ -0,0 +1,18 @@ +using Bugsnag.iOS; + +namespace Bugsnag.Maui; + +public partial class BugsnagBuilder +{ + internal partial BugsnagMaui Build() + { + ArgumentException.ThrowIfNullOrEmpty(this.apiKey); + + var config = new BugsnagConfiguration(this.apiKey); + config.AutoDetectErrors = this.autoDetectErrors; + config.LaunchDurationMillis = (uint?)this.launchDurationMillis ?? config.LaunchDurationMillis; + config.ReleaseStage = this.releaseStage.ToString().ToLowerInvariant(); + + return new BugsnagMaui(config); + } +} \ No newline at end of file diff --git a/Bugsnag.Maui/BugsnagBuilder.standard.cs b/Bugsnag.Maui/BugsnagBuilder.standard.cs new file mode 100644 index 0000000..60a7933 --- /dev/null +++ b/Bugsnag.Maui/BugsnagBuilder.standard.cs @@ -0,0 +1,11 @@ +namespace Bugsnag.Maui; + +public partial class BugsnagBuilder +{ + internal partial BugsnagMaui Build() + { + ArgumentException.ThrowIfNullOrEmpty(this.apiKey); + + return new BugsnagMaui(); + } +} \ No newline at end of file diff --git a/Bugsnag.Maui/BugsnagMaui.android.cs b/Bugsnag.Maui/BugsnagMaui.android.cs index d93fa44..1db191a 100644 --- a/Bugsnag.Maui/BugsnagMaui.android.cs +++ b/Bugsnag.Maui/BugsnagMaui.android.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Com.Bugsnag.Android; using Org.Json; using AndroidBreadcrumbType = Com.Bugsnag.Android.BreadcrumbType; using AndroidBugsnag = Com.Bugsnag.Android.Bugsnag; @@ -8,11 +9,19 @@ namespace Bugsnag.Maui; -public partial class BugsnagMaui +public partial class BugsnagMaui : Object, IOnErrorCallback { + private readonly AndroidConfiguration config; private readonly BugsnagWrapper client = new(); - public void Start(ReleaseStage releaseStage) + public BugsnagMaui(AndroidConfiguration config) + { + this.config = config; + this.config.AddOnError(this); + this.ConfigureErrorHandling(); + } + + public void Start() { if (AndroidBugsnag.IsStarted) { @@ -20,12 +29,34 @@ public void Start(ReleaseStage releaseStage) } var context = Platform.CurrentActivity!; - var config = new AndroidConfiguration(this.apiKey); - config.AutoDetectErrors = false; - config.LaunchDurationMillis = 0; - config.ReleaseStage = releaseStage.ToString().ToLowerInvariant(); client.Start(context, config); } + + + public bool OnError(Event bugsnagEvent) + { + if (!bugsnagEvent.Unhandled + || !(bugsnagEvent.Errors?.Count > 0) + || !(bugsnagEvent.Errors[0].Stacktrace?.Count > 1)) + { + return true; + } + + // Maui Errors are handled in the Maui layer, so we do not want to send the + // native crash reports to Bugsnag. only go back 5 frames since the + // xamaerin_unhandled_exception_handler is the best indicator that the error + // was handled in the Maui layer. + var stackTrace = bugsnagEvent.Errors[0].Stacktrace; + for (int frame = 0; frame < 5; frame++) + { + if (stackTrace[frame].Method?.Contains("xamarin_unhandled_exception_handler") == true) + { + return false; + } + } + + return true; + } public void MarkLaunchCompleted() { @@ -73,7 +104,7 @@ private partial void PlatformNotify(Dictionary report, bool unha // Create a new JSONObject var jsonObject = new JSONObject(reportJson); - jsonObject.Put("type", "android"); + jsonObject.Put("type", "csharp"); var bugsnagEvent = client.CreateEvent(jsonObject, unhandled, false); diff --git a/Bugsnag.Maui/BugsnagMaui.cs b/Bugsnag.Maui/BugsnagMaui.cs index 2981b37..929b3c1 100644 --- a/Bugsnag.Maui/BugsnagMaui.cs +++ b/Bugsnag.Maui/BugsnagMaui.cs @@ -2,23 +2,20 @@ public partial class BugsnagMaui : IBugsnag { - private string apiKey; + public void Notify(Exception exception) + { + var report = new Payload.Exception(exception); + PlatformNotify(report, false); + } + + private partial void PlatformNotify(Dictionary report, bool unhandled); - public BugsnagMaui(string apiKey) + private void ConfigureErrorHandling() { MauiExceptions.UnhandledException += (sender, args) => { var report = new Payload.Exception((Exception)args.ExceptionObject); PlatformNotify(report, true); }; - this.apiKey = apiKey; } - - public void Notify(Exception exception) - { - var report = new Payload.Exception(exception); - PlatformNotify(report, false); - } - - private partial void PlatformNotify(Dictionary report, bool unhandled); } diff --git a/Bugsnag.Maui/BugsnagMaui.ios.cs b/Bugsnag.Maui/BugsnagMaui.ios.cs index 47f1e80..b523107 100644 --- a/Bugsnag.Maui/BugsnagMaui.ios.cs +++ b/Bugsnag.Maui/BugsnagMaui.ios.cs @@ -1,59 +1,118 @@ using System.Text.Json; using Bugsnag.iOS; +using Foundation; namespace Bugsnag.Maui; public partial class BugsnagMaui { - public void Start(ReleaseStage releaseStage) + private BugsnagBindingClient? bugsnagBindingClient; + private readonly BugsnagConfiguration config; + + public BugsnagMaui(BugsnagConfiguration config) + { + this.config = config; + this.config.AddOnSendErrorBlock(ShouldSendError); + this.ConfigureErrorHandling(); + } + + public void Start() + { + if (bugsnagBindingClient != null) + { + return; + } + + this.bugsnagBindingClient = new BugsnagBindingClient(); + this.bugsnagBindingClient.Start(config); + } + + private bool ShouldSendError(BugsnagEvent bugsnagEvent) { - var config = new BugsnagConfiguration(); - config.ApiKey = this.apiKey; - config.ReleaseStage = releaseStage.ToString().ToLowerInvariant(); - config.AutoDetectErrors = true; - BugsnagBindingClient.StartBugsnagWithConfiguration(config, "0.0.0"); + if (!bugsnagEvent.Unhandled + || !(bugsnagEvent.Errors?.Length > 0) + || !(bugsnagEvent.Errors[0].Stacktrace?.Length > 1)) + { + return true; + } + + // Maui Errors are handled in the Maui layer, so we do not want to send the + // native crash reports to Bugsnag. only go back 5 frames since the + // xamaerin_unhandled_exception_handler is the best indicator that the error + // was handled in the Maui layer. + var stackTrace = bugsnagEvent.Errors[0].Stacktrace; + for (int frame = 0; frame < 5; frame++) + { + if (stackTrace[frame].Method?.Contains("xamarin_unhandled_exception_handler") == true) + { + return false; + } + } + + return true; } public void MarkLaunchCompleted() { - BugsnagBindingClient.MarkLaunchCompleted(); + this.bugsnagBindingClient?.MarkLaunchCompleted(); } public void AddFeatureFlag(string name, string variant) { - BugsnagBindingClient.AddFeatureFlag(name, variant); + this.bugsnagBindingClient?.AddFeatureFlag(name, variant); } public void ClearFeatureFlag(string name) { - BugsnagBindingClient.ClearFeatureFlag(name); + this.bugsnagBindingClient?.ClearFeatureFlag(name); } public void ClearFeatureFlags() { - BugsnagBindingClient.ClearFeatureFlags(); + this.bugsnagBindingClient?.ClearFeatureFlags(); } public void SetUser(string userId) { - BugsnagBindingClient.SetUser(userId, null, null); + this.bugsnagBindingClient?.SetUser(userId, null, null); } public void LeaveBreadcrumb(string message) { - BugsnagBindingClient.AddBreadcrumb(message, "State", null); + this.bugsnagBindingClient?.LeaveBreadcrumb(message); } public void LeaveBreadcrumb(string message, Dictionary metadata) { - BugsnagBindingClient.AddBreadcrumb(message, "State", JsonSerializer.Serialize(metadata)); + + this.bugsnagBindingClient?.LeaveBreadcrumb(message, ConvertJsonToNsDictionary(JsonSerializer.Serialize(metadata)), BSGBreadcrumbType.State); } private partial void PlatformNotify(Dictionary report, bool unhandled) { + if (this.bugsnagBindingClient == null) + { + return; + } - - + var @event = this.bugsnagBindingClient.CreateEvent(ConvertJsonToNsDictionary(JsonSerializer.Serialize(report)), unhandled, false); + this.bugsnagBindingClient.DeliverEvent(@event); + } + + public static NSDictionary ConvertJsonToNsDictionary(string jsonString) + { + NSError error; + NSData jsonData = NSData.FromString(jsonString, NSStringEncoding.UTF8); + NSDictionary dictionary = (NSDictionary)NSJsonSerialization.Deserialize(jsonData, NSJsonReadingOptions.MutableContainers, out error); + + if (error != null) + { + // Handle error + Console.WriteLine($"Error converting JSON to NSDictionary: {error.LocalizedDescription}"); + return null; + } + + return dictionary; } } diff --git a/Bugsnag.Maui/BugsnagMaui.standard.cs b/Bugsnag.Maui/BugsnagMaui.standard.cs index 8bb13ff..4b230e9 100644 --- a/Bugsnag.Maui/BugsnagMaui.standard.cs +++ b/Bugsnag.Maui/BugsnagMaui.standard.cs @@ -2,7 +2,7 @@ public partial class BugsnagMaui { - public void Start(ReleaseStage releaseStage) { } + public void Start() { } public void MarkLaunchCompleted() { } diff --git a/Bugsnag.Maui/IBugsnag.cs b/Bugsnag.Maui/IBugsnag.cs index 0ff9c03..a6dfc94 100644 --- a/Bugsnag.Maui/IBugsnag.cs +++ b/Bugsnag.Maui/IBugsnag.cs @@ -2,7 +2,7 @@ namespace Bugsnag.Maui; public interface IBugsnag { - void Start(ReleaseStage releaseStage); + void Start(); void MarkLaunchCompleted(); diff --git a/Bugsnag.Maui/MauiAppBuilderExtensions.cs b/Bugsnag.Maui/MauiAppBuilderExtensions.cs index 09b160b..5c03529 100644 --- a/Bugsnag.Maui/MauiAppBuilderExtensions.cs +++ b/Bugsnag.Maui/MauiAppBuilderExtensions.cs @@ -6,24 +6,28 @@ public static class MauiAppBuilderExtensions { public static MauiAppBuilder UseBugsnag( this MauiAppBuilder builder, - ReleaseStage releaseStage, - string apiKey + Action configureDelegate ) { - var instance = new BugsnagMaui(apiKey); + var configurationBuilder = new BugsnagBuilder(); + + configureDelegate(configurationBuilder); + + var instance = configurationBuilder.Build(); + builder.Services.AddSingleton(_ => instance); builder.ConfigureLifecycleEvents(events => { #if ANDROID events.AddAndroid(android => - android.OnCreate((activity, bundle) => instance.Start(releaseStage)) + android.OnCreate((activity, bundle) => instance.Start()) ); #elif IOS events.AddiOS(ios => ios.FinishedLaunching( (_, _) => { - instance.Start(releaseStage); + instance.Start(); return true; } ) diff --git a/Bugsnag.iOS/ApiDefinitions.cs b/Bugsnag.iOS/ApiDefinitions.cs index ce1d9c8..a534e2b 100644 --- a/Bugsnag.iOS/ApiDefinitions.cs +++ b/Bugsnag.iOS/ApiDefinitions.cs @@ -4,350 +4,119 @@ namespace Bugsnag.iOS { - [BaseType(typeof(NSObject))] - interface BugsnagBindingClient -{ - [Static] - [Export("startBugsnagWithConfiguration:")] - void StartBugsnagWithConfiguration(BugsnagConfiguration configuration, string notifierVersion); - - [Static] - [Export("clearMetadata:")] - void ClearMetadata(string section); - - [Static] - [Export("clearMetadataWithKey:section:")] - void ClearMetadataWithKey(string section, string key); - - [Static] - [Export("getDictionaryFromMetadataJson:")] - NSDictionary GetDictionaryFromMetadataJson(string jsonString); - - [Static] - [Export("getEventMetaData:tab:")] - string GetEventMetaData(IntPtr eventPtr, string tab); + // Delegate for BugsnagOnErrorBlock + delegate void BugsnagOnErrorBlock(BugsnagEvent @event); - [Static] - [Export("clearEventMetadataWithKey:section:key:")] - void ClearEventMetadataWithKey(IntPtr eventPtr, string section, string key); - - [Static] - [Export("clearEventMetadataSection:section:")] - void ClearEventMetadataSection(IntPtr eventPtr, string section); - - [Static] - [Export("setEventMetadata:tab:metadataJson:")] - void SetEventMetadata(IntPtr eventPtr, string tab, string metadataJson); - - [Static] - [Export("getUserFromSession:")] - BugsnagUser GetUserFromSession(IntPtr session); - - [Static] - [Export("setUserFromSession:userId:userEmail:userName:")] - void SetUserFromSession(IntPtr session, string userId, string userEmail, string userName); - - [Static] - [Export("getUserFromEvent:")] - BugsnagUser GetUserFromEvent(IntPtr eventPtr); + // Delegate for BugsnagOnSessionBlock + delegate bool BugsnagOnSessionBlock(BugsnagSession session); - [Static] - [Export("setUserFromEvent:userId:userEmail:userName:")] - void SetUserFromEvent(IntPtr eventPtr, string userId, string userEmail, string userName); + // Delegate for BugsnagOnBreadcrumbBlock + delegate bool BugsnagOnBreadcrumbBlock(BugsnagBreadcrumb breadcrumb); - [Static] - [Export("setEventSeverity:event:severity:")] - void SetEventSeverity(IntPtr eventPtr, string severity); - - [Static] - [Export("getSeverityFromEvent:")] - string GetSeverityFromEvent(IntPtr eventPtr); - - [Static] - [Export("getFeatureFlagsFromEvent:")] - string GetFeatureFlagsFromEvent(BugsnagEvent eventPtr); - - [Static] - [Export("getBreadcrumbMetadata:")] - string GetBreadcrumbMetadata(IntPtr breadcrumb); - - [Static] - [Export("setBreadcrumbMetadata:metadataJson:")] - void SetBreadcrumbMetadata(IntPtr breadcrumb, string metadataJson); - - [Static] - [Export("getBreadcrumbType:")] - string GetBreadcrumbType(IntPtr breadcrumb); - - [Static] - [Export("setBreadcrumbType:type:")] - void SetBreadcrumbType(IntPtr breadcrumb, string type); - - [Static] - [Export("getValueAsString:object:key:")] - string GetValueAsString(IntPtr obj, string key); - - [Static] - [Export("setNumberValue:object:key:value:")] - void SetNumberValue(IntPtr obj, string key, string value); - - [Static] - [Export("getTimestampFromDateInObject:object:key:")] - double GetTimestampFromDateInObject(IntPtr obj, string key); - - [Static] - [Export("setTimestampFromDateInObject:object:key:timeStamp:")] - void SetTimestampFromDateInObject(IntPtr obj, string key, double timeStamp); - - [Static] - [Export("setRuntimeVersionsFromDevice:device:versions:count:")] - void SetRuntimeVersionsFromDevice(IntPtr device, string[] versions, int count); - - [Static] - [Export("getRuntimeVersionsFromDevice:")] - string GetRuntimeVersionsFromDevice(IntPtr device); - - [Static] - [Export("setBoolValue:object:key:value:")] - void SetBoolValue(IntPtr obj, string key, string value); - - [Static] - [Export("setStringValue:object:key:value:")] - void SetStringValue(IntPtr obj, string key, string value); - - [Static] - [Export("getErrorTypeFromError:error:")] - string GetErrorTypeFromError(IntPtr error); - - [Static] - [Export("getThreadTypeFromThread:thread:")] - string GetThreadTypeFromThread(IntPtr thread); - - [Static] - [Export("getAppFromSession:session:")] - BugsnagApp GetAppFromSession(IntPtr session); - - [Static] - [Export("getAppFromEvent:event:")] - BugsnagAppWithState GetAppFromEvent(IntPtr eventPtr); - - [Static] - [Export("getDeviceFromSession:session:")] - BugsnagDevice GetDeviceFromSession(IntPtr session); - - [Static] - [Export("getDeviceFromEvent:event:")] - BugsnagDeviceWithState GetDeviceFromEvent(IntPtr eventPtr); - - [Static] - [Export("registerForSessionCallbacks:configuration:callback:")] - void RegisterForSessionCallbacks(IntPtr configuration, Func callback); - - [Static] - [Export("registerForOnSendCallbacks:configuration:callback:")] - void RegisterForOnSendCallbacks(IntPtr configuration, Func callback); - - [Static] - [Export("registerSession:sessionId:startedAt:unhandledCount:handledCount:")] - void RegisterSession(string sessionId, long startedAt, int unhandledCount, int handledCount); - - [Static] - [Export("retrieveCurrentSession:ptr:callback:")] - void RetrieveCurrentSession(IntPtr ptr, Action callback); - - [Static] - [Export("markLaunchCompleted")] - void MarkLaunchCompleted(); - - [Static] - [Export("registerForSessionCallbacksAfterStart:callback:")] - void RegisterForSessionCallbacksAfterStart(Func callback); - - [Static] - [Export("createConfiguration:apiKey:")] - IntPtr CreateConfiguration(string apiKey); - - [Static] - [Export("setReleaseStage:configuration:releaseStage:")] - void SetReleaseStage(IntPtr configuration, string releaseStage); - - [Static] - [Export("addFeatureFlagOnConfig:configuration:name:variant:")] - void AddFeatureFlagOnConfig(IntPtr configuration, string name, string variant); - - [Static] - [Export("addFeatureFlag:name:variant:")] - void AddFeatureFlag(string name, string variant); - - [Static] - [Export("clearFeatureFlag:name:")] - void ClearFeatureFlag(string name); - - [Static] - [Export("clearFeatureFlags")] - void ClearFeatureFlags(); - - [Static] - [Export("addFeatureFlagOnEvent:event:name:variant:")] - void AddFeatureFlagOnEvent(IntPtr eventPtr, string name, string variant); - - [Static] - [Export("clearFeatureFlagOnEvent:event:name:")] - void ClearFeatureFlagOnEvent(IntPtr eventPtr, string name); + [BaseType(typeof(NSObject))] + interface BugsnagBindingClient + { + [Export("start")] + BugsnagClient Start(); - [Static] - [Export("clearFeatureFlagsOnEvent:event:")] - void ClearFeatureFlagsOnEvent(IntPtr eventPtr); + [Export("startWithApiKey:")] + BugsnagClient Start(string apiKey); - [Static] - [Export("setNotifyReleaseStages:configuration:releaseStages:releaseStagesCount:")] - void SetNotifyReleaseStages(IntPtr configuration, string[] releaseStages, int releaseStagesCount); + [Export("startWithConfiguration:")] + BugsnagClient Start(BugsnagConfiguration configuration); + [Export("isStarted")] + bool IsStarted { get; } - [Static] - [Export("setAppVersion:configuration:appVersion:")] - void SetAppVersion(IntPtr configuration, string appVersion); + [Static] + [Export("lastRunInfo")] + BugsnagLastRunInfo LastRunInfo { get; } - [Static] - [Export("setAppHangThresholdMillis:configuration:appHangThresholdMillis:")] - void SetAppHangThresholdMillis(IntPtr configuration, nuint appHangThresholdMillis); + [Export("markLaunchCompleted")] + void MarkLaunchCompleted(); - [Static] - [Export("setLaunchDurationMillis:configuration:launchDurationMillis:")] - void SetLaunchDurationMillis(IntPtr configuration, nuint launchDurationMillis); + [Export("notify:")] + void Notify(NSException exception); - [Static] - [Export("setBundleVersion:configuration:bundleVersion:")] - void SetBundleVersion(IntPtr configuration, string bundleVersion); + [Export("notify:block:")] + void Notify(NSException exception, [NullAllowed] Action block); - [Static] - [Export("setAppType:configuration:appType:")] - void SetAppType(IntPtr configuration, string appType); + [Export("notifyError:")] + void NotifyError(NSError error); - [Static] - [Export("setContext:configuration:context:")] - void SetContext(IntPtr configuration, string context); + [Export("notifyError:block:")] + void NotifyError(NSError error, [NullAllowed] Action block); - [Static] - [Export("setContextConfig:configuration:context:")] - void SetContextConfig(IntPtr configuration, string context); - - [Static] - [Export("setMaxBreadcrumbs:configuration:maxBreadcrumbs:")] - void SetMaxBreadcrumbs(IntPtr configuration, int maxBreadcrumbs); + [Export("leaveBreadcrumbWithMessage:")] + void LeaveBreadcrumb(string message); - [Static] - [Export("setMaxStringValueLength:configuration:maxStringValueLength:")] - void SetMaxStringValueLength(IntPtr configuration, int maxStringValueLength); - - [Static] - [Export("setMaxPersistedEvents:configuration:maxPersistedEvents:")] - void SetMaxPersistedEvents(IntPtr configuration, int maxPersistedEvents); + [Export("leaveBreadcrumbForNotificationName:")] + void LeaveBreadcrumbForNotificationName(string notificationName); - [Static] - [Export("setMaxPersistedSessions:configuration:maxPersistedSessions:")] - void SetMaxPersistedSessions(IntPtr configuration, int maxPersistedSessions); - - [Static] - [Export("setEnabledBreadcrumbTypes:configuration:types:count:")] - void SetEnabledBreadcrumbTypes(IntPtr configuration, string[] types, int count); - - [Static] - [Export("setEnabledTelemetryTypes:configuration:types:count:")] - void SetEnabledTelemetryTypes(IntPtr configuration, string[] types, int count); + [Export("leaveBreadcrumbWithMessage:metadata:andType:")] + void LeaveBreadcrumb(string message, [NullAllowed] NSDictionary metadata, BSGBreadcrumbType type); - [Static] - [Export("setThreadSendPolicy:configuration:threadSendPolicy:")] - void SetThreadSendPolicy(IntPtr configuration, string threadSendPolicy); + [Export("leaveNetworkRequestBreadcrumbForTask:metrics:")] + void LeaveNetworkRequestBreadcrumb(NSUrlSessionTask task, NSUrlSessionTaskMetrics metrics); - [Static] - [Export("setEnabledErrorTypes:configuration:types:count:")] - void SetEnabledErrorTypes(IntPtr configuration, string[] types, int count); + [Export("breadcrumbs")] + BugsnagBreadcrumb[] Breadcrumbs { get; } - [Static] - [Export("setDiscardClasses:configuration:classNames:count:")] - void SetDiscardClasses(IntPtr configuration, string[] classNames, int count); + [Export("startSession")] + void StartSession(); - [Static] - [Export("setUserInConfig:configuration:userId:userEmail:userName:")] - void SetUserInConfig(IntPtr configuration, string userId, string userEmail, string userName); + [Export("pauseSession")] + void PauseSession(); - [Static] - [Export("setRedactedKeys:configuration:redactedKeys:count:")] - void SetRedactedKeys(IntPtr configuration, string[] redactedKeys, int count); + [Export("resumeSession")] + bool ResumeSession(); - [Static] - [Export("setAutoNotifyConfig:configuration:autoNotify:")] - void SetAutoNotifyConfig(IntPtr configuration, bool autoNotify); + [Export("setContext:")] + void SetContext([NullAllowed] string context); - [Static] - [Export("setAutoTrackSessions:configuration:autoTrackSessions:")] - void SetAutoTrackSessions(IntPtr configuration, bool autoTrackSessions); + [Export("context")] + [NullAllowed] + string Context { get; } - [Static] - [Export("setPersistUser:configuration:persistUser:")] - void SetPersistUser(IntPtr configuration, bool persistUser); + [Export("user")] + BugsnagUser User { get; } - [Static] - [Export("setSendLaunchCrashesSynchronously:configuration:sendLaunchCrashesSynchronously:")] - void SetSendLaunchCrashesSynchronously(IntPtr configuration, bool sendLaunchCrashesSynchronously); + [Export("setUser:withEmail:andName:")] + void SetUser([NullAllowed] string userId, [NullAllowed] string email, [NullAllowed] string name); - [Static] - [Export("setEndpoints:configuration:notifyURL:sessionsURL:")] - void SetEndpoints(IntPtr configuration, string notifyURL, string sessionsURL); + [Export("addFeatureFlagWithName:variant:")] + void AddFeatureFlag(string name, [NullAllowed] string variant); - [Static] - [Export("setMetadata:section:jsonString:")] - void SetMetadata(string section, string jsonString); + [Export("addFeatureFlagWithName:")] + void AddFeatureFlag(string name); - [Static] - [Export("retrieveMetaData")] - string RetrieveMetaData(); + [Export("addFeatureFlags:")] + void AddFeatureFlags(BugsnagFeatureFlag[] featureFlags); - [Static] - [Export("removeMetadata:configuration:tab:")] - void RemoveMetadata(IntPtr configuration, string tab); + [Export("clearFeatureFlagWithName:")] + void ClearFeatureFlag(string name); - [Static] - [Export("addBreadcrumb:message:type:metadataJson:")] - void AddBreadcrumb(string message, string type, string metadataJson); + [Export("clearFeatureFlags")] + void ClearFeatureFlags(); - [Static] - [Export("retrieveBreadcrumbs:managedBreadcrumbs:breadcrumb:")] - void RetrieveBreadcrumbs(IntPtr managedBreadcrumbs, Action breadcrumb); + [Export("addOnSessionBlock:")] + NSObject AddOnSessionBlock(Func block); - [Static] - [Export("retrieveAppData")] - string RetrieveAppData(); + [Export("removeOnSession:")] + void RemoveOnSession(NSObject callback); - [Static] - [Export("retrieveLastRunInfo:lastRuninfo:callback:")] - void RetrieveLastRunInfo(IntPtr lastRuninfo, Action callback); + [Export("addOnBreadcrumbBlock:")] + NSObject AddOnBreadcrumbBlock(Func block); - [Static] - [Export("retrieveDeviceData:deviceData:callback:")] - void RetrieveDeviceData(IntPtr deviceData, Action callback); + [Export("removeOnBreadcrumb:")] + void RemoveOnBreadcrumb(NSObject callback); - [Static] - [Export("populateUser:user:")] - void PopulateUser(ref BugsnagUser user); + [Export("createEvent:unhandled:deliver:")] + NSDictionary CreateEvent(NSDictionary jsonError, bool unhandled, bool deliver); - [Static] - [Export("setUser:userId:userEmail:userName:")] - void SetUser(string userId, string userEmail, string userName); - - [Static] - [Export("startSession")] - void StartSession(); - - [Static] - [Export("pauseSession")] - void PauseSession(); + [Export("deliverEvent:")] + void DeliverEvent(NSDictionary json); + } - [Static] - [Export("resumeSession")] - bool ResumeSession(); -} - [BaseType(typeof(NSObject))] interface BugsnagEvent { @@ -746,4 +515,87 @@ interface BugsnagSession [Export("setUser:withEmail:andName:")] void SetUser([NullAllowed] string userId, [NullAllowed] string email, [NullAllowed] string name); } + + + + [BaseType(typeof(NSObject))] + interface BugsnagClient + { + [Export("notify:")] + void Notify(NSException exception); + + [Export("notify:block:")] + void Notify(NSException exception, [NullAllowed] BugsnagOnErrorBlock block); + + [Export("notifyError:")] + void NotifyError(NSError error); + + [Export("notifyError:block:")] + void NotifyError(NSError error, [NullAllowed] BugsnagOnErrorBlock block); + + [Export("leaveBreadcrumbWithMessage:")] + void LeaveBreadcrumb(string message); + + [Export("leaveBreadcrumbForNotificationName:")] + void LeaveBreadcrumbForNotificationName(string notificationName); + + [Export("leaveBreadcrumbWithMessage:metadata:andType:")] + void LeaveBreadcrumb(string message, [NullAllowed] NSDictionary metadata, BSGBreadcrumbType type); + + [Export("leaveNetworkRequestBreadcrumbForTask:metrics:")] + void LeaveNetworkRequestBreadcrumb(NSUrlSessionTask task, NSUrlSessionTaskMetrics metrics); + + [Export("breadcrumbs")] + BugsnagBreadcrumb[] Breadcrumbs { get; } + + [Export("startSession")] + void StartSession(); + + [Export("pauseSession")] + void PauseSession(); + + [Export("resumeSession")] + bool ResumeSession(); + + [Export("addOnSessionBlock:")] + NSObject AddOnSessionBlock(Func block); + + [Export("removeOnSession:")] + void RemoveOnSession(NSObject callback); + + [Export("context")] + string Context { get; set; } + + [Export("lastRunInfo")] + BugsnagLastRunInfo LastRunInfo { get; } + + [Export("markLaunchCompleted")] + void MarkLaunchCompleted(); + + [Export("user")] + BugsnagUser User { get; } + + [Export("setUser:withEmail:andName:")] + void SetUser([NullAllowed] string userId, [NullAllowed] string email, [NullAllowed] string name); + + [Export("addOnBreadcrumbBlock:")] + NSObject AddOnBreadcrumbBlock(Func block); + + [Export("removeOnBreadcrumb:")] + void RemoveOnBreadcrumb(NSObject callback); +} + + [BaseType(typeof(NSObject))] + interface BugsnagLastRunInfo + { + [Export("consecutiveLaunchCrashes")] + nuint ConsecutiveLaunchCrashes { get; } + + [Export("crashed")] + bool Crashed { get; } + + [Export("crashedDuringLaunch")] + bool CrashedDuringLaunch { get; } + } + } \ No newline at end of file diff --git a/SampleApp/MauiProgram.cs b/SampleApp/MauiProgram.cs index bbcd6e7..5d20c47 100644 --- a/SampleApp/MauiProgram.cs +++ b/SampleApp/MauiProgram.cs @@ -10,7 +10,10 @@ public static MauiApp CreateMauiApp() var builder = MauiApp.CreateBuilder(); builder .UseMauiApp() - .UseBugsnag(ReleaseStage.Local, "4fa1861e2c0ed84146c19c8cf248a53a") + .UseBugsnag(bugsnag => bugsnag + .WithApiKey("4fa1861e2c0ed84146c19c8cf248a53a") + .WithReleaseStage(ReleaseStage.Local) + ) .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); diff --git a/bugsnag-cocoa-build/bugsnag-ios.xcodeproj/project.pbxproj b/bugsnag-cocoa-build/bugsnag-ios.xcodeproj/project.pbxproj index bf3a6b9..d8a9ba4 100644 --- a/bugsnag-cocoa-build/bugsnag-ios.xcodeproj/project.pbxproj +++ b/bugsnag-cocoa-build/bugsnag-ios.xcodeproj/project.pbxproj @@ -991,6 +991,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.ramseysolutions.BugsnagBinding; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -1015,6 +1016,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.ramseysolutions.BugsnagBinding; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -1088,6 +1090,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; VALID_ARCHS = "x86_64 armv7 arm64"; + PRODUCT_BUNDLE_IDENTIFIER = com.ramseysolutions.BugsnagBinding; }; name = Release; }; diff --git a/native/Android/gradlew b/native/Android/gradlew old mode 100644 new mode 100755 diff --git a/native/ios/BugsnagBindingClient.h b/native/ios/BugsnagBindingClient.h index dc11951..11ef3a2 100644 --- a/native/ios/BugsnagBindingClient.h +++ b/native/ios/BugsnagBindingClient.h @@ -1,145 +1,324 @@ #import -#import "Bugsnag.h" -#import "BugsnagUser.h" -#import "BugsnagEvent.h" -#import "BugsnagApp.h" -#import "BugsnagAppWithState.h" -#import "BugsnagDevice.h" -#import "BugsnagDeviceWithState.h" -#import "BugsnagConfiguration.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct bugsnag_user { - const char *user_id; - const char *user_name; - const char *user_email; -}; - -void bugsnag_startBugsnagWithConfiguration(BugsnagConfiguration *configuration, char *notifierVersion); - -void bugsnag_clearMetadata(const char *section); -void bugsnag_clearMetadataWithKey(const char *section, const char *key); - -NSDictionary *getDictionaryFromMetadataJson(const char *jsonString); - -const char *bugsnag_getEventMetaData(const void *event, const char *tab); -void bugsnag_clearEventMetadataWithKey(const void *event, const char *section, const char *key); -void bugsnag_clearEventMetadataSection(const void *event, const char *section); -void bugsnag_setEventMetadata(const void *event, const char *tab, const char *metadataJson); - -BugsnagUser *bugsnag_getUserFromSession(const void *session); -void bugsnag_setUserFromSession(const void *session, char *userId, char *userEmail, char *userName); -BugsnagUser *bugsnag_getUserFromEvent(const void *event); -void bugsnag_setUserFromEvent(const void *event, char *userId, char *userEmail, char *userName); - -void bugsnag_getThreadsFromEvent(const void *event, const void *instance, void (*callback)(const void *instance, void *threads[], int threads_size)); - -void bugsnag_setEventSeverity(const void *event, const char *severity); -const char *bugsnag_getSeverityFromEvent(const void *event); - -void bugsnag_getStackframesFromError(const void *error, const void *instance, void (*callback)(const void *instance, void *stackframes[], int stackframes_size)); -void bugsnag_getStackframesFromThread(const void *thread, const void *instance, void (*callback)(const void *instance, void *stackframes[], int stackframes_size)); - -void bugsnag_getErrorsFromEvent(const void *event, const void *instance, void (*callback)(const void *instance, void *errors[], int errors_size)); -void bugsnag_getBreadcrumbsFromEvent(const void *event, const void *instance, void (*callback)(const void *instance, void *breadcrumbs[], int breadcrumbs_size)); - -const char *bugsnag_getFeatureFlagsFromEvent(BugsnagEvent *event); - -const char *bugsnag_getBreadcrumbMetadata(const void *breadcrumb); -void bugsnag_setBreadcrumbMetadata(const void *breadcrumb, const char *jsonString); - -const char *bugsnag_getBreadcrumbType(const void *breadcrumb); -void bugsnag_setBreadcrumbType(const void *breadcrumb, char *type); - -const char *bugsnag_getValueAsString(const void *object, char *key); -void bugsnag_setNumberValue(const void *object, char *key, const char *value); - -double bugsnag_getTimestampFromDateInObject(const void *object, char *key); -void bugsnag_setTimestampFromDateInObject(const void *object, char *key, double timeStamp); - -void bugsnag_setRuntimeVersionsFromDevice(const void *device, const char *versions[], int count); -const char *bugsnag_getRuntimeVersionsFromDevice(const void *device); - -void bugsnag_setBoolValue(const void *object, char *key, char *value); -void bugsnag_setStringValue(const void *object, char *key, char *value); - -const char *bugsnag_getErrorTypeFromError(const void *error); -const char *bugsnag_getThreadTypeFromThread(const void *thread); - -BugsnagApp *bugsnag_getAppFromSession(const void *session); -BugsnagAppWithState *bugsnag_getAppFromEvent(const void *event); - -BugsnagDevice *bugsnag_getDeviceFromSession(const void *session); -BugsnagDeviceWithState *bugsnag_getDeviceFromEvent(const void *event); - -void bugsnag_registerForSessionCallbacks(const void *configuration, bool (*callback)(void *session)); -void bugsnag_registerForOnSendCallbacks(const void *configuration, bool (*callback)(void *event)); - -void bugsnag_registerSession(char *sessionId, long startedAt, int unhandledCount, int handledCount); -void bugsnag_retrieveCurrentSession(const void *ptr, void (*callback)(const void *instance, const char *sessionId, const char *startedAt, int handled, int unhandled)); - -void bugsnag_markLaunchCompleted(); -void bugsnag_registerForSessionCallbacksAfterStart(bool (*callback)(void *session)); - -void *bugsnag_createConfiguration(char *apiKey); -void bugsnag_setReleaseStage(const void *configuration, char *releaseStage); - -void bugsnag_addFeatureFlagOnConfig(const void *configuration, char *name, char *variant); -void bugsnag_addFeatureFlag(char *name, char *variant); -void bugsnag_clearFeatureFlag(char *name); -void bugsnag_clearFeatureFlags(); - -void bugsnag_addFeatureFlagOnEvent(const void *event, char *name, char *variant); -void bugsnag_clearFeatureFlagOnEvent(const void *event, char *name); -void bugsnag_clearFeatureFlagsOnEvent(const void *event); - -void bugsnag_setNotifyReleaseStages(const void *configuration, const char *releaseStages[], int releaseStagesCount); -void bugsnag_setAppVersion(const void *configuration, char *appVersion); -void bugsnag_setAppHangThresholdMillis(const void *configuration, NSUInteger appHangThresholdMillis); -void bugsnag_setLaunchDurationMillis(const void *configuration, NSUInteger launchDurationMillis); -void bugsnag_setBundleVersion(const void *configuration, char *bundleVersion); -void bugsnag_setAppType(const void *configuration, char *appType); -void bugsnag_setContext(const void *configuration, char *context); -void bugsnag_setContextConfig(const void *configuration, char *context); -void bugsnag_setMaxBreadcrumbs(const void *configuration, int maxBreadcrumbs); -void bugsnag_setMaxStringValueLength(const void *configuration, int maxStringValueLength); -void bugsnag_setMaxPersistedEvents(const void *configuration, int maxPersistedEvents); -void bugsnag_setMaxPersistedSessions(const void *configuration, int maxPersistedSessions); -void bugsnag_setEnabledBreadcrumbTypes(const void *configuration, const char *types[], int count); -void bugsnag_setEnabledTelemetryTypes(const void *configuration, const char *types[], int count); -void bugsnag_setThreadSendPolicy(const void *configuration, char *threadSendPolicy); -void bugsnag_setEnabledErrorTypes(const void *configuration, const char *types[], int count); -void bugsnag_setDiscardClasses(const void *configuration, const char *classNames[], int count); -void bugsnag_setUserInConfig(const void *configuration, char *userId, char *userEmail, char *userName); -void bugsnag_setRedactedKeys(const void *configuration, const char *redactedKeys[], int count); -void bugsnag_setAutoNotifyConfig(const void *configuration, bool autoNotify); -void bugsnag_setAutoTrackSessions(const void *configuration, bool autoTrackSessions); -void bugsnag_setPersistUser(const void *configuration, bool persistUser); -void bugsnag_setSendLaunchCrashesSynchronously(const void *configuration, bool sendLaunchCrashesSynchronously); -void bugsnag_setEndpoints(const void *configuration, char *notifyURL, char *sessionsURL); - -void bugsnag_setMetadata(const char *section, const char *jsonString); -const char *bugsnag_retrieveMetaData(); -void bugsnag_removeMetadata(const void *configuration, const char *tab); - -void bugsnag_addBreadcrumb(char *message, char *type, char *metadataJson); -void bugsnag_retrieveBreadcrumbs(const void *managedBreadcrumbs, void (*breadcrumb)(const void *instance, const char *name, const char *timestamp, const char *type, const char *metadataJson)); - -char *bugsnag_retrieveAppData(); -void bugsnag_retrieveLastRunInfo(const void *lastRuninfo, void (*callback)(const void *instance, bool crashed, bool crashedDuringLaunch, int consecutiveLaunchCrashes)); -char *bugsnag_retrieveDeviceData(const void *deviceData, void (*callback)(const void *instance, const char *key, const char *value)); - -void bugsnag_populateUser(struct bugsnag_user *user); -void bugsnag_setUser(char *userId, char *userEmail, char *userName); -void bugsnag_startSession(); -void bugsnag_pauseSession(); -bool bugsnag_resumeSession(); - -#ifdef __cplusplus -} -#endif - -#endif /* BugsnagBindingClient_h */ \ No newline at end of file +#import + +@interface BugsnagBindingClient : NSObject + +/** + * All Bugsnag access is class-level. Prevent the creation of instances. + */ +- (instancetype _Nonnull )init NS_UNAVAILABLE NS_SWIFT_UNAVAILABLE("Use class methods to initialise Bugsnag."); + +/** + * Start listening for crashes. + * + * This method initializes Bugsnag with the configuration set in your Info.plist. + * + * If a Bugsnag apiKey string has not been added to your Info.plist or is empty, an + * NSException will be thrown to indicate that the configuration is not valid. + * + * Once successfully initialized, NSExceptions, C-- exceptions, Mach exceptions and + * signals will be logged to disk before your app crashes. The next time your app + * launches, these reports will be sent to your Bugsnag dashboard. + */ +- (BugsnagClient *_Nonnull)start; + +/** + * Start listening for crashes. + * + * This method initializes Bugsnag with the default configuration and the provided + * apiKey. + * + * If apiKey is nil or is empty, an NSException will be thrown to indicate that the + * configuration is not valid. + * + * Once successfully initialized, NSExceptions, C-- exceptions, Mach exceptions and + * signals will be logged to disk before your app crashes. The next time your app + * launches, these reports will be sent to your Bugsnag dashboard. + * + * @param apiKey The API key from your Bugsnag dashboard. + */ +- (BugsnagClient *_Nonnull)startWithApiKey:(NSString *_Nonnull)apiKey; + +/** + * Start listening for crashes. + * + * This method initializes Bugsnag with the provided configuration object. + * + * If the configuration's apiKey is nil or is empty, an NSException will be thrown + * to indicate that the configuration is not valid. + * + * Once successfully initialized, NSExceptions, C-- exceptions, Mach exceptions and + * signals will be logged to disk before your app crashes. The next time your app + * launches, these reports will be sent to your Bugsnag dashboard. + * + * @param configuration The configuration to use. + */ +- (BugsnagClient *_Nonnull)startWithConfiguration:(BugsnagConfiguration *_Nonnull)configuration; + +/** + * @return YES if and only if a Bugsnag.start() has been called + * and Bugsnag has initialized such that any calls to the Bugsnag methods can succeed + */ +- (BOOL)isStarted; + +/** + * Information about the last run of the app, and whether it crashed. + */ +@property (class, readonly, nullable, nonatomic) BugsnagLastRunInfo *lastRunInfo; + +/** + * Tells Bugsnag that your app has finished launching. + * + * Errors reported after calling this method will have the `BugsnagAppWithState.isLaunching` + * property set to false. + */ +- (void)markLaunchCompleted; + +// ============================================================================= +// MARK: - Notify +// ============================================================================= + +/** + * Send a custom or caught exception to Bugsnag. + * + * The exception will be sent to Bugsnag in the background allowing your + * app to continue running. + * + * @param exception The exception. + */ +- (void)notify:(NSException *_Nonnull)exception; + +/** + * Send a custom or caught exception to Bugsnag + * + * @param exception The exception + * @param block A block for optionally configuring the error report + */ +- (void)notify:(NSException *_Nonnull)exception + block:(BugsnagOnErrorBlock _Nullable)block; + +/** + * Send an error to Bugsnag + * + * @param error The error + */ +- (void)notifyError:(NSError *_Nonnull)error; + +/** + * Send an error to Bugsnag + * + * @param error The error + * @param block A block for optionally configuring the error report + */ +- (void)notifyError:(NSError *_Nonnull)error + block:(BugsnagOnErrorBlock _Nullable)block; + +// ============================================================================= +// MARK: - Breadcrumbs +// ============================================================================= + +/** + * Leave a "breadcrumb" log message, representing an action that occurred + * in your app, to aid with debugging. + * + * @param message the log message to leave + */ +- (void)leaveBreadcrumbWithMessage:(NSString *_Nonnull)message; + +/** + * Leave a "breadcrumb" log message each time a notification with a provided + * name is received by the application + * + * @param notificationName name of the notification to capture + */ +- (void)leaveBreadcrumbForNotificationName:(NSString *_Nonnull)notificationName; + +/** + * Leave a "breadcrumb" log message, representing an action that occurred + * in your app, to aid with debugging, along with additional metadata and + * a type. + * + * @param message The log message to leave. + * @param metadata Diagnostic data relating to the breadcrumb. + * Values should be serializable to JSON with NSJSONSerialization. + * @param type A BSGBreadcrumbTypeValue denoting the type of breadcrumb. + */ +- (void)leaveBreadcrumbWithMessage:(NSString *_Nonnull)message + metadata:(NSDictionary *_Nullable)metadata + andType:(BSGBreadcrumbType)type + NS_SWIFT_NAME(leaveBreadcrumb(_:metadata:type:)); + +/** + * Leave a "breadcrumb" log message representing a completed network request. + */ +- (void)leaveNetworkRequestBreadcrumbForTask:(nonnull NSURLSessionTask *)task + metrics:(nonnull NSURLSessionTaskMetrics *)metrics + API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) + NS_SWIFT_NAME(leaveNetworkRequestBreadcrumb(task:metrics:)); + +/** + * Returns the current buffer of breadcrumbs that will be sent with captured events. This + * ordered list represents the most recent breadcrumbs to be captured up to the limit + * set in `BugsnagConfiguration.maxBreadcrumbs` + */ +- (NSArray *_Nonnull)breadcrumbs; + +// ============================================================================= +// MARK: - Session +// ============================================================================= + +/** + * Starts tracking a new session. + * + * By default, sessions are automatically started when the application enters the foreground. + * If you wish to manually call startSession at + * the appropriate time in your application instead, the default behaviour can be disabled via + * autoTrackSessions. + * + * Any errors which occur in an active session count towards your application's + * stability score. You can prevent errors from counting towards your stability + * score by calling pauseSession and resumeSession at the appropriate + * time in your application. + * + * @see pauseSession: + * @see resumeSession: + */ +- (void)startSession; + +/** + * Stops tracking a session. + * + * When a session is stopped, errors will not count towards your application's + * stability score. This can be advantageous if you do not wish these calculations to + * include a certain type of error, for example, a crash in a background service. + * You should disable automatic session tracking via autoTrackSessions if you call this method. + * + * A stopped session can be resumed by calling resumeSession, + * which will make any subsequent errors count towards your application's + * stability score. Alternatively, an entirely new session can be created by calling startSession. + * + * @see startSession: + * @see resumeSession: + */ +- (void)pauseSession; + +/** + * Resumes a session which has previously been stopped, or starts a new session if none exists. + * + * If a session has already been resumed or started and has not been stopped, calling this + * method will have no effect. You should disable automatic session tracking via + * autoTrackSessions if you call this method. + * + * It's important to note that sessions are stored in memory for the lifetime of the + * application process and are not persisted on disk. Therefore calling this method on app + * startup would start a new session, rather than continuing any previous session. + * + * You should call this at the appropriate time in your application when you wish to + * resume a previously started session. Any subsequent errors which occur in your application + * will be reported to Bugsnag and will count towards your application's stability score. + * + * @see startSession: + * @see pauseSession: + * + * @return true if a previous session was resumed, false if a new session was started. + */ +- (BOOL)resumeSession; + +// ============================================================================= +// MARK: - Other methods +// ============================================================================= + +/** + * Retrieves the context - a general summary of what was happening in the application + */ +- (void)setContext:(NSString *_Nullable)context; + +/** + * Retrieves the context - a general summary of what was happening in the application + */ +- (NSString *_Nullable)context; + +// ============================================================================= +// MARK: - User +// ============================================================================= + +/** + * The current user + */ +- (BugsnagUser *_Nonnull)user; + +/** + * Set user metadata + * + * @param userId ID of the user + * @param name Name of the user + * @param email Email address of the user + * + * If user ID is nil, a Bugsnag-generated Device ID is used for the `user.id` property of events and sessions. + */ +- (void)setUser:(NSString *_Nullable)userId + withEmail:(NSString *_Nullable)email + andName:(NSString *_Nullable)name; + +// ============================================================================= +// MARK: - Feature flags +// ============================================================================= + +- (void)addFeatureFlagWithName:(nonnull NSString *)name variant:(nullable NSString *)variant + NS_SWIFT_NAME(addFeatureFlag(name:variant:)); + +- (void)addFeatureFlagWithName:(nonnull NSString *)name + NS_SWIFT_NAME(addFeatureFlag(name:)); + +- (void)addFeatureFlags:(nonnull NSArray *)featureFlags + NS_SWIFT_NAME(addFeatureFlags(_:)); + +- (void)clearFeatureFlagWithName:(nonnull NSString *)name + NS_SWIFT_NAME(clearFeatureFlag(name:)); + +- (void)clearFeatureFlags; + +// ============================================================================= +// MARK: - onSession +// ============================================================================= + +/** + * Add a callback to be invoked before a session is sent to Bugsnag. + * + * @param block A block which can modify the session + * + * @returns An opaque reference to the callback which can be passed to `removeOnSession:` + */ +- (nonnull BugsnagOnSessionRef)addOnSessionBlock:(nonnull BugsnagOnSessionBlock)block + NS_SWIFT_NAME(addOnSession(block:)); + + + +// ============================================================================= +// MARK: - onBreadcrumb +// ============================================================================= + +/** + * Add a callback to be invoked when a breadcrumb is captured by Bugsnag, to + * change the breadcrumb contents as needed + * + * @param block A block which returns YES if the breadcrumb should be captured + * + * @returns An opaque reference to the callback which can be passed to `removeOnBreadcrumb:` + */ +- (nonnull BugsnagOnBreadcrumbRef)addOnBreadcrumbBlock:(nonnull BugsnagOnBreadcrumbBlock)block + NS_SWIFT_NAME(addOnBreadcrumb(block:)); + +/** + * Remove the callback that would be invoked when a breadcrumb is captured. + * + * @param callback The opaque reference of the callback, returned by `addOnBreadcrumbBlock:` + */ +- (void)removeOnBreadcrumb:(nonnull BugsnagOnBreadcrumbRef)callback + NS_SWIFT_NAME(removeOnBreadcrumb(_:)); + +- (NSDictionary *)createEvent:(NSDictionary *)jsonError unhandled:(Boolean)unhandled deliver:(Boolean)deliver; + +- (void)deliverEvent:(NSDictionary *)json; + +@end diff --git a/native/ios/BugsnagBindingClient.m b/native/ios/BugsnagBindingClient.m index 0b48de2..af45e2c 100644 --- a/native/ios/BugsnagBindingClient.m +++ b/native/ios/BugsnagBindingClient.m @@ -1,811 +1,191 @@ +#import "BugsnagBindingClient.h" #import "BugsnagInternals.h" - struct bugsnag_user { - const char *user_id; - const char *user_name; - const char *user_email; - }; - -void bugsnag_startBugsnagWithConfiguration(BugsnagConfiguration *configuration, const char *notifierVersion) { - if (notifierVersion) { - configuration.notifier = [[BugsnagNotifier alloc] initWithName:@"Maui Bugsnag Notifier" - version:@(notifierVersion) - url:@"https://github.com/bugsnag/bugsnag-unity" - dependencies:@[[[BugsnagNotifier alloc] init]]]; - } - [Bugsnag startWithConfiguration:configuration]; - // Memory introspection is unused in a C/C++ context -} - -static const char * getJson(id obj) { - if (!obj) { - return NULL; - } - @try { - NSError *error = nil; - NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:0 error:&error]; - if (data) { - return strndup((const char *)data.bytes, data.length); - } else { - NSLog(@"%@", error); - } - } @catch (NSException *exception) { - NSLog(@"%@", exception); - } - return NULL; -} - -void bugsnag_clearMetadata(const char * section){ - if(section == NULL) - { - return; - } - [Bugsnag clearMetadataFromSection:@(section)]; -} - -void bugsnag_clearMetadataWithKey(const char * section, const char * key){ - if(section == NULL || key == NULL) - { - return; - } - [Bugsnag clearMetadataFromSection:@(section) withKey:@(key)]; -} - -NSDictionary * getDictionaryFromMetadataJson(const char * jsonString){ - @try { - NSError *error = nil; - NSData *data = [NSData dataWithBytesNoCopy:(void *)jsonString length:strlen(jsonString) freeWhenDone:NO]; - id metadata = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - if ([metadata isKindOfClass:[NSDictionary class]]) { - return metadata; - } - } @catch (NSException *exception) { - NSLog(@"%@", exception); - } - return nil; -} - -static NSString * stringFromDate(NSDate *date) { - static NSDateFormatter *dateFormatter; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - dateFormatter = [NSDateFormatter new]; - dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; - dateFormatter.dateFormat = @"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'"; - }); - return [date isKindOfClass:[NSDate class]] ? [dateFormatter stringFromDate:date] : nil; -} - -const char * bugsnag_getEventMetaData(const void *event, const char *tab) { - NSDictionary* sectionDictionary = [((__bridge BugsnagEvent *)event) getMetadataFromSection:@(tab)]; - return getJson(sectionDictionary); -} - -void bugsnag_clearEventMetadataWithKey(const void *event, const char *section, const char *key){ - [((__bridge BugsnagEvent *)event) clearMetadataFromSection:@(section) withKey:@(key)]; -} - -void bugsnag_clearEventMetadataSection(const void *event, const char *section){ - [((__bridge BugsnagEvent *)event) clearMetadataFromSection:@(section)]; -} - -void bugsnag_setEventMetadata(const void *event, const char *tab, const char *metadataJson) { - - if (tab == NULL || metadataJson == NULL) - { - return; - } - [((__bridge BugsnagEvent *)event) addMetadata:getDictionaryFromMetadataJson(metadataJson) toSection:@(tab)]; -} - - -BugsnagUser * bugsnag_getUserFromSession(const void *session){ - return ((__bridge BugsnagSession *)session).user; -} - -void bugsnag_setUserFromSession(const void *session, char *userId, char *userEmail, char *userName){ - [((__bridge BugsnagSession *)session) setUser:userId == NULL ? nil : [NSString stringWithUTF8String:userId] - withEmail:userEmail == NULL ? nil : [NSString stringWithUTF8String:userEmail] - andName:userName == NULL ? nil : [NSString stringWithUTF8String:userName]]; -} - -BugsnagUser * bugsnag_getUserFromEvent(const void *event){ - return ((__bridge BugsnagEvent *)event).user; -} - -void bugsnag_setUserFromEvent(const void *event, char *userId, char *userEmail, char *userName){ - [((__bridge BugsnagEvent *)event) setUser:userId == NULL ? nil : [NSString stringWithUTF8String:userId] - withEmail:userEmail == NULL ? nil : [NSString stringWithUTF8String:userEmail] - andName:userName == NULL ? nil : [NSString stringWithUTF8String:userName]]; -} - -void bugsnag_getThreadsFromEvent(const void *event,const void *instance, void (*callback)(const void *instance, void *threads[], int threads_size)) { - NSArray * theThreads = ((__bridge BugsnagEvent *)event).threads; - void **c_array = (void **) malloc(sizeof(void *) * ((size_t)theThreads.count)); - for (NSUInteger i = 0; i < (NSUInteger)theThreads.count; i++) { - c_array[i] = (__bridge void *)theThreads[i]; - } - callback(instance, c_array, [theThreads count]); - free(c_array); -} - - -void bugsnag_setEventSeverity(const void *event, const char *severity){ - if (strcmp(severity, "error") == 0) - { - ((__bridge BugsnagEvent *)event).severity = BSGSeverityError; - } - else if (strcmp(severity, "warning") == 0) - { - ((__bridge BugsnagEvent *)event).severity = BSGSeverityWarning; - } - else - { - ((__bridge BugsnagEvent *)event).severity = BSGSeverityInfo; - } -} - -const char * bugsnag_getSeverityFromEvent(const void *event){ - BSGSeverity theSeverity = ((__bridge BugsnagEvent *)event).severity; - if(theSeverity == BSGSeverityError) - { - return strdup("error"); - } - if(theSeverity == BSGSeverityWarning) - { - return strdup("warning"); - } - return strdup("info"); -} - - -void bugsnag_getStackframesFromError(const void *error,const void *instance, void (*callback)(const void *instance, void *stackframes[], int stackframes_size)) { - - NSArray * theStackframes = ((__bridge BugsnagError *)error).stacktrace; - void **c_array = (void **) malloc(sizeof(void *) * ((size_t)theStackframes.count)); - for (NSUInteger i = 0; i < (NSUInteger)theStackframes.count; i++) { - c_array[i] = (__bridge void *)theStackframes[i]; - } - callback(instance, c_array, [theStackframes count]); - free(c_array); -} - -void bugsnag_getStackframesFromThread(const void *thread,const void *instance, void (*callback)(const void *instance, void *stackframes[], int stackframes_size)) { +@implementation BugsnagBindingClient - NSArray * theStackframes = ((__bridge BugsnagThread *)thread).stacktrace; - void **c_array = (void **) malloc(sizeof(void *) * ((size_t)theStackframes.count)); - for (NSUInteger i = 0; i < (NSUInteger)theStackframes.count; i++) { - c_array[i] = (__bridge void *)theStackframes[i]; - } - callback(instance, c_array, [theStackframes count]); - free(c_array); -} - -void bugsnag_getErrorsFromEvent(const void *event,const void *instance, void (*callback)(const void *instance, void *errors[], int errors_size)) { - - NSArray * theErrors = ((__bridge BugsnagEvent *)event).errors; - void **c_array = (void **) malloc(sizeof(void *) * ((size_t)theErrors.count)); - for (NSUInteger i = 0; i < (NSUInteger)theErrors.count; i++) { - c_array[i] = (__bridge void *)theErrors[i]; - } - callback(instance, c_array, [theErrors count]); - free(c_array); -} - -void bugsnag_getBreadcrumbsFromEvent(const void *event,const void *instance, void (*callback)(const void *instance, void *breadcrumbs[], int breadcrumbs_size)) { - - NSArray * theBreadcrumbs = ((__bridge BugsnagEvent *)event).breadcrumbs; - void **c_array = (void **) malloc(sizeof(void *) * ((size_t)theBreadcrumbs.count)); - for (NSUInteger i = 0; i < (NSUInteger)theBreadcrumbs.count; i++) { - c_array[i] = (__bridge void *)theBreadcrumbs[i]; - } - callback(instance, c_array, [theBreadcrumbs count]); - free(c_array); -} - -const char * bugsnag_getFeatureFlagsFromEvent(BugsnagEvent *event) { - NSMutableArray *array = [NSMutableArray array]; - for (BugsnagFeatureFlag *flag in event.featureFlags) { - [array addObject:[NSDictionary dictionaryWithObjectsAndKeys: - flag.name, @"featureFlag", - flag.variant, @"variant", - nil]]; - } - return getJson(array); -} - -const char * bugsnag_getBreadcrumbMetadata(const void *breadcrumb) { - NSDictionary* sectionDictionary = ((__bridge BugsnagBreadcrumb *)breadcrumb).metadata; - return getJson(sectionDictionary); -} - -void bugsnag_setBreadcrumbMetadata(const void *breadcrumb, const char *jsonString) { - ((__bridge BugsnagBreadcrumb *)breadcrumb).metadata = jsonString ? getDictionaryFromMetadataJson(jsonString) : nil; +- (BugsnagClient *)start { + return [Bugsnag start]; } -const char * bugsnag_getBreadcrumbType(const void *breadcrumb){ - return strdup([BSGBreadcrumbTypeValue(((__bridge BugsnagBreadcrumb *)breadcrumb).type) UTF8String]); +- (BugsnagClient *)startWithApiKey:(NSString *)apiKey { + return [Bugsnag startWithApiKey:apiKey]; } -void bugsnag_setBreadcrumbType(const void *breadcrumb, char * type){ - ((__bridge BugsnagBreadcrumb *)breadcrumb).type = BSGBreadcrumbTypeFromString(@(type)); -} - -const char * bugsnag_getValueAsString(const void *object, char *key) { - id value = [(__bridge id)object valueForKey:@(key)]; - if ([value isKindOfClass:[NSString class]]) { - return strdup([value UTF8String]); - } else if ([value respondsToSelector:@selector(stringValue)]) { - return strdup([[value stringValue] UTF8String]); - } else { - return NULL; - } +- (BugsnagClient *)startWithConfiguration:(BugsnagConfiguration *)configuration { + return [Bugsnag startWithConfiguration:configuration]; } -void bugsnag_setNumberValue(const void *object, char * key, const char * value){ - NSNumberFormatter *f = [[NSNumberFormatter alloc] init]; - f.numberStyle = NSNumberFormatterDecimalStyle; - NSNumber *myNumber = [f numberFromString:@(value)]; - [(__bridge id)object setValue:myNumber forKey:@(key)]; +- (BOOL)isStarted { + return [Bugsnag isStarted]; } - - -double bugsnag_getTimestampFromDateInObject(const void *object, char * key){ - NSDate *value = (NSDate *)[(__bridge id)object valueForKey:@(key)]; - if(value != NULL && [value isKindOfClass:[NSDate class]]) - { - return [value timeIntervalSince1970]; - } - return -1; -} - -void bugsnag_setTimestampFromDateInObject(const void *object, char * key, double timeStamp){ - if(timeStamp < 0) - { - [(__bridge id)object setValue:NULL forKey:@(key)]; - } - else - { - [(__bridge id)object setValue:[NSDate dateWithTimeIntervalSince1970:timeStamp] forKey:@(key)]; - } +- (BugsnagLastRunInfo *)lastRunInfo { + return [Bugsnag lastRunInfo]; } -void bugsnag_setRuntimeVersionsFromDevice(const void *device, const char *versions[], int count){ - - NSMutableDictionary *versionsDict = [[NSMutableDictionary alloc] init]; - for (int i = 0; i < count; i+=2) { - - NSString *key = @(versions[i]); - NSString *value = @(versions[i+1]); - - versionsDict[key] = value; - } - - ((__bridge BugsnagDevice *)device).runtimeVersions = versionsDict; - +- (void)markLaunchCompleted { + [Bugsnag markLaunchCompleted]; } -const char * bugsnag_getRuntimeVersionsFromDevice(const void *device){ - NSDictionary * versions = ((__bridge BugsnagDevice *)device).runtimeVersions; - NSMutableString *returnString = [[NSMutableString alloc] initWithString:@""]; - for(id key in versions) { - NSString *keyString = @([key UTF8String]); - [returnString appendString:keyString]; - [returnString appendString:@"|"]; - - NSString *valueString = @([[versions objectForKey:key] UTF8String]); - [returnString appendString:valueString]; - [returnString appendString:@"|"]; - } - return strdup([returnString UTF8String]); +- (void)notify:(NSException *)exception { + [Bugsnag notify:exception]; } - -void bugsnag_setBoolValue(const void *object, char * key, char * value){ - NSString *nsValue = @(value); - if([nsValue isEqualToString:@"null"]) - { - [(__bridge id)object setValue:NULL forKey:@(key)]; - } - else - { - [(__bridge id)object setValue:@([nsValue boolValue]) forKey:@(key)]; - } +- (void)notify:(NSException *)exception block:(BugsnagOnErrorBlock)block { + [Bugsnag notify:exception block:block]; } -void bugsnag_setStringValue(const void *object, char * key, char * value) -{ - [(__bridge id)object setValue:value ? @(value) : nil forKey:@(key)]; +- (void)notifyError:(NSError *)error { + [Bugsnag notifyError:error]; } -const char * bugsnag_getErrorTypeFromError(const void *error){ - BSGErrorType theType = ((__bridge BugsnagError *)error).type; - if(theType == BSGErrorTypeCocoa) - { - return strdup("cocoa"); - } - if(theType == BSGErrorTypeC) - { - return strdup("c"); - } - return strdup(""); +- (void)notifyError:(NSError *)error block:(BugsnagOnErrorBlock)block { + [Bugsnag notifyError:error block:block]; } -const char * bugsnag_getThreadTypeFromThread(const void *thread){ - BSGThreadType theType = ((__bridge BugsnagThread *)thread).type; - if(theType == BSGThreadTypeCocoa) - { - return strdup("cocoa"); - } - return strdup(""); +- (void)leaveBreadcrumbWithMessage:(NSString *)message { + [Bugsnag leaveBreadcrumbWithMessage:message]; } -BugsnagApp * bugsnag_getAppFromSession(const void *session){ - return ((__bridge BugsnagSession *)session).app; +- (void)leaveBreadcrumbForNotificationName:(NSString *)notificationName { + [Bugsnag leaveBreadcrumbForNotificationName:notificationName]; } -BugsnagAppWithState * bugsnag_getAppFromEvent(const void *event){ - return ((__bridge BugsnagEvent *)event).app; +- (void)leaveBreadcrumbWithMessage:(NSString *)message metadata:(NSDictionary *)metadata andType:(BSGBreadcrumbType)type { + [Bugsnag leaveBreadcrumbWithMessage:message metadata:metadata andType:type]; } -BugsnagDevice * bugsnag_getDeviceFromSession(const void *session){ - return ((__bridge BugsnagSession *)session).device; +- (void)leaveNetworkRequestBreadcrumbForTask:(NSURLSessionTask *)task metrics:(NSURLSessionTaskMetrics *)metrics { + [Bugsnag leaveNetworkRequestBreadcrumbForTask:task metrics:metrics]; } -BugsnagDeviceWithState * bugsnag_getDeviceFromEvent(const void *event){ - return ((__bridge BugsnagEvent *)event).device; +- (NSArray *)breadcrumbs { + return [Bugsnag breadcrumbs]; } -void bugsnag_registerForSessionCallbacks(const void *configuration, bool (*callback)(void *session)){ - [((__bridge BugsnagConfiguration *)configuration) addOnSessionBlock:^BOOL (BugsnagSession *session) { - return callback((__bridge void *)session); - }]; +- (void)startSession { + [Bugsnag startSession]; } -void bugsnag_registerForOnSendCallbacks(const void *configuration, bool (*callback)(void *event)){ - [((__bridge BugsnagConfiguration *)configuration) addOnSendErrorBlock:^BOOL (BugsnagEvent *event) { - return callback((__bridge void *)event); - }]; +- (void)pauseSession { + [Bugsnag pauseSession]; } -// Called when a C# error is reported in order to keep handledCount and unhandledCount in sync -void bugsnag_registerSession(char *sessionId, long startedAt, int unhandledCount, int handledCount) { - [Bugsnag.client updateSession:^(BugsnagSession * _Nullable session) { - session.handledCount = (NSUInteger)handledCount; - session.unhandledCount = (NSUInteger)unhandledCount; - return session; - }]; +- (BOOL)resumeSession { + return [Bugsnag resumeSession]; } -void bugsnag_retrieveCurrentSession(const void *ptr, void (*callback)(const void *instance, const char *sessionId, const char *startedAt, int handled, int unhandled)) { - BugsnagSession *session = Bugsnag.client.session; - NSString *startedAt = stringFromDate(session.startedAt); - callback(ptr, session.id.UTF8String, startedAt.UTF8String, (int)session.handledCount, (int)session.unhandledCount); +- (void)setContext:(NSString *)context { + [Bugsnag setContext:context]; } -void bugsnag_markLaunchCompleted() { - [Bugsnag markLaunchCompleted]; +- (NSString *)context { + return [Bugsnag context]; } -void bugsnag_registerForSessionCallbacksAfterStart(bool (*callback)(void *session)){ - [Bugsnag addOnSessionBlock:^BOOL (BugsnagSession *session) { - return callback((__bridge void *)session); - }]; +- (BugsnagUser *)user { + return [Bugsnag user]; } -void *bugsnag_createConfiguration(char *apiKey) { - return (void *)CFBridgingRetain([[BugsnagConfiguration alloc] initWithApiKey:@(apiKey)]); +- (void)setUser:(NSString *)userId withEmail:(NSString *)email andName:(NSString *)name { + [Bugsnag setUser:userId withEmail:email andName:name]; } -void bugsnag_setReleaseStage(const void *configuration, char *releaseStage) { - NSString *ns_releaseStage = releaseStage == NULL ? nil : [NSString stringWithUTF8String: releaseStage]; - ((__bridge BugsnagConfiguration *)configuration).releaseStage = ns_releaseStage; +- (void)addFeatureFlagWithName:(NSString *)name variant:(NSString *)variant { + [Bugsnag addFeatureFlagWithName:name variant:variant]; } -void bugsnag_addFeatureFlagOnConfig(const void *configuration, char *name, char *variant) { - NSString *ns_name = name == NULL ? nil : [NSString stringWithUTF8String: name]; - NSString *ns_variant = variant == NULL ? nil : [NSString stringWithUTF8String: variant]; - [(__bridge BugsnagConfiguration *)configuration addFeatureFlagWithName:ns_name variant:ns_variant]; +- (void)addFeatureFlagWithName:(NSString *)name { + [Bugsnag addFeatureFlagWithName:name]; } -void bugsnag_addFeatureFlag(char *name, char *variant) { - NSString *ns_name = name == NULL ? nil : [NSString stringWithUTF8String: name]; - NSString *ns_variant = variant == NULL ? nil : [NSString stringWithUTF8String: variant]; - [Bugsnag addFeatureFlagWithName:ns_name variant:ns_variant]; +- (void)addFeatureFlags:(NSArray *)featureFlags { + [Bugsnag addFeatureFlags:featureFlags]; } -void bugsnag_clearFeatureFlag(char *name) { - NSString *ns_name = name == NULL ? nil : [NSString stringWithUTF8String: name]; - [Bugsnag clearFeatureFlagWithName:ns_name]; +- (void)clearFeatureFlagWithName:(NSString *)name { + [Bugsnag clearFeatureFlagWithName:name]; } -void bugsnag_clearFeatureFlags() { +- (void)clearFeatureFlags { [Bugsnag clearFeatureFlags]; } -void bugsnag_addFeatureFlagOnEvent(const void *event, char *name, char *variant) { - NSString *ns_name = name == NULL ? nil : [NSString stringWithUTF8String: name]; - NSString *ns_variant = variant == NULL ? nil : [NSString stringWithUTF8String: variant]; - [(__bridge BugsnagEvent *)event addFeatureFlagWithName:ns_name variant:ns_variant]; -} - -void bugsnag_clearFeatureFlagOnEvent(const void *event, char *name) { - NSString *ns_name = name == NULL ? nil : [NSString stringWithUTF8String: name]; - [(__bridge BugsnagEvent *)event clearFeatureFlagWithName:ns_name]; -} - -void bugsnag_clearFeatureFlagsOnEvent(const void *event) { - [(__bridge BugsnagEvent *)event clearFeatureFlags]; -} - -NSMutableSet * getSetFromStringArray(const char *values[], int valuesCount) -{ - NSMutableSet *theSet = [NSMutableSet new]; - for (int i = 0; i < valuesCount; i++) { - const char *value = values[i]; - if (value != nil) { - [theSet addObject: @(value)]; - } - } - return theSet; -} - -void bugsnag_setNotifyReleaseStages(const void *configuration, const char *releaseStages[], int releaseStagesCount){ - ((__bridge BugsnagConfiguration *)configuration).enabledReleaseStages = getSetFromStringArray(releaseStages,releaseStagesCount); -} - -void bugsnag_setAppVersion(const void *configuration, char *appVersion) { - NSString *ns_appVersion = appVersion == NULL ? nil : [NSString stringWithUTF8String: appVersion]; - ((__bridge BugsnagConfiguration *)configuration).appVersion = ns_appVersion; -} - -void bugsnag_setAppHangThresholdMillis(const void *configuration, NSUInteger appHangThresholdMillis) { - if(appHangThresholdMillis == 0) - { - ((__bridge BugsnagConfiguration *)configuration).appHangThresholdMillis = BugsnagAppHangThresholdFatalOnly; - return; - } - ((__bridge BugsnagConfiguration *)configuration).appHangThresholdMillis = appHangThresholdMillis; -} - -void bugsnag_setLaunchDurationMillis(const void *configuration, NSUInteger launchDurationMillis) { - ((__bridge BugsnagConfiguration *)configuration).launchDurationMillis = launchDurationMillis; -} - -void bugsnag_setBundleVersion(const void *configuration, char *bundleVersion) { - NSString *ns_bundleVersion = bundleVersion == NULL ? nil : [NSString stringWithUTF8String: bundleVersion]; - ((__bridge BugsnagConfiguration *)configuration).bundleVersion = ns_bundleVersion; -} - -void bugsnag_setAppType(const void *configuration, char *appType) { - NSString *ns_appType = appType == NULL ? nil : [NSString stringWithUTF8String: appType]; - ((__bridge BugsnagConfiguration *)configuration).appType = ns_appType; -} - -void bugsnag_setContext(const void *configuration, char *context) { - NSString *ns_Context = context == NULL ? nil : [NSString stringWithUTF8String: context]; - [Bugsnag.client setContext:ns_Context]; -} - -void bugsnag_setContextConfig(const void *configuration, char *context) { - NSString *ns_Context = context == NULL ? nil : [NSString stringWithUTF8String: context]; - ((__bridge BugsnagConfiguration *)configuration).context = ns_Context; -} - -void bugsnag_setMaxBreadcrumbs(const void *configuration, int maxBreadcrumbs) { - ((__bridge BugsnagConfiguration *)configuration).maxBreadcrumbs = maxBreadcrumbs; -} - -void bugsnag_setMaxStringValueLength(const void *configuration, int maxStringValueLength) { - ((__bridge BugsnagConfiguration *)configuration).maxStringValueLength = maxStringValueLength; -} - -void bugsnag_setMaxPersistedEvents(const void *configuration, int maxPersistedEvents) { - ((__bridge BugsnagConfiguration *)configuration).maxPersistedEvents = maxPersistedEvents; -} - -void bugsnag_setMaxPersistedSessions(const void *configuration, int maxPersistedSessions) { - ((__bridge BugsnagConfiguration *)configuration).maxPersistedSessions = maxPersistedSessions; -} - -void bugsnag_setEnabledBreadcrumbTypes(const void *configuration, const char *types[], int count){ - if(types == NULL) - { - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes = BSGEnabledBreadcrumbTypeAll; - return; - } - - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes = BSGEnabledBreadcrumbTypeNone; - - for (int i = 0; i < count; i++) { - const char *enabledType = types[i]; - if (enabledType != nil) { - - NSString *typeString = [[NSString alloc] initWithUTF8String:enabledType]; - - if([typeString isEqualToString:@"Navigation"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes |= BSGEnabledBreadcrumbTypeNavigation; - } - if([typeString isEqualToString:@"Request"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes |= BSGEnabledBreadcrumbTypeRequest; - } - if([typeString isEqualToString:@"Process"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes |= BSGEnabledBreadcrumbTypeProcess; - } - if([typeString isEqualToString:@"Log"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes |= BSGEnabledBreadcrumbTypeLog; - } - if([typeString isEqualToString:@"User"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes |= BSGEnabledBreadcrumbTypeUser; - } - if([typeString isEqualToString:@"State"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes |= BSGEnabledBreadcrumbTypeState; - } - if([typeString isEqualToString:@"Error"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledBreadcrumbTypes |= BSGEnabledBreadcrumbTypeError; - } - } - } -} - -void bugsnag_setEnabledTelemetryTypes(const void *configuration, const char *types[], int count){ - if(types == NULL) - { - return; - } - - ((__bridge BugsnagConfiguration *)configuration).telemetry = 0; - for (int i = 0; i < count; i++) { - const char *enabledType = types[i]; - if (enabledType != nil) { - NSString *typeString = [[NSString alloc] initWithUTF8String:enabledType]; - if([typeString isEqualToString:@"InternalErrors"]) - { - ((__bridge BugsnagConfiguration *)configuration).telemetry |= BSGTelemetryInternalErrors; - } - if([typeString isEqualToString:@"Usage"]) - { - ((__bridge BugsnagConfiguration *)configuration).telemetry |= BSGTelemetryUsage; - } - } - } -} - -void bugsnag_setThreadSendPolicy(const void *configuration, char *threadSendPolicy){ - NSString *ns_threadSendPolicy = [[NSString alloc] initWithUTF8String:threadSendPolicy]; - if([ns_threadSendPolicy isEqualToString:@"Always"]) - { - ((__bridge BugsnagConfiguration *)configuration).sendThreads = BSGThreadSendPolicyAlways; - } - if([ns_threadSendPolicy isEqualToString:@"UnhandledOnly"]) - { - ((__bridge BugsnagConfiguration *)configuration).sendThreads = BSGThreadSendPolicyUnhandledOnly; - } - if([ns_threadSendPolicy isEqualToString:@"Never"]) - { - ((__bridge BugsnagConfiguration *)configuration).sendThreads = BSGThreadSendPolicyNever; - } -} - - -void bugsnag_setEnabledErrorTypes(const void *configuration, const char *types[], int count){ - - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.appHangs = NO; - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.cppExceptions = NO; - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.signals = NO; - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.cppExceptions = NO; - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.machExceptions = NO; - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.ooms = NO; - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.thermalKills = NO; - - for (int i = 0; i < count; i++) { - const char *enabledType = types[i]; - if (enabledType != nil) { - - NSString *typeString = [[NSString alloc] initWithUTF8String:enabledType]; - - if([typeString isEqualToString:@"AppHangs"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.appHangs = YES; - } - if([typeString isEqualToString:@"UnhandledExceptions"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.cppExceptions = YES; - } - if([typeString isEqualToString:@"Signals"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.signals = YES; - } - if([typeString isEqualToString:@"CppExceptions"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.cppExceptions = YES; - } - if([typeString isEqualToString:@"MachExceptions"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.machExceptions = YES; - } - if([typeString isEqualToString:@"OOMs"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.ooms = YES; - } - if([typeString isEqualToString:@"ThermalKills"]) - { - ((__bridge BugsnagConfiguration *)configuration).enabledErrorTypes.thermalKills = YES; - } - } - } -} - -void bugsnag_setDiscardClasses(const void *configuration, const char *classNames[], int count){ - ((__bridge BugsnagConfiguration *)configuration).discardClasses = getSetFromStringArray(classNames, count); -} - -void bugsnag_setUserInConfig(const void *configuration, char *userId, char *userEmail, char *userName) -{ - [((__bridge BugsnagConfiguration *)configuration) setUser:userId == NULL ? nil : [NSString stringWithUTF8String:userId] - withEmail:userEmail == NULL ? nil : [NSString stringWithUTF8String:userEmail] - andName:userName == NULL ? nil : [NSString stringWithUTF8String:userName]]; -} - -void bugsnag_setRedactedKeys(const void *configuration, const char *redactedKeys[], int count){ - ((__bridge BugsnagConfiguration *)configuration).redactedKeys = getSetFromStringArray(redactedKeys, count); -} - -void bugsnag_setAutoNotifyConfig(const void *configuration, bool autoNotify) { - ((__bridge BugsnagConfiguration *)configuration).autoDetectErrors = autoNotify; -} - -void bugsnag_setAutoTrackSessions(const void *configuration, bool autoTrackSessions) { - ((__bridge BugsnagConfiguration *)configuration).autoTrackSessions = autoTrackSessions; -} - -void bugsnag_setPersistUser(const void *configuration, bool persistUser) { - ((__bridge BugsnagConfiguration *)configuration).persistUser = persistUser; -} - -void bugsnag_setSendLaunchCrashesSynchronously(const void *configuration, bool sendLaunchCrashesSynchronously) { - ((__bridge BugsnagConfiguration *)configuration).sendLaunchCrashesSynchronously = sendLaunchCrashesSynchronously; -} - -void bugsnag_setEndpoints(const void *configuration, char *notifyURL, char *sessionsURL) { - if (notifyURL == NULL || sessionsURL == NULL) - return; - - NSString *ns_notifyURL = [NSString stringWithUTF8String: notifyURL]; - NSString *ns_sessionsURL = [NSString stringWithUTF8String: sessionsURL]; - - ((__bridge BugsnagConfiguration *)configuration).endpoints = [[BugsnagEndpointConfiguration alloc] initWithNotify:ns_notifyURL sessions:ns_sessionsURL]; -} - -void bugsnag_setMetadata(const char *section, const char *jsonString) { - - if (section == NULL || jsonString == NULL) - { - return; - } - - NSString *tabName = [NSString stringWithUTF8String: section]; - - [Bugsnag.client addMetadata:getDictionaryFromMetadataJson(jsonString) toSection:tabName]; - -} - -const char * bugsnag_retrieveMetaData() { - return getJson([Bugsnag.client metadata].dictionary); -} - -void bugsnag_removeMetadata(const void *configuration, const char *tab) { - if (tab == NULL) - return; - - NSString *tabName = [NSString stringWithUTF8String:tab]; - [Bugsnag.client clearMetadataFromSection:tabName]; -} - -void bugsnag_addBreadcrumb(char *message, char *type, char *metadataJson) { - [Bugsnag leaveBreadcrumbWithMessage:message ? @(message) : @"" - metadata:metadataJson ? getDictionaryFromMetadataJson(metadataJson) : nil - andType:BSGBreadcrumbTypeFromString(@(type))]; -} - -void bugsnag_retrieveBreadcrumbs(const void *managedBreadcrumbs, void (*breadcrumb)(const void *instance, const char *name, const char *timestamp, const char *type, const char *metadataJson)) { - [Bugsnag.breadcrumbs enumerateObjectsUsingBlock:^(BugsnagBreadcrumb *crumb, __unused NSUInteger index, __unused BOOL *stop){ - const char *message = [crumb.message UTF8String]; - const char *timestamp = stringFromDate(crumb.timestamp).UTF8String; - const char *type = [BSGBreadcrumbTypeValue(crumb.type) UTF8String]; - - NSDictionary *metadata = crumb.metadata; - breadcrumb(managedBreadcrumbs, message, timestamp, type, getJson(metadata)); - - }]; +- (BugsnagOnSessionRef)addOnSessionBlock:(BugsnagOnSessionBlock)block { + return [Bugsnag addOnSessionBlock:block]; } -char * bugsnag_retrieveAppData() { - BugsnagAppWithState *app = [Bugsnag.client generateAppWithState:BSGGetSystemInfo()]; - if (app == nil) { - return NULL; - } - - NSMutableDictionary *appDictionary = [NSMutableDictionary dictionary]; - - if (app.bundleVersion != nil) { - [appDictionary setObject:app.bundleVersion forKey:@"bundleVersion"]; - } - if (app.id != nil) { - [appDictionary setObject:app.id forKey:@"id"]; - } - [appDictionary setObject:(app.isLaunching ? @"true" : @"false") forKey:@"isLaunching"]; - if (app.type != nil) { - [appDictionary setObject:app.type forKey:@"type"]; - } - if (app.version != nil) { - [appDictionary setObject:app.version forKey:@"version"]; - } - - return getJson(appDictionary); +- (BugsnagOnBreadcrumbRef)addOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock)block { + return [Bugsnag addOnBreadcrumbBlock:block]; } -void bugsnag_retrieveLastRunInfo(const void *lastRuninfo, void (*callback)(const void *instance, bool crashed, bool crashedDuringLaunch, int consecutiveLaunchCrashes)) { - - int consecutiveLaunchCrashes = Bugsnag.lastRunInfo.consecutiveLaunchCrashes; - bool crashed = Bugsnag.lastRunInfo.crashed; - bool crashedDuringLaunch = Bugsnag.lastRunInfo.crashedDuringLaunch; - - callback(lastRuninfo, crashed, crashedDuringLaunch, consecutiveLaunchCrashes); - +- (void)removeOnBreadcrumb:(BugsnagOnBreadcrumbRef)callback { + [Bugsnag removeOnBreadcrumb:callback]; } -char * bugsnag_retrieveDeviceData(const void *deviceData, void (*callback)(const void *instance, const char *key, const char *value)) { - BugsnagDeviceWithState *device = [Bugsnag.client generateDeviceWithState:BSGGetSystemInfo()]; - NSMutableDictionary *deviceDictionary = [[NSMutableDictionary alloc] init]; - - if (device.freeDisk != nil) [deviceDictionary setObject:device.freeDisk forKey:@"freeDisk"]; - if (device.freeMemory != nil) [deviceDictionary setObject:device.freeMemory forKey:@"freeMemory"]; - if (device.id != nil) [deviceDictionary setObject:device.id forKey:@"id"]; - if (device.jailbroken) { - [deviceDictionary setObject:@"true" forKey:@"jailbroken"]; +- (NSDictionary *)createEvent:(NSDictionary *)jsonError unhandled:(Boolean)unhandled deliver:(Boolean)deliver { + NSDictionary *systemInfo = BSGGetSystemInfo(); + BugsnagClient *client = Bugsnag.client; + BugsnagError *error = [BugsnagError errorFromJson:jsonError]; + BugsnagEvent *event = [[BugsnagEvent alloc] initWithApp:[client generateAppWithState:systemInfo] + device:[client generateDeviceWithState:systemInfo] + handledState:[BugsnagHandledState handledStateWithSeverityReason: + unhandled ? UnhandledException : HandledException] + user:client.user + metadata:[client.metadata copy] + breadcrumbs:[client breadcrumbs] + errors:@[error] + threads:@[] + session:nil /* set by -[BugsnagClient notifyInternal:block:] */]; + event.apiKey = client.configuration.apiKey; + event.context = client.context; + + // TODO: Expose BugsnagClient's featureFlagStore or provide a better way to create an event + id featureFlagStore = [client valueForKey:@"featureFlagStore"]; + @synchronized (featureFlagStore) { + event.featureFlagStore = [featureFlagStore copy]; + } + +// for (BugsnagStackframe *frame in error.stacktrace) { +// if ([frame.type isEqualToString:@"dart"] && !frame.codeIdentifier) { +// frame.codeIdentifier = DartCodeBuildId; +// } +// } +// + if (client.configuration.sendThreads == BSGThreadSendPolicyAlways) { + event.threads = [BugsnagThread allThreads:YES callStackReturnAddresses:NSThread.callStackReturnAddresses]; + } + +// NSDictionary *metadata = json[@"flutterMetadata"]; +// if (metadata != nil) { +// [event addMetadata:metadata toSection:@"flutter"]; +// if (!metadata[@"buildID"]) { +// [event addMetadata:DartCodeBuildId withKey:@"buildID" toSection:@"flutter"]; +// } +// } + +// NSDictionary *correlation = json[@"correlation"]; +// if (correlation != nil) { +// NSString *traceId = correlation[@"traceId"]; +// NSString *spanId = correlation[@"spanId"]; +// if (traceId != nil && spanId != nil) { +// [event setCorrelationTraceId:traceId spanId:spanId]; +// } +// } + + if (deliver) { + [client notifyInternal:event block:nil]; + return nil; } else { - [deviceDictionary setObject:@"false" forKey:@"jailbroken"]; + // A BugsnagStackframe initialized from JSON won't symbolicate, so do it now. + [event symbolicateIfNeeded]; + return [event toJsonWithRedactedKeys:nil]; } - if (device.locale != nil) [deviceDictionary setObject:device.locale forKey:@"locale"]; - if (device.manufacturer != nil) [deviceDictionary setObject:device.manufacturer forKey:@"manufacturer"]; - if (device.model != nil) [deviceDictionary setObject:device.model forKey:@"model"]; - if (device.modelNumber != nil) [deviceDictionary setObject:device.modelNumber forKey:@"modelNumber"]; - if (device.runtimeVersions[@"osBuild"] != nil) [deviceDictionary setObject:device.runtimeVersions[@"osBuild"] forKey:@"osBuild"]; - if (device.osName != nil) [deviceDictionary setObject:device.osName forKey:@"osName"]; - if (device.osVersion != nil) [deviceDictionary setObject:device.osVersion forKey:@"osVersion"]; - - return getJson(deviceDictionary); } - -void bugsnag_populateUser(struct bugsnag_user *user) { - user->user_id = BSGGetDefaultDeviceId().UTF8String; +- (void)deliverEvent:(NSDictionary *)json { + BugsnagEvent *event = [[BugsnagEvent alloc] initWithJson:json]; + [Bugsnag.client notifyInternal:event block:nil]; } -void bugsnag_setUser(char *userId, char *userEmail, char *userName) { - [Bugsnag setUser:userId == NULL ? nil : [NSString stringWithUTF8String:userId] - withEmail:userEmail == NULL ? nil : [NSString stringWithUTF8String:userEmail] - andName:userName == NULL ? nil : [NSString stringWithUTF8String:userName]]; -} - -void bugsnag_startSession() { - [Bugsnag startSession]; -} - -void bugsnag_pauseSession() { - [Bugsnag pauseSession]; -} - -bool bugsnag_resumeSession() { - return [Bugsnag resumeSession]; -} +@end diff --git a/native/ios/Info.plist b/native/ios/Info.plist new file mode 100644 index 0000000..4021301 --- /dev/null +++ b/native/ios/Info.plist @@ -0,0 +1,15 @@ + + + + + CFBundleIdentifier + com.ramseysolutions.BugsnagBinding + CFBundleName + BugsnagBinding + CFBundleVersion + 1.0 + CFBundleShortVersionString + 1.0 + + + \ No newline at end of file diff --git a/scripts/build-framework.sh b/scripts/build-framework.sh new file mode 100644 index 0000000..005bac9 --- /dev/null +++ b/scripts/build-framework.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Define the path to the Bugsnag.xcframework +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) + +/usr/bin/xcodebuild \ + -project "$SCRIPT_DIR/../bugsnag-cocoa-build/bugsnag-ios.xcodeproj" archive \ + -scheme BugsnagBinding \ + -configuration Release \ + -archivePath "$SCRIPT_DIR/../bugsnag-cocoa-build/bin/Release/net8.0-ios/bugsnag-iosiOS.xcarchive" \ + -destination "generic/platform=iOS" \ + ENABLE_BITCODE=NO \ + SKIP_INSTALL=NO \ + SWIFT_INSTALL_OBJC_HEADER=YES \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + OTHER_LDFLAGS="-ObjC" \ + OTHER_SWIFT_FLAGS="-no-verify-emitted-module-interface" \ + OBJC_CFLAGS="-fno-objc-msgsend-selector-stubs -ObjC" \ No newline at end of file diff --git a/scripts/download-maven.sh b/scripts/download-maven.sh index d22e6f5..36ab969 100755 --- a/scripts/download-maven.sh +++ b/scripts/download-maven.sh @@ -3,7 +3,7 @@ # Get the script location scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Get the version from the .maven-sdk-version file +# Get the version from the .sdk-version file version=$(cat "$scriptDir/../.maven-sdk-version") # Define the Maven group, artifact, and type @@ -11,7 +11,7 @@ group="com.bugsnag" outputDirectory="$scriptDir/../Bugsnag.Android/Native" # Replace . in group with / for the URL -groupUrl="${group//./\/}" +groupUrl="${group//.//}" # Create the output directory if it doesn't exist if [ -d "$outputDirectory" ]; then @@ -20,7 +20,7 @@ else mkdir -p "$outputDirectory" fi -function Get-AAR { +function GetAAR { local groupUrl=$1 local artifact=$2 local version=$3 @@ -28,12 +28,12 @@ function Get-AAR { local outputDir=$5 # Define the URL to download the AAR from - url="https://repo1.maven.org/maven2/$groupUrl/$artifact/$version/$artifact-$version.$type" + local url="https://repo1.maven.org/maven2/$groupUrl/$artifact/$version/$artifact-$version.$type" # Define the output file - outputFile="$outputDir/$artifact-$version.$type" + local outputFile="$outputDir/$artifact-$version.$type" - echo "Downloading $artifact-$version.$type from $url" + echo "Downloading $artifact-$version.$type from $url" # Download the AAR file curl -o "$outputFile" "$url" @@ -42,8 +42,8 @@ function Get-AAR { } # Call the function -Get-AAR "$groupUrl" "bugsnag-android-core" "$version" "aar" "$outputDirectory" -Get-AAR "$groupUrl" "bugsnag-plugin-android-anr" "$version" "aar" "$outputDirectory" -Get-AAR "$groupUrl" "bugsnag-plugin-android-ndk" "$version" "aar" "$outputDirectory" +GetAAR "$groupUrl" "bugsnag-android-core" "$version" "aar" "$outputDirectory" +GetAAR "$groupUrl" "bugsnag-plugin-android-anr" "$version" "aar" "$outputDirectory" +GetAAR "$groupUrl" "bugsnag-plugin-android-ndk" "$version" "aar" "$outputDirectory" echo "$version" > "$scriptDir/../Bugsnag.Android/.maven-sdk-version" \ No newline at end of file