diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp index e8171ec625e52a..3010fbf37878eb 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp @@ -22,14 +22,8 @@ namespace facebook::react::jsinspector_modern { #define ANSI_WEIGHT_BOLD "\x1B[1m" #define ANSI_WEIGHT_RESET "\x1B[22m" -#define ANSI_STYLE_ITALIC "\x1B[3m" -#define ANSI_STYLE_RESET "\x1B[23m" #define ANSI_COLOR_BG_YELLOW "\x1B[48;2;253;247;231m" - -static constexpr auto kModernCDPBackendNotice = - ANSI_COLOR_BG_YELLOW ANSI_WEIGHT_BOLD - "NOTE:" ANSI_WEIGHT_RESET " You are using the " ANSI_STYLE_ITALIC - "modern" ANSI_STYLE_RESET " CDP backend for React Native (HostTarget)."sv; +#define CSS_STYLE_PLACEHOLDER "%c" HostAgent::HostAgent( FrontendChannel frontendChannel, @@ -51,12 +45,15 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) { if (req.method == "Log.enable") { sessionState_.isLogDomainEnabled = true; - // Send a log entry identifying the modern CDP backend. - sendInfoLogEntry(kModernCDPBackendNotice); + if (sessionState_.isFuseboxClientDetected) { + sendFuseboxNotice(); + } // Send a log entry with the integration name. if (sessionMetadata_.integrationName) { - sendInfoLogEntry("Integration: " + *sessionMetadata_.integrationName); + sendInfoLogEntry( + ANSI_COLOR_BG_YELLOW "Debugger integration: " + + *sessionMetadata_.integrationName); } shouldSendOKResponse = true; @@ -100,6 +97,15 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) { : std::nullopt, }); + shouldSendOKResponse = true; + isFinishedHandlingRequest = true; + } else if (req.method == "FuseboxClient.setClientMetadata") { + sessionState_.isFuseboxClientDetected = true; + + if (sessionState_.isLogDomainEnabled) { + sendFuseboxNotice(); + } + shouldSendOKResponse = true; isFinishedHandlingRequest = true; } @@ -120,7 +126,23 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) { req.method + " not implemented yet")); } -void HostAgent::sendInfoLogEntry(std::string_view text) { +void HostAgent::sendFuseboxNotice() { + static constexpr auto kFuseboxNotice = ANSI_COLOR_BG_YELLOW + "Welcome to the new React Native debugger (codename " ANSI_WEIGHT_BOLD + "React Fusebox " CSS_STYLE_PLACEHOLDER + "⚡️" CSS_STYLE_PLACEHOLDER ANSI_WEIGHT_RESET ")."sv; + + sendInfoLogEntry( + kFuseboxNotice, {"font-family: sans-serif;", "font-family: monospace;"}); +} + +void HostAgent::sendInfoLogEntry( + std::string_view text, + std::initializer_list args) { + folly::dynamic argsArray = folly::dynamic::array(); + for (auto arg : args) { + argsArray.push_back(arg); + } frontendChannel_(cdp::jsonNotification( "Log.entryAdded", folly::dynamic::object( @@ -130,7 +152,7 @@ void HostAgent::sendInfoLogEntry(std::string_view text) { duration_cast( system_clock::now().time_since_epoch()) .count())("source", "other")( - "level", "info")("text", text)))); + "level", "info")("text", text)("args", std::move(argsArray))))); } void HostAgent::setCurrentInstanceAgent( diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h index aec355bc3ce284..855214f3886909 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h @@ -71,7 +71,11 @@ class HostAgent final { * Runtime.consoleAPICalled is that the latter requires an execution context * ID, which does not exist at the Host level. */ - void sendInfoLogEntry(std::string_view text); + void sendInfoLogEntry( + std::string_view text, + std::initializer_list args = {}); + + void sendFuseboxNotice(); FrontendChannel frontendChannel_; HostTargetController& targetController_; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/SessionState.h b/packages/react-native/ReactCommon/jsinspector-modern/SessionState.h index 6a61ff5468524e..757d283e6ed243 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/SessionState.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/SessionState.h @@ -36,6 +36,8 @@ struct SessionState { std::unordered_map subscribedBindings; + bool isFuseboxClientDetected{false}; + /** * Stores the state object exported from the last main RuntimeAgent, if any, * before it was destroyed. diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/HostTargetTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/HostTargetTest.cpp index f27baef9567379..dc57b7e774abbe 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/HostTargetTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/HostTargetTest.cpp @@ -155,8 +155,7 @@ TEST_F(HostTargetProtocolTest, InjectLogsToIdentifyBackend) { onMessage(JsonParsed(AllOf( AtJsonPtr("/method", "Log.entryAdded"), AtJsonPtr("/params/entry", Not(IsEmpty())))))) - .Times(2) - .RetiresOnSaturation(); + .Times(AtLeast(1)); EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({ "id": 1, "result": {} diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp index 6255c9e568ce31..b35f83d9cd724a 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp @@ -317,6 +317,21 @@ TYPED_TEST(JsiIntegrationPortableTest, ExceptionDuringAddBindingIsIgnored) { EXPECT_TRUE(this->eval("globalThis.foo === 42").getBool()); } +TYPED_TEST(JsiIntegrationPortableTest, FuseboxSetClientMetadata) { + this->connect(); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "FuseboxClient.setClientMetadata", + "params": {} + })"); +} + #pragma endregion // AllEngines #pragma region AllHermesVariants