aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-06-05 04:31:13 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-06-05 04:31:13 -0700
commitc87d65856c06a03a2470fdc1bd48bc4042dadaec (patch)
treed85b47e7d4df571f56adffeab6b7075b60d4bccb
parent9b996e702ef32d03b01b745642292e7a747485fa (diff)
downloadbun-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.zig22
-rw-r--r--src/bun.js/bindings/BunInspector.cpp184
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);
+ }
+};
+}