diff options
author | 2023-06-05 04:31:13 -0700 | |
---|---|---|
committer | 2023-06-05 04:31:13 -0700 | |
commit | c87d65856c06a03a2470fdc1bd48bc4042dadaec (patch) | |
tree | d85b47e7d4df571f56adffeab6b7075b60d4bccb | |
parent | 9b996e702ef32d03b01b745642292e7a747485fa (diff) | |
download | bun-c87d65856c06a03a2470fdc1bd48bc4042dadaec.tar.gz bun-c87d65856c06a03a2470fdc1bd48bc4042dadaec.tar.zst bun-c87d65856c06a03a2470fdc1bd48bc4042dadaec.zip |
[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.
-rw-r--r-- | src/bun.js/api/server.zig | 22 | ||||
-rw-r--r-- | src/bun.js/bindings/BunInspector.cpp | 184 |
2 files changed, 205 insertions, 1 deletions
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 <uws/src/App.h> + +#include <JavaScriptCore/InspectorFrontendChannel.h> +#include <JavaScriptCore/JSGlobalObjectDebuggable.h> + +namespace Bun { +using namespace JSC; +template<bool isSSL> +class BunInspectorConnection : public Inspector::FrontendChannel { +public: + using BunInspectorSocket = uWS::WebSocket<isSSL, true, BunInspectorConnection*>; + + 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<WTF::String> pendingMessages; + JSC::JSGlobalObject* globalObject; + BunInspectorSocket* ws; +}; + +using BunInspectorConnectionNoSSL = BunInspectorConnection<false>; +using SSLBunInspectorConnection = BunInspectorConnection<true>; + +template<bool isSSL> +static void addInspector(void* app, JSC::JSGlobalObject* globalObject) +{ + if constexpr (isSSL) { + auto handler = uWS::SSLApp::WebSocketBehavior<SSLBunInspectorConnection*> { + /* 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<SSLBunInspectorConnection*>("/bun:inspect", std::move(handler)); + } else { + + auto handler = uWS::App::WebSocketBehavior<BunInspectorConnectionNoSSL*> { + /* 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<BunInspectorConnectionNoSSL*>("/bun:inspect", std::move(handler)); + } +} + +extern "C" void Bun__addInspector(bool isSSL, void* app, JSC::JSGlobalObject* globalObject) +{ + if (isSSL) { + addInspector<true>((uWS::TemplatedApp<true>*)app, globalObject); + } else { + addInspector<false>((uWS::TemplatedApp<false>*)app, globalObject); + } +}; +} |