diff --git a/CHANGELOG.md b/CHANGELOG.md index f408c8dd91..c5abaa65c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Improvements + +- Add native SDK information in the replay option event (#4663) + ### Features - Add protocol for custom screenName for UIViewControllers (#4646) diff --git a/Sources/Sentry/SentryClient.m b/Sources/Sentry/SentryClient.m index 49ef244578..07a2191139 100644 --- a/Sources/Sentry/SentryClient.m +++ b/Sources/Sentry/SentryClient.m @@ -553,10 +553,17 @@ - (void)captureReplayEvent:(SentryReplayEvent *)replayEvent return; } - SentryEnvelope *envelope = [[SentryEnvelope alloc] - initWithHeader:[[SentryEnvelopeHeader alloc] initWithId:replayEvent.eventId] - items:@[ videoEnvelopeItem ]]; - + // Hybrid SDKs may override the sdk info for a replay Event, + // the same SDK should be used for the envelope header. + SentrySdkInfo *sdkInfo = replayEvent.sdk ? [[SentrySdkInfo alloc] initWithDict:replayEvent.sdk] + : [SentrySdkInfo global]; + SentryEnvelopeHeader *envelopeHeader = + [[SentryEnvelopeHeader alloc] initWithId:replayEvent.eventId + sdkInfo:sdkInfo + traceContext:nil]; + + SentryEnvelope *envelope = [[SentryEnvelope alloc] initWithHeader:envelopeHeader + items:@[ videoEnvelopeItem ]]; [self captureEnvelope:envelope]; } diff --git a/Sources/Sentry/include/SentryPrivate.h b/Sources/Sentry/include/SentryPrivate.h index 27c7ad8d64..c1415fe148 100644 --- a/Sources/Sentry/include/SentryPrivate.h +++ b/Sources/Sentry/include/SentryPrivate.h @@ -13,6 +13,7 @@ #import "SentryDisplayLinkWrapper.h" #import "SentryLevelHelper.h" #import "SentryLogC.h" +#import "SentryMeta.h" #import "SentryRandom.h" #import "SentrySdkInfo.h" #import "SentrySession.h" diff --git a/Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebOptionsEvent.swift b/Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebOptionsEvent.swift index 2558819d3e..fa7adb1999 100644 --- a/Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebOptionsEvent.swift +++ b/Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebOptionsEvent.swift @@ -9,7 +9,9 @@ import Foundation "errorSampleRate": options.onErrorSampleRate, "maskAllText": options.maskAllText, "maskAllImages": options.maskAllImages, - "quality": String(describing: options.quality) + "quality": String(describing: options.quality), + "nativeSdkName": SentryMeta.sdkName, + "nativeSdkVersion": SentryMeta.versionString ] if !options.maskedViewClasses.isEmpty { diff --git a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift index 7e2a8e0029..c2636ca751 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift @@ -1,3 +1,4 @@ +@_implementationOnly import _SentryPrivate import Foundation @objcMembers @@ -146,6 +147,11 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { */ let maximumDuration = TimeInterval(3_600) + /** + * Used by hybrid SDKs to be able to configure SDK info for Session Replay + */ + var sdkInfo: [String: Any]? + /** * Inittialize session replay options disabled */ @@ -183,5 +189,6 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { if let quality = SentryReplayQuality(rawValue: dictionary["quality"] as? Int ?? -1) { self.quality = quality } + sdkInfo = dictionary["sdkInfo"] as? [String: Any] } } diff --git a/Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift b/Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift index c26507ab31..bbc9d319dc 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift @@ -248,6 +248,7 @@ class SentrySessionReplay: NSObject { private func captureSegment(segment: Int, video: SentryVideoInfo, replayId: SentryId, replayType: SentryReplayType) { let replayEvent = SentryReplayEvent(eventId: replayId, replayStartTimestamp: video.start, replayType: replayType, segmentId: segment) + replayEvent.sdk = self.replayOptions.sdkInfo replayEvent.timestamp = video.end replayEvent.urls = video.screens diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift index 11d680a2a6..747a0b3a47 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayTests.swift @@ -253,6 +253,25 @@ class SentrySessionReplayTests: XCTestCase { XCTAssertFalse(fixture.displayLink.isRunning()) } + func testSdkInfoIsSet() throws { + let fixture = Fixture() + let options = SentryReplayOptions(sessionSampleRate: 1, onErrorSampleRate: 1) + options.sdkInfo = ["version": "6.0.1", "name": "sentry.test"] + + let sut = fixture.getSut(options: options) + sut.start(rootView: fixture.rootView, fullSession: true) + + fixture.dateProvider.advance(by: 1) + Dynamic(sut).newFrame(nil) + fixture.dateProvider.advance(by: 5) + Dynamic(sut).newFrame(nil) + + let event = try XCTUnwrap(fixture.lastReplayEvent) + + XCTAssertEqual(event.sdk?["version"] as? String, "6.0.1") + XCTAssertEqual(event.sdk?["name"] as? String, "sentry.test") + } + func testSaveScreenShotInBufferMode() { let fixture = Fixture() @@ -428,6 +447,8 @@ class SentrySessionReplayTests: XCTestCase { XCTAssertNil(options["maskedViewClasses"]) XCTAssertNil(options["unmaskedViewClasses"]) XCTAssertEqual(options["quality"] as? String, "medium") + XCTAssertEqual(options["nativeSdkName"] as? String, SentryMeta.sdkName) + XCTAssertEqual(options["nativeSdkVersion"] as? String, SentryMeta.versionString) } func testOptionsInTheEventAllChanged() throws { diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index 7d1da2aea8..1e212f8a0f 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -1953,6 +1953,26 @@ class SentryClientTest: XCTestCase { XCTAssertNil(replayEvent.debugMeta) } + func testCaptureReplayEvent_overrideEnvelopeHeaderSDKInfo() throws { + let sut = fixture.getSut() + let replayEvent = SentryReplayEvent(eventId: SentryId(), replayStartTimestamp: Date(), replayType: .session, segmentId: 2) + replayEvent.sdk = ["name": "Test SDK", "version": "1.0.0"] + let replayRecording = SentryReplayRecording(segmentId: 2, size: 200, start: Date(timeIntervalSince1970: 2), duration: 5_000, frameCount: 5, frameRate: 1, height: 930, width: 390, extraEvents: []) + + //Not a video url, but its ok for test the envelope + let movieUrl = try XCTUnwrap(Bundle(for: self.classForCoder).url(forResource: "Resources/raw", withExtension: "json")) + + let scope = Scope() + scope.addBreadcrumb(Breadcrumb(level: .debug, category: "Test Breadcrumb")) + + sut.capture(replayEvent, replayRecording: replayRecording, video: movieUrl, with: scope) + + let header = try XCTUnwrap(self.fixture.transport.sentEnvelopes.first?.header) + + XCTAssertEqual(header.sdkInfo?.name, "Test SDK") + XCTAssertEqual(header.sdkInfo?.version, "1.0.0") + } + func testCaptureCrashEventSetReplayInScope() { let sut = fixture.getSut() let event = Event()