diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentryFeedback.cpp b/plugin-dev/Source/Sentry/Private/Android/AndroidSentryFeedback.cpp index f28c78ecd..2c06d8670 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentryFeedback.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentryFeedback.cpp @@ -2,6 +2,7 @@ #include "AndroidSentryFeedback.h" +#include "AndroidSentryHint.h" #include "AndroidSentryId.h" #include "Infrastructure/AndroidSentryJavaClasses.h" @@ -9,6 +10,7 @@ FAndroidSentryFeedback::FAndroidSentryFeedback(const FString& message) : FSentryJavaObjectWrapper(SentryJavaClasses::Feedback, "(Ljava/lang/String;)V", *GetJString(message)) + , Hint(nullptr) { SetupClassMethods(); } @@ -69,3 +71,18 @@ FString FAndroidSentryFeedback::GetAssociatedEvent() const TSharedPtr eventId = MakeShareable(new FAndroidSentryId(*idAndroid)); return eventId->ToString(); } + +void FAndroidSentryFeedback::AddAttachment(TSharedPtr attachment) +{ + if (!Hint) + { + Hint = MakeShareable(new FAndroidSentryHint()); + } + + Hint->AddAttachment(attachment); +} + +TSharedPtr FAndroidSentryFeedback::GetHint() +{ + return Hint; +} diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentryFeedback.h b/plugin-dev/Source/Sentry/Private/Android/AndroidSentryFeedback.h index 7ac7a8381..06bac9e85 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentryFeedback.h +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentryFeedback.h @@ -7,6 +7,7 @@ #include "Infrastructure/AndroidSentryJavaObjectWrapper.h" class ISentryId; +class FAndroidSentryHint; class FAndroidSentryFeedback : public ISentryFeedback, public FSentryJavaObjectWrapper { @@ -22,6 +23,9 @@ class FAndroidSentryFeedback : public ISentryFeedback, public FSentryJavaObjectW virtual FString GetContactEmail() const override; virtual void SetAssociatedEvent(const FString& eventId) override; virtual FString GetAssociatedEvent() const override; + virtual void AddAttachment(TSharedPtr attachment) override; + + TSharedPtr GetHint(); private: FSentryJavaMethod GetMessageMethod; @@ -31,6 +35,8 @@ class FAndroidSentryFeedback : public ISentryFeedback, public FSentryJavaObjectW FSentryJavaMethod GetContactEmailMethod; FSentryJavaMethod SetAssociatedEventMethod; FSentryJavaMethod GetAssociatedEventMethod; + + TSharedPtr Hint; }; typedef FAndroidSentryFeedback FPlatformSentryFeedback; \ No newline at end of file diff --git a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp index 144cb2b5e..7afac8b69 100644 --- a/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/Android/AndroidSentrySubsystem.cpp @@ -6,6 +6,7 @@ #include "AndroidSentryBreadcrumb.h" #include "AndroidSentryEvent.h" #include "AndroidSentryFeedback.h" +#include "AndroidSentryHint.h" #include "AndroidSentryId.h" #include "AndroidSentryTransaction.h" #include "AndroidSentryTransactionContext.h" @@ -307,8 +308,10 @@ void FAndroidSentrySubsystem::CaptureFeedback(TSharedPtr feedba { TSharedPtr feedbackAndroid = StaticCastSharedPtr(feedback); - FSentryJavaObjectWrapper::CallStaticObjectMethod(SentryJavaClasses::Sentry, "captureFeedback", "(Lio/sentry/protocol/Feedback;)Lio/sentry/protocol/SentryId;", - feedbackAndroid->GetJObject()); + TSharedPtr hintAndroid = feedbackAndroid->GetHint(); + + FSentryJavaObjectWrapper::CallStaticObjectMethod(SentryJavaClasses::Sentry, "captureFeedback", "(Lio/sentry/protocol/Feedback;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;", + feedbackAndroid->GetJObject(), hintAndroid ? hintAndroid->GetJObject() : nullptr); } void FAndroidSentrySubsystem::SetUser(TSharedPtr user) diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentryFeedback.cpp b/plugin-dev/Source/Sentry/Private/Apple/AppleSentryFeedback.cpp index d98244d41..8cf17d036 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentryFeedback.cpp +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentryFeedback.cpp @@ -2,11 +2,14 @@ #include "AppleSentryFeedback.h" +#include "AppleSentryAttachment.h" #include "AppleSentryId.h" #include "Convenience/AppleSentryInclude.h" #include "Convenience/AppleSentryMacro.h" +#include "Misc/FileHelper.h" + FAppleSentryFeedback::FAppleSentryFeedback(const FString& message) : Message(message) { @@ -52,6 +55,11 @@ FString FAppleSentryFeedback::GetAssociatedEvent() const return EventId; } +void FAppleSentryFeedback::AddAttachment(TSharedPtr attachment) +{ + Attachments.Add(attachment); +} + SentryFeedback* FAppleSentryFeedback::CreateSentryFeedback(TSharedPtr feedback) { SentryId* id = nil; @@ -61,10 +69,40 @@ SentryFeedback* FAppleSentryFeedback::CreateSentryFeedback(TSharedPtrGetNativeObject(); } + NSMutableArray* attachments = nil; + if (feedback->Attachments.Num() > 0) + { + attachments = [NSMutableArray arrayWithCapacity:feedback->Attachments.Num()]; + + for (const TSharedPtr& attachment : feedback->Attachments) + { + NSData* data = nil; + + if (attachment->GetData().Num() > 0) + { + const TArray& bytes = attachment->GetData(); + data = [NSData dataWithBytes:bytes.GetData() length:bytes.Num()]; + } + else if (!attachment->GetPath().IsEmpty()) + { + TArray fileData; + if (FFileHelper::LoadFileToArray(fileData, *attachment->GetPath())) + { + data = [NSData dataWithBytes:fileData.GetData() length:fileData.Num()]; + } + } + + if (data != nil) + { + [attachments addObject:data]; + } + } + } + return [[SENTRY_APPLE_CLASS(SentryFeedback) alloc] initWithMessage:feedback->Message.GetNSString() name:feedback->Name.GetNSString() email:feedback->Email.GetNSString() source:SentryFeedbackSourceCustom associatedEventId:id - attachments:nil]; + attachments:attachments]; } diff --git a/plugin-dev/Source/Sentry/Private/Apple/AppleSentryFeedback.h b/plugin-dev/Source/Sentry/Private/Apple/AppleSentryFeedback.h index a35369482..e52ca3178 100644 --- a/plugin-dev/Source/Sentry/Private/Apple/AppleSentryFeedback.h +++ b/plugin-dev/Source/Sentry/Private/Apple/AppleSentryFeedback.h @@ -19,6 +19,7 @@ class FAppleSentryFeedback : public ISentryFeedback virtual FString GetContactEmail() const override; virtual void SetAssociatedEvent(const FString& eventId) override; virtual FString GetAssociatedEvent() const override; + virtual void AddAttachment(TSharedPtr attachment) override; static SentryFeedback* CreateSentryFeedback(TSharedPtr feedback); @@ -27,6 +28,8 @@ class FAppleSentryFeedback : public ISentryFeedback FString Name; FString Email; FString EventId; + + TArray> Attachments; }; typedef FAppleSentryFeedback FPlatformSentryFeedback; \ No newline at end of file diff --git a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.cpp b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.cpp index 38379dea4..e2bce92e7 100644 --- a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.cpp +++ b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.cpp @@ -1,17 +1,20 @@ // Copyright (c) 2025 Sentry. All Rights Reserved. #include "GenericPlatformSentryFeedback.h" +#include "GenericPlatformSentryAttachment.h" #include "Infrastructure/GenericPlatformSentryConverters.h" #if USE_SENTRY_NATIVE FGenericPlatformSentryFeedback::FGenericPlatformSentryFeedback() + : Hint(nullptr) { Feedback = sentry_value_new_object(); } FGenericPlatformSentryFeedback::FGenericPlatformSentryFeedback(const FString& message) + : Hint(nullptr) { Feedback = sentry_value_new_object(); sentry_value_set_by_key(Feedback, "message", sentry_value_new_string(TCHAR_TO_UTF8(*message))); @@ -69,4 +72,55 @@ FString FGenericPlatformSentryFeedback::GetAssociatedEvent() const return FString(sentry_value_as_string(comment)); } +void FGenericPlatformSentryFeedback::AddAttachment(TSharedPtr attachment) +{ + if (!Hint) + { + Hint = sentry_feedback_hint_new(); + } + + TSharedPtr platformAttachment = StaticCastSharedPtr(attachment); + + if (!platformAttachment->GetPath().IsEmpty()) + { + AddFileAttachment(platformAttachment); + } + else + { + AddByteAttachment(platformAttachment); + } +} + +sentry_feedback_hint_t* FGenericPlatformSentryFeedback::GetHintNativeObject() +{ + return Hint; +} + +void FGenericPlatformSentryFeedback::AddFileAttachment(TSharedPtr attachment) +{ + sentry_attachment_t* nativeAttachment = + sentry_feedback_hint_attach_file(Hint, TCHAR_TO_UTF8(*attachment->GetPath())); + + if (!attachment->GetFilename().IsEmpty()) + sentry_attachment_set_filename(nativeAttachment, TCHAR_TO_UTF8(*attachment->GetFilename())); + + if (!attachment->GetContentType().IsEmpty()) + sentry_attachment_set_content_type(nativeAttachment, TCHAR_TO_UTF8(*attachment->GetContentType())); + + attachment->SetNativeObject(nativeAttachment); +} + +void FGenericPlatformSentryFeedback::AddByteAttachment(TSharedPtr attachment) +{ + const TArray& byteBuf = attachment->GetDataByRef(); + + sentry_attachment_t* nativeAttachment = + sentry_feedback_hint_attach_bytes(Hint, reinterpret_cast(byteBuf.GetData()), byteBuf.Num(), TCHAR_TO_UTF8(*attachment->GetFilename())); + + if (!attachment->GetContentType().IsEmpty()) + sentry_attachment_set_content_type(nativeAttachment, TCHAR_TO_UTF8(*attachment->GetContentType())); + + attachment->SetNativeObject(nativeAttachment); +} + #endif diff --git a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.h b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.h index 083ba822c..4239db1ab 100644 --- a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.h +++ b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentryFeedback.h @@ -8,6 +8,8 @@ #if USE_SENTRY_NATIVE +class FGenericPlatformSentryAttachment; + class FGenericPlatformSentryFeedback : public ISentryFeedback { public: @@ -24,6 +26,15 @@ class FGenericPlatformSentryFeedback : public ISentryFeedback virtual FString GetContactEmail() const override; virtual void SetAssociatedEvent(const FString& eventId) override; virtual FString GetAssociatedEvent() const override; + virtual void AddAttachment(TSharedPtr attachment) override; + + sentry_feedback_hint_t* GetHintNativeObject(); + +protected: + virtual void AddFileAttachment(TSharedPtr attachment); + virtual void AddByteAttachment(TSharedPtr attachment); + + sentry_feedback_hint_t* Hint; private: sentry_value_t Feedback; diff --git a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp index 4efc57485..4f962868a 100644 --- a/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp +++ b/plugin-dev/Source/Sentry/Private/GenericPlatform/GenericPlatformSentrySubsystem.cpp @@ -649,7 +649,8 @@ TSharedPtr FGenericPlatformSentrySubsystem::CaptureEnsure(const FStri void FGenericPlatformSentrySubsystem::CaptureFeedback(TSharedPtr feedback) { TSharedPtr Feedback = StaticCastSharedPtr(feedback); - sentry_capture_feedback(Feedback->GetNativeObject()); + + sentry_capture_feedback_with_hint(Feedback->GetNativeObject(), Feedback->GetHintNativeObject()); } void FGenericPlatformSentrySubsystem::SetUser(TSharedPtr InUser) diff --git a/plugin-dev/Source/Sentry/Private/Interface/SentryFeedbackInterface.h b/plugin-dev/Source/Sentry/Private/Interface/SentryFeedbackInterface.h index 2a3434482..3e56e7831 100644 --- a/plugin-dev/Source/Sentry/Private/Interface/SentryFeedbackInterface.h +++ b/plugin-dev/Source/Sentry/Private/Interface/SentryFeedbackInterface.h @@ -4,6 +4,8 @@ #include "CoreMinimal.h" +class ISentryAttachment; + class ISentryFeedback { public: @@ -16,4 +18,5 @@ class ISentryFeedback virtual FString GetContactEmail() const = 0; virtual void SetAssociatedEvent(const FString& eventId) = 0; virtual FString GetAssociatedEvent() const = 0; + virtual void AddAttachment(TSharedPtr attachment) = 0; }; \ No newline at end of file diff --git a/plugin-dev/Source/Sentry/Private/Microsoft/MicrosoftSentryFeedback.cpp b/plugin-dev/Source/Sentry/Private/Microsoft/MicrosoftSentryFeedback.cpp new file mode 100644 index 000000000..bbb8ee7a3 --- /dev/null +++ b/plugin-dev/Source/Sentry/Private/Microsoft/MicrosoftSentryFeedback.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2025 Sentry. All Rights Reserved. + +#include "MicrosoftSentryFeedback.h" + +#include "GenericPlatform/GenericPlatformSentryAttachment.h" + +#if USE_SENTRY_NATIVE + +void FMicrosoftSentryFeedback::AddFileAttachment(TSharedPtr attachment) +{ + sentry_attachment_t* nativeAttachment = + sentry_feedback_hint_attach_filew(Hint, *attachment->GetPath()); + + if (!attachment->GetFilename().IsEmpty()) + sentry_attachment_set_filenamew(nativeAttachment, *attachment->GetFilename()); + + if (!attachment->GetContentType().IsEmpty()) + sentry_attachment_set_content_type(nativeAttachment, TCHAR_TO_UTF8(*attachment->GetContentType())); + + attachment->SetNativeObject(nativeAttachment); +} + +void FMicrosoftSentryFeedback::AddByteAttachment(TSharedPtr attachment) +{ + const TArray& byteBuf = attachment->GetDataByRef(); + + sentry_attachment_t* nativeAttachment = + sentry_feedback_hint_attach_bytesw(Hint, reinterpret_cast(byteBuf.GetData()), byteBuf.Num(), *attachment->GetFilename()); + + if (!attachment->GetContentType().IsEmpty()) + sentry_attachment_set_content_type(nativeAttachment, TCHAR_TO_UTF8(*attachment->GetContentType())); + + attachment->SetNativeObject(nativeAttachment); +} + +#endif diff --git a/plugin-dev/Source/Sentry/Private/Microsoft/MicrosoftSentryFeedback.h b/plugin-dev/Source/Sentry/Private/Microsoft/MicrosoftSentryFeedback.h new file mode 100644 index 000000000..7b9f91ef8 --- /dev/null +++ b/plugin-dev/Source/Sentry/Private/Microsoft/MicrosoftSentryFeedback.h @@ -0,0 +1,19 @@ +// Copyright (c) 2025 Sentry. All Rights Reserved. + +#pragma once + +#if USE_SENTRY_NATIVE + +#include "GenericPlatform/GenericPlatformSentryFeedback.h" + +class FMicrosoftSentryFeedback : public FGenericPlatformSentryFeedback +{ +public: + virtual ~FMicrosoftSentryFeedback() override = default; + +protected: + virtual void AddFileAttachment(TSharedPtr attachment) override; + virtual void AddByteAttachment(TSharedPtr attachment) override; +}; + +#endif diff --git a/plugin-dev/Source/Sentry/Private/Null/NullSentryFeedback.h b/plugin-dev/Source/Sentry/Private/Null/NullSentryFeedback.h index 4d27234aa..28e1840fb 100644 --- a/plugin-dev/Source/Sentry/Private/Null/NullSentryFeedback.h +++ b/plugin-dev/Source/Sentry/Private/Null/NullSentryFeedback.h @@ -20,6 +20,7 @@ class FNullSentryFeedback final : public ISentryFeedback virtual FString GetContactEmail() const override { return TEXT(""); } virtual void SetAssociatedEvent(const FString& eventId) override {} virtual FString GetAssociatedEvent() const override { return TEXT(""); } + virtual void AddAttachment(TSharedPtr attachment) override {} }; typedef FNullSentryFeedback FPlatformSentryFeedback; diff --git a/plugin-dev/Source/Sentry/Private/SentryFeedback.cpp b/plugin-dev/Source/Sentry/Private/SentryFeedback.cpp index 0b5b6c72f..9db7b50ca 100644 --- a/plugin-dev/Source/Sentry/Private/SentryFeedback.cpp +++ b/plugin-dev/Source/Sentry/Private/SentryFeedback.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2025 Sentry. All Rights Reserved. #include "SentryFeedback.h" +#include "SentryAttachment.h" #include "HAL/PlatformSentryFeedback.h" @@ -67,3 +68,11 @@ FString USentryFeedback::GetAssociatedEvent() const return NativeImpl->GetAssociatedEvent(); } + +void USentryFeedback::AddAttachment(USentryAttachment* Attachment) +{ + if (!NativeImpl) + return; + + return NativeImpl->AddAttachment(Attachment->GetNativeObject()); +} diff --git a/plugin-dev/Source/Sentry/Public/SentryFeedback.h b/plugin-dev/Source/Sentry/Public/SentryFeedback.h index d0d87486b..b7b7f021f 100644 --- a/plugin-dev/Source/Sentry/Public/SentryFeedback.h +++ b/plugin-dev/Source/Sentry/Public/SentryFeedback.h @@ -7,6 +7,7 @@ #include "SentryFeedback.generated.h" class ISentryFeedback; +class USentryAttachment; /** * Additional information about what happened to be sent to Sentry. @@ -52,4 +53,8 @@ class SENTRY_API USentryFeedback : public UObject, public TSentryImplWrapper