Skip to content

Commit ebe4655

Browse files
antonisclaudealwxlucas-zimerman
authored
fix(ios): Resolve getNewScreenTimeToDisplay Promise with number not array (#6438)
* fix(ios): Resolve getNewScreenTimeToDisplay Promise with number not array `getTimeToDisplay:` typed its block as `RCTResponseSenderBlock` (the callback convention that wraps results in an `NSArray`), but it is only invoked via the Promise-based `getNewScreenTimeToDisplay` bridge method, whose `RCTPromiseResolveBlock` expects a raw value. The Promise therefore resolved with a single-element array instead of a timestamp number. The array survived the duration arithmetic via coercion but was assigned directly as the TTID span's `timestamp`, producing a spurious `deadline_exceeded` status and an inflated duration. Retype the block to `RCTPromiseResolveBlock` and resolve with the raw timestamp (and `nil` on non-iOS instead of an empty array). Adds a native regression test asserting the resolve value is a scalar `NSNumber`. Fixes #6433 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Update changelog --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander <alex@apantiukhov.com> Co-authored-by: LucasZF <lucas-zimerman1@hotmail.com>
1 parent ffe2d6c commit ebe4655

4 files changed

Lines changed: 29 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
### Fixes
1616

17+
- Fix iOS time-to-initial-display fallback spans reporting a spurious `deadline_exceeded` status and inflated duration ([#6438](https://github.com/getsentry/sentry-react-native/pull/6438))
1718
- Fix `TypeError` when `showFeedbackForm`/`showFeedbackButton`/`showScreenshotButton` is called before `FeedbackFormProvider` mounts ([#6435](https://github.com/getsentry/sentry-react-native/pull/6435))
1819
- Fix orphaned TTID/TTFD spans in the trace view ([#6437](https://github.com/getsentry/sentry-react-native/pull/6437))
1920

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTimeToDisplayTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@ final class RNSentryTimeToDisplayTests: XCTestCase {
3030
XCTAssertNotNil(newestEntry)
3131
}
3232

33+
func testGetTimeToDisplayResolvesWithNumberNotArray() {
34+
let sut = RNSentryTimeToDisplay()
35+
let expectation = self.expectation(description: "resolve block is called")
36+
var resolvedValue: Any?
37+
38+
// `getTimeToDisplay:` is invoked with an `RCTPromiseResolveBlock` from the
39+
// Promise-based `getNewScreenTimeToDisplay` bridge method, so it must resolve
40+
// with a raw timestamp value and never wrap it in an array.
41+
sut.getTimeToDisplay { value in
42+
resolvedValue = value
43+
expectation.fulfill()
44+
}
45+
46+
waitForExpectations(timeout: 5)
47+
48+
XCTAssertFalse(resolvedValue is [Any],
49+
"getTimeToDisplay must resolve with a scalar timestamp, not an array")
50+
let number = try? XCTUnwrap(resolvedValue as? NSNumber,
51+
"getTimeToDisplay must resolve with an NSNumber timestamp")
52+
XCTAssertNotNil(number)
53+
XCTAssertGreaterThan(number?.doubleValue ?? 0, 0)
54+
}
55+
3356
func testHandlesEarlyPoppedValues() {
3457
let maxSize = TIME_TO_DISPLAY_ENTRIES_MAX_SIZE + 1
3558
for i in 1...maxSize {

packages/core/ios/RNSentryTimeToDisplay.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ static const int TIME_TO_DISPLAY_ENTRIES_MAX_SIZE = 50;
99
+ (void)setActiveSpanId:(NSString *)spanId;
1010
+ (void)putTimeToInitialDisplayForActiveSpan:(NSNumber *)timestampSeconds;
1111

12-
- (void)getTimeToDisplay:(RCTResponseSenderBlock)callback;
12+
- (void)getTimeToDisplay:(RCTPromiseResolveBlock)callback;
1313

1414
@end

packages/core/ios/RNSentryTimeToDisplay.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// React Native bridge / JS thread (setActiveSpanId, pop). Synchronize every access.
77
@implementation RNSentryTimeToDisplay {
88
CADisplayLink *displayLink;
9-
RCTResponseSenderBlock resolveBlock;
9+
RCTPromiseResolveBlock resolveBlock;
1010
}
1111

1212
static NSMutableDictionary<NSString *, NSNumber *> *screenIdToRenderDuration;
@@ -92,7 +92,7 @@ + (void)putTimeToDisplayFor:(NSString *)screenId value:(NSNumber *)value
9292
}
9393

9494
// Rename requestAnimationFrame to getTimeToDisplay
95-
- (void)getTimeToDisplay:(RCTResponseSenderBlock)callback
95+
- (void)getTimeToDisplay:(RCTPromiseResolveBlock)callback
9696
{
9797
// Store the resolve block to use in the callback.
9898
resolveBlock = callback;
@@ -102,7 +102,7 @@ - (void)getTimeToDisplay:(RCTResponseSenderBlock)callback
102102
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
103103
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
104104
#else
105-
resolveBlock(@[]); // Return nothing if not iOS.
105+
resolveBlock(nil); // Return nothing if not iOS.
106106
#endif
107107
}
108108

@@ -114,7 +114,7 @@ - (void)handleDisplayLink:(CADisplayLink *)link
114114

115115
// Ensure the callback is valid and pass the current time back
116116
if (resolveBlock) {
117-
resolveBlock(@[ @(currentTime) ]); // Call the callback with the current time
117+
resolveBlock(@(currentTime)); // Resolve the promise with the current time
118118
resolveBlock = nil; // Clear the block after it's called
119119
}
120120

0 commit comments

Comments
 (0)