From c87d65856c06a03a2470fdc1bd48bc4042dadaec Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Mon, 5 Jun 2023 04:31:13 -0700 Subject: [Inspector] Introduce `inspector: true` in Bun.serve() This exposes the WebKit inspector debugger protocol over WebSockets at the endpoint `/bun:inspect` when `Bun.serve()`. To enable, pass: ```js Bun.serve({inspector: true, development: true, fetch(req){ /* rest of params *... }); ``` Both `development` and `inspector` must be true, as this is very security sensitive to expose publicly. --- src/bun.js/api/server.zig | 22 ++++- src/bun.js/bindings/BunInspector.cpp | 184 +++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 src/bun.js/bindings/BunInspector.cpp diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 094f5e385..30889964d 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -136,6 +136,8 @@ pub const ServerConfig = struct { websocket: ?WebSocketServer = null, + inspector: bool = false, + pub const SSLConfig = struct { server_name: [*c]const u8 = null, @@ -741,7 +743,19 @@ pub const ServerConfig = struct { } if (arg.get(global, "development")) |dev| { - args.development = dev.toBoolean(); + args.development = dev.coerce(bool, global); + } + + if (arg.get(global, "inspector")) |inspector| { + args.inspector = inspector.coerce(bool, global); + + if (args.inspector and !args.development) { + JSC.throwInvalidArguments("Cannot enable inspector in production. Please set development: true in Bun.serve()", .{}, global, exception); + if (args.ssl_config) |*conf| { + conf.deinit(); + } + return args; + } } if (arg.getTruthy(global, "tls")) |tls| { @@ -5239,6 +5253,10 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { if (comptime debug_mode) { this.app.get("/bun:info", *ThisServer, this, onBunInfoRequest); + if (this.config.inspector) { + Bun__addInspector(ssl_enabled, this.app, this.globalThis); + } + this.app.get("/src:/*", *ThisServer, this, onSrcRequest); } @@ -5290,3 +5308,5 @@ pub const AnyServer = union(enum) { }; const welcome_page_html_gz = @embedFile("welcome-page.html.gz"); + +extern fn Bun__addInspector(bool, *anyopaque, *JSC.JSGlobalObject) void; diff --git a/src/bun.js/bindings/BunInspector.cpp b/src/bun.js/bindings/BunInspector.cpp new file mode 100644 index 000000000..ccb0a702d --- /dev/null +++ b/src/bun.js/bindings/BunInspector.cpp @@ -0,0 +1,184 @@ +#include "root.h" +#include + +#include +#include + +namespace Bun { +using namespace JSC; +template +class BunInspectorConnection : public Inspector::FrontendChannel { +public: + using BunInspectorSocket = uWS::WebSocket; + + BunInspectorConnection(BunInspectorSocket* ws, JSC::JSGlobalObject* globalObject) + : ws(ws) + , globalObject(globalObject) + , pendingMessages() + { + } + + ~BunInspectorConnection() + { + } + + Inspector::FrontendChannel::ConnectionType connectionType() const override + { + return Inspector::FrontendChannel::ConnectionType::Remote; + } + + void onOpen(JSC::JSGlobalObject* globalObject) + { + this->globalObject = globalObject; + this->globalObject->inspectorDebuggable().connect(*this); + } + + void onClose() + { + this->globalObject->inspectorDebuggable().disconnect(*this); + this->pendingMessages.clear(); + } + + void sendMessageToFrontend(const String& message) override + { + send(message); + } + + void send(const WTF::String& message) + { + if (ws->getBufferedAmount() == 0) { + WTF::CString messageCString = message.utf8(); + ws->send(std::string_view { messageCString.data(), messageCString.length() }, uWS::OpCode::TEXT); + } else { + pendingMessages.append(message); + } + } + + void onMessage(std::string_view message) + { + WTF::String messageString = WTF::String::fromUTF8(message.data(), message.length()); + this->globalObject->inspectorDebuggable().dispatchMessageFromRemote(WTFMove(messageString)); + } + + void drain() + { + if (pendingMessages.size() == 0) + return; + + if (ws->getBufferedAmount() == 0) { + ws->cork([&]() { + for (auto& message : pendingMessages) { + WTF::CString messageCString = message.utf8(); + ws->send(std::string_view { messageCString.data(), messageCString.length() }, uWS::OpCode::TEXT); + } + pendingMessages.clear(); + }); + } + } + + WTF::Vector pendingMessages; + JSC::JSGlobalObject* globalObject; + BunInspectorSocket* ws; +}; + +using BunInspectorConnectionNoSSL = BunInspectorConnection; +using SSLBunInspectorConnection = BunInspectorConnection; + +template +static void addInspector(void* app, JSC::JSGlobalObject* globalObject) +{ + if constexpr (isSSL) { + auto handler = uWS::SSLApp::WebSocketBehavior { + /* Settings */ + .compression = uWS::DISABLED, + .maxPayloadLength = 16 * 1024 * 1024, + .idleTimeout = 960, + .maxBackpressure = 16 * 1024 * 1024, + .closeOnBackpressureLimit = false, + .resetIdleTimeoutOnSend = true, + .sendPingsAutomatically = true, + /* Handlers */ + .upgrade = nullptr, + .open = [globalObject](auto* ws) { + globalObject->setInspectable(true); + *ws->getUserData() = new SSLBunInspectorConnection(ws, globalObject); + SSLBunInspectorConnection* inspector = *ws->getUserData(); + inspector->onOpen(globalObject); + // + }, + .message = [](auto* ws, std::string_view message, uWS::OpCode opCode) { + SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData(); + inspector->onMessage(message); + // + }, + .drain = [](auto* ws) { + SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData(); + inspector->drain(); + // + }, + .ping = [](auto* /*ws*/, std::string_view) { + /* Not implemented yet */ }, + .pong = [](auto* /*ws*/, std::string_view) { + /* Not implemented yet */ }, + + .close = [](auto* ws, int /*code*/, std::string_view /*message*/) { + SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData(); + inspector->onClose(); + delete inspector; } + }; + + ((uWS::SSLApp*)app)->ws("/bun:inspect", std::move(handler)); + } else { + + auto handler = uWS::App::WebSocketBehavior { + /* Settings */ + .compression = uWS::DISABLED, + .maxPayloadLength = 16 * 1024 * 1024, + .idleTimeout = 960, + .maxBackpressure = 16 * 1024 * 1024, + .closeOnBackpressureLimit = false, + .resetIdleTimeoutOnSend = true, + .sendPingsAutomatically = true, + /* Handlers */ + .upgrade = nullptr, + .open = [globalObject](auto* ws) { + globalObject->setInspectable(true); + *ws->getUserData() = new BunInspectorConnectionNoSSL(ws, globalObject); + BunInspectorConnectionNoSSL* inspector = *ws->getUserData(); + inspector->onOpen(globalObject); + // + }, + .message = [](auto* ws, std::string_view message, uWS::OpCode opCode) { + BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData(); + inspector->onMessage(message); + // + }, + .drain = [](auto* ws) { + BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData(); + inspector->drain(); + // + }, + .ping = [](auto* /*ws*/, std::string_view) { + /* Not implemented yet */ }, + .pong = [](auto* /*ws*/, std::string_view) { + /* Not implemented yet */ }, + + .close = [](auto* ws, int /*code*/, std::string_view /*message*/) { + BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData(); + inspector->onClose(); + delete inspector; } + }; + + ((uWS::App*)app)->ws("/bun:inspect", std::move(handler)); + } +} + +extern "C" void Bun__addInspector(bool isSSL, void* app, JSC::JSGlobalObject* globalObject) +{ + if (isSSL) { + addInspector((uWS::TemplatedApp*)app, globalObject); + } else { + addInspector((uWS::TemplatedApp*)app, globalObject); + } +}; +} -- cgit v1.2.3