Skip to content
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Copied from BugsnagPerformanceConfiguration.h in bugsnag-cocoa-performance
#import "BugsnagPerformanceSpan.h"

NS_ASSUME_NONNULL_BEGIN

typedef void (^ BugsnagPerformanceSpanStartCallback)(BugsnagPerformanceSpan *span);

typedef BOOL (^ BugsnagPerformanceSpanEndCallback)(BugsnagPerformanceSpan *span);

@interface BugsnagPerformanceConfiguration : NSObject

@property (nonatomic) NSString *apiKey;
Expand Down
36 changes: 36 additions & 0 deletions packages/platforms/react-native/ios/BugsnagPerformancePlugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copied from BugsnagPerformancePlugin.h in bugsnag-cocoa-performance
#import <Foundation/Foundation.h>

@class BugsnagPerformancePluginContext;

NS_ASSUME_NONNULL_BEGIN

/**
* A plugin interface that provides a way to extend the functionality of the performance monitoring
* library. Plugins are added to the library via the -[BugsnagPerformanceConfiguration addPlugin:]
* method, and are called when the library is started.
*/
@protocol BugsnagPerformancePlugin <NSObject>

/**
* Called when the plugin is loaded. This is where you can set up any necessary resources or
* configurations for the plugin. This is called synchronously as part of
* +[BugsnagPerformance startWithConfiguration:] to configure any callbacks and hooks that the plugin needs to
* perform its work.
*
* @param context The context in which the plugin is being loaded. The context should not be used again after this method returns.
* @see +[BugsnagPerformance startWithConfiguration:]
*/
- (void)installWithContext:(BugsnagPerformancePluginContext *)context;

/**
* Start the plugin. This is called after all plugins have been installed and is where you can
* start any background tasks or other operations that the plugin needs to perform. This is
* called asynchronously after [BugsnagPerformance.start] to allow the plugin to perform
* any necessary work without blocking the main thread.
*/
- (void)start;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copied from BugsnagPerformancePluginContext.h in bugsnag-cocoa-performance
#import <Foundation/Foundation.h>
#import "BugsnagPerformancePriority.h"
#import "BugsnagPerformanceSpan.h"
#import "BugsnagPerformanceConfiguration.h"

NS_ASSUME_NONNULL_BEGIN

OBJC_EXPORT
@interface BugsnagPerformancePluginContext : NSObject

/**
* The user provided configuration for the performance monitoring library. Changes made by
* the plugin to this configuration may be *ignored* by the library, so plugins should not
* modify this configuration directly (instead making any changes via the [BugsnagPerformancePluginContext] methods).
*/
@property (nonatomic, readonly) BugsnagPerformanceConfiguration *cofiguration;

- (instancetype)init NS_UNAVAILABLE;

/**
* Add a [BugsnagPerformanceSpanStartCallback] to the list of callbacks that will be called when a span is
* started. This is a convenience method that is the same as calling
* -[BugsnagPerformancePluginContext addOnSpanStartCallback:priority:] with the default priority of [BugsnagPerformancePriorityMedium].
*
* @param callback Callback to be called on span start. Adding the same callback multiple times will not take effect
* @see -[BugsnagPerformancePluginContext addOnSpanStartCallback:priority:]
*/
- (void)addOnSpanStartCallback:(BugsnagPerformanceSpanStartCallback)callback;

/**
* Add a [BugsnagPerformanceSpanStartCallback] to the list of callbacks that will be called when a span is
* started. The priority of the callback determines the order in which it will be called,
* with lower numbers being called first. The default priority is [BugsnagPerformancePriorityMedium] (which
* is also the priority of the [BugsnagPerformanceSpanStartCallback] added in
* -[BugsnagPerformanceConfiguration addOnSpanStartCallback:]).
*
* @param callback Callback to be called on span start. Adding the same callback multiple times will not take effect
* @param priority The priority of the callback determines the order in which it will be called, with higher priorities being called first.
* @see -[BugsnagPerformanceConfiguration addOnSpanStartCallback:]
*/
- (void)addOnSpanStartCallback:(BugsnagPerformanceSpanStartCallback)callback priority:(BugsnagPerformancePriority)priority;

/**
* Add a [BugsnagPerformanceSpanEndCallback] to the list of callbacks that will be called when a span is
* ended. This is a convenience method that is the same as calling
* -[BugsnagPerformancePluginContext addOnSpanEndCallback:priority:] with the default priority of [BugsnagPerformancePriorityMedium].
*
* @param callback Callback to be called on span end. Adding the same callback multiple times will not take effect
* @see -[BugsnagPerformancePluginContext addOnSpanEndCallback:priority:]
*/
- (void)addOnSpanEndCallback:(BugsnagPerformanceSpanEndCallback)callback;

