Skip to content

Commit bd6cf1e

Browse files
authored
Merge branch 'main' into alwx/refactor/migrate-private-sentry-sdk-only
2 parents acf4112 + ef0ab1c commit bd6cf1e

7 files changed

Lines changed: 61 additions & 7 deletions

File tree

.github/pull_request_template.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
## :scroll: Description
10-
<!--- Describe your changes in detail -->
10+
<!--- Describe your changes in detail and optionally add screenshots -->
1111

1212

1313
## :bulb: Motivation and Context
@@ -16,6 +16,10 @@
1616

1717

1818
## :green_heart: How did you test it?
19+
<!---
20+
Include a link to Sentry when applicable:
21+
* Link to Sentry: <LINK>
22+
-->
1923

2024

2125
## :pencil: Checklist

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
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))
18+
- Fix `TypeError` when `showFeedbackForm`/`showFeedbackButton`/`showScreenshotButton` is called before `FeedbackFormProvider` mounts ([#6435](https://github.com/getsentry/sentry-react-native/pull/6435))
1719
- Fix orphaned TTID/TTFD spans in the trace view ([#6437](https://github.com/getsentry/sentry-react-native/pull/6437))
1820

1921
### Internal

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
@@ -15,6 +15,6 @@ extern const int TIME_TO_DISPLAY_ENTRIES_MAX_SIZE;
1515
+ (void)setActiveSpanId:(NSString *)spanId;
1616
+ (void)putTimeToInitialDisplayForActiveSpan:(NSNumber *)timestampSeconds;
1717

18-
- (void)getTimeToDisplay:(RCTResponseSenderBlock)callback;
18+
- (void)getTimeToDisplay:(RCTPromiseResolveBlock)callback;
1919

2020
@end

packages/core/ios/RNSentryTimeToDisplay.m

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

1414
static NSMutableDictionary<NSString *, NSNumber *> *screenIdToRenderDuration;
@@ -94,7 +94,7 @@ + (void)putTimeToDisplayFor:(NSString *)screenId value:(NSNumber *)value
9494
}
9595

9696
// Rename requestAnimationFrame to getTimeToDisplay
97-
- (void)getTimeToDisplay:(RCTResponseSenderBlock)callback
97+
- (void)getTimeToDisplay:(RCTPromiseResolveBlock)callback
9898
{
9999
// Store the resolve block to use in the callback.
100100
resolveBlock = callback;
@@ -104,7 +104,7 @@ - (void)getTimeToDisplay:(RCTResponseSenderBlock)callback
104104
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
105105
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
106106
#else
107-
resolveBlock(@[]); // Return nothing if not iOS.
107+
resolveBlock(nil); // Return nothing if not iOS.
108108
#endif
109109
}
110110

@@ -116,7 +116,7 @@ - (void)handleDisplayLink:(CADisplayLink *)link
116116

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

packages/core/src/js/feedback/FeedbackFormManager.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ const NOOP_SET_VISIBILITY = (): void => {
1919

2020
abstract class FeedbackManager {
2121
protected static _isVisible = false;
22-
protected static _setVisibility: (visible: boolean) => void;
22+
// Default to NOOP so `show()`/`hide()` don't call `undefined(...)` when
23+
// invoked before `FeedbackFormProvider` mounts and calls `initialize()`.
24+
protected static _setVisibility: (visible: boolean) => void = NOOP_SET_VISIBILITY;
2325

2426
protected static get _feedbackComponentName(): string {
2527
throw new Error('Subclasses must override feedbackComponentName');

packages/core/test/feedback/FeedbackFormManager.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@ describe('FeedbackFormManager', () => {
8787
}).not.toThrow();
8888
});
8989

90+
it('showFeedbackForm does not throw before any provider or reset has run', () => {
91+
// Regression for SDK-CRASHES-REACT-NATIVE-62C: `_setVisibility` used to be
92+
// uninitialized, so calling show() before FeedbackFormProvider mounted threw
93+
// `TypeError: this._setVisibility is not a function`. Load the module fresh
94+
// so the class statics have never been touched by other tests.
95+
jest.isolateModules(() => {
96+
// eslint-disable-next-line @typescript-eslint/no-require-imports
97+
const freshManager = require('../../src/js/feedback/FeedbackFormManager');
98+
expect(() => freshManager.showFeedbackForm()).not.toThrow();
99+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("requires 'Sentry.wrap(RootComponent)'"));
100+
});
101+
});
102+
103+
it('hideFeedbackForm does not throw before any provider or reset has run', () => {
104+
// Symmetric to show(): hide() reads the same uninitialized static field.
105+
jest.isolateModules(() => {
106+
// eslint-disable-next-line @typescript-eslint/no-require-imports
107+
const freshManager = require('../../src/js/feedback/FeedbackFormManager');
108+
expect(() => freshManager.FeedbackFormManager.hide()).not.toThrow();
109+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("requires 'Sentry.wrap(RootComponent)'"));
110+
});
111+
});
112+
90113
it('showFeedbackForm displays the form with the feedbackIntegration options', async () => {
91114
mockedIsModalSupported.mockReturnValue(true);
92115
const { getByPlaceholderText, getByText } = render(

0 commit comments

Comments
 (0)