diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 8a0a4b6a8c0..8db49037598 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -650,8 +650,6 @@ 84B7FA4629B2935F00AD93B1 /* ClearTestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD47B4C268F0B080076A663 /* ClearTestState.swift */; }; 84BA62272CAE2EEF0049F636 /* SentryUserFeedbackWidgetButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA62262CAE2EEF0049F636 /* SentryUserFeedbackWidgetButtonView.swift */; }; 84CFA4CA2C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */; }; - 84CFA4CD2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */; }; - 84CFA4CE2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */; }; 84DBC62C2CE82F12000C4904 /* SentryFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */; }; 84DEE86B2B686BD400A7BC17 /* SentrySamplerDecision.h in Headers */ = {isa = PBXBuildFile; fileRef = 84DEE86A2B686BD400A7BC17 /* SentrySamplerDecision.h */; }; 84DEE8762B69AD6400A7BC17 /* SentryLaunchProfiling.h in Headers */ = {isa = PBXBuildFile; fileRef = 84DEE8752B69AD6400A7BC17 /* SentryLaunchProfiling.h */; }; @@ -1101,6 +1099,8 @@ FA8E58F12E0AD4270049F69D /* SentryDispatchQueueWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8E58F02E0AD4220049F69D /* SentryDispatchQueueWrapper.swift */; }; FA90FAA82E06614E008CAAE8 /* SentryExtraPackages.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */; }; FA90FAFD2E070A3B008CAAE8 /* SentryURLRequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */; }; + FA914E592ECF968500C54BDD /* UserFeedbackIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA914E532ECF968000C54BDD /* UserFeedbackIntegration.swift */; }; + FA914E5B2ECF988900C54BDD /* Integrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA914E5A2ECF988700C54BDD /* Integrations.swift */; }; FA94E6912E6B92C100576666 /* SentryClientReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E68B2E6B92BE00576666 /* SentryClientReport.swift */; }; FA94E6B22E6D265800576666 /* SentryEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E6B12E6D265500576666 /* SentryEnvelope.swift */; }; FA94E7242E6F339400576666 /* SentryEnvelopeItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E7232E6F32FA00576666 /* SentryEnvelopeItemType.swift */; }; @@ -2004,8 +2004,6 @@ 84BA62262CAE2EEF0049F636 /* SentryUserFeedbackWidgetButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUserFeedbackWidgetButtonView.swift; sourceTree = ""; }; 84C47B2B2A09239100DAEB8A /* .codecov.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .codecov.yml; sourceTree = ""; }; 84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUserFeedbackWidget.swift; sourceTree = ""; }; - 84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUserFeedbackIntegration.h; path = ../../../Sentry/include/SentryUserFeedbackIntegration.h; sourceTree = ""; }; - 84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SentryUserFeedbackIntegration.m; path = ../../../Sentry/SentryUserFeedbackIntegration.m; sourceTree = ""; }; 84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFeedback.swift; sourceTree = ""; }; 84DEE86A2B686BD400A7BC17 /* SentrySamplerDecision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySamplerDecision.h; path = include/SentrySamplerDecision.h; sourceTree = ""; }; 84DEE8752B69AD6400A7BC17 /* SentryLaunchProfiling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryLaunchProfiling.h; path = Sources/Sentry/include/SentryLaunchProfiling.h; sourceTree = SOURCE_ROOT; }; @@ -2483,6 +2481,8 @@ FA8E58F02E0AD4220049F69D /* SentryDispatchQueueWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDispatchQueueWrapper.swift; sourceTree = ""; }; FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryExtraPackages.swift; sourceTree = ""; }; FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryURLRequestFactory.swift; sourceTree = ""; }; + FA914E532ECF968000C54BDD /* UserFeedbackIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFeedbackIntegration.swift; sourceTree = ""; }; + FA914E5A2ECF988700C54BDD /* Integrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Integrations.swift; sourceTree = ""; }; FA94E68B2E6B92BE00576666 /* SentryClientReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryClientReport.swift; sourceTree = ""; }; FA94E6B12E6D265500576666 /* SentryEnvelope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelope.swift; sourceTree = ""; }; FA94E7232E6F32FA00576666 /* SentryEnvelopeItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelopeItemType.swift; sourceTree = ""; }; @@ -4100,8 +4100,7 @@ 8482FA992DD7C397000E9283 /* SentryFeedbackAPI.h */, 8482FA9A2DD7C397000E9283 /* SentryFeedbackAPI.m */, 84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */, - 84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */, - 84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */, + FA914E532ECF968000C54BDD /* UserFeedbackIntegration.swift */, 84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */, 84B0DFF32CD2CF64007FB332 /* SentryUserFeedbackFormController.swift */, 84A903702D39F66F00690CE4 /* SentryUserFeedbackFormViewModel.swift */, @@ -4989,6 +4988,7 @@ FA67DCD02DDBD4EA00896B02 /* ANR */, FA67DCD22DDBD4EA00896B02 /* FramesTracking */, FA67DCD62DDBD4EA00896B02 /* Performance */, + FA914E5A2ECF988700C54BDD /* Integrations.swift */, ); path = Integrations; sourceTree = ""; @@ -5097,7 +5097,6 @@ 8E133FA625E72EB400ABD0BF /* SentrySamplingContext.h in Headers */, 0A9BF4E428A114B50068D266 /* SentryViewHierarchyIntegration.h in Headers */, D8BBD32728FD9FC00011F850 /* SentrySwift.h in Headers */, - 84CFA4CE2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h in Headers */, 8482FA9B2DD7C397000E9283 /* SentryFeedbackAPI.h in Headers */, 8E4E7C7425DAAB49006AB9E2 /* SentrySpanProtocol.h in Headers */, 8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */, @@ -5755,6 +5754,7 @@ 628094742D39584C00B3F18B /* SentryUserCodable.swift in Sources */, 631E6D341EBC679C00712345 /* SentryQueueableRequestManager.m in Sources */, 7B8713B426415BAA006D6004 /* SentryAppStartTracker.m in Sources */, + FA914E5B2ECF988900C54BDD /* Integrations.swift in Sources */, 7BDB03BB2513652900BAE198 /* _SentryDispatchQueueWrapperInternal.m in Sources */, FA6FC0A32E0B5ACE00ED2669 /* SentrySdkPackage.swift in Sources */, F48F78692E61DE28009D4E7D /* SentryReachability.swift in Sources */, @@ -5820,7 +5820,6 @@ 848A451D2BBF9504006AAAEC /* SentryProfilerTestHelpers.m in Sources */, 7B63459F280EBA7200CFA05A /* SentryUIEventTracker.m in Sources */, 7BF9EF782722B35D00B5BBEF /* SentrySubClassFinder.m in Sources */, - 84CFA4CD2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m in Sources */, 7BCFA71627D0BB50008C662C /* SentryANRTrackerV1.m in Sources */, 8459FCC02BD73EB20038E9C9 /* SentryProfilerSerialization.m in Sources */, 620078722D38F00D0022CB67 /* SentryGeoCodable.swift in Sources */, @@ -5928,6 +5927,7 @@ 6276350C2D59FACC00F7CEF6 /* SentryEventDecoder.swift in Sources */, 7B6C5EDC264E8DA80010D138 /* SentryFramesTrackingIntegration.m in Sources */, F4FE9E082E6248E40014FED5 /* SentryCrashWrapper.swift in Sources */, + FA914E592ECF968500C54BDD /* UserFeedbackIntegration.swift in Sources */, 849B8F9A2C6E906900148E1F /* SentryUserFeedbackConfiguration.swift in Sources */, 63295AF71EF3C7DB002D4490 /* SentryNSDictionarySanitize.m in Sources */, 7B8ECBFC26498958005FE2EF /* SentryDefaultAppStateManager.m in Sources */, diff --git a/Sources/Sentry/SentryFeedbackAPI.m b/Sources/Sentry/SentryFeedbackAPI.m index 67cd6f3cefb..ef38e5a0454 100644 --- a/Sources/Sentry/SentryFeedbackAPI.m +++ b/Sources/Sentry/SentryFeedbackAPI.m @@ -12,22 +12,18 @@ # import "SentryHub+Private.h" # import "SentryLogC.h" # import "SentrySDK+Private.h" -# import "SentryUserFeedbackIntegration.h" +# import "SentrySwift.h" @implementation SentryFeedbackAPI - (void)showWidget { - SentryUserFeedbackIntegration *feedback = [[SentrySDKInternal currentHub] - getInstalledIntegration:[SentryUserFeedbackIntegration class]]; - [feedback showWidget]; + [SentryUserFeedbackAccessor showWidget]; } - (void)hideWidget { - SentryUserFeedbackIntegration *feedback = [SentrySDKInternal.currentHub - getInstalledIntegration:[SentryUserFeedbackIntegration class]]; - [feedback hideWidget]; + [SentryUserFeedbackAccessor hideWidget]; } @end diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index e30e53cbccd..cc1bf15f408 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -630,10 +630,10 @@ - (BOOL)isIntegrationInstalled:(Class)integrationClass } } -- (nullable id)getInstalledIntegration:(Class)integrationClass +- (nullable id)getInstalledIntegration:(Class)integrationClass { @synchronized(_integrationsLock) { - for (id item in _installedIntegrations) { + for (id item in _installedIntegrations) { if ([item isKindOfClass:integrationClass]) { return item; } @@ -651,7 +651,7 @@ - (BOOL)hasIntegration:(NSString *)integrationName } } -- (void)addInstalledIntegration:(id)integration name:(NSString *)name +- (void)addInstalledIntegration:(id)integration name:(NSString *)name { @synchronized(_integrationsLock) { [_installedIntegrations addObject:integration]; diff --git a/Sources/Sentry/SentrySDKInternal.m b/Sources/Sentry/SentrySDKInternal.m index 5707f7d0738..34662e384d8 100644 --- a/Sources/Sentry/SentrySDKInternal.m +++ b/Sources/Sentry/SentrySDKInternal.m @@ -26,7 +26,6 @@ #import "SentrySwiftAsyncIntegration.h" #import "SentryTransactionContext.h" #import "SentryUseNSExceptionCallstackWrapper.h" -#import "SentryUserFeedbackIntegration.h" #if SENTRY_HAS_UIKIT # import "SentryAppStartTrackingIntegration.h" @@ -34,7 +33,6 @@ # import "SentryPerformanceTrackingIntegration.h" # import "SentryScreenshotIntegration.h" # import "SentryUIEventTrackingIntegration.h" -# import "SentryUserFeedbackIntegration.h" # import "SentryViewHierarchyIntegration.h" # import "SentryWatchdogTerminationTrackingIntegration.h" #endif // SENTRY_HAS_UIKIT @@ -541,10 +539,6 @@ + (void)endSession [SentryFileIOTrackingIntegration class], [SentryNetworkTrackingIntegration class], [SentrySwiftAsyncIntegration class], nil]; -#if TARGET_OS_IOS && SENTRY_HAS_UIKIT - [defaultIntegrations addObject:[SentryUserFeedbackIntegration class]]; -#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT - #if SENTRY_HAS_METRIC_KIT [defaultIntegrations addObject:[SentryMetricKitIntegration class]]; #endif // SENTRY_HAS_METRIC_KIT @@ -582,6 +576,7 @@ + (void)installIntegrations name:NSStringFromClass(integrationClass)]; } } + [SentryIntegrationInstaller installWith:options]; } + (void)reportFullyDisplayed diff --git a/Sources/Sentry/SentryUserFeedbackIntegration.m b/Sources/Sentry/SentryUserFeedbackIntegration.m deleted file mode 100644 index 8aa0f65f961..00000000000 --- a/Sources/Sentry/SentryUserFeedbackIntegration.m +++ /dev/null @@ -1,52 +0,0 @@ -#import "SentryUserFeedbackIntegration.h" -#import "SentryInternalDefines.h" -#import "SentrySDK+Private.h" -#import "SentrySwift.h" - -#if TARGET_OS_IOS && SENTRY_HAS_UIKIT - -@interface SentryUserFeedbackIntegration () -@end - -@implementation SentryUserFeedbackIntegration { - SentryUserFeedbackIntegrationDriver *_driver; -} - -- (BOOL)installWithOptions:(SentryOptions *)options -{ - if (options.userFeedbackConfiguration == nil) { - return NO; - } - - // The screenshot source is coupled to the options, but due to the dependency container being - // tightly to the options anyways, it was decided to not pass it to the container. - SentryScreenshotSource *screenshotSource - = SentryDependencyContainer.sharedInstance.screenshotSource; - _driver = [[SentryUserFeedbackIntegrationDriver alloc] - initWithConfiguration:SENTRY_UNWRAP_NULLABLE(SentryUserFeedbackConfiguration, - options.userFeedbackConfiguration) - delegate:self - screenshotSource:screenshotSource]; - return YES; -} - -- (void)showWidget -{ - [_driver showWidget]; -} - -- (void)hideWidget -{ - [_driver hideWidget]; -} - -// MARK: SentryUserFeedbackIntegrationDriverDelegate - -- (void)captureWithFeedback:(SentryFeedback *)feedback -{ - [SentrySDK captureFeedback:feedback]; -} - -@end - -#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/include/SentryHub+Private.h b/Sources/Sentry/include/SentryHub+Private.h index f378b54c041..f5990e757d5 100644 --- a/Sources/Sentry/include/SentryHub+Private.h +++ b/Sources/Sentry/include/SentryHub+Private.h @@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, strong) SentrySession *session; -@property (nonatomic, strong) NSMutableArray> *installedIntegrations; +@property (nonatomic, strong) NSMutableArray *installedIntegrations; @property (nonatomic, readonly, strong) NSObject *_swiftLogger; @@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSArray *)trimmedInstalledIntegrationNames; -- (void)addInstalledIntegration:(id)integration name:(NSString *)name; +- (void)addInstalledIntegration:(id)integration name:(NSString *)name; - (void)removeAllIntegrations; - (SentryClientInternal *_Nullable)client; @@ -84,7 +84,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)registerSessionListener:(id)listener; - (void)unregisterSessionListener:(id)listener; -- (nullable id)getInstalledIntegration:(Class)integrationClass; +- (nullable id)getInstalledIntegration:(Class)integrationClass; - (NSSet *)installedIntegrationNames; #if SENTRY_TARGET_REPLAY_SUPPORTED diff --git a/Sources/Sentry/include/SentryUserFeedbackIntegration.h b/Sources/Sentry/include/SentryUserFeedbackIntegration.h deleted file mode 100644 index 2ac1edd2f8b..00000000000 --- a/Sources/Sentry/include/SentryUserFeedbackIntegration.h +++ /dev/null @@ -1,17 +0,0 @@ -#import "SentryBaseIntegration.h" - -#import "SentryDefines.h" - -#if TARGET_OS_IOS && SENTRY_HAS_UIKIT - -NS_ASSUME_NONNULL_BEGIN - -NS_EXTENSION_UNAVAILABLE("Sentry User Feedback UI cannot be used from app extensions.") -@interface SentryUserFeedbackIntegration : SentryBaseIntegration -- (void)showWidget; -- (void)hideWidget; -@end - -NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT diff --git a/Sources/Swift/Core/Integrations/Integrations.swift b/Sources/Swift/Core/Integrations/Integrations.swift new file mode 100644 index 00000000000..d411522bd1c --- /dev/null +++ b/Sources/Swift/Core/Integrations/Integrations.swift @@ -0,0 +1,41 @@ +@_implementationOnly import _SentryPrivate + +protocol Integration: AnyObject { + associatedtype Dependencies + + @MainActor func start(with options: Options, dependencies: Dependencies) -> Bool +} + +// Type erases the Dependencies to be SentryDependencyContainer +private struct AnyIntegration { + let start: (Options, SentryDependencyContainer) -> Bool + let integration: any Integration + let name: String + + @MainActor init(_ integration: I) where I.Dependencies == SentryDependencyContainer { + name = NSStringFromClass(I.self) + self.integration = integration + start = { + integration.start(with: $0, dependencies: $1) + } + } + func start(with options: Options, dependencies: SentryDependencyContainer) -> Bool { + start(options, dependencies) + } +} + +@_spi(Private) @objc public final class SentryIntegrationInstaller: NSObject { + @objc @MainActor public class func install(with options: Options) { + let dependencies = SentryDependencyContainer.sharedInstance() + #if os(iOS) && !SENTRY_NO_UIKIT + let integrations: [AnyIntegration] = [.init(UserFeedbackIntegration())] + #else + let integrations: [AnyIntegration] = [] + #endif + integrations.forEach { anyIntegration in + guard anyIntegration.start(with: options, dependencies: dependencies) else { return } + + SentrySDKInternal.currentHub().addInstalledIntegration(anyIntegration.integration, name: anyIntegration.name) + } + } +} diff --git a/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift b/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift index 64b8498db40..57862bf7c62 100644 --- a/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift +++ b/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift @@ -3,27 +3,21 @@ import Foundation @_implementationOnly import _SentryPrivate import UIKit -@objc -@_spi(Private) public protocol SentryUserFeedbackIntegrationDriverDelegate: NSObjectProtocol { - func capture(feedback: SentryFeedback) -} - /** * An integration managing a workflow for end users to report feedback via Sentry. * - note: The default method to show the feedback form is via a floating widget placed in the bottom trailing corner of the screen. See the configuration classes for alternative options. */ @available(iOSApplicationExtension, unavailable) -@objcMembers -@_spi(Private) public class SentryUserFeedbackIntegrationDriver: NSObject { +final class SentryUserFeedbackIntegrationDriver: NSObject { let configuration: SentryUserFeedbackConfiguration private var widget: SentryUserFeedbackWidget? - weak var delegate: (any SentryUserFeedbackIntegrationDriverDelegate)? + fileprivate let callback: (SentryFeedback) -> Void let screenshotSource: SentryScreenshotSource weak var customButton: UIButton? - @_spi(Private) public init(configuration: SentryUserFeedbackConfiguration, delegate: any SentryUserFeedbackIntegrationDriverDelegate, screenshotSource: SentryScreenshotSource) { + init(configuration: SentryUserFeedbackConfiguration, screenshotSource: SentryScreenshotSource, callback: @escaping (SentryFeedback) -> Void) { self.configuration = configuration - self.delegate = delegate + self.callback = callback self.screenshotSource = screenshotSource super.init() @@ -64,7 +58,7 @@ import UIKit customButton?.removeTarget(self, action: #selector(showForm(sender:)), for: .touchUpInside) } - @objc public func showWidget() { + func showWidget() { if widget == nil { widget = SentryUserFeedbackWidget(config: configuration, delegate: self) } @@ -72,7 +66,7 @@ import UIKit widget?.rootVC.setWidget(visible: true, animated: configuration.animations) } - @objc public func hideWidget() { + func hideWidget() { widget?.rootVC.setWidget(visible: false, animated: configuration.animations) } @@ -88,7 +82,7 @@ import UIKit extension SentryUserFeedbackIntegrationDriver: SentryUserFeedbackFormDelegate { func finished(with feedback: SentryFeedback?) { if let feedback = feedback { - delegate?.capture(feedback: feedback) + callback(feedback) } presenter?.dismiss(animated: configuration.animations) { self.configuration.onFormClose?() diff --git a/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift b/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift new file mode 100644 index 00000000000..ccf088e6b2a --- /dev/null +++ b/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift @@ -0,0 +1,46 @@ +@_implementationOnly import _SentryPrivate + +#if os(iOS) && !SENTRY_NO_UIKIT + +protocol ScreenshotSourceProvider { + var screenshotSource: SentryScreenshotSource? { get } +} + +final class UserFeedbackIntegration: Integration { + + fileprivate var driver: SentryUserFeedbackIntegrationDriver? + + @MainActor func start(with options: Options, dependencies: Dependencies) -> Bool { + guard let configuration = options.userFeedbackConfiguration else { + return false + } + + // The screenshot source is coupled to the options, but due to the dependency container being + // tightly to the options anyways, it was decided to not pass it to the container. + guard let screenshotSource = dependencies.screenshotSource else { + return false + } + + driver = SentryUserFeedbackIntegrationDriver(configuration: configuration, screenshotSource: screenshotSource) { feedback in + SentrySDK.capture(feedback: feedback) + } + return true + } +} + +// So that ObjC code can still access the UserFeedbackIntegration. This is used by SentryFeedbackAPI.m If that was written in Swift +// this wouldn't be necessary. +@_spi(Private) @objc public final class SentryUserFeedbackAccessor: NSObject { + + @objc public class func showWidget() { + let feedback = SentrySDKInternal.currentHub().getInstalledIntegration(UserFeedbackIntegration.self) as? UserFeedbackIntegration + feedback?.driver?.showWidget() + } + + @objc public class func hideWidget() { + let feedback = SentrySDKInternal.currentHub().getInstalledIntegration(UserFeedbackIntegration.self) as? UserFeedbackIntegration + feedback?.driver?.hideWidget() + } +} + +#endif diff --git a/Sources/Swift/SentryDependencyContainer.swift b/Sources/Swift/SentryDependencyContainer.swift index 3036115aecd..742ec889fd3 100644 --- a/Sources/Swift/SentryDependencyContainer.swift +++ b/Sources/Swift/SentryDependencyContainer.swift @@ -242,3 +242,7 @@ extension SentryFileManager: SentryFileManagerProtocol { } } } } + +#if os(iOS) && !SENTRY_NO_UIKIT +extension SentryDependencyContainer: ScreenshotSourceProvider { } +#endif diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index a3415b9acff..08dd1a5b438 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -1395,7 +1395,7 @@ class SentryHubTests: XCTestCase { let integration = EmptyIntegration() sut.addInstalledIntegration(integration, name: "EmptyIntegration") - let installedIntegration = sut.getInstalledIntegration(EmptyIntegration.self) + let installedIntegration = sut.getInstalledIntegration(EmptyIntegration.self) as? NSObject XCTAssert(integration === installedIntegration) }