/**
* Add a [BugsnagPerformanceSpanEndCallback] to the list of callbacks that will be called when a span is
* ended. The priority of the callback determines the order in which it will be called,
* with lower numbers being called first. The default priority is [BugsnagPerformancePriorityMedium] (which
* is also the priority of the [BugsnagPerformanceSpanEndCallback] added in
* -[BugsnagPerformanceConfiguration addOnSpanEndCallback:]).
*
* @param callback Callback to be called on span end. Adding the same callback multiple times will not take effect
* @param priority The priority of the callback determines the order in which it will be called, with higher priorities being called first.
* @see -[BugsnagPerformanceConfiguration addOnSpanEndCallback:]
*/
- (void)addOnSpanEndCallback:(BugsnagPerformanceSpanEndCallback)callback priority:(BugsnagPerformancePriority)priority;

@end

NS_ASSUME_NONNULL_END
22 changes: 22 additions & 0 deletions packages/platforms/react-native/ios/BugsnagPerformancePriority.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copied from BugsnagPerformancePriority.h in bugsnag-cocoa-performance
#import <Foundation/Foundation.h>

/**
* Calling priority for callbacks and providers.
*/
typedef NSInteger BugsnagPerformancePriority;

/**
* High priority, for callbacks and providers that need to be called early
*/
extern const BugsnagPerformancePriority BugsnagPerformancePriorityHigh;

/**
* Default priority
*/
extern const BugsnagPerformancePriority BugsnagPerformancePriorityMedium;

/**
* Low priority, for callbacks and providers that need to be called late
*/
extern const BugsnagPerformancePriority BugsnagPerformancePriorityLow;
3 changes: 3 additions & 0 deletions packages/platforms/react-native/ios/BugsnagPerformanceSpan.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copied from BugsnagPerformanceSpan.h in bugsnag-cocoa-performance
#import "BugsnagPerformanceSpanContext.h"
#import "BugsnagPerformanceSpanCondition.h"

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -26,6 +27,8 @@ OBJC_EXPORT

- (void)setAttribute:(NSString *)attributeName withValue:(_Nullable id)value;

- (BugsnagPerformanceSpanCondition *_Nullable)blockWithTimeout:(NSTimeInterval)timeout NS_SWIFT_NAME(block(timeout:));

#pragma mark Private APIs (BugsnagPerformanceSpan+Private.h)

@property (nonatomic,readonly) NSMutableDictionary *attributes;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copied from BugsnagPerformanceSpanCondition.h in bugsnag-cocoa-performance
#import <Foundation/Foundation.h>
#import "BugsnagPerformanceSpanContext.h"

OBJC_EXPORT
@interface BugsnagPerformanceSpanCondition: NSObject

@property (nonatomic) BOOL isActive;

- (void)closeWithEndTime:(NSDate *)endTime NS_SWIFT_NAME(close(endTime:));
- (BugsnagPerformanceSpanContext *)upgrade;
- (void)cancel;

@end

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ OBJC_EXPORT

- (instancetype) initWithTraceIdHi:(uint64_t)traceIdHi traceIdLo:(uint64_t)traceIdLo spanId:(SpanId)spanId;

- (NSString *)encodedAsTraceParent;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#import "BugsnagReactNativeAppStartPlugin.h"

NS_ASSUME_NONNULL_BEGIN

@interface BugsnagReactNativeAppStartPlugin ()

+ (id _Nullable)singleton;

- (NSString * _Nullable)getAppStartParent;

- (void)endAppStart:(NSDate *)endTime;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
#import "BugsnagPerformancePlugin.h"

NS_ASSUME_NONNULL_BEGIN

@interface BugsnagReactNativeAppStartPlugin: NSObject <BugsnagPerformancePlugin>

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#import "BugsnagPerformancePluginContext.h"
#import "BugsnagPerformanceSpan.h"
#import "BugsnagPerformanceSpanCondition.h"
#import "BugsnagPerformanceSpanContext.h"
#import "BugsnagReactNativeAppStartPlugin+Private.h"

static const NSTimeInterval kSpanBlockTimeoutInterval = 0.5; // 500ms timeout

@interface BugsnagReactNativeAppStartPlugin ()
@property (nonatomic, strong) NSString *currentSpanId;
@property (nonatomic, strong) BugsnagPerformanceSpanCondition *currentCondition;
@property (atomic, assign) BOOL appStartComplete;
@end

@implementation BugsnagReactNativeAppStartPlugin

static BugsnagReactNativeAppStartPlugin *_sharedInstance = nil;

+ (id)singleton {
return _sharedInstance;
}

- (void)installWithContext:(BugsnagPerformancePluginContext *)context {
_sharedInstance = self;
__weak BugsnagReactNativeAppStartPlugin *weakSelf = self;

// Add span start callback with high priority (equivalent to NORM_PRIORITY + 1)
BugsnagPerformanceSpanStartCallback spanStartCallback = ^(BugsnagPerformanceSpan *span) {
[weakSelf onSpanStart:span];
};

// Add span end callback with low priority (equivalent to NORM_PRIORITY - 1)
BugsnagPerformanceSpanEndCallback spanEndCallback = ^(BugsnagPerformanceSpan *span) {
return [weakSelf onSpanEnd:span];
};

[context addOnSpanStartCallback:spanStartCallback priority:BugsnagPerformancePriorityHigh];
[context addOnSpanEndCallback:spanEndCallback priority:BugsnagPerformancePriorityLow];
}

- (void)start {
// Plugin start implementation
}

- (NSString *)getAppStartParent {
BugsnagPerformanceSpanCondition *condition;
@synchronized (self) {
condition = _currentCondition;
}

if (condition) {
BugsnagPerformanceSpanContext *nativeParent = [condition upgrade];
if (nativeParent) {
return [nativeParent encodedAsTraceParent];
}
}
return nil;
}

- (void)endAppStart:(NSDate *)endTime {
BugsnagPerformanceSpanCondition *condition;
@synchronized (self) {
condition = _currentCondition;
_currentCondition = nil;
_currentSpanId = nil;
_appStartComplete = YES;
}

if (condition) {
[condition closeWithEndTime:endTime];
}
}

- (void)onSpanStart:(BugsnagPerformanceSpan *)span {
// Check app start completion status (atomic read)
if (_appStartComplete) {
return;
}

// Check if this is a view_load span by examining attributes
NSString *category = span.attributes[@"bugsnag.span.category"];
if (![category isEqualToString:@"view_load"]) {
return;
}

// Block the span (outside synchronized block)
BugsnagPerformanceSpanCondition *spanCondition = [span blockWithTimeout:kSpanBlockTimeoutInterval];

// Update shared state atomically
@synchronized (self) {
// Cancel any existing condition
if (_currentCondition) {
[_currentCondition cancel];
}

// Only set current condition if block() returned a non-null value
if (spanCondition) {
_currentSpanId = [span encodedAsTraceParent];
_currentCondition = spanCondition;
}
}
}

- (BOOL)onSpanEnd:(BugsnagPerformanceSpan *)span {
NSString *spanId = [span encodedAsTraceParent];

BugsnagPerformanceSpanCondition *conditionToCancel = nil;
@synchronized (self) {
if (_currentCondition && [spanId isEqualToString:_currentSpanId]) {
conditionToCancel = _currentCondition;
_currentCondition = nil;
_currentSpanId = nil;
}
}

if (conditionToCancel) {
[conditionToCancel cancel];
}

return YES;
}

@end
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#import "BugsnagReactNativeAppStartPlugin+Private.h"
#import "BugsnagReactNativePerformance.h"
#import "BugsnagReactNativePerformanceCrossTalkAPIClient.h"
#import "ReactNativeSpanAttributes.h"
Expand Down Expand Up @@ -286,6 +287,14 @@ static uint64_t hexStringToUInt64(NSString *hexString) {
config[@"enabledReleaseStages"] = [nativeConfig.enabledReleaseStages allObjects];
}

BugsnagReactNativeAppStartPlugin *plugin = [BugsnagReactNativeAppStartPlugin singleton];
if (plugin != nil) {
NSString *appStartParent = [plugin getAppStartParent];
if (appStartParent != nil) {
config[@"appStartParentContext"] = appStartParent;
}
}

return config;
}

Expand Down Expand Up @@ -403,6 +412,11 @@ static uint64_t hexStringToUInt64(NSString *hexString) {
RCT_EXPORT_METHOD(endNativeAppStart:(double)endTime
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
BugsnagReactNativeAppStartPlugin *plugin = [BugsnagReactNativeAppStartPlugin singleton];
if (plugin != nil) {
NSDate *nativeEndTime = [NSDate dateWithTimeIntervalSince1970: endTime / NSEC_PER_SEC];
[plugin endAppStart:nativeEndTime];
}
resolve(nil);
}

Expand Down
Loading