first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "BoundedRequestBuffer.h"
namespace facebook::react::jsinspector_modern {
bool BoundedRequestBuffer::put(
const std::string& requestId,
std::string_view data,
bool base64Encoded) noexcept {
if (data.size() > REQUEST_BUFFER_MAX_SIZE_BYTES) {
return false;
}
// Remove existing request with the same ID, if any
if (auto it = responses_.find(requestId); it != responses_.end()) {
currentSize_ -= it->second->data.size();
responses_.erase(it);
// Update order: remove requestId from deque
for (auto orderIt = order_.begin(); orderIt != order_.end(); ++orderIt) {
if (*orderIt == requestId) {
order_.erase(orderIt);
break;
}
}
}
// Evict oldest requests if necessary to make space
while (currentSize_ + data.size() > REQUEST_BUFFER_MAX_SIZE_BYTES &&
!order_.empty()) {
const auto& oldestId = order_.front();
auto it = responses_.find(oldestId);
if (it != responses_.end()) {
currentSize_ -= it->second->data.size();
responses_.erase(it);
}
order_.pop_front();
}
// If still no space, reject the new data (this should not be reached)
if (currentSize_ + data.size() > REQUEST_BUFFER_MAX_SIZE_BYTES) {
return false;
}
currentSize_ += data.size();
// `data` is copied at the point of insertion
responses_.emplace(
requestId,
std::make_shared<ResponseBody>(ResponseBody{
.data = std::string(data), .base64Encoded = base64Encoded}));
order_.push_back(requestId);
return true;
}
std::shared_ptr<const BoundedRequestBuffer::ResponseBody>
BoundedRequestBuffer::get(const std::string& requestId) const {
auto it = responses_.find(requestId);
if (it != responses_.end()) {
return it->second;
}
return nullptr;
}
void BoundedRequestBuffer::clear() {
responses_.clear();
order_.clear();
currentSize_ = 0;
}
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <deque>
#include <memory>
#include <string>
#include <unordered_map>
namespace facebook::react::jsinspector_modern {
/**
* Maximum memory size (in bytes) to store buffered text and image request
* bodies.
*/
constexpr size_t REQUEST_BUFFER_MAX_SIZE_BYTES = 100 * 1024 * 1024; // 100MB
/**
* A class to store network response previews keyed by requestId, with a fixed
* memory limit. Evicts oldest responses when memory is exceeded.
*/
class BoundedRequestBuffer {
public:
struct ResponseBody {
std::string data;
bool base64Encoded;
};
/**
* Store a response preview with the given requestId and data.
* If adding the data exceeds the memory limit, removes oldest requests until
* there is enough space or the buffer is empty.
* \param requestId Unique identifier for the request.
* \param data The request preview data (e.g. text or image body).
* \param base64Encoded True if the data is base64-encoded, false otherwise.
* \return True if the response body was stored, false otherwise.
*/
bool put(const std::string &requestId, std::string_view data, bool base64Encoded) noexcept;
/**
* Retrieve a response preview by requestId.
* \param requestId The unique identifier for the request.
* \return A shared pointer to the request data if found, otherwise nullptr.
*/
std::shared_ptr<const ResponseBody> get(const std::string &requestId) const;
/**
* Remove all entries from the buffer.
*/
void clear();
private:
std::unordered_map<std::string, std::shared_ptr<const ResponseBody>> responses_;
std::deque<std::string> order_;
size_t currentSize_ = 0;
};
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,27 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake)
add_compile_options(
-fexceptions
-std=c++20
-Wall
-Wpedantic)
file(GLOB jsinspector_network_SRC CONFIGURE_DEPENDS *.cpp)
add_library(jsinspector_network OBJECT ${jsinspector_network_SRC})
target_merge_so(jsinspector_network)
target_include_directories(jsinspector_network PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(jsinspector_network
folly_runtime
glog
jsinspector_cdp)

View File

@@ -0,0 +1,167 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "CdpNetwork.h"
#include "HttpUtils.h"
namespace facebook::react::jsinspector_modern::cdp::network {
namespace {
folly::dynamic headersToDynamic(const Headers& headers) {
folly::dynamic result = folly::dynamic::object;
for (const auto& [key, value] : headers) {
result[key] = value;
}
return result;
}
} // namespace
folly::dynamic Request::toDynamic() const {
folly::dynamic result = folly::dynamic::object;
result["url"] = url;
result["method"] = method;
result["headers"] = headersToDynamic(headers);
result["postData"] = postData.value_or("");
return result;
}
/* static */ Response Response::fromInputParams(
const std::string& url,
uint16_t status,
const Headers& headers,
int encodedDataLength) {
return {
.url = url,
.status = status,
.statusText = httpReasonPhrase(status),
.headers = headers,
.mimeType = mimeTypeFromHeaders(headers),
.encodedDataLength = encodedDataLength,
};
}
folly::dynamic Response::toDynamic() const {
folly::dynamic result = folly::dynamic::object;
result["url"] = url;
result["status"] = status;
result["statusText"] = statusText;
result["headers"] = headersToDynamic(headers);
result["mimeType"] = mimeType;
result["encodedDataLength"] = encodedDataLength;
return result;
}
folly::dynamic RequestWillBeSentParams::toDynamic() const {
folly::dynamic params = folly::dynamic::object;
params["requestId"] = requestId;
params["loaderId"] = loaderId;
params["documentURL"] = documentURL;
params["request"] = request.toDynamic();
params["timestamp"] = timestamp;
params["wallTime"] = wallTime;
params["initiator"] = initiator;
params["redirectHasExtraInfo"] = redirectResponse.has_value();
if (redirectResponse.has_value()) {
params["redirectResponse"] = redirectResponse->toDynamic();
}
return params;
}
folly::dynamic RequestWillBeSentExtraInfoParams::toDynamic() const {
folly::dynamic params = folly::dynamic::object;
params["requestId"] = requestId;
params["associatedCookies"] = folly::dynamic::array;
params["headers"] = headersToDynamic(headers);
params["connectTiming"] =
folly::dynamic::object("requestTime", connectTiming.requestTime);
return params;
}
folly::dynamic ResponseReceivedParams::toDynamic() const {
folly::dynamic params = folly::dynamic::object;
params["requestId"] = requestId;
params["loaderId"] = loaderId;
params["timestamp"] = timestamp;
params["type"] = type;
params["response"] = response.toDynamic();
params["hasExtraInfo"] = hasExtraInfo;
return params;
}
folly::dynamic DataReceivedParams::toDynamic() const {
folly::dynamic params = folly::dynamic::object;
params["requestId"] = requestId;
params["timestamp"] = timestamp;
params["dataLength"] = dataLength;
params["encodedDataLength"] = encodedDataLength;
return params;
}
folly::dynamic LoadingFailedParams::toDynamic() const {
folly::dynamic params = folly::dynamic::object;
params["requestId"] = requestId;
params["timestamp"] = timestamp;
params["type"] = type;
params["errorText"] = errorText;
params["canceled"] = canceled;
return params;
}
folly::dynamic LoadingFinishedParams::toDynamic() const {
folly::dynamic params = folly::dynamic::object;
params["requestId"] = requestId;
params["timestamp"] = timestamp;
params["encodedDataLength"] = encodedDataLength;
return params;
}
std::string resourceTypeFromMimeType(const std::string& mimeType) {
if (mimeType.find("image/") == 0) {
return "Image";
}
if (mimeType.find("video/") == 0 || mimeType.find("audio/") == 0) {
return "Media";
}
if (mimeType == "application/javascript" || mimeType == "text/javascript" ||
mimeType == "application/x-javascript") {
return "Script";
}
if (mimeType == "application/json" || mimeType.find("application/xml") == 0 ||
mimeType == "text/xml") {
// Assume XHR for JSON/XML types
return "XHR";
}
return "Other";
}
} // namespace facebook::react::jsinspector_modern::cdp::network

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <folly/dynamic.h>
#include <string>
// Data containers for CDP Network domain types, supporting serialization to
// folly::dynamic objects.
namespace facebook::react::jsinspector_modern::cdp::network {
using Headers = std::map<std::string, std::string>;
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Request
*/
struct Request {
std::string url;
std::string method;
Headers headers;
std::optional<std::string> postData;
folly::dynamic toDynamic() const;
};
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Response
*/
struct Response {
std::string url;
uint16_t status;
std::string statusText;
Headers headers;
std::string mimeType;
int encodedDataLength;
/**
* Convenience function to construct a `Response` from the generic
* `ResponseInfo` input object.
*/
static Response
fromInputParams(const std::string &url, uint16_t status, const Headers &headers, int encodedDataLength);
folly::dynamic toDynamic() const;
};
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ConnectTiming
*/
struct ConnectTiming {
double requestTime;
};
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-requestWillBeSent
*/
struct RequestWillBeSentParams {
std::string requestId;
std::string loaderId;
std::string documentURL;
Request request;
double timestamp;
double wallTime;
folly::dynamic initiator;
bool redirectHasExtraInfo;
std::optional<Response> redirectResponse;
folly::dynamic toDynamic() const;
};
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-requestWillBeSentExtraInfo
*/
struct RequestWillBeSentExtraInfoParams {
std::string requestId;
Headers headers;
ConnectTiming connectTiming;
folly::dynamic toDynamic() const;
};
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-responseReceived
*/
struct ResponseReceivedParams {
std::string requestId;
std::string loaderId;
double timestamp;
std::string type;
Response response;
bool hasExtraInfo;
folly::dynamic toDynamic() const;
};
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-dataReceived
*/
struct DataReceivedParams {
std::string requestId;
double timestamp;
int dataLength;
int encodedDataLength;
folly::dynamic toDynamic() const;
};
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-loadingFailed
*/
struct LoadingFailedParams {
std::string requestId;
double timestamp;
std::string type;
std::string errorText;
bool canceled;
folly::dynamic toDynamic() const;
};
/**
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-loadingFinished
*/
struct LoadingFinishedParams {
std::string requestId;
double timestamp;
int encodedDataLength;
folly::dynamic toDynamic() const;
};
/**
* Get the CDP `ResourceType` for a given MIME type.
*
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ResourceType
*/
std::string resourceTypeFromMimeType(const std::string &mimeType);
} // namespace facebook::react::jsinspector_modern::cdp::network

View File

@@ -0,0 +1,174 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "HttpUtils.h"
#include <algorithm>
namespace facebook::react::jsinspector_modern {
std::string httpReasonPhrase(uint16_t status) {
switch (status) {
case 100:
return "Continue";
case 101:
return "Switching Protocols";
case 102:
return "Processing";
case 103:
return "Early Hints";
case 200:
return "OK";
case 201:
return "Created";
case 202:
return "Accepted";
case 203:
return "Non-Authoritative Information";
case 204:
return "No Content";
case 205:
return "Reset Content";
case 206:
return "Partial Content";
case 207:
return "Multi-Status";
case 208:
return "Already Reported";
case 226:
return "IM Used";
case 300:
return "Multiple Choices";
case 301:
return "Moved Permanently";
case 302:
return "Found";
case 303:
return "See Other";
case 304:
return "Not Modified";
case 305:
return "Use Proxy";
case 307:
return "Temporary Redirect";
case 308:
return "Permanent Redirect";
case 400:
return "Bad Request";
case 401:
return "Unauthorized";
case 402:
return "Payment Required";
case 403:
return "Forbidden";
case 404:
return "Not Found";
case 405:
return "Method Not Allowed";
case 406:
return "Not Acceptable";
case 407:
return "Proxy Authentication Required";
case 408:
return "Request Timeout";
case 409:
return "Conflict";
case 410:
return "Gone";
case 411:
return "Length Required";
case 412:
return "Precondition Failed";
case 413:
return "Payload Too Large";
case 414:
return "URI Too Long";
case 415:
return "Unsupported Media Type";
case 416:
return "Range Not Satisfiable";
case 417:
return "Expectation Failed";
case 418:
return "I'm a teapot";
case 421:
return "Misdirected Request";
case 422:
return "Unprocessable Entity";
case 423:
return "Locked";
case 424:
return "Failed Dependency";
case 425:
return "Too Early";
case 426:
return "Upgrade Required";
case 428:
return "Precondition Required";
case 429:
return "Too Many Requests";
case 431:
return "Request Header Fields Too Large";
case 451:
return "Unavailable For Legal Reasons";
case 500:
return "Internal Server Error";
case 501:
return "Not Implemented";
case 502:
return "Bad Gateway";
case 503:
return "Service Unavailable";
case 504:
return "Gateway Time-out";
case 505:
return "HTTP Version Not Supported";
case 506:
return "Variant Also Negotiates";
case 507:
return "Insufficient Storage";
case 508:
return "Loop Detected";
case 510:
return "Not Extended";
case 511:
return "Network Authentication Required";
}
return "<Unknown>";
}
std::string mimeTypeFromHeaders(
const std::map<std::string, std::string>& headers) {
std::string mimeType = "application/octet-stream";
for (const auto& [name, value] : headers) {
std::string lowerName = name;
std::transform(
lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
if (lowerName == "content-type") {
// Parse MIME type (discarding any parameters after ";") from the
// Content-Type header
// https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.1
size_t pos = value.find(';');
if (pos != std::string::npos) {
mimeType = value.substr(0, pos);
} else {
mimeType = value;
}
break;
}
}
return mimeType;
}
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <map>
#include <string>
namespace facebook::react::jsinspector_modern {
using Headers = std::map<std::string, std::string>;
/**
* Get the HTTP reason phrase for a given status code (RFC 9110).
*/
std::string httpReasonPhrase(uint16_t status);
/**
* Get the MIME type for a response based on the 'Content-Type' header. If
* the header is not present, returns 'application/octet-stream'.
*/
std::string mimeTypeFromHeaders(const Headers &headers);
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,246 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "NetworkHandler.h"
#include <jsinspector-modern/cdp/CdpJson.h>
#include <glog/logging.h>
#include <chrono>
namespace facebook::react::jsinspector_modern {
namespace {
/**
* Get the current Unix timestamp in seconds (µs precision, CDP format).
*/
double getCurrentUnixTimestampSeconds() {
auto now = std::chrono::system_clock::now().time_since_epoch();
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now).count();
auto micros =
std::chrono::duration_cast<std::chrono::microseconds>(now).count() %
1000000;
return static_cast<double>(seconds) +
(static_cast<double>(micros) / 1000000.0);
}
} // namespace
NetworkHandler& NetworkHandler::getInstance() {
static NetworkHandler instance;
return instance;
}
void NetworkHandler::setFrontendChannel(FrontendChannel frontendChannel) {
frontendChannel_ = std::move(frontendChannel);
}
bool NetworkHandler::enable() {
if (enabled_.load(std::memory_order_acquire)) {
return false;
}
enabled_.store(true, std::memory_order_release);
return true;
}
bool NetworkHandler::disable() {
if (!enabled_.load(std::memory_order_acquire)) {
return false;
}
enabled_.store(false, std::memory_order_release);
responseBodyBuffer_.clear();
return true;
}
void NetworkHandler::onRequestWillBeSent(
const std::string& requestId,
const cdp::network::Request& request,
const std::optional<cdp::network::Response>& redirectResponse) {
if (!isEnabledNoSync()) {
return;
}
double timestamp = getCurrentUnixTimestampSeconds();
std::optional<folly::dynamic> initiator;
initiator = consumeStoredRequestInitiator(requestId);
auto params = cdp::network::RequestWillBeSentParams{
.requestId = requestId,
.loaderId = "",
.documentURL = "mobile",
.request = request,
// NOTE: Both timestamp and wallTime use the same unit, however wallTime
// is relative to an "arbitrary epoch". In our implementation, use the
// Unix epoch for both.
.timestamp = timestamp,
.wallTime = timestamp,
.initiator = initiator.has_value()
? std::move(initiator.value())
: folly::dynamic::object("type", "script"),
.redirectHasExtraInfo = redirectResponse.has_value(),
.redirectResponse = redirectResponse,
};
frontendChannel_(
cdp::jsonNotification("Network.requestWillBeSent", params.toDynamic()));
}
void NetworkHandler::onRequestWillBeSentExtraInfo(
const std::string& requestId,
const Headers& headers) {
if (!isEnabledNoSync()) {
return;
}
auto params = cdp::network::RequestWillBeSentExtraInfoParams{
.requestId = requestId,
.headers = headers,
.connectTiming = {.requestTime = getCurrentUnixTimestampSeconds()},
};
frontendChannel_(
cdp::jsonNotification(
"Network.requestWillBeSentExtraInfo", params.toDynamic()));
}
void NetworkHandler::onResponseReceived(
const std::string& requestId,
const cdp::network::Response& response) {
if (!isEnabledNoSync()) {
return;
}
auto resourceType = cdp::network::resourceTypeFromMimeType(response.mimeType);
{
std::lock_guard<std::mutex> lock(requestMetadataMutex_);
resourceTypeMap_.emplace(requestId, resourceType);
}
auto params = cdp::network::ResponseReceivedParams{
.requestId = requestId,
.loaderId = "",
.timestamp = getCurrentUnixTimestampSeconds(),
.type = resourceType,
.response = response,
.hasExtraInfo = false,
};
frontendChannel_(
cdp::jsonNotification("Network.responseReceived", params.toDynamic()));
}
void NetworkHandler::onDataReceived(
const std::string& requestId,
int dataLength,
int encodedDataLength) {
if (!isEnabledNoSync()) {
return;
}
auto params = cdp::network::DataReceivedParams{
.requestId = requestId,
.timestamp = getCurrentUnixTimestampSeconds(),
.dataLength = dataLength,
.encodedDataLength = encodedDataLength,
};
frontendChannel_(
cdp::jsonNotification("Network.dataReceived", params.toDynamic()));
}
void NetworkHandler::onLoadingFinished(
const std::string& requestId,
int encodedDataLength) {
if (!isEnabledNoSync()) {
return;
}
auto params = cdp::network::LoadingFinishedParams{
.requestId = requestId,
.timestamp = getCurrentUnixTimestampSeconds(),
.encodedDataLength = encodedDataLength,
};
frontendChannel_(
cdp::jsonNotification("Network.loadingFinished", params.toDynamic()));
}
void NetworkHandler::onLoadingFailed(
const std::string& requestId,
bool cancelled) {
if (!isEnabledNoSync()) {
return;
}
{
std::lock_guard<std::mutex> lock(requestMetadataMutex_);
auto params = cdp::network::LoadingFailedParams{
.requestId = requestId,
.timestamp = getCurrentUnixTimestampSeconds(),
.type = resourceTypeMap_.find(requestId) != resourceTypeMap_.end()
? resourceTypeMap_.at(requestId)
: "Other",
.errorText = cancelled ? "net::ERR_ABORTED" : "net::ERR_FAILED",
.canceled = cancelled,
};
frontendChannel_(
cdp::jsonNotification("Network.loadingFailed", params.toDynamic()));
}
}
void NetworkHandler::storeResponseBody(
const std::string& requestId,
std::string_view body,
bool base64Encoded) {
std::lock_guard<std::mutex> lock(requestBodyMutex_);
responseBodyBuffer_.put(requestId, body, base64Encoded);
}
std::optional<std::tuple<std::string, bool>> NetworkHandler::getResponseBody(
const std::string& requestId) {
std::lock_guard<std::mutex> lock(requestBodyMutex_);
auto responseBody = responseBodyBuffer_.get(requestId);
if (responseBody == nullptr) {
return std::nullopt;
}
return std::make_optional<std::tuple<std::string, bool>>(
responseBody->data, responseBody->base64Encoded);
}
void NetworkHandler::recordRequestInitiatorStack(
const std::string& requestId,
folly::dynamic stackTrace) {
if (!isEnabledNoSync()) {
return;
}
std::lock_guard<std::mutex> lock(requestMetadataMutex_);
requestInitiatorById_.emplace(
requestId,
folly::dynamic::object("type", "script")("stack", std::move(stackTrace)));
}
std::optional<folly::dynamic> NetworkHandler::consumeStoredRequestInitiator(
const std::string& requestId) {
std::lock_guard<std::mutex> lock(requestMetadataMutex_);
auto it = requestInitiatorById_.find(requestId);
if (it == requestInitiatorById_.end()) {
return std::nullopt;
}
// Remove and return
auto result = std::move(it->second);
requestInitiatorById_.erase(it);
return result;
}
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "BoundedRequestBuffer.h"
#include "CdpNetwork.h"
#include <folly/dynamic.h>
#include <atomic>
#include <mutex>
#include <string>
#include <tuple>
namespace facebook::react::jsinspector_modern {
/**
* A callback that can be used to send debugger messages (method responses and
* events) to the frontend. The message must be a JSON-encoded string.
* The callback may be called from any thread.
*/
using FrontendChannel = std::function<void(std::string_view messageJson)>;
using Headers = std::map<std::string, std::string>;
/**
* [Experimental] Handler for reporting network events via CDP.
*/
class NetworkHandler {
public:
static NetworkHandler &getInstance();
/**
* Set the channel used to send CDP events to the frontend. This should be
* supplied before calling `enable`.
*/
void setFrontendChannel(FrontendChannel frontendChannel);
/**
* Enable network debugging. Returns `false` if already enabled.
*
* @cdp Network.enable
*/
bool enable();
/**
* Disable network debugging. Returns `false` if not initially enabled.
*
* @cdp Network.disable
*/
bool disable();
/**
* Returns whether network debugging is currently enabled.
*/
inline bool isEnabled() const
{
return enabled_.load(std::memory_order_acquire);
}
/**
* @cdp Network.requestWillBeSent
*/
void onRequestWillBeSent(
const std::string &requestId,
const cdp::network::Request &request,
const std::optional<cdp::network::Response> &redirectResponse);
/**
* @cdp Network.requestWillBeSentExtraInfo
*/
void onRequestWillBeSentExtraInfo(const std::string &requestId, const Headers &headers);
/**
* @cdp Network.responseReceived
*/
void onResponseReceived(const std::string &requestId, const cdp::network::Response &response);
/**
* @cdp Network.dataReceived
*/
void onDataReceived(const std::string &requestId, int dataLength, int encodedDataLength);
/**
* @cdp Network.loadingFinished
*/
void onLoadingFinished(const std::string &requestId, int encodedDataLength);
/**
* @cdp Network.loadingFailed
*/
void onLoadingFailed(const std::string &requestId, bool cancelled);
/**
* Store the fetched response body for a text or image network response.
*
* Reponse bodies are stored in a bounded buffer with a fixed maximum memory
* size, where oldest responses will be evicted if the buffer is exceeded.
*
* Should be called after checking \ref NetworkHandler::isEnabled.
*/
void storeResponseBody(const std::string &requestId, std::string_view body, bool base64Encoded);
/**
* Retrieve a stored response body for a given request ID.
*
* \returns An optional tuple of [responseBody, base64Encoded]. Returns
* nullopt if no entry is found in the buffer.
*/
std::optional<std::tuple<std::string, bool>> getResponseBody(const std::string &requestId);
/**
* Associate the given stack trace with the given request ID.
*/
void recordRequestInitiatorStack(const std::string &requestId, folly::dynamic stackTrace);
private:
NetworkHandler() = default;
NetworkHandler(const NetworkHandler &) = delete;
NetworkHandler &operator=(const NetworkHandler &) = delete;
~NetworkHandler() = default;
std::atomic<bool> enabled_{false};
inline bool isEnabledNoSync() const
{
return enabled_.load(std::memory_order_relaxed);
}
std::optional<folly::dynamic> consumeStoredRequestInitiator(const std::string &requestId);
FrontendChannel frontendChannel_;
std::map<std::string, std::string> resourceTypeMap_{};
std::map<std::string, folly::dynamic> requestInitiatorById_{};
std::mutex requestMetadataMutex_{};
BoundedRequestBuffer responseBodyBuffer_{};
std::mutex requestBodyMutex_;
};
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,50 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../..\""
end
header_dir = 'jsinspector-modern/network'
module_name = "jsinspector_modernnetwork"
Pod::Spec.new do |s|
s.name = "React-jsinspectornetwork"
s.version = version
s.summary = "Network inspection for React Native DevTools"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("*.{cpp,h}", "*.h")
s.header_dir = header_dir
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES"}
resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: module_name)
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